order.mt.service.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import { GenericCrudService } from '@d8d/shared-crud';
  2. import { DataSource, Repository } from 'typeorm';
  3. import { OrderMt } from '../entities/order.mt.entity';
  4. import { OrderGoodsMt } from '../entities/order-goods.mt.entity';
  5. import { GoodsMt } from '@d8d/goods-module-mt';
  6. import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt';
  7. import type { CreateOrderRequest } from '../schemas/create-order.schema';
  8. export class OrderMtService extends GenericCrudService<OrderMt> {
  9. private orderGoodsRepository: Repository<OrderGoodsMt>;
  10. private goodsRepository: Repository<GoodsMt>;
  11. private deliveryAddressRepository: Repository<DeliveryAddressMt>;
  12. constructor(dataSource: DataSource) {
  13. super(dataSource, OrderMt, {
  14. tenantOptions: { enabled: true, tenantIdField: 'tenantId' }
  15. });
  16. this.orderGoodsRepository = dataSource.getRepository(OrderGoodsMt);
  17. this.goodsRepository = dataSource.getRepository(GoodsMt);
  18. this.deliveryAddressRepository = dataSource.getRepository(DeliveryAddressMt);
  19. }
  20. /**
  21. * 创建订单
  22. * @param data 创建订单请求数据
  23. * @param userId 用户ID
  24. * @param tenantId 租户ID
  25. * @returns 创建的订单信息
  26. */
  27. async createOrder(data: CreateOrderRequest, userId: number, tenantId: number): Promise<{
  28. orderId: number;
  29. orderNo: string;
  30. amount: number;
  31. payAmount: number;
  32. }> {
  33. const queryRunner = this.dataSource.createQueryRunner();
  34. await queryRunner.connect();
  35. await queryRunner.startTransaction();
  36. try {
  37. const { addressId, productOwn, consumeFrom, products } = data;
  38. // 验证商品信息并计算总价
  39. let totalAmount = 0;
  40. let totalCostAmount = 0;
  41. const goodsInfo = [];
  42. for (const item of products) {
  43. const goods = await this.goodsRepository.findOne({
  44. where: { id: item.id, tenantId }
  45. });
  46. if (!goods) {
  47. throw new Error(`商品ID ${item.id} 不存在`);
  48. }
  49. if (goods.stock < item.num) {
  50. throw new Error(`商品 ${goods.name} 库存不足`);
  51. }
  52. const itemAmount = goods.price * item.num;
  53. const itemCostAmount = goods.costPrice * item.num;
  54. totalAmount += itemAmount;
  55. totalCostAmount += itemCostAmount;
  56. goodsInfo.push({
  57. goods,
  58. quantity: item.num,
  59. amount: itemAmount,
  60. costAmount: itemCostAmount
  61. });
  62. }
  63. // 获取收货地址信息
  64. let deliveryAddress = null;
  65. if (addressId) {
  66. deliveryAddress = await this.deliveryAddressRepository.findOne({
  67. where: { id: addressId, userId, tenantId }
  68. });
  69. if (!deliveryAddress) {
  70. throw new Error('收货地址不存在');
  71. }
  72. }
  73. // 生成订单号
  74. const orderNo = this.generateOrderNo();
  75. // 创建订单
  76. const order = this.repository.create({
  77. tenantId,
  78. orderNo,
  79. userId,
  80. amount: totalAmount,
  81. costAmount: totalCostAmount,
  82. payAmount: totalAmount, // 这里可以根据优惠规则计算实际支付金额
  83. orderType: 1, // 实物订单
  84. payType: consumeFrom === '积分兑换' ? 1 : 2, // 根据消费来源设置支付类型
  85. payState: 0, // 未支付
  86. state: 0, // 未发货
  87. addressId: addressId || 0,
  88. merchantId: goodsInfo[0]?.goods.merchantId || 0,
  89. supplierId: goodsInfo[0]?.goods.supplierId || 0,
  90. createdBy: userId,
  91. updatedBy: userId,
  92. // 设置收货地址信息(如果提供)
  93. ...(deliveryAddress && {
  94. receiverMobile: deliveryAddress.phone,
  95. recevierName: deliveryAddress.name,
  96. recevierProvince: deliveryAddress.receiverProvince,
  97. recevierCity: deliveryAddress.receiverCity,
  98. recevierDistrict: deliveryAddress.receiverDistrict,
  99. recevierTown: deliveryAddress.receiverTown,
  100. address: deliveryAddress.address
  101. })
  102. });
  103. const savedOrder = await queryRunner.manager.save(order);
  104. // 创建订单商品明细
  105. const orderGoodsList = goodsInfo.map(info => ({
  106. tenantId,
  107. orderId: savedOrder.id,
  108. orderNo,
  109. goodsId: info.goods.id,
  110. goodsName: info.goods.name,
  111. imageFileId: info.goods.imageFileId,
  112. goodsType: info.goods.goodsType,
  113. supplierId: info.goods.supplierId,
  114. costPrice: info.goods.costPrice,
  115. price: info.goods.price,
  116. num: info.quantity,
  117. freightAmount: 0,
  118. state: 0,
  119. createdBy: userId,
  120. updatedBy: userId,
  121. expressName: null,
  122. expressNo: null
  123. }));
  124. await queryRunner.manager.save(OrderGoodsMt, orderGoodsList);
  125. // 更新商品库存
  126. for (const item of goodsInfo) {
  127. await queryRunner.manager.update(GoodsMt, item.goods.id, {
  128. stock: () => `stock - ${item.quantity}`,
  129. salesNum: () => `sales_num + ${item.quantity}`
  130. });
  131. }
  132. await queryRunner.commitTransaction();
  133. return {
  134. orderId: savedOrder.id,
  135. orderNo: savedOrder.orderNo,
  136. amount: savedOrder.amount,
  137. payAmount: savedOrder.payAmount
  138. };
  139. } catch (error) {
  140. await queryRunner.rollbackTransaction();
  141. throw error;
  142. } finally {
  143. await queryRunner.release();
  144. }
  145. }
  146. /**
  147. * 取消订单
  148. * @param tenantId 租户ID
  149. * @param orderId 订单ID
  150. * @param reason 取消原因
  151. * @param userId 用户ID
  152. * @returns Promise<void>
  153. */
  154. async cancelOrder(tenantId: number, orderId: number, reason: string, userId: number): Promise<void> {
  155. const queryRunner = this.dataSource.createQueryRunner();
  156. await queryRunner.connect();
  157. await queryRunner.startTransaction();
  158. try {
  159. // 查询订单信息
  160. const order = await this.repository.findOne({
  161. where: { id: orderId, tenantId }
  162. });
  163. if (!order) {
  164. throw new Error('订单不存在');
  165. }
  166. // 验证订单状态(仅允许取消未发货且支付状态为0或2的订单)
  167. if (order.state !== 0) {
  168. throw new Error('当前订单状态不允许取消');
  169. }
  170. // 验证支付状态
  171. if (order.payState !== 0 && order.payState !== 2) {
  172. throw new Error('当前订单状态不允许取消');
  173. }
  174. // 对于已支付订单(支付状态=2),需要触发退款流程
  175. if (order.payState === 2) {
  176. // TODO: 调用支付模块的退款功能(将在Story 3中实现)
  177. // 这里先记录日志,后续集成支付退款功能
  178. console.log(`订单 ${orderId} 已支付,需要触发退款流程`);
  179. }
  180. // 更新订单状态为5(订单关闭),记录取消时间和原因
  181. await queryRunner.manager.update(OrderMt, { id: orderId, tenantId }, {
  182. payState: 5, // 订单关闭
  183. cancelReason: reason,
  184. cancelTime: new Date(),
  185. updatedBy: userId,
  186. updatedAt: new Date()
  187. });
  188. // 如果订单是未支付状态,需要恢复商品库存
  189. if (order.payState === 0) {
  190. // 查询订单商品明细
  191. const orderGoods = await this.orderGoodsRepository.find({
  192. where: { orderId, tenantId }
  193. });
  194. // 恢复商品库存
  195. for (const item of orderGoods) {
  196. await queryRunner.manager.update(GoodsMt, { id: item.goodsId, tenantId }, {
  197. stock: () => `stock + ${item.num}`,
  198. salesNum: () => `sales_num - ${item.num}`
  199. });
  200. }
  201. }
  202. await queryRunner.commitTransaction();
  203. } catch (error) {
  204. await queryRunner.rollbackTransaction();
  205. throw error;
  206. } finally {
  207. await queryRunner.release();
  208. }
  209. }
  210. /**
  211. * 生成订单号
  212. * 格式:年月日时分秒 + 6位随机数
  213. */
  214. private generateOrderNo(): string {
  215. const now = new Date();
  216. const year = now.getFullYear();
  217. const month = String(now.getMonth() + 1).padStart(2, '0');
  218. const day = String(now.getDate()).padStart(2, '0');
  219. const hour = String(now.getHours()).padStart(2, '0');
  220. const minute = String(now.getMinutes()).padStart(2, '0');
  221. const second = String(now.getSeconds()).padStart(2, '0');
  222. const random = String(Math.floor(Math.random() * 1000000)).padStart(6, '0');
  223. return `ORD${year}${month}${day}${hour}${minute}${second}${random}`;
  224. }
  225. }