From d8f9f0d02920483d359e97c6eace5ce8c6a36d6e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 15 May 2026 11:00:15 +0800 Subject: [PATCH] feat(wms):调整 check 的实现 --- yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java | 3 +++ yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java | 6 ++++++ yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuService.java | 10 ++++++++++ yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java | 7 +++++++ yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/check/WmsCheckOrderServiceImpl.java | 32 +++++++------------------------- yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 158 insertions(+), 25 deletions(-) diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java index 387592c..1b07e1d 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java @@ -23,10 +23,12 @@ public interface ErrorCodeConstants { ErrorCode ITEM_CATEGORY_PARENT_IS_CHILD = new ErrorCode(1_060_102_004, "不能设置自己的子商品分类为父商品分类"); ErrorCode ITEM_CATEGORY_HAS_CHILDREN = new ErrorCode(1_060_102_005, "删除失败!请先删除该分类下的子分类!"); ErrorCode ITEM_CATEGORY_HAS_ITEM = new ErrorCode(1_060_102_006, "删除失败!分类已被商品使用!"); + ErrorCode ITEM_CATEGORY_CODE_DUPLICATE = new ErrorCode(1_060_102_007, "商品分类编号重复"); // ========== WMS 基础数据-商品品牌 1-060-103-000 ========== ErrorCode ITEM_BRAND_NOT_EXISTS = new ErrorCode(1_060_103_000, "商品品牌不存在"); ErrorCode ITEM_BRAND_HAS_ITEM = new ErrorCode(1_060_103_001, "删除失败!品牌已被商品使用!"); + ErrorCode ITEM_BRAND_CODE_DUPLICATE = new ErrorCode(1_060_103_002, "商品品牌编号重复"); // ========== WMS 基础数据-商品 1-060-104-000 ========== ErrorCode ITEM_NOT_EXISTS = new ErrorCode(1_060_104_000, "商品不存在"); @@ -75,6 +77,7 @@ public interface ErrorCodeConstants { ErrorCode CHECK_ORDER_DETAIL_REQUIRED = new ErrorCode(1_060_203_003, "盘库单至少包含一条明细"); ErrorCode CHECK_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_203_005, "盘库单状态不是草稿或已作废,不能删除"); ErrorCode CHECK_ORDER_DETAIL_NOT_EXISTS = new ErrorCode(1_060_203_006, "盘库单明细不存在"); + ErrorCode CHECK_ORDER_INVENTORY_CHANGED = new ErrorCode(1_060_203_007, "盘库单库存已变化,请重新加载库存后再完成"); // ========== WMS 库存 1-060-300-000 ========== ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_000, diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java index 2009f89..8fc9650 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java @@ -77,10 +77,13 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { return; } + // 1. 逐条复核账面库存,并计算库存调整和库存流水 List updateInventories = new ArrayList<>(reqDTO.getItems().size()); List histories = new ArrayList<>(reqDTO.getItems().size()); for (WmsInventoryCheckReqDTO.Item item : reqDTO.getItems()) { + // 1.1 锁定或创建库存余额行 WmsInventoryDO inventory = getOrCreateCheckInventory(item); + // 1.2 无盈亏时不更新库存,也不生成库存流水 BigDecimal beforeQuantity = inventory.getQuantity(); BigDecimal afterQuantity = item.getCheckQuantity(); if (beforeQuantity.compareTo(afterQuantity) == 0) { @@ -89,9 +92,12 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { updateInventories.add(new WmsInventoryDO().setId(inventory.getId()).setQuantity(afterQuantity)); histories.add(buildInventoryHistory(reqDTO, item, beforeQuantity, afterQuantity)); } + + // 2.1 批量更新库存余额 if (CollUtil.isNotEmpty(updateInventories)) { inventoryMapper.updateBatch(updateInventories); } + // 2.2 批量写入库存流水 if (CollUtil.isNotEmpty(histories)) { inventoryHistoryService.createInventoryHistoryList(histories); } diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuService.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuService.java index d5ab997..456742c 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuService.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuService.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.wms.service.md.item; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuPageReqVO; import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSaveReqVO; import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; import jakarta.validation.Valid; @@ -84,6 +86,14 @@ public interface WmsItemSkuService { List getItemSkuListByIds(Collection ids); /** + * 按 SKU 维度分页查询,支持商品 / 品牌 / 分类多表联查筛选。 + * + * @param pageReqVO 分页与筛选条件 + * @return SKU 分页结果 + */ + PageResult getItemSkuPage(WmsItemSkuPageReqVO pageReqVO); + + /** * 按编号集合获得 SKU Map * * @param ids 编号集合 diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java index ddb69b4..d321032 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java +++ b/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; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuPageReqVO; import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSaveReqVO; import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemSkuMapper; @@ -139,6 +141,11 @@ public class WmsItemSkuServiceImpl implements WmsItemSkuService { return itemSkuMapper.selectByIds(ids); } + @Override + public PageResult getItemSkuPage(WmsItemSkuPageReqVO pageReqVO) { + return itemSkuMapper.selectPage(pageReqVO); + } + private void validateItemSkuList(List skus) { // 校验至少存在一个商品 SKU if (CollUtil.isEmpty(skus)) { diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/check/WmsCheckOrderServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/check/WmsCheckOrderServiceImpl.java index 7c2c5f2..5fb2ce5 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/check/WmsCheckOrderServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum; import cn.iocoder.yudao.module.wms.enums.order.WmsOrderStatusEnum; import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService; -import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO; +import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryCheckReqDTO; import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; @@ -21,7 +21,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; -import java.util.ArrayList; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -108,7 +107,7 @@ public class WmsCheckOrderServiceImpl implements WmsCheckOrderService { } // 3. 盘点库存 - changeInventory(order, details); + inventoryService.checkInventory(buildCheckInventoryReqDTO(order, details)); } @Override @@ -207,29 +206,12 @@ public class WmsCheckOrderServiceImpl implements WmsCheckOrderService { return order; } - /** - * 盘点盘库单对应库存 - * - * @param order 盘库单 - * @param details 盘库单明细列表 - */ - private void changeInventory(WmsCheckOrderDO order, List details) { - List items = new ArrayList<>(details.size()); - for (WmsCheckOrderDetailDO detail : details) { - BigDecimal differenceQuantity = calculateDifferenceQuantity(detail.getQuantity(), detail.getCheckQuantity()); - if (differenceQuantity.compareTo(BigDecimal.ZERO) == 0) { - continue; - } - WmsInventoryChangeReqDTO.Item item = BeanUtils.toBean(detail, WmsInventoryChangeReqDTO.Item.class) - .setQuantity(differenceQuantity); - items.add(item); - } - if (CollUtil.isEmpty(items)) { - return; - } - inventoryService.changeInventory(new WmsInventoryChangeReqDTO() + private WmsInventoryCheckReqDTO buildCheckInventoryReqDTO(WmsCheckOrderDO order, + List details) { + return new WmsInventoryCheckReqDTO() .setOrderId(order.getId()).setOrderNo(order.getNo()) - .setOrderType(WmsOrderTypeEnum.CHECK.getType()).setItems(items)); + .setOrderType(WmsOrderTypeEnum.CHECK.getType()) + .setItems(BeanUtils.toBean(details, WmsInventoryCheckReqDTO.Item.class)); } } diff --git a/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java b/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java index 451292d..e0fe0e5 100644 --- a/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java +++ b/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; import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemSkuMapper; import cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum; import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO; +import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryCheckReqDTO; import cn.iocoder.yudao.module.wms.service.md.item.WmsItemService; import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService; import jakarta.annotation.Resource; @@ -32,6 +33,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.CHECK_ORDER_INVENTORY_CHANGED; import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.INVENTORY_QUANTITY_NOT_ENOUGH; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -325,6 +327,113 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { } @Test + public void testCheckInventory_updateExistingInventory() { + // mock 数据 + WmsItemDO item = createItem("ITEM-001", "红富士苹果"); + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装"); + WmsInventoryDO inventory = createInventory(sku.getId(), 100L, "10.00"); + inventoryMapper.insert(inventory); + WmsInventoryCheckReqDTO reqDTO = createCheckReq(inventory.getId(), sku.getId(), 100L, + "10.00", "7.00"); + + // 调用 + inventoryService.checkInventory(reqDTO); + + // 断言:库存余额直接调整为实盘数量 + WmsInventoryDO dbInventory = inventoryMapper.selectById(inventory.getId()); + assertNotNull(dbInventory); + assertEquals(0, new BigDecimal("7.00").compareTo(dbInventory.getQuantity())); + // 断言:库存流水记录盘库差异 + List histories = inventoryHistoryMapper.selectList(); + assertEquals(1, histories.size()); + WmsInventoryHistoryDO history = histories.get(0); + assertEquals(sku.getId(), history.getSkuId()); + assertEquals(100L, history.getWarehouseId()); + assertEquals(0, new BigDecimal("10.00").compareTo(history.getBeforeQuantity())); + assertEquals(0, new BigDecimal("7.00").compareTo(history.getAfterQuantity())); + assertEquals(0, new BigDecimal("-3.00").compareTo(history.getQuantity())); + assertEquals(reqDTO.getOrderId(), history.getOrderId()); + assertEquals(reqDTO.getOrderNo(), history.getOrderNo()); + assertEquals(reqDTO.getOrderType(), history.getOrderType()); + } + + @Test + public void testCheckInventory_existingInventoryChanged() { + // mock 数据 + WmsItemDO item = createItem("ITEM-001", "红富士苹果"); + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装"); + WmsInventoryDO inventory = createInventory(sku.getId(), 100L, "12.00"); + inventoryMapper.insert(inventory); + WmsInventoryCheckReqDTO reqDTO = createCheckReq(inventory.getId(), sku.getId(), 100L, + "10.00", "7.00"); + + // 调用,并断言 + assertServiceException(() -> inventoryService.checkInventory(reqDTO), CHECK_ORDER_INVENTORY_CHANGED); + WmsInventoryDO dbInventory = inventoryMapper.selectById(inventory.getId()); + assertNotNull(dbInventory); + assertEquals(0, new BigDecimal("12.00").compareTo(dbInventory.getQuantity())); + assertEquals(0L, inventoryHistoryMapper.selectCount()); + } + + @Test + public void testCheckInventory_createNewInventory() { + // mock 数据 + WmsItemDO item = createItem("ITEM-001", "红富士苹果"); + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装"); + WmsInventoryCheckReqDTO reqDTO = createCheckReq(null, sku.getId(), 100L, + "0.00", "5.00"); + + // 调用 + inventoryService.checkInventory(reqDTO); + + // 断言:新增库存直接落为实盘数量 + WmsInventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseId(sku.getId(), 100L); + assertNotNull(inventory); + assertEquals(0, new BigDecimal("5.00").compareTo(inventory.getQuantity())); + // 断言:库存流水记录从 0 到实盘数量 + List histories = inventoryHistoryMapper.selectList(); + assertEquals(1, histories.size()); + assertEquals(0, BigDecimal.ZERO.compareTo(histories.get(0).getBeforeQuantity())); + assertEquals(0, new BigDecimal("5.00").compareTo(histories.get(0).getAfterQuantity())); + assertEquals(0, new BigDecimal("5.00").compareTo(histories.get(0).getQuantity())); + } + + @Test + public void testCheckInventory_createNewZeroInventory() { + // mock 数据 + WmsItemDO item = createItem("ITEM-001", "红富士苹果"); + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装"); + WmsInventoryCheckReqDTO reqDTO = createCheckReq(null, sku.getId(), 100L, + "0.00", "0.00"); + + // 调用 + inventoryService.checkInventory(reqDTO); + + // 断言:新增零库存商品也会生成库存余额行 + WmsInventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseId(sku.getId(), 100L); + assertNotNull(inventory); + assertEquals(0, BigDecimal.ZERO.compareTo(inventory.getQuantity())); + assertEquals(0L, inventoryHistoryMapper.selectCount()); + } + + @Test + public void testCheckInventory_newInventoryAlreadyExists() { + // mock 数据 + WmsItemDO item = createItem("ITEM-001", "红富士苹果"); + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装"); + inventoryMapper.insert(createInventory(sku.getId(), 100L, "3.00")); + WmsInventoryCheckReqDTO reqDTO = createCheckReq(null, sku.getId(), 100L, + "0.00", "5.00"); + + // 调用,并断言 + assertServiceException(() -> inventoryService.checkInventory(reqDTO), CHECK_ORDER_INVENTORY_CHANGED); + WmsInventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseId(sku.getId(), 100L); + assertNotNull(inventory); + assertEquals(0, new BigDecimal("3.00").compareTo(inventory.getQuantity())); + assertEquals(0L, inventoryHistoryMapper.selectCount()); + } + + @Test public void testGetInventoryPage_filterByWarehouse() { // mock 数据 WmsItemDO item = createItem("ITEM-001", "红富士苹果"); @@ -487,4 +596,20 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { .setRemark("测试入库"))); } + private static WmsInventoryCheckReqDTO createCheckReq(Long inventoryId, Long skuId, Long warehouseId, + String quantity, String checkQuantity) { + return new WmsInventoryCheckReqDTO() + .setOrderId(2L) + .setOrderNo("PK202605120001") + .setOrderType(WmsOrderTypeEnum.CHECK.getType()) + .setItems(Collections.singletonList(new WmsInventoryCheckReqDTO.Item() + .setInventoryId(inventoryId) + .setSkuId(skuId) + .setWarehouseId(warehouseId) + .setQuantity(new BigDecimal(quantity)) + .setCheckQuantity(new BigDecimal(checkQuantity)) + .setPrice(new BigDecimal("100.00")) + .setRemark("测试盘库"))); + } + } -- libgit2 0.21.4