فهرست منبع

fix(backend): 组合扣库/回补按订单组合行数量而非订单总量(混合订单库存准确性)

Xiaogang Liao 1 هفته پیش
والد
کامیت
f8873c74ef

+ 22 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java

@@ -1297,7 +1297,7 @@ public class ProductOrderService {
     void deductComboStock(ProductSku combo, ProductOrder order, String orderNo) {
         for (ProductSkuComboItem ci : combo.getComboItems()) {
             int unit = ci.getQuantity() == null ? 1 : ci.getQuantity();
-            int need = unit * order.getQuantity();
+            int need = unit * comboLineQuantity(order);
             if (ci.getChildSkuId() != null) {
                 if (!productSkuService.decreaseStock(ci.getChildSkuId(), need)) {
                     throw new RuntimeException("组合商品库存不足: skuId=" + ci.getChildSkuId());
@@ -1323,7 +1323,7 @@ public class ProductOrderService {
         if (!productSkuService.hasComboItems(sku)) return false;
         for (ProductSkuComboItem ci : sku.getComboItems()) {
             int unit = ci.getQuantity() == null ? 1 : ci.getQuantity();
-            int need = unit * order.getQuantity();
+            int need = unit * comboLineQuantity(order);
             if (ci.getChildSkuId() != null) {
                 productSkuService.increaseStock(ci.getChildSkuId(), need);
                 inventoryService.recordRefundInbound(ci.getChildProductId(), ci.getChildSkuId(), need,
@@ -1337,6 +1337,26 @@ public class ProductOrderService {
         return true;
     }
 
+    /**
+     * 组合订单的组合行数量:查 order_items 中与订单 SKU 匹配行的数量合计。
+     * 混合订单(组合行 + 其他行)下 order.quantity 为订单总量,不能用于子项扣减/回补;
+     * 无匹配行(如旧数据/单测缺省)时回退 order.quantity。包级可见,供单测直接调用。
+     */
+    int comboLineQuantity(ProductOrder order) {
+        if (order.getId() == null || order.getSkuId() == null) {
+            return order.getQuantity() == null ? 1 : order.getQuantity();
+        }
+        List<OrderItem> comboLines = orderItemMapper.selectList(
+                new LambdaQueryWrapper<OrderItem>()
+                        .eq(OrderItem::getOrderId, order.getId())
+                        .eq(OrderItem::getSkuId, order.getSkuId()));
+        int q = 0;
+        for (OrderItem li : comboLines) {
+            q += li.getQuantity() == null ? 0 : li.getQuantity();
+        }
+        return q > 0 ? q : (order.getQuantity() == null ? 1 : order.getQuantity());
+    }
+
     public Result<String> cancel(String orderNo, Long userId) {
         ProductOrder order = orderMapper.selectOne(
             new LambdaQueryWrapper<ProductOrder>().eq(ProductOrder::getOrderNo, orderNo));

+ 24 - 0
cfc-backend/src/test/java/com/etotem/cfc/service/ProductOrderComboStockTest.java

@@ -1,8 +1,10 @@
 package com.etotem.cfc.service;
 
+import com.etotem.cfc.entity.OrderItem;
 import com.etotem.cfc.entity.ProductOrder;
 import com.etotem.cfc.entity.ProductSku;
 import com.etotem.cfc.entity.ProductSkuComboItem;
+import com.etotem.cfc.mapper.OrderItemMapper;
 import com.etotem.cfc.service.InventoryService;
 import com.etotem.cfc.service.ProductOrderService;
 import com.etotem.cfc.service.ProductService;
@@ -23,6 +25,7 @@ class ProductOrderComboStockTest {
     private ProductSkuService skuService;
     private ProductService productService;
     private InventoryService inventoryService;
+    private OrderItemMapper orderItemMapper;
 
     @BeforeEach
     void setUp() {
@@ -30,9 +33,11 @@ class ProductOrderComboStockTest {
         skuService = mock(ProductSkuService.class);
         productService = mock(ProductService.class);
         inventoryService = mock(InventoryService.class);
+        orderItemMapper = mock(OrderItemMapper.class);
         ReflectionTestUtils.setField(orderService, "productSkuService", skuService);
         ReflectionTestUtils.setField(orderService, "productService", productService);
         ReflectionTestUtils.setField(orderService, "inventoryService", inventoryService);
+        ReflectionTestUtils.setField(orderService, "orderItemMapper", orderItemMapper);
     }
 
     private ProductSku combo() {
@@ -102,4 +107,23 @@ class ProductOrderComboStockTest {
         // 父商品(order.productId)不得回补:本用例未设置 productId,仍断言未调用
         verify(productService, never()).increaseStock(null, 3);
     }
+
+    @Test
+    void deductComboStock_mixedOrder_usesComboLineQuantity() {
+        ProductOrder o = order();
+        o.setQuantity(6); // 订单总数量含其他行,组合行自身仅 1
+        OrderItem comboLine = new OrderItem();
+        comboLine.setSkuId(9L);
+        comboLine.setQuantity(1);
+        when(orderItemMapper.selectList(any())).thenReturn(java.util.Collections.singletonList(comboLine));
+        when(skuService.decreaseStock(100L, 2)).thenReturn(true); // 2(组合unit) × 1(行数量)
+        when(productService.decreaseStock(11L, 1)).thenReturn(true);
+
+        orderService.deductComboStock(combo(), o, "PO1");
+
+        verify(skuService).decreaseStock(100L, 2);
+        verify(inventoryService).recordOutbound(10L, 100L, 2, "order", 77L, null, "组合出库:PO1");
+        verify(productService).decreaseStock(11L, 1);
+        verify(inventoryService).recordOutbound(11L, null, 1, "order", 77L, null, "组合出库:PO1");
+    }
 }