Commit d8f9f0d02920483d359e97c6eace5ce8c6a36d6e

Authored by YunaiV
1 parent 10ef8074

feat(wms):调整 check 的实现

yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java
@@ -23,10 +23,12 @@ public interface ErrorCodeConstants { @@ -23,10 +23,12 @@ public interface ErrorCodeConstants {
23 ErrorCode ITEM_CATEGORY_PARENT_IS_CHILD = new ErrorCode(1_060_102_004, "不能设置自己的子商品分类为父商品分类"); 23 ErrorCode ITEM_CATEGORY_PARENT_IS_CHILD = new ErrorCode(1_060_102_004, "不能设置自己的子商品分类为父商品分类");
24 ErrorCode ITEM_CATEGORY_HAS_CHILDREN = new ErrorCode(1_060_102_005, "删除失败!请先删除该分类下的子分类!"); 24 ErrorCode ITEM_CATEGORY_HAS_CHILDREN = new ErrorCode(1_060_102_005, "删除失败!请先删除该分类下的子分类!");
25 ErrorCode ITEM_CATEGORY_HAS_ITEM = new ErrorCode(1_060_102_006, "删除失败!分类已被商品使用!"); 25 ErrorCode ITEM_CATEGORY_HAS_ITEM = new ErrorCode(1_060_102_006, "删除失败!分类已被商品使用!");
  26 + ErrorCode ITEM_CATEGORY_CODE_DUPLICATE = new ErrorCode(1_060_102_007, "商品分类编号重复");
26 27
27 // ========== WMS 基础数据-商品品牌 1-060-103-000 ========== 28 // ========== WMS 基础数据-商品品牌 1-060-103-000 ==========
28 ErrorCode ITEM_BRAND_NOT_EXISTS = new ErrorCode(1_060_103_000, "商品品牌不存在"); 29 ErrorCode ITEM_BRAND_NOT_EXISTS = new ErrorCode(1_060_103_000, "商品品牌不存在");
29 ErrorCode ITEM_BRAND_HAS_ITEM = new ErrorCode(1_060_103_001, "删除失败!品牌已被商品使用!"); 30 ErrorCode ITEM_BRAND_HAS_ITEM = new ErrorCode(1_060_103_001, "删除失败!品牌已被商品使用!");
  31 + ErrorCode ITEM_BRAND_CODE_DUPLICATE = new ErrorCode(1_060_103_002, "商品品牌编号重复");
30 32
31 // ========== WMS 基础数据-商品 1-060-104-000 ========== 33 // ========== WMS 基础数据-商品 1-060-104-000 ==========
32 ErrorCode ITEM_NOT_EXISTS = new ErrorCode(1_060_104_000, "商品不存在"); 34 ErrorCode ITEM_NOT_EXISTS = new ErrorCode(1_060_104_000, "商品不存在");
@@ -75,6 +77,7 @@ public interface ErrorCodeConstants { @@ -75,6 +77,7 @@ public interface ErrorCodeConstants {
75 ErrorCode CHECK_ORDER_DETAIL_REQUIRED = new ErrorCode(1_060_203_003, "盘库单至少包含一条明细"); 77 ErrorCode CHECK_ORDER_DETAIL_REQUIRED = new ErrorCode(1_060_203_003, "盘库单至少包含一条明细");
76 ErrorCode CHECK_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_203_005, "盘库单状态不是草稿或已作废,不能删除"); 78 ErrorCode CHECK_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_203_005, "盘库单状态不是草稿或已作废,不能删除");
77 ErrorCode CHECK_ORDER_DETAIL_NOT_EXISTS = new ErrorCode(1_060_203_006, "盘库单明细不存在"); 79 ErrorCode CHECK_ORDER_DETAIL_NOT_EXISTS = new ErrorCode(1_060_203_006, "盘库单明细不存在");
  80 + ErrorCode CHECK_ORDER_INVENTORY_CHANGED = new ErrorCode(1_060_203_007, "盘库单库存已变化,请重新加载库存后再完成");
78 81
79 // ========== WMS 库存 1-060-300-000 ========== 82 // ========== WMS 库存 1-060-300-000 ==========
80 ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_000, 83 ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_000,
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java
@@ -77,10 +77,13 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { @@ -77,10 +77,13 @@ public class WmsInventoryServiceImpl implements WmsInventoryService {
77 return; 77 return;
78 } 78 }
79 79
  80 + // 1. 逐条复核账面库存,并计算库存调整和库存流水
80 List<WmsInventoryDO> updateInventories = new ArrayList<>(reqDTO.getItems().size()); 81 List<WmsInventoryDO> updateInventories = new ArrayList<>(reqDTO.getItems().size());
81 List<WmsInventoryHistoryDO> histories = new ArrayList<>(reqDTO.getItems().size()); 82 List<WmsInventoryHistoryDO> histories = new ArrayList<>(reqDTO.getItems().size());
82 for (WmsInventoryCheckReqDTO.Item item : reqDTO.getItems()) { 83 for (WmsInventoryCheckReqDTO.Item item : reqDTO.getItems()) {
  84 + // 1.1 锁定或创建库存余额行
83 WmsInventoryDO inventory = getOrCreateCheckInventory(item); 85 WmsInventoryDO inventory = getOrCreateCheckInventory(item);
  86 + // 1.2 无盈亏时不更新库存,也不生成库存流水
84 BigDecimal beforeQuantity = inventory.getQuantity(); 87 BigDecimal beforeQuantity = inventory.getQuantity();
85 BigDecimal afterQuantity = item.getCheckQuantity(); 88 BigDecimal afterQuantity = item.getCheckQuantity();
86 if (beforeQuantity.compareTo(afterQuantity) == 0) { 89 if (beforeQuantity.compareTo(afterQuantity) == 0) {
@@ -89,9 +92,12 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { @@ -89,9 +92,12 @@ public class WmsInventoryServiceImpl implements WmsInventoryService {
89 updateInventories.add(new WmsInventoryDO().setId(inventory.getId()).setQuantity(afterQuantity)); 92 updateInventories.add(new WmsInventoryDO().setId(inventory.getId()).setQuantity(afterQuantity));
90 histories.add(buildInventoryHistory(reqDTO, item, beforeQuantity, afterQuantity)); 93 histories.add(buildInventoryHistory(reqDTO, item, beforeQuantity, afterQuantity));
91 } 94 }
  95 +
  96 + // 2.1 批量更新库存余额
92 if (CollUtil.isNotEmpty(updateInventories)) { 97 if (CollUtil.isNotEmpty(updateInventories)) {
93 inventoryMapper.updateBatch(updateInventories); 98 inventoryMapper.updateBatch(updateInventories);
94 } 99 }
  100 + // 2.2 批量写入库存流水
95 if (CollUtil.isNotEmpty(histories)) { 101 if (CollUtil.isNotEmpty(histories)) {
96 inventoryHistoryService.createInventoryHistoryList(histories); 102 inventoryHistoryService.createInventoryHistoryList(histories);
97 } 103 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuService.java
1 package cn.iocoder.yudao.module.wms.service.md.item; 1 package cn.iocoder.yudao.module.wms.service.md.item;
2 2
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuPageReqVO;
3 import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSaveReqVO; 5 import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSaveReqVO;
4 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; 6 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
5 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
@@ -84,6 +86,14 @@ public interface WmsItemSkuService { @@ -84,6 +86,14 @@ public interface WmsItemSkuService {
84 List<WmsItemSkuDO> getItemSkuListByIds(Collection<Long> ids); 86 List<WmsItemSkuDO> getItemSkuListByIds(Collection<Long> ids);
85 87
86 /** 88 /**
  89 + * 按 SKU 维度分页查询,支持商品 / 品牌 / 分类多表联查筛选。
  90 + *
  91 + * @param pageReqVO 分页与筛选条件
  92 + * @return SKU 分页结果
  93 + */
  94 + PageResult<WmsItemSkuDO> getItemSkuPage(WmsItemSkuPageReqVO pageReqVO);
  95 +
  96 + /**
87 * 按编号集合获得 SKU Map 97 * 按编号集合获得 SKU Map
88 * 98 *
89 * @param ids 编号集合 99 * @param ids 编号集合
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java
@@ -2,7 +2,9 @@ package cn.iocoder.yudao.module.wms.service.md.item; @@ -2,7 +2,9 @@ package cn.iocoder.yudao.module.wms.service.md.item;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.ListUtil; 4 import cn.hutool.core.collection.ListUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuPageReqVO;
6 import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSaveReqVO; 8 import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSaveReqVO;
7 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; 9 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
8 import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemSkuMapper; 10 import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemSkuMapper;
@@ -139,6 +141,11 @@ public class WmsItemSkuServiceImpl implements WmsItemSkuService { @@ -139,6 +141,11 @@ public class WmsItemSkuServiceImpl implements WmsItemSkuService {
139 return itemSkuMapper.selectByIds(ids); 141 return itemSkuMapper.selectByIds(ids);
140 } 142 }
141 143
  144 + @Override
  145 + public PageResult<WmsItemSkuDO> getItemSkuPage(WmsItemSkuPageReqVO pageReqVO) {
  146 + return itemSkuMapper.selectPage(pageReqVO);
  147 + }
  148 +
142 private void validateItemSkuList(List<WmsItemSkuSaveReqVO> skus) { 149 private void validateItemSkuList(List<WmsItemSkuSaveReqVO> skus) {
143 // 校验至少存在一个商品 SKU 150 // 校验至少存在一个商品 SKU
144 if (CollUtil.isEmpty(skus)) { 151 if (CollUtil.isEmpty(skus)) {
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/check/WmsCheckOrderServiceImpl.java
@@ -13,7 +13,7 @@ import cn.iocoder.yudao.module.wms.dal.mysql.order.check.WmsCheckOrderMapper; @@ -13,7 +13,7 @@ import cn.iocoder.yudao.module.wms.dal.mysql.order.check.WmsCheckOrderMapper;
13 import cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum; 13 import cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum;
14 import cn.iocoder.yudao.module.wms.enums.order.WmsOrderStatusEnum; 14 import cn.iocoder.yudao.module.wms.enums.order.WmsOrderStatusEnum;
15 import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService; 15 import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService;
16 -import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO; 16 +import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryCheckReqDTO;
17 import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService; 17 import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService;
18 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
19 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
@@ -21,7 +21,6 @@ import org.springframework.transaction.annotation.Transactional; @@ -21,7 +21,6 @@ import org.springframework.transaction.annotation.Transactional;
21 import org.springframework.validation.annotation.Validated; 21 import org.springframework.validation.annotation.Validated;
22 22
23 import java.math.BigDecimal; 23 import java.math.BigDecimal;
24 -import java.util.ArrayList;  
25 import java.util.List; 24 import java.util.List;
26 25
27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -108,7 +107,7 @@ public class WmsCheckOrderServiceImpl implements WmsCheckOrderService { @@ -108,7 +107,7 @@ public class WmsCheckOrderServiceImpl implements WmsCheckOrderService {
108 } 107 }
109 108
110 // 3. 盘点库存 109 // 3. 盘点库存
111 - changeInventory(order, details); 110 + inventoryService.checkInventory(buildCheckInventoryReqDTO(order, details));
112 } 111 }
113 112
114 @Override 113 @Override
@@ -207,29 +206,12 @@ public class WmsCheckOrderServiceImpl implements WmsCheckOrderService { @@ -207,29 +206,12 @@ public class WmsCheckOrderServiceImpl implements WmsCheckOrderService {
207 return order; 206 return order;
208 } 207 }
209 208
210 - /**  
211 - * 盘点盘库单对应库存  
212 - *  
213 - * @param order 盘库单  
214 - * @param details 盘库单明细列表  
215 - */  
216 - private void changeInventory(WmsCheckOrderDO order, List<WmsCheckOrderDetailDO> details) {  
217 - List<WmsInventoryChangeReqDTO.Item> items = new ArrayList<>(details.size());  
218 - for (WmsCheckOrderDetailDO detail : details) {  
219 - BigDecimal differenceQuantity = calculateDifferenceQuantity(detail.getQuantity(), detail.getCheckQuantity());  
220 - if (differenceQuantity.compareTo(BigDecimal.ZERO) == 0) {  
221 - continue;  
222 - }  
223 - WmsInventoryChangeReqDTO.Item item = BeanUtils.toBean(detail, WmsInventoryChangeReqDTO.Item.class)  
224 - .setQuantity(differenceQuantity);  
225 - items.add(item);  
226 - }  
227 - if (CollUtil.isEmpty(items)) {  
228 - return;  
229 - }  
230 - inventoryService.changeInventory(new WmsInventoryChangeReqDTO() 209 + private WmsInventoryCheckReqDTO buildCheckInventoryReqDTO(WmsCheckOrderDO order,
  210 + List<WmsCheckOrderDetailDO> details) {
  211 + return new WmsInventoryCheckReqDTO()
231 .setOrderId(order.getId()).setOrderNo(order.getNo()) 212 .setOrderId(order.getId()).setOrderNo(order.getNo())
232 - .setOrderType(WmsOrderTypeEnum.CHECK.getType()).setItems(items)); 213 + .setOrderType(WmsOrderTypeEnum.CHECK.getType())
  214 + .setItems(BeanUtils.toBean(details, WmsInventoryCheckReqDTO.Item.class));
233 } 215 }
234 216
235 } 217 }
yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemMapper; @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemMapper;
13 import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemSkuMapper; 13 import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemSkuMapper;
14 import cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum; 14 import cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum;
15 import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO; 15 import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
  16 +import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryCheckReqDTO;
16 import cn.iocoder.yudao.module.wms.service.md.item.WmsItemService; 17 import cn.iocoder.yudao.module.wms.service.md.item.WmsItemService;
17 import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService; 18 import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService;
18 import jakarta.annotation.Resource; 19 import jakarta.annotation.Resource;
@@ -32,6 +33,7 @@ import java.util.concurrent.Future; @@ -32,6 +33,7 @@ import java.util.concurrent.Future;
32 import java.util.concurrent.TimeUnit; 33 import java.util.concurrent.TimeUnit;
33 34
34 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 35 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  36 +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.CHECK_ORDER_INVENTORY_CHANGED;
35 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.INVENTORY_QUANTITY_NOT_ENOUGH; 37 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.INVENTORY_QUANTITY_NOT_ENOUGH;
36 import static org.junit.jupiter.api.Assertions.assertEquals; 38 import static org.junit.jupiter.api.Assertions.assertEquals;
37 import static org.junit.jupiter.api.Assertions.assertTrue; 39 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -325,6 +327,113 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { @@ -325,6 +327,113 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest {
325 } 327 }
326 328
327 @Test 329 @Test
  330 + public void testCheckInventory_updateExistingInventory() {
  331 + // mock 数据
  332 + WmsItemDO item = createItem("ITEM-001", "红富士苹果");
  333 + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装");
  334 + WmsInventoryDO inventory = createInventory(sku.getId(), 100L, "10.00");
  335 + inventoryMapper.insert(inventory);
  336 + WmsInventoryCheckReqDTO reqDTO = createCheckReq(inventory.getId(), sku.getId(), 100L,
  337 + "10.00", "7.00");
  338 +
  339 + // 调用
  340 + inventoryService.checkInventory(reqDTO);
  341 +
  342 + // 断言:库存余额直接调整为实盘数量
  343 + WmsInventoryDO dbInventory = inventoryMapper.selectById(inventory.getId());
  344 + assertNotNull(dbInventory);
  345 + assertEquals(0, new BigDecimal("7.00").compareTo(dbInventory.getQuantity()));
  346 + // 断言:库存流水记录盘库差异
  347 + List<WmsInventoryHistoryDO> histories = inventoryHistoryMapper.selectList();
  348 + assertEquals(1, histories.size());
  349 + WmsInventoryHistoryDO history = histories.get(0);
  350 + assertEquals(sku.getId(), history.getSkuId());
  351 + assertEquals(100L, history.getWarehouseId());
  352 + assertEquals(0, new BigDecimal("10.00").compareTo(history.getBeforeQuantity()));
  353 + assertEquals(0, new BigDecimal("7.00").compareTo(history.getAfterQuantity()));
  354 + assertEquals(0, new BigDecimal("-3.00").compareTo(history.getQuantity()));
  355 + assertEquals(reqDTO.getOrderId(), history.getOrderId());
  356 + assertEquals(reqDTO.getOrderNo(), history.getOrderNo());
  357 + assertEquals(reqDTO.getOrderType(), history.getOrderType());
  358 + }
  359 +
  360 + @Test
  361 + public void testCheckInventory_existingInventoryChanged() {
  362 + // mock 数据
  363 + WmsItemDO item = createItem("ITEM-001", "红富士苹果");
  364 + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装");
  365 + WmsInventoryDO inventory = createInventory(sku.getId(), 100L, "12.00");
  366 + inventoryMapper.insert(inventory);
  367 + WmsInventoryCheckReqDTO reqDTO = createCheckReq(inventory.getId(), sku.getId(), 100L,
  368 + "10.00", "7.00");
  369 +
  370 + // 调用,并断言
  371 + assertServiceException(() -> inventoryService.checkInventory(reqDTO), CHECK_ORDER_INVENTORY_CHANGED);
  372 + WmsInventoryDO dbInventory = inventoryMapper.selectById(inventory.getId());
  373 + assertNotNull(dbInventory);
  374 + assertEquals(0, new BigDecimal("12.00").compareTo(dbInventory.getQuantity()));
  375 + assertEquals(0L, inventoryHistoryMapper.selectCount());
  376 + }
  377 +
  378 + @Test
  379 + public void testCheckInventory_createNewInventory() {
  380 + // mock 数据
  381 + WmsItemDO item = createItem("ITEM-001", "红富士苹果");
  382 + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装");
  383 + WmsInventoryCheckReqDTO reqDTO = createCheckReq(null, sku.getId(), 100L,
  384 + "0.00", "5.00");
  385 +
  386 + // 调用
  387 + inventoryService.checkInventory(reqDTO);
  388 +
  389 + // 断言:新增库存直接落为实盘数量
  390 + WmsInventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseId(sku.getId(), 100L);
  391 + assertNotNull(inventory);
  392 + assertEquals(0, new BigDecimal("5.00").compareTo(inventory.getQuantity()));
  393 + // 断言:库存流水记录从 0 到实盘数量
  394 + List<WmsInventoryHistoryDO> histories = inventoryHistoryMapper.selectList();
  395 + assertEquals(1, histories.size());
  396 + assertEquals(0, BigDecimal.ZERO.compareTo(histories.get(0).getBeforeQuantity()));
  397 + assertEquals(0, new BigDecimal("5.00").compareTo(histories.get(0).getAfterQuantity()));
  398 + assertEquals(0, new BigDecimal("5.00").compareTo(histories.get(0).getQuantity()));
  399 + }
  400 +
  401 + @Test
  402 + public void testCheckInventory_createNewZeroInventory() {
  403 + // mock 数据
  404 + WmsItemDO item = createItem("ITEM-001", "红富士苹果");
  405 + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装");
  406 + WmsInventoryCheckReqDTO reqDTO = createCheckReq(null, sku.getId(), 100L,
  407 + "0.00", "0.00");
  408 +
  409 + // 调用
  410 + inventoryService.checkInventory(reqDTO);
  411 +
  412 + // 断言:新增零库存商品也会生成库存余额行
  413 + WmsInventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseId(sku.getId(), 100L);
  414 + assertNotNull(inventory);
  415 + assertEquals(0, BigDecimal.ZERO.compareTo(inventory.getQuantity()));
  416 + assertEquals(0L, inventoryHistoryMapper.selectCount());
  417 + }
  418 +
  419 + @Test
  420 + public void testCheckInventory_newInventoryAlreadyExists() {
  421 + // mock 数据
  422 + WmsItemDO item = createItem("ITEM-001", "红富士苹果");
  423 + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装");
  424 + inventoryMapper.insert(createInventory(sku.getId(), 100L, "3.00"));
  425 + WmsInventoryCheckReqDTO reqDTO = createCheckReq(null, sku.getId(), 100L,
  426 + "0.00", "5.00");
  427 +
  428 + // 调用,并断言
  429 + assertServiceException(() -> inventoryService.checkInventory(reqDTO), CHECK_ORDER_INVENTORY_CHANGED);
  430 + WmsInventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseId(sku.getId(), 100L);
  431 + assertNotNull(inventory);
  432 + assertEquals(0, new BigDecimal("3.00").compareTo(inventory.getQuantity()));
  433 + assertEquals(0L, inventoryHistoryMapper.selectCount());
  434 + }
  435 +
  436 + @Test
328 public void testGetInventoryPage_filterByWarehouse() { 437 public void testGetInventoryPage_filterByWarehouse() {
329 // mock 数据 438 // mock 数据
330 WmsItemDO item = createItem("ITEM-001", "红富士苹果"); 439 WmsItemDO item = createItem("ITEM-001", "红富士苹果");
@@ -487,4 +596,20 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { @@ -487,4 +596,20 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest {
487 .setRemark("测试入库"))); 596 .setRemark("测试入库")));
488 } 597 }
489 598
  599 + private static WmsInventoryCheckReqDTO createCheckReq(Long inventoryId, Long skuId, Long warehouseId,
  600 + String quantity, String checkQuantity) {
  601 + return new WmsInventoryCheckReqDTO()
  602 + .setOrderId(2L)
  603 + .setOrderNo("PK202605120001")
  604 + .setOrderType(WmsOrderTypeEnum.CHECK.getType())
  605 + .setItems(Collections.singletonList(new WmsInventoryCheckReqDTO.Item()
  606 + .setInventoryId(inventoryId)
  607 + .setSkuId(skuId)
  608 + .setWarehouseId(warehouseId)
  609 + .setQuantity(new BigDecimal(quantity))
  610 + .setCheckQuantity(new BigDecimal(checkQuantity))
  611 + .setPrice(new BigDecimal("100.00"))
  612 + .setRemark("测试盘库")));
  613 + }
  614 +
490 } 615 }