Ver Fonte

✨ feat(order): 为订单服务添加结构化日志记录

- 引入共享工具包的 createServiceLogger 来创建服务专用日志记录器
- 在订单创建流程的关键节点添加信息日志,包括开始创建、数据准备、成功创建、商品明细创建和流程完成
- 在订单创建失败时添加错误日志,记录失败原因和上下文信息
- 在订单取消流程中,将原有的 console.debug 调用替换为结构化日志记录,涵盖退款流程、额度恢复和错误处理
- 在支付记录查询和支付信息获取中添加调试和错误日志,提升问题排查能力

✅ test(order): 增强规格商品订单的集成测试覆盖

- 新增测试用例验证规格商品的价格计算和库存管理逻辑
- 新增测试用例验证库存不足时的订单创建拒绝行为
- 在测试中创建父商品和多个规格商品(子商品)以模拟真实场景
- 验证订单总金额基于规格特有价格正确计算
- 验证订单商品明细正确保存了规格信息(如商品名称、价格、数量)
- 验证当购买数量超过规格库存时,系统正确返回错误响应

📝 docs(entity): 完善订单实体关系映射

- 在 Order 实体中添加与 OrderGoods 的一对多关系定义
- 通过 @OneToMany 装饰器建立 Order 到 OrderGoods 的关联
- 确保实体关系的完整性,支持通过订单查询关联的商品明细

🔧 chore(test): 更新集成测试的实体依赖

- 在管理员订单管理集成测试中,将 OrderGoods、Goods 和 GoodsCategory 实体添加到测试钩子的实体列表中
- 在用户订单管理集成测试中,同样添加上述实体以确保测试环境的数据完整性
- 确保集成测试能够正确加载和操作所有相关的订单和商品实体
yourname há 4 semanas atrás
pai
commit
46fd4bf889

+ 35 - 13
packages/orders-module-mt/src/services/order.mt.service.ts

@@ -9,6 +9,7 @@ import { PaymentMtService } from '@d8d/mini-payment-mt';
 import { CreditBalanceService } from '@d8d/credit-balance-module-mt';
 import { PaymentMtEntity, PaymentStatus } from '@d8d/mini-payment-mt';
 import type { CreateOrderRequest } from '../schemas/create-order.schema';
+import { createServiceLogger } from '@d8d/shared-utils';
 
 export class OrderMtService extends GenericCrudService<OrderMt> {
   private orderGoodsRepository: Repository<OrderGoodsMt>;
@@ -17,6 +18,7 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
   private orderRefundRepository: Repository<OrderRefundMt>;
   private paymentMtService: PaymentMtService;
   private creditBalanceService: CreditBalanceService;
+  private logger: ReturnType<typeof createServiceLogger>;
 
   constructor(dataSource: DataSource) {
     super(dataSource, OrderMt, {
@@ -28,6 +30,7 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
     this.orderRefundRepository = dataSource.getRepository(OrderRefundMt);
     this.paymentMtService = new PaymentMtService(dataSource);
     this.creditBalanceService = new CreditBalanceService(dataSource);
+    this.logger = createServiceLogger('order-mt-service');
   }
 
   /**
@@ -52,6 +55,9 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
     try {
       const { addressId, productOwn, consumeFrom, products, remark } = data;
 
+      // 记录开始创建订单
+      this.logger.info('开始创建订单', { tenantId, userId, productCount: products.length, addressId });
+
       // 验证商品信息并计算总价
       let totalAmount = 0;
       let totalCostAmount = 0;
@@ -121,7 +127,8 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
       const expireTime = new Date();
       expireTime.setHours(expireTime.getHours() + 24);
 
-      const order = this.repository.create({
+
+      const createData ={
         tenantId,
         orderNo,
         userId,
@@ -149,7 +156,14 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
           recevierTown: deliveryAddress.receiverTown,
           address: deliveryAddress.address
         })
-      });
+      };
+
+      this.logger.info('createData:',createData);
+
+      const order = this.repository.create(createData);
+
+      this.logger.info('order:',order);
+      this.logger.info('订单创建成功', { tenantId, userId, orderNo: order.orderNo, amount: order.amount, payAmount: order.payAmount });
 
       const savedOrder = await queryRunner.manager.save(order);
 
@@ -184,6 +198,9 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
         };
       });
 
+      this.logger.info('orderGoodsList:',orderGoodsList);
+      this.logger.info('订单商品明细创建成功', { tenantId, orderId: savedOrder.id, goodsCount: orderGoodsList.length });
+
       await queryRunner.manager.save(OrderGoodsMt, orderGoodsList);
 
       // 更新商品库存
@@ -196,6 +213,8 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
 
       await queryRunner.commitTransaction();
 
+      this.logger.info('订单创建流程完成', { tenantId, userId, orderId: savedOrder.id, orderNo: savedOrder.orderNo, totalAmount: savedOrder.amount });
+
       return {
         success: true,
         orderId: savedOrder.id,
@@ -207,6 +226,7 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
 
     } catch (error) {
       await queryRunner.rollbackTransaction();
+      this.logger.error('订单创建失败', { tenantId, userId, error: error instanceof Error ? error.message : String(error) });
       throw error;
     } finally {
       await queryRunner.release();
@@ -248,7 +268,7 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
 
       // 对于已支付订单(支付状态=2),需要触发退款流程
       if (order.payState === 2) {
-        console.debug(`[租户${tenantId}] 订单 ${orderId} 已支付,开始触发退款流程`);
+        this.logger.debug('订单已支付,开始触发退款流程', { tenantId, orderId, orderNo: order.orderNo });
 
         try {
           // 调用支付模块的退款功能
@@ -259,13 +279,13 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
             `订单取消:${reason}`
           );
 
-          console.debug(`[租户${tenantId}] 退款处理成功,退款流水号: ${refundResult.refundId}`);
+          this.logger.debug('退款处理成功', { tenantId, orderId, orderNo: order.orderNo, refundId: refundResult.refundId });
 
           // 创建退款记录
           await this.createRefundRecord(tenantId, order, refundResult, userId);
 
         } catch (error) {
-          console.debug(`[租户${tenantId}] 退款处理失败,订单ID: ${orderId}, 错误:`, error);
+          this.logger.warn('退款处理失败', { tenantId, orderId, orderNo: order.orderNo, error: error instanceof Error ? error.message : String(error) });
           // 退款失败不影响订单取消,但记录错误信息
           // 可以在这里添加重试机制或通知管理员
         }
@@ -282,7 +302,7 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
 
       // 如果是额度支付订单(payType === 3),需要恢复额度
       if (order.payType === 3) {
-        console.debug(`[租户${tenantId}] 额度支付订单 ${orderId} 取消,开始恢复额度`);
+        this.logger.debug('额度支付订单取消,开始恢复额度', { tenantId, orderId, orderNo: order.orderNo });
         try {
           await this.creditBalanceService.restoreBalanceForCancelOrder(
             tenantId,
@@ -291,9 +311,9 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
             order.payAmount,
             userId
           );
-          console.debug(`[租户${tenantId}] 额度恢复成功,订单号: ${order.orderNo}`);
+          this.logger.debug('额度恢复成功', { tenantId, orderId, orderNo: order.orderNo });
         } catch (error) {
-          console.debug(`[租户${tenantId}] 额度恢复失败,订单ID: ${orderId}, 错误:`, error);
+          this.logger.warn('额度恢复失败', { tenantId, orderId, orderNo: order.orderNo, error: error instanceof Error ? error.message : String(error) });
           // 额度恢复失败不影响订单取消,但记录错误信息
         }
       }
@@ -429,7 +449,7 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
     refundRecord.updatedBy = userId;
 
     await this.orderRefundRepository.save(refundRecord);
-    console.debug(`[租户${tenantId}] 退款记录创建成功,退款订单号: ${refundResult.outRefundNo}`);
+    this.logger.debug('退款记录创建成功', { tenantId, orderNo: order.orderNo, refundOrderNo: refundResult.outRefundNo });
   }
 
   /**
@@ -466,11 +486,13 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
       });
 
       if (!paymentRecord) {
-        console.debug(`[租户${tenantId}] 未找到订单ID ${orderId} 的支付记录`);
+        this.logger.debug('未找到订单的支付记录', { tenantId, orderId, userId });
         return null;
       }
 
-      console.debug(`[租户${tenantId}] 找到订单ID ${orderId} 的支付记录:`, {
+      this.logger.debug('找到订单的支付记录', {
+        tenantId,
+        orderId,
         paymentId: paymentRecord.id,
         outTradeNo: paymentRecord.outTradeNo,
         wechatTransactionId: paymentRecord.wechatTransactionId,
@@ -480,7 +502,7 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
 
       return paymentRecord;
     } catch (error) {
-      console.error(`[租户${tenantId}] 查询订单ID ${orderId} 的支付记录失败:`, error);
+      this.logger.error('查询订单支付记录失败', { tenantId, orderId, error: error instanceof Error ? error.message : String(error) });
       throw new Error(`查询支付记录失败: ${error instanceof Error ? error.message : '未知错误'}`);
     }
   }
@@ -547,7 +569,7 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
         paymentStatusDetail
       };
     } catch (error) {
-      console.error(`[租户${tenantId}] 获取订单ID ${orderId} 的支付信息失败:`, error);
+      this.logger.error('获取订单支付信息失败', { tenantId, orderId, error: error instanceof Error ? error.message : String(error) });
       return {
         outTradeNo: null,
         wechatTransactionId: null,

+ 165 - 0
packages/orders-module-mt/tests/integration/user-orders-routes.integration.test.ts

@@ -389,6 +389,171 @@ describe('多租户用户订单管理API集成测试', () => {
         expect(orderGoods[0].goodsName).toBe('单规格商品');
       }
     });
+
+    it('应该正确处理规格商品的价格计算和库存', async () => {
+      // 创建必要的关联实体
+      const testSupplier = await testFactory.createTestSupplier(testUser.id, { tenantId: 1 });
+      const testMerchant = await testFactory.createTestMerchant(testUser.id, { tenantId: 1 });
+      const testDeliveryAddress = await testFactory.createTestDeliveryAddress(testUser.id, { tenantId: 1 });
+
+      // 创建父商品
+      const parentGoods = await testFactory.createTestGoods(testUser.id, {
+        tenantId: 1,
+        merchantId: testMerchant.id,
+        supplierId: testSupplier.id,
+        name: '智能手机',
+        price: 2999.00, // 父商品价格
+        stock: 100
+      });
+
+      // 创建规格商品(子商品)- 256GB版本
+      const specGoods256GB = await testFactory.createTestGoods(testUser.id, {
+        tenantId: 1,
+        merchantId: testMerchant.id,
+        supplierId: testSupplier.id,
+        name: '256GB 黑色',
+        spuId: parentGoods.id,
+        price: 3299.00, // 规格特有价格
+        stock: 50, // 规格特有库存
+        originalPrice: 3499.00 // 规格原价
+      });
+
+      // 创建规格商品(子商品)- 512GB版本
+      const specGoods512GB = await testFactory.createTestGoods(testUser.id, {
+        tenantId: 1,
+        merchantId: testMerchant.id,
+        supplierId: testSupplier.id,
+        name: '512GB 黑色',
+        spuId: parentGoods.id,
+        price: 3799.00, // 规格特有价格
+        stock: 30, // 规格特有库存
+        originalPrice: 3999.00 // 规格原价
+      });
+
+      const orderData = {
+        addressId: testDeliveryAddress.id,
+        productOwn: '自营',
+        consumeFrom: '积分兑换',
+        products: [
+          { id: specGoods256GB.id, num: 2 }, // 购买2件256GB版本
+          { id: specGoods512GB.id, num: 1 }  // 购买1件512GB版本
+        ]
+      };
+
+      const response = await client['create-order'].$post({
+        json: orderData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('规格商品订单创建响应状态码:', response.status);
+      if (response.status !== 201) {
+        const errorResult = await response.json();
+        console.debug('规格商品订单创建错误响应:', errorResult);
+      }
+      expect(response.status).toBe(201);
+      if (response.status === 201) {
+        const createdOrder = await response.json();
+
+        // 验证订单创建成功
+        expect(createdOrder.success).toBe(true);
+        expect(createdOrder.orderId).toBeGreaterThan(0);
+        expect(createdOrder.orderNo).toBeDefined();
+
+        // 验证订单总金额计算正确
+        // 256GB: 3299.00 * 2 = 6598.00
+        // 512GB: 3799.00 * 1 = 3799.00
+        // 总计: 6598.00 + 3799.00 = 10397.00
+        expect(createdOrder.amount).toBe(10397.00);
+        expect(createdOrder.payAmount).toBe(10397.00);
+
+        // 查询订单商品明细,验证规格信息正确保存
+        const dataSource = await IntegrationTestDatabase.getDataSource();
+        const orderGoods = await dataSource.getRepository(OrderGoodsMt).find({
+          where: { orderId: createdOrder.orderId, tenantId: 1 },
+          order: { goodsId: 'ASC' }
+        });
+
+        expect(orderGoods).toHaveLength(2);
+
+        // 验证第一个商品(256GB)
+        const goods256GB = orderGoods.find(og => og.goodsId === specGoods256GB.id);
+        expect(goods256GB).toBeDefined();
+        expect(goods256GB?.goodsName).toBe('智能手机 256GB 黑色');
+        expect(Number(goods256GB?.price)).toBe(3299.00);
+        expect(goods256GB?.num).toBe(2);
+        // totalPrice字段在OrderGoodsMt实体中不存在,移除验证
+
+        // 验证第二个商品(512GB)
+        const goods512GB = orderGoods.find(og => og.goodsId === specGoods512GB.id);
+        expect(goods512GB).toBeDefined();
+        expect(goods512GB?.goodsName).toBe('智能手机 512GB 黑色');
+        expect(Number(goods512GB?.price)).toBe(3799.00);
+        expect(goods512GB?.num).toBe(1);
+        // totalPrice字段在OrderGoodsMt实体中不存在,移除验证
+      }
+    });
+
+    it('应该拒绝创建库存不足的规格商品订单', async () => {
+      // 创建必要的关联实体
+      const testSupplier = await testFactory.createTestSupplier(testUser.id, { tenantId: 1 });
+      const testMerchant = await testFactory.createTestMerchant(testUser.id, { tenantId: 1 });
+      const testDeliveryAddress = await testFactory.createTestDeliveryAddress(testUser.id, { tenantId: 1 });
+
+      // 创建父商品
+      const parentGoods = await testFactory.createTestGoods(testUser.id, {
+        tenantId: 1,
+        merchantId: testMerchant.id,
+        supplierId: testSupplier.id,
+        name: '限量版商品',
+        price: 100.00,
+        stock: 100
+      });
+
+      // 创建规格商品(子商品)- 库存较少
+      const specGoods = await testFactory.createTestGoods(testUser.id, {
+        tenantId: 1,
+        merchantId: testMerchant.id,
+        supplierId: testSupplier.id,
+        name: '红色',
+        spuId: parentGoods.id,
+        price: 120.00,
+        stock: 5 // 只有5件库存
+      });
+
+      // 尝试购买6件,超过库存数量
+      const orderData = {
+        addressId: testDeliveryAddress.id,
+        productOwn: '自营',
+        consumeFrom: '积分兑换',
+        products: [
+          { id: specGoods.id, num: 6 } // 库存只有5,尝试购买6件
+        ]
+      };
+
+      const response = await client['create-order'].$post({
+        json: orderData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('库存不足订单创建响应状态码:', response.status);
+
+      // 应该返回错误状态码(不是201),如400、422等
+      expect(response.status).not.toBe(201);
+
+      // 验证错误消息包含库存不足的信息
+      if (response.status !== 201) {
+        const errorResult = await response.json();
+        console.debug('库存不足错误响应:', errorResult);
+        // 错误响应结构为 { error: '商品 红色 库存不足' }
+        expect(errorResult.error).toContain('库存');
+      }
+    });
   });
 
   describe('取消订单功能验证', () => {

+ 5 - 1
packages/orders-module/src/entities/order.entity.ts

@@ -1,8 +1,9 @@
-import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany, JoinColumn } from 'typeorm';
 import { UserEntity as User } from '@d8d/user-module';
 import { Merchant } from '@d8d/merchant-module';
 import { Supplier } from '@d8d/supplier-module';
 import { DeliveryAddress } from '@d8d/delivery-address-module';
+import { OrderGoods } from './order-goods.entity';
 
 @Entity('orders')
 export class Order {
@@ -136,4 +137,7 @@ export class Order {
   @ManyToOne(() => DeliveryAddress)
   @JoinColumn({ name: 'address_id', referencedColumnName: 'id' })
   deliveryAddress!: DeliveryAddress;
+
+  @OneToMany(() => OrderGoods, orderGoods => orderGoods.order)
+  orderGoods!: OrderGoods[];
 }

+ 3 - 2
packages/orders-module/tests/integration/admin-orders.integration.test.ts

@@ -8,12 +8,13 @@ import { AreaEntity } from '@d8d/geo-areas';
 import { Merchant } from '@d8d/merchant-module';
 import { Supplier } from '@d8d/supplier-module';
 import { File } from '@d8d/file-module';
+import { Goods, GoodsCategory } from '@d8d/goods-module';
 import adminOrderRoutes from '../../src/routes/admin/orders';
-import { Order } from '../../src/entities';
+import { Order, OrderGoods } from '../../src/entities';
 
 // 设置集成测试钩子
 setupIntegrationDatabaseHooksWithEntities([
-  UserEntity, Role, Order, DeliveryAddress, Merchant, Supplier, File, AreaEntity
+  UserEntity, Role, Order, OrderGoods, DeliveryAddress, Merchant, Supplier, File, Goods, GoodsCategory, AreaEntity
 ])
 
 describe('管理员订单管理API集成测试', () => {

+ 3 - 2
packages/orders-module/tests/integration/user-orders.integration.test.ts

@@ -8,12 +8,13 @@ import { AreaEntity } from '@d8d/geo-areas';
 import { Merchant } from '@d8d/merchant-module';
 import { Supplier } from '@d8d/supplier-module';
 import { File } from '@d8d/file-module';
+import { Goods, GoodsCategory } from '@d8d/goods-module';
 import userOrderRoutes from '../../src/routes/user/orders';
-import { Order } from '../../src/entities';
+import { Order, OrderGoods } from '../../src/entities';
 
 // 设置集成测试钩子
 setupIntegrationDatabaseHooksWithEntities([
-  UserEntity, Role, Order, DeliveryAddress, Merchant, Supplier, File, AreaEntity
+  UserEntity, Role, Order, OrderGoods, DeliveryAddress, Merchant, Supplier, File, Goods, GoodsCategory, AreaEntity
 ])
 
 describe('用户订单管理API集成测试', () => {