Commit e838637fd3cfef50856e52e54b320db288592901

Authored by YunaiV
1 parent c96487f5

feat(wms):增加 SKU、仓库、商户删除前的使用校验

Showing 36 changed files with 736 additions and 24 deletions
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryDetailMapper.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.wms.dal.mysql.inventory; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.wms.dal.mysql.inventory;
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; 7 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
7 import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInventoryDetailPageReqVO; 8 import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInventoryDetailPageReqVO;
8 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO; 9 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO;
@@ -15,6 +16,8 @@ import java.math.BigDecimal; @@ -15,6 +16,8 @@ import java.math.BigDecimal;
15 import java.time.LocalDate; 16 import java.time.LocalDate;
16 import java.time.LocalDateTime; 17 import java.time.LocalDateTime;
17 import java.time.LocalTime; 18 import java.time.LocalTime;
  19 +import java.util.Collection;
  20 +import java.util.List;
18 21
19 /** 22 /**
20 * WMS 库存明细 Mapper 23 * WMS 库存明细 Mapper
@@ -73,4 +76,29 @@ public interface WmsInventoryDetailMapper extends BaseMapperX<WmsInventoryDetail @@ -73,4 +76,29 @@ public interface WmsInventoryDetailMapper extends BaseMapperX<WmsInventoryDetail
73 throw new IllegalArgumentException("未知库存明细统计维度:" + type); 76 throw new IllegalArgumentException("未知库存明细统计维度:" + type);
74 } 77 }
75 78
  79 + default WmsInventoryDetailDO selectByIdForUpdate(Long id) {
  80 + return selectOneForUpdate(WmsInventoryDetailDO::getId, id);
  81 + }
  82 +
  83 + default List<WmsInventoryDetailDO> selectListByIdsForUpdate(Collection<Long> ids) {
  84 + return selectList(new LambdaQueryWrapperX<WmsInventoryDetailDO>()
  85 + .in(WmsInventoryDetailDO::getId, ids)
  86 + .orderByAsc(WmsInventoryDetailDO::getId)
  87 + .last("FOR UPDATE"));
  88 + }
  89 +
  90 + /**
  91 + * 增量更新库存明细剩余数量
  92 + *
  93 + * @param id 库存明细编号
  94 + * @param quantity 变更数量
  95 + * @return 更新行数
  96 + */
  97 + @SuppressWarnings("UnusedReturnValue")
  98 + default int updateRemainQuantityIncr(Long id, BigDecimal quantity) {
  99 + return update(null, new LambdaUpdateWrapper<WmsInventoryDetailDO>()
  100 + .eq(WmsInventoryDetailDO::getId, id)
  101 + .setSql("remain_quantity = remain_quantity + (" + quantity + ")"));
  102 + }
  103 +
76 } 104 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/check/WmsCheckOrderDetailMapper.java
@@ -31,4 +31,12 @@ public interface WmsCheckOrderDetailMapper extends BaseMapperX&lt;WmsCheckOrderDeta @@ -31,4 +31,12 @@ public interface WmsCheckOrderDetailMapper extends BaseMapperX&lt;WmsCheckOrderDeta
31 delete(WmsCheckOrderDetailDO::getOrderId, orderId); 31 delete(WmsCheckOrderDetailDO::getOrderId, orderId);
32 } 32 }
33 33
  34 + default Long selectCountBySkuId(Long skuId) {
  35 + return selectCount(WmsCheckOrderDetailDO::getSkuId, skuId);
  36 + }
  37 +
  38 + default Long selectCountByAreaId(Long areaId) {
  39 + return selectCount(WmsCheckOrderDetailDO::getAreaId, areaId);
  40 + }
  41 +
34 } 42 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/check/WmsCheckOrderMapper.java
@@ -37,4 +37,12 @@ public interface WmsCheckOrderMapper extends BaseMapperX&lt;WmsCheckOrderDO&gt; { @@ -37,4 +37,12 @@ public interface WmsCheckOrderMapper extends BaseMapperX&lt;WmsCheckOrderDO&gt; {
37 return selectOne(WmsCheckOrderDO::getNo, no); 37 return selectOne(WmsCheckOrderDO::getNo, no);
38 } 38 }
39 39
  40 + default Long selectCountByWarehouseId(Long warehouseId) {
  41 + return selectCount(WmsCheckOrderDO::getWarehouseId, warehouseId);
  42 + }
  43 +
  44 + default Long selectCountByAreaId(Long areaId) {
  45 + return selectCount(WmsCheckOrderDO::getAreaId, areaId);
  46 + }
  47 +
40 } 48 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/movement/WmsMovementOrderDetailMapper.java
@@ -31,4 +31,14 @@ public interface WmsMovementOrderDetailMapper extends BaseMapperX&lt;WmsMovementOrd @@ -31,4 +31,14 @@ public interface WmsMovementOrderDetailMapper extends BaseMapperX&lt;WmsMovementOrd
31 delete(WmsMovementOrderDetailDO::getOrderId, orderId); 31 delete(WmsMovementOrderDetailDO::getOrderId, orderId);
32 } 32 }
33 33
  34 + default Long selectCountBySkuId(Long skuId) {
  35 + return selectCount(WmsMovementOrderDetailDO::getSkuId, skuId);
  36 + }
  37 +
  38 + default Long selectCountByAreaId(Long areaId) {
  39 + return selectCount(new LambdaQueryWrapperX<WmsMovementOrderDetailDO>()
  40 + .and(query -> query.eq(WmsMovementOrderDetailDO::getSourceAreaId, areaId)
  41 + .or().eq(WmsMovementOrderDetailDO::getTargetAreaId, areaId)));
  42 + }
  43 +
34 } 44 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/movement/WmsMovementOrderMapper.java
@@ -37,4 +37,16 @@ public interface WmsMovementOrderMapper extends BaseMapperX&lt;WmsMovementOrderDO&gt; @@ -37,4 +37,16 @@ public interface WmsMovementOrderMapper extends BaseMapperX&lt;WmsMovementOrderDO&gt;
37 return selectOne(WmsMovementOrderDO::getNo, no); 37 return selectOne(WmsMovementOrderDO::getNo, no);
38 } 38 }
39 39
  40 + default Long selectCountByWarehouseId(Long warehouseId) {
  41 + return selectCount(new LambdaQueryWrapperX<WmsMovementOrderDO>()
  42 + .and(query -> query.eq(WmsMovementOrderDO::getSourceWarehouseId, warehouseId)
  43 + .or().eq(WmsMovementOrderDO::getTargetWarehouseId, warehouseId)));
  44 + }
  45 +
  46 + default Long selectCountByAreaId(Long areaId) {
  47 + return selectCount(new LambdaQueryWrapperX<WmsMovementOrderDO>()
  48 + .and(query -> query.eq(WmsMovementOrderDO::getSourceAreaId, areaId)
  49 + .or().eq(WmsMovementOrderDO::getTargetAreaId, areaId)));
  50 + }
  51 +
40 } 52 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/receipt/WmsReceiptOrderDetailMapper.java
@@ -31,4 +31,12 @@ public interface WmsReceiptOrderDetailMapper extends BaseMapperX&lt;WmsReceiptOrder @@ -31,4 +31,12 @@ public interface WmsReceiptOrderDetailMapper extends BaseMapperX&lt;WmsReceiptOrder
31 delete(WmsReceiptOrderDetailDO::getOrderId, orderId); 31 delete(WmsReceiptOrderDetailDO::getOrderId, orderId);
32 } 32 }
33 33
  34 + default Long selectCountBySkuId(Long skuId) {
  35 + return selectCount(WmsReceiptOrderDetailDO::getSkuId, skuId);
  36 + }
  37 +
  38 + default Long selectCountByAreaId(Long areaId) {
  39 + return selectCount(WmsReceiptOrderDetailDO::getAreaId, areaId);
  40 + }
  41 +
34 } 42 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/receipt/WmsReceiptOrderMapper.java
@@ -39,4 +39,16 @@ public interface WmsReceiptOrderMapper extends BaseMapperX&lt;WmsReceiptOrderDO&gt; { @@ -39,4 +39,16 @@ public interface WmsReceiptOrderMapper extends BaseMapperX&lt;WmsReceiptOrderDO&gt; {
39 return selectOne(WmsReceiptOrderDO::getNo, no); 39 return selectOne(WmsReceiptOrderDO::getNo, no);
40 } 40 }
41 41
  42 + default Long selectCountByMerchantId(Long merchantId) {
  43 + return selectCount(WmsReceiptOrderDO::getMerchantId, merchantId);
  44 + }
  45 +
  46 + default Long selectCountByWarehouseId(Long warehouseId) {
  47 + return selectCount(WmsReceiptOrderDO::getWarehouseId, warehouseId);
  48 + }
  49 +
  50 + default Long selectCountByAreaId(Long areaId) {
  51 + return selectCount(WmsReceiptOrderDO::getAreaId, areaId);
  52 + }
  53 +
42 } 54 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/shipment/WmsShipmentOrderDetailMapper.java
@@ -31,4 +31,12 @@ public interface WmsShipmentOrderDetailMapper extends BaseMapperX&lt;WmsShipmentOrd @@ -31,4 +31,12 @@ public interface WmsShipmentOrderDetailMapper extends BaseMapperX&lt;WmsShipmentOrd
31 delete(WmsShipmentOrderDetailDO::getOrderId, orderId); 31 delete(WmsShipmentOrderDetailDO::getOrderId, orderId);
32 } 32 }
33 33
  34 + default Long selectCountBySkuId(Long skuId) {
  35 + return selectCount(WmsShipmentOrderDetailDO::getSkuId, skuId);
  36 + }
  37 +
  38 + default Long selectCountByAreaId(Long areaId) {
  39 + return selectCount(WmsShipmentOrderDetailDO::getAreaId, areaId);
  40 + }
  41 +
34 } 42 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/shipment/WmsShipmentOrderMapper.java
@@ -39,4 +39,16 @@ public interface WmsShipmentOrderMapper extends BaseMapperX&lt;WmsShipmentOrderDO&gt; @@ -39,4 +39,16 @@ public interface WmsShipmentOrderMapper extends BaseMapperX&lt;WmsShipmentOrderDO&gt;
39 return selectOne(WmsShipmentOrderDO::getNo, no); 39 return selectOne(WmsShipmentOrderDO::getNo, no);
40 } 40 }
41 41
  42 + default Long selectCountByMerchantId(Long merchantId) {
  43 + return selectCount(WmsShipmentOrderDO::getMerchantId, merchantId);
  44 + }
  45 +
  46 + default Long selectCountByWarehouseId(Long warehouseId) {
  47 + return selectCount(WmsShipmentOrderDO::getWarehouseId, warehouseId);
  48 + }
  49 +
  50 + default Long selectCountByAreaId(Long areaId) {
  51 + return selectCount(WmsShipmentOrderDO::getAreaId, areaId);
  52 + }
  53 +
42 } 54 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java
@@ -12,7 +12,5 @@ public interface DictTypeConstants { @@ -12,7 +12,5 @@ public interface DictTypeConstants {
12 String RECEIPT_ORDER_TYPE = "wms_receipt_order_type"; 12 String RECEIPT_ORDER_TYPE = "wms_receipt_order_type";
13 String SHIPMENT_ORDER_TYPE = "wms_shipment_order_type"; 13 String SHIPMENT_ORDER_TYPE = "wms_shipment_order_type";
14 String INVENTORY_HISTORY_TYPE = "wms_inventory_history_type"; 14 String INVENTORY_HISTORY_TYPE = "wms_inventory_history_type";
15 - String MOVEMENT_STATUS = "wms_movement_status";  
16 - String CHECK_STATUS = "wms_check_status";  
17 15
18 } 16 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java
@@ -17,12 +17,14 @@ public interface ErrorCodeConstants { @@ -17,12 +17,14 @@ public interface ErrorCodeConstants {
17 ErrorCode WAREHOUSE_NAME_DUPLICATE = new ErrorCode(1_060_100_001, "仓库名称重复"); 17 ErrorCode WAREHOUSE_NAME_DUPLICATE = new ErrorCode(1_060_100_001, "仓库名称重复");
18 ErrorCode WAREHOUSE_CODE_DUPLICATE = new ErrorCode(1_060_100_002, "仓库编号重复"); 18 ErrorCode WAREHOUSE_CODE_DUPLICATE = new ErrorCode(1_060_100_002, "仓库编号重复");
19 ErrorCode WAREHOUSE_HAS_AREA = new ErrorCode(1_060_100_003, "删除失败!请先删除该仓库下的库区!"); 19 ErrorCode WAREHOUSE_HAS_AREA = new ErrorCode(1_060_100_003, "删除失败!请先删除该仓库下的库区!");
  20 + ErrorCode WAREHOUSE_HAS_ORDER = new ErrorCode(1_060_100_004, "删除失败!仓库已被{}使用!");
20 21
21 // ========== WMS 基础数据-库区 1-060-101-000 ========== 22 // ========== WMS 基础数据-库区 1-060-101-000 ==========
22 ErrorCode WAREHOUSE_AREA_NOT_EXISTS = new ErrorCode(1_060_101_000, "库区不存在"); 23 ErrorCode WAREHOUSE_AREA_NOT_EXISTS = new ErrorCode(1_060_101_000, "库区不存在");
23 ErrorCode WAREHOUSE_AREA_NAME_DUPLICATE = new ErrorCode(1_060_101_001, "库区名称重复"); 24 ErrorCode WAREHOUSE_AREA_NAME_DUPLICATE = new ErrorCode(1_060_101_001, "库区名称重复");
24 ErrorCode WAREHOUSE_AREA_CODE_DUPLICATE = new ErrorCode(1_060_101_002, "库区编号重复"); 25 ErrorCode WAREHOUSE_AREA_CODE_DUPLICATE = new ErrorCode(1_060_101_002, "库区编号重复");
25 ErrorCode WAREHOUSE_AREA_NOT_MATCH_WAREHOUSE = new ErrorCode(1_060_101_003, "库区不属于仓库"); 26 ErrorCode WAREHOUSE_AREA_NOT_MATCH_WAREHOUSE = new ErrorCode(1_060_101_003, "库区不属于仓库");
  27 + ErrorCode WAREHOUSE_AREA_HAS_ORDER = new ErrorCode(1_060_101_004, "删除失败!库区已被{}使用!");
26 28
27 // ========== WMS 基础数据-商品分类 1-060-102-000 ========== 29 // ========== WMS 基础数据-商品分类 1-060-102-000 ==========
28 ErrorCode ITEM_CATEGORY_NOT_EXISTS = new ErrorCode(1_060_102_000, "商品分类不存在"); 30 ErrorCode ITEM_CATEGORY_NOT_EXISTS = new ErrorCode(1_060_102_000, "商品分类不存在");
@@ -44,11 +46,13 @@ public interface ErrorCodeConstants { @@ -44,11 +46,13 @@ public interface ErrorCodeConstants {
44 ErrorCode ITEM_SKU_NAME_DUPLICATE = new ErrorCode(1_060_104_003, "商品规格名称【{}】重复"); 46 ErrorCode ITEM_SKU_NAME_DUPLICATE = new ErrorCode(1_060_104_003, "商品规格名称【{}】重复");
45 ErrorCode ITEM_SKU_NOT_EXISTS = new ErrorCode(1_060_104_004, "商品规格不存在"); 47 ErrorCode ITEM_SKU_NOT_EXISTS = new ErrorCode(1_060_104_004, "商品规格不存在");
46 ErrorCode ITEM_SKU_HAS_INVENTORY = new ErrorCode(1_060_104_005, "删除失败!商品规格【{}】已被库存业务使用!"); 48 ErrorCode ITEM_SKU_HAS_INVENTORY = new ErrorCode(1_060_104_005, "删除失败!商品规格【{}】已被库存业务使用!");
  49 + ErrorCode ITEM_SKU_HAS_ORDER = new ErrorCode(1_060_104_006, "删除失败!商品规格【{}】已被{}使用!");
47 50
48 // ========== WMS 基础数据-往来企业 1-060-105-000 ========== 51 // ========== WMS 基础数据-往来企业 1-060-105-000 ==========
49 ErrorCode MERCHANT_NOT_EXISTS = new ErrorCode(1_060_105_000, "往来企业不存在"); 52 ErrorCode MERCHANT_NOT_EXISTS = new ErrorCode(1_060_105_000, "往来企业不存在");
50 ErrorCode MERCHANT_NOT_SUPPLIER = new ErrorCode(1_060_105_001, "往来企业必须是供应商或客户/供应商类型"); 53 ErrorCode MERCHANT_NOT_SUPPLIER = new ErrorCode(1_060_105_001, "往来企业必须是供应商或客户/供应商类型");
51 ErrorCode MERCHANT_NOT_CUSTOMER = new ErrorCode(1_060_105_002, "往来企业必须是客户或客户/供应商类型"); 54 ErrorCode MERCHANT_NOT_CUSTOMER = new ErrorCode(1_060_105_002, "往来企业必须是客户或客户/供应商类型");
  55 + ErrorCode MERCHANT_HAS_ORDER = new ErrorCode(1_060_105_003, "删除失败!往来企业已被{}使用!");
52 56
53 // ========== WMS 入库单 1-060-200-000 ========== 57 // ========== WMS 入库单 1-060-200-000 ==========
54 ErrorCode RECEIPT_ORDER_NOT_EXISTS = new ErrorCode(1_060_200_000, "入库单不存在"); 58 ErrorCode RECEIPT_ORDER_NOT_EXISTS = new ErrorCode(1_060_200_000, "入库单不存在");
@@ -68,6 +72,25 @@ public interface ErrorCodeConstants { @@ -68,6 +72,25 @@ public interface ErrorCodeConstants {
68 ErrorCode SHIPMENT_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_201_005, "出库单状态不是草稿或已作废,不能删除"); 72 ErrorCode SHIPMENT_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_201_005, "出库单状态不是草稿或已作废,不能删除");
69 ErrorCode SHIPMENT_ORDER_DETAIL_NOT_EXISTS = new ErrorCode(1_060_201_007, "出库单明细不存在"); 73 ErrorCode SHIPMENT_ORDER_DETAIL_NOT_EXISTS = new ErrorCode(1_060_201_007, "出库单明细不存在");
70 74
  75 + // ========== WMS 移库单 1-060-202-000 ==========
  76 + ErrorCode MOVEMENT_ORDER_NOT_EXISTS = new ErrorCode(1_060_202_000, "移库单不存在");
  77 + ErrorCode MOVEMENT_ORDER_NO_DUPLICATE = new ErrorCode(1_060_202_001, "移库单号重复");
  78 + ErrorCode MOVEMENT_ORDER_STATUS_NOT_PREPARE = new ErrorCode(1_060_202_002, "移库单状态不是草稿,不能操作");
  79 + ErrorCode MOVEMENT_ORDER_DETAIL_REQUIRED = new ErrorCode(1_060_202_003, "移库单至少包含一条明细");
  80 + ErrorCode MOVEMENT_ORDER_AREA_REQUIRED = new ErrorCode(1_060_202_004, "库区模式下,移库单库区不能为空");
  81 + ErrorCode MOVEMENT_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_202_005, "移库单状态不是草稿或已作废,不能删除");
  82 + ErrorCode MOVEMENT_ORDER_DETAIL_NOT_EXISTS = new ErrorCode(1_060_202_006, "移库单明细不存在");
  83 + ErrorCode MOVEMENT_ORDER_WAREHOUSE_AREA_SAME = new ErrorCode(1_060_202_007, "来源仓库库区和目标仓库库区不能相同");
  84 +
  85 + // ========== WMS 盘库单 1-060-203-000 ==========
  86 + ErrorCode CHECK_ORDER_NOT_EXISTS = new ErrorCode(1_060_203_000, "盘库单不存在");
  87 + ErrorCode CHECK_ORDER_NO_DUPLICATE = new ErrorCode(1_060_203_001, "盘库单号重复");
  88 + ErrorCode CHECK_ORDER_STATUS_NOT_PREPARE = new ErrorCode(1_060_203_002, "盘库单状态不是草稿,不能操作");
  89 + ErrorCode CHECK_ORDER_DETAIL_REQUIRED = new ErrorCode(1_060_203_003, "盘库单至少包含一条明细");
  90 + ErrorCode CHECK_ORDER_AREA_REQUIRED = new ErrorCode(1_060_203_004, "库区模式下,盘库单库区不能为空");
  91 + ErrorCode CHECK_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_203_005, "盘库单状态不是草稿或已作废,不能删除");
  92 + ErrorCode CHECK_ORDER_DETAIL_NOT_EXISTS = new ErrorCode(1_060_203_006, "盘库单明细不存在");
  93 +
71 // ========== WMS 库存 1-060-300-000 ========== 94 // ========== WMS 库存 1-060-300-000 ==========
72 ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_000, "库存不足,当前库存:{},变更数量:{}"); 95 ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_000, "库存不足,当前库存:{},变更数量:{}");
73 ErrorCode INVENTORY_DETAIL_NOT_EXISTS = new ErrorCode(1_060_300_001, "库存明细不存在"); 96 ErrorCode INVENTORY_DETAIL_NOT_EXISTS = new ErrorCode(1_060_300_001, "库存明细不存在");
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/order/WmsOrderTypeConstants.java
@@ -12,6 +12,13 @@ public final class WmsOrderTypeConstants { @@ -12,6 +12,13 @@ public final class WmsOrderTypeConstants {
12 12
13 private WmsOrderTypeConstants() {} 13 private WmsOrderTypeConstants() {}
14 14
  15 + // ========== 单据名称 ==========
  16 +
  17 + public static final String ORDER_NAME_RECEIPT = "入库单";
  18 + public static final String ORDER_NAME_SHIPMENT = "出库单";
  19 + public static final String ORDER_NAME_MOVEMENT = "移库单";
  20 + public static final String ORDER_NAME_CHECK = "盘库单";
  21 +
15 // ========== 入库单类型 [100, 200) ========== 22 // ========== 入库单类型 [100, 200) ==========
16 23
17 public static final int RECEIPT_PRODUCTION = 100; // 生产入库:WmsReceiptOrderDO 24 public static final int RECEIPT_PRODUCTION = 100; // 生产入库:WmsReceiptOrderDO
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryDetailService.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.wms.service.inventory; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.wms.service.inventory;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInventoryDetailPageReqVO; 4 import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInventoryDetailPageReqVO;
5 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO; 5 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO;
  6 +import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
6 7
7 import java.util.List; 8 import java.util.List;
8 9
@@ -28,4 +29,11 @@ public interface WmsInventoryDetailService { @@ -28,4 +29,11 @@ public interface WmsInventoryDetailService {
28 */ 29 */
29 void createInventoryDetailList(List<WmsInventoryDetailDO> list); 30 void createInventoryDetailList(List<WmsInventoryDetailDO> list);
30 31
  32 + /**
  33 + * 扣减库存明细剩余数量
  34 + *
  35 + * @param items 库存变更明细列表
  36 + */
  37 + void decreaseInventoryDetailList(List<WmsInventoryChangeReqDTO.Item> items);
  38 +
31 } 39 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryDetailServiceImpl.java
1 package cn.iocoder.yudao.module.wms.service.inventory; 1 package cn.iocoder.yudao.module.wms.service.inventory;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInventoryDetailPageReqVO; 6 import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInventoryDetailPageReqVO;
6 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO; 7 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO;
7 import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryDetailMapper; 8 import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryDetailMapper;
  9 +import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO;
8 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
9 import org.springframework.stereotype.Service; 11 import org.springframework.stereotype.Service;
10 import org.springframework.transaction.annotation.Transactional; 12 import org.springframework.transaction.annotation.Transactional;
11 import org.springframework.validation.annotation.Validated; 13 import org.springframework.validation.annotation.Validated;
12 14
  15 +import java.math.BigDecimal;
  16 +import java.util.Comparator;
  17 +import java.util.LinkedHashMap;
13 import java.util.List; 18 import java.util.List;
  19 +import java.util.Map;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  23 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  24 +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.INVENTORY_DETAIL_NOT_EXISTS;
  25 +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.INVENTORY_DETAIL_REMAIN_QUANTITY_NOT_ENOUGH;
14 26
15 /** 27 /**
16 * WMS 库存明细 Service 实现类 28 * WMS 库存明细 Service 实现类
@@ -38,4 +50,47 @@ public class WmsInventoryDetailServiceImpl implements WmsInventoryDetailService @@ -38,4 +50,47 @@ public class WmsInventoryDetailServiceImpl implements WmsInventoryDetailService
38 inventoryDetailMapper.insertBatch(list); 50 inventoryDetailMapper.insertBatch(list);
39 } 51 }
40 52
  53 + @Override
  54 + @Transactional(rollbackFor = Exception.class)
  55 + public void decreaseInventoryDetailList(List<WmsInventoryChangeReqDTO.Item> items) {
  56 + if (CollUtil.isEmpty(items)) {
  57 + return;
  58 + }
  59 + if (items.stream().anyMatch(item -> item.getInventoryDetailId() == null)) {
  60 + throw exception(INVENTORY_DETAIL_NOT_EXISTS);
  61 + }
  62 + List<WmsInventoryChangeReqDTO.Item> sortedItems = items.stream()
  63 + .sorted(Comparator.comparing(WmsInventoryChangeReqDTO.Item::getInventoryDetailId))
  64 + .toList();
  65 +
  66 + // 1. 按库存明细编号固定顺序加锁,避免多明细并发扣减时交叉拿锁
  67 + Map<Long, WmsInventoryDetailDO> detailMap = convertMap(inventoryDetailMapper.selectListByIdsForUpdate(
  68 + convertSet(sortedItems, WmsInventoryChangeReqDTO.Item::getInventoryDetailId)),
  69 + WmsInventoryDetailDO::getId);
  70 +
  71 + // 2. 校验库存明细归属,并合并相同库存明细的扣减数量
  72 + Map<Long, BigDecimal> decreaseQuantityMap = new LinkedHashMap<>();
  73 + for (WmsInventoryChangeReqDTO.Item item : sortedItems) {
  74 + WmsInventoryDetailDO detail = detailMap.get(item.getInventoryDetailId());
  75 + if (detail == null
  76 + || ObjectUtil.notEqual(detail.getSkuId(), item.getSkuId())
  77 + || ObjectUtil.notEqual(detail.getWarehouseId(), item.getWarehouseId())
  78 + || ObjectUtil.notEqual(detail.getAreaId(), item.getAreaId())) {
  79 + throw exception(INVENTORY_DETAIL_NOT_EXISTS);
  80 + }
  81 + decreaseQuantityMap.merge(detail.getId(), item.getQuantity().abs(), BigDecimal::add);
  82 + }
  83 +
  84 + // 3. 校验并扣减库存明细剩余数量
  85 + for (Map.Entry<Long, BigDecimal> entry : decreaseQuantityMap.entrySet()) {
  86 + WmsInventoryDetailDO detail = detailMap.get(entry.getKey());
  87 + BigDecimal decreaseQuantity = entry.getValue();
  88 + if (detail.getRemainQuantity().compareTo(decreaseQuantity) < 0) {
  89 + throw exception(INVENTORY_DETAIL_REMAIN_QUANTITY_NOT_ENOUGH,
  90 + detail.getRemainQuantity(), decreaseQuantity);
  91 + }
  92 + inventoryDetailMapper.updateRemainQuantityIncr(detail.getId(), decreaseQuantity.negate());
  93 + }
  94 + }
  95 +
41 } 96 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImpl.java
@@ -19,7 +19,13 @@ import org.springframework.validation.annotation.Validated; @@ -19,7 +19,13 @@ import org.springframework.validation.annotation.Validated;
19 19
20 import java.math.BigDecimal; 20 import java.math.BigDecimal;
21 import java.util.ArrayList; 21 import java.util.ArrayList;
  22 +import java.util.Comparator;
  23 +import java.util.IdentityHashMap;
22 import java.util.List; 24 import java.util.List;
  25 +import java.util.Map;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*;
23 29
24 /** 30 /**
25 * WMS 库存 Service 实现类 31 * WMS 库存 Service 实现类
@@ -61,44 +67,72 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { @@ -61,44 +67,72 @@ public class WmsInventoryServiceImpl implements WmsInventoryService {
61 return; 67 return;
62 } 68 }
63 69
64 - // 1. 变更库存余额,并构建库存流水与批次库存明细 70 + // 1. 按固定顺序变更库存余额,避免多 SKU/仓库并发操作时交叉拿锁
  71 + Map<WmsInventoryChangeReqDTO.Item, Tuple> resultMap = changeInventoryList(reqDTO.getItems());
  72 +
  73 + // 2. 构建库存流水与批次库存明细
65 boolean batchEnabled = wmsProperties.isBatchEnabled(); 74 boolean batchEnabled = wmsProperties.isBatchEnabled();
66 List<WmsInventoryHistoryDO> histories = new ArrayList<>(reqDTO.getItems().size()); 75 List<WmsInventoryHistoryDO> histories = new ArrayList<>(reqDTO.getItems().size());
67 List<WmsInventoryDetailDO> details = batchEnabled ? new ArrayList<>(reqDTO.getItems().size()) : List.of(); 76 List<WmsInventoryDetailDO> details = batchEnabled ? new ArrayList<>(reqDTO.getItems().size()) : List.of();
  77 + List<WmsInventoryChangeReqDTO.Item> decreaseItems = batchEnabled ? new ArrayList<>(reqDTO.getItems().size()) : List.of();
68 for (WmsInventoryChangeReqDTO.Item item : reqDTO.getItems()) { 78 for (WmsInventoryChangeReqDTO.Item item : reqDTO.getItems()) {
69 - // 1.1 变更库存余额  
70 - Tuple result = changeInventory0(item);  
71 - // 1.2 构建库存流水与批次库存明细  
72 - histories.add(buildInventoryHistory(reqDTO, item, result)); 79 + histories.add(buildInventoryHistory(reqDTO, item, resultMap.get(item)));
73 if (batchEnabled) { 80 if (batchEnabled) {
74 - details.add(buildInventoryDetail(reqDTO, item)); 81 + if (isIncrease(item)) {
  82 + details.add(buildInventoryDetail(reqDTO, item));
  83 + } else {
  84 + decreaseItems.add(item);
  85 + }
75 } 86 }
76 } 87 }
77 88
78 - // 2.1 批量写入库存流水  
79 - inventoryHistoryService.createInventoryHistoryList(histories); 89 + // 3. 按固定顺序变更批次库存明细
80 if (batchEnabled) { 90 if (batchEnabled) {
81 - // 2.2 批量写入批次库存明细 91 + inventoryDetailService.decreaseInventoryDetailList(decreaseItems);
82 inventoryDetailService.createInventoryDetailList(details); 92 inventoryDetailService.createInventoryDetailList(details);
83 } 93 }
  94 +
  95 + // 4. 批量写入库存流水
  96 + inventoryHistoryService.createInventoryHistoryList(histories);
84 } 97 }
85 98
86 /** 99 /**
87 - * 变更库存余额 100 + * 批量变更库存余额
88 * 101 *
89 - * @param item 库存变更明细  
90 - * @return 变更前数量、变更后数量 102 + * 按 SKU、仓库、库区固定顺序拿锁;相同库存余额只更新一次,避免同一批操作内反复更新。
  103 + *
  104 + * @param items 库存变更明细列表
  105 + * @return 每条变更明细对应的变更前数量、变更后数量
91 */ 106 */
92 - private Tuple changeInventory0(WmsInventoryChangeReqDTO.Item item) {  
93 - // 1. 获取或创建库存余额,使用 FOR UPDATE 锁定行  
94 - WmsInventoryDO inventory = getOrCreateInventory(item);  
95 - // 2. 变更库存余额  
96 - BigDecimal beforeQuantity = inventory.getQuantity();  
97 - BigDecimal afterQuantity = beforeQuantity.add(item.getQuantity());  
98 - inventoryMapper.updateQuantity(inventory.getId(), item.getQuantity());  
99 - return new Tuple(beforeQuantity, afterQuantity);  
100 - } 107 + private Map<WmsInventoryChangeReqDTO.Item, Tuple> changeInventoryList(List<WmsInventoryChangeReqDTO.Item> items) {
  108 + List<WmsInventoryChangeReqDTO.Item> sortedItems = sortByInventoryKey(items);
  109 + Map<WmsInventoryChangeReqDTO.Item, Tuple> resultMap = new IdentityHashMap<>(items.size());
  110 + for (int i = 0; i < sortedItems.size(); ) {
  111 + WmsInventoryChangeReqDTO.Item firstItem = sortedItems.get(i);
  112 + WmsInventoryDO inventory = getOrCreateInventory(firstItem);
  113 + BigDecimal currentQuantity = inventory.getQuantity();
  114 + BigDecimal totalChangeQuantity = BigDecimal.ZERO;
  115 +
  116 + int j = i;
  117 + while (j < sortedItems.size() && isSameInventoryKey(firstItem, sortedItems.get(j))) {
  118 + WmsInventoryChangeReqDTO.Item item = sortedItems.get(j);
  119 + BigDecimal beforeQuantity = currentQuantity;
  120 + currentQuantity = currentQuantity.add(item.getQuantity());
  121 + if (currentQuantity.compareTo(BigDecimal.ZERO) < 0) {
  122 + throw exception(INVENTORY_QUANTITY_NOT_ENOUGH, beforeQuantity, item.getQuantity());
  123 + }
  124 + resultMap.put(item, new Tuple(beforeQuantity, currentQuantity));
  125 + totalChangeQuantity = totalChangeQuantity.add(item.getQuantity());
  126 + j++;
  127 + }
101 128
  129 + if (totalChangeQuantity.compareTo(BigDecimal.ZERO) != 0) {
  130 + inventoryMapper.updateQuantity(inventory.getId(), totalChangeQuantity);
  131 + }
  132 + i = j;
  133 + }
  134 + return resultMap;
  135 + }
102 136
103 private WmsInventoryDO getOrCreateInventory(WmsInventoryChangeReqDTO.Item item) { 137 private WmsInventoryDO getOrCreateInventory(WmsInventoryChangeReqDTO.Item item) {
104 // 1. 查询库存余额,使用 FOR UPDATE 锁定行 138 // 1. 查询库存余额,使用 FOR UPDATE 锁定行
@@ -147,4 +181,23 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { @@ -147,4 +181,23 @@ public class WmsInventoryServiceImpl implements WmsInventoryService {
147 .setOrderId(reqDTO.getOrderId()).setOrderNo(reqDTO.getOrderNo()).setOrderType(reqDTO.getOrderType()); 181 .setOrderId(reqDTO.getOrderId()).setOrderNo(reqDTO.getOrderNo()).setOrderType(reqDTO.getOrderType());
148 } 182 }
149 183
  184 + private boolean isIncrease(WmsInventoryChangeReqDTO.Item item) {
  185 + return item.getQuantity().compareTo(BigDecimal.ZERO) > 0;
  186 + }
  187 +
  188 + private static List<WmsInventoryChangeReqDTO.Item> sortByInventoryKey(List<WmsInventoryChangeReqDTO.Item> items) {
  189 + return items.stream()
  190 + .sorted(Comparator.comparing(WmsInventoryChangeReqDTO.Item::getSkuId)
  191 + .thenComparing(WmsInventoryChangeReqDTO.Item::getWarehouseId)
  192 + .thenComparing(WmsInventoryChangeReqDTO.Item::getAreaId))
  193 + .toList();
  194 + }
  195 +
  196 + private static boolean isSameInventoryKey(WmsInventoryChangeReqDTO.Item item1,
  197 + WmsInventoryChangeReqDTO.Item item2) {
  198 + return item1.getSkuId().equals(item2.getSkuId())
  199 + && item1.getWarehouseId().equals(item2.getWarehouseId())
  200 + && item1.getAreaId().equals(item2.getAreaId());
  201 + }
  202 +
150 } 203 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/item/WmsItemSkuServiceImpl.java
@@ -8,6 +8,10 @@ import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSav @@ -8,6 +8,10 @@ import cn.iocoder.yudao.module.wms.controller.admin.md.item.vo.sku.WmsItemSkuSav
8 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; 8 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
9 import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemSkuMapper; 9 import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemSkuMapper;
10 import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService; 10 import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService;
  11 +import cn.iocoder.yudao.module.wms.service.order.check.WmsCheckOrderDetailService;
  12 +import cn.iocoder.yudao.module.wms.service.order.movement.WmsMovementOrderDetailService;
  13 +import cn.iocoder.yudao.module.wms.service.order.receipt.WmsReceiptOrderDetailService;
  14 +import cn.iocoder.yudao.module.wms.service.order.shipment.WmsShipmentOrderDetailService;
11 import cn.iocoder.yudao.module.wms.util.WmsUtils; 15 import cn.iocoder.yudao.module.wms.util.WmsUtils;
12 import jakarta.annotation.Resource; 16 import jakarta.annotation.Resource;
13 import org.springframework.context.annotation.Lazy; 17 import org.springframework.context.annotation.Lazy;
@@ -23,6 +27,7 @@ import java.util.Set; @@ -23,6 +27,7 @@ import java.util.Set;
23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
24 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
25 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*; 29 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*;
  30 +import static cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeConstants.*;
26 31
27 /** 32 /**
28 * WMS 商品 SKU Service 实现类 33 * WMS 商品 SKU Service 实现类
@@ -38,6 +43,18 @@ public class WmsItemSkuServiceImpl implements WmsItemSkuService { @@ -38,6 +43,18 @@ public class WmsItemSkuServiceImpl implements WmsItemSkuService {
38 @Resource 43 @Resource
39 @Lazy // 延迟加载,避免循环依赖 44 @Lazy // 延迟加载,避免循环依赖
40 private WmsInventoryService inventoryService; 45 private WmsInventoryService inventoryService;
  46 + @Resource
  47 + @Lazy // 延迟加载,避免循环依赖
  48 + private WmsReceiptOrderDetailService receiptOrderDetailService;
  49 + @Resource
  50 + @Lazy // 延迟加载,避免循环依赖
  51 + private WmsShipmentOrderDetailService shipmentOrderDetailService;
  52 + @Resource
  53 + @Lazy // 延迟加载,避免循环依赖
  54 + private WmsMovementOrderDetailService movementOrderDetailService;
  55 + @Resource
  56 + @Lazy // 延迟加载,避免循环依赖
  57 + private WmsCheckOrderDetailService checkOrderDetailService;
41 58
42 @Override 59 @Override
43 @Transactional(rollbackFor = Exception.class) 60 @Transactional(rollbackFor = Exception.class)
@@ -146,12 +163,28 @@ public class WmsItemSkuServiceImpl implements WmsItemSkuService { @@ -146,12 +163,28 @@ public class WmsItemSkuServiceImpl implements WmsItemSkuService {
146 return; 163 return;
147 } 164 }
148 for (WmsItemSkuDO sku : skus) { 165 for (WmsItemSkuDO sku : skus) {
  166 + validateItemSkuOrderUnused(sku);
149 if (inventoryService.getInventoryCountBySkuId(sku.getId()) > 0) { 167 if (inventoryService.getInventoryCountBySkuId(sku.getId()) > 0) {
150 throw exception(ITEM_SKU_HAS_INVENTORY, sku.getName()); 168 throw exception(ITEM_SKU_HAS_INVENTORY, sku.getName());
151 } 169 }
152 } 170 }
153 } 171 }
154 172
  173 + private void validateItemSkuOrderUnused(WmsItemSkuDO sku) {
  174 + if (receiptOrderDetailService.getReceiptOrderDetailCountBySkuId(sku.getId()) > 0) {
  175 + throw exception(ITEM_SKU_HAS_ORDER, sku.getName(), ORDER_NAME_RECEIPT);
  176 + }
  177 + if (shipmentOrderDetailService.getShipmentOrderDetailCountBySkuId(sku.getId()) > 0) {
  178 + throw exception(ITEM_SKU_HAS_ORDER, sku.getName(), ORDER_NAME_SHIPMENT);
  179 + }
  180 + if (movementOrderDetailService.getMovementOrderDetailCountBySkuId(sku.getId()) > 0) {
  181 + throw exception(ITEM_SKU_HAS_ORDER, sku.getName(), ORDER_NAME_MOVEMENT);
  182 + }
  183 + if (checkOrderDetailService.getCheckOrderDetailCountBySkuId(sku.getId()) > 0) {
  184 + throw exception(ITEM_SKU_HAS_ORDER, sku.getName(), ORDER_NAME_CHECK);
  185 + }
  186 + }
  187 +
155 /** 188 /**
156 * 填充空的条形码 189 * 填充空的条形码
157 * 190 *
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/merchant/WmsMerchantServiceImpl.java
@@ -10,7 +10,10 @@ import cn.iocoder.yudao.module.wms.controller.admin.md.merchant.vo.WmsMerchantSa @@ -10,7 +10,10 @@ import cn.iocoder.yudao.module.wms.controller.admin.md.merchant.vo.WmsMerchantSa
10 import cn.iocoder.yudao.module.wms.dal.dataobject.md.merchant.WmsMerchantDO; 10 import cn.iocoder.yudao.module.wms.dal.dataobject.md.merchant.WmsMerchantDO;
11 import cn.iocoder.yudao.module.wms.dal.mysql.md.merchant.WmsMerchantMapper; 11 import cn.iocoder.yudao.module.wms.dal.mysql.md.merchant.WmsMerchantMapper;
12 import cn.iocoder.yudao.module.wms.enums.md.WmsMerchantTypeEnum; 12 import cn.iocoder.yudao.module.wms.enums.md.WmsMerchantTypeEnum;
  13 +import cn.iocoder.yudao.module.wms.service.order.receipt.WmsReceiptOrderService;
  14 +import cn.iocoder.yudao.module.wms.service.order.shipment.WmsShipmentOrderService;
13 import jakarta.annotation.Resource; 15 import jakarta.annotation.Resource;
  16 +import org.springframework.context.annotation.Lazy;
14 import org.springframework.stereotype.Service; 17 import org.springframework.stereotype.Service;
15 import org.springframework.validation.annotation.Validated; 18 import org.springframework.validation.annotation.Validated;
16 19
@@ -19,6 +22,7 @@ import java.util.List; @@ -19,6 +22,7 @@ import java.util.List;
19 22
20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
21 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*; 24 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*;
  25 +import static cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeConstants.*;
22 26
23 /** 27 /**
24 * WMS 往来企业 Service 实现类 28 * WMS 往来企业 Service 实现类
@@ -31,6 +35,12 @@ public class WmsMerchantServiceImpl implements WmsMerchantService { @@ -31,6 +35,12 @@ public class WmsMerchantServiceImpl implements WmsMerchantService {
31 35
32 @Resource 36 @Resource
33 private WmsMerchantMapper merchantMapper; 37 private WmsMerchantMapper merchantMapper;
  38 + @Resource
  39 + @Lazy // 延迟加载,避免循环依赖
  40 + private WmsReceiptOrderService receiptOrderService;
  41 + @Resource
  42 + @Lazy // 延迟加载,避免循环依赖
  43 + private WmsShipmentOrderService shipmentOrderService;
34 44
35 @Override 45 @Override
36 public Long createMerchant(WmsMerchantSaveReqVO createReqVO) { 46 public Long createMerchant(WmsMerchantSaveReqVO createReqVO) {
@@ -53,12 +63,22 @@ public class WmsMerchantServiceImpl implements WmsMerchantService { @@ -53,12 +63,22 @@ public class WmsMerchantServiceImpl implements WmsMerchantService {
53 public void deleteMerchant(Long id) { 63 public void deleteMerchant(Long id) {
54 // 校验存在 64 // 校验存在
55 validateMerchantExists(id); 65 validateMerchantExists(id);
56 - // TODO 入库/出库单实现后,校验往来企业不存在业务关联后再删除 66 + // 校验未被单据使用
  67 + validateMerchantUnused(id);
57 68
58 // 删除 69 // 删除
59 merchantMapper.deleteById(id); 70 merchantMapper.deleteById(id);
60 } 71 }
61 72
  73 + private void validateMerchantUnused(Long id) {
  74 + if (receiptOrderService.getReceiptOrderCountByMerchantId(id) > 0) {
  75 + throw exception(MERCHANT_HAS_ORDER, ORDER_NAME_RECEIPT);
  76 + }
  77 + if (shipmentOrderService.getShipmentOrderCountByMerchantId(id) > 0) {
  78 + throw exception(MERCHANT_HAS_ORDER, ORDER_NAME_SHIPMENT);
  79 + }
  80 + }
  81 +
62 @Override 82 @Override
63 public WmsMerchantDO validateMerchantExists(Long id) { 83 public WmsMerchantDO validateMerchantExists(Long id) {
64 WmsMerchantDO merchant = merchantMapper.selectById(id); 84 WmsMerchantDO merchant = merchantMapper.selectById(id);
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/warehouse/WmsWarehouseAreaServiceImpl.java
@@ -11,7 +11,16 @@ import cn.iocoder.yudao.module.wms.controller.admin.md.warehouse.vo.area.WmsWare @@ -11,7 +11,16 @@ import cn.iocoder.yudao.module.wms.controller.admin.md.warehouse.vo.area.WmsWare
11 import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseAreaDO; 11 import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseAreaDO;
12 import cn.iocoder.yudao.module.wms.dal.mysql.md.warehouse.WmsWarehouseAreaMapper; 12 import cn.iocoder.yudao.module.wms.dal.mysql.md.warehouse.WmsWarehouseAreaMapper;
13 import cn.iocoder.yudao.module.wms.framework.config.WmsProperties; 13 import cn.iocoder.yudao.module.wms.framework.config.WmsProperties;
  14 +import cn.iocoder.yudao.module.wms.service.order.check.WmsCheckOrderDetailService;
  15 +import cn.iocoder.yudao.module.wms.service.order.check.WmsCheckOrderService;
  16 +import cn.iocoder.yudao.module.wms.service.order.movement.WmsMovementOrderDetailService;
  17 +import cn.iocoder.yudao.module.wms.service.order.movement.WmsMovementOrderService;
  18 +import cn.iocoder.yudao.module.wms.service.order.receipt.WmsReceiptOrderDetailService;
  19 +import cn.iocoder.yudao.module.wms.service.order.receipt.WmsReceiptOrderService;
  20 +import cn.iocoder.yudao.module.wms.service.order.shipment.WmsShipmentOrderDetailService;
  21 +import cn.iocoder.yudao.module.wms.service.order.shipment.WmsShipmentOrderService;
14 import jakarta.annotation.Resource; 22 import jakarta.annotation.Resource;
  23 +import org.springframework.context.annotation.Lazy;
15 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
16 import org.springframework.validation.annotation.Validated; 25 import org.springframework.validation.annotation.Validated;
17 26
@@ -21,6 +30,7 @@ import java.util.List; @@ -21,6 +30,7 @@ import java.util.List;
21 30
22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 31 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
23 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*; 32 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*;
  33 +import static cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeConstants.*;
24 34
25 /** 35 /**
26 * WMS 库区 Service 实现类 36 * WMS 库区 Service 实现类
@@ -37,6 +47,30 @@ public class WmsWarehouseAreaServiceImpl implements WmsWarehouseAreaService { @@ -37,6 +47,30 @@ public class WmsWarehouseAreaServiceImpl implements WmsWarehouseAreaService {
37 private WmsWarehouseService warehouseService; 47 private WmsWarehouseService warehouseService;
38 @Resource 48 @Resource
39 private WmsProperties wmsProperties; 49 private WmsProperties wmsProperties;
  50 + @Resource
  51 + @Lazy // 延迟加载,避免循环依赖
  52 + private WmsReceiptOrderService receiptOrderService;
  53 + @Resource
  54 + @Lazy // 延迟加载,避免循环依赖
  55 + private WmsReceiptOrderDetailService receiptOrderDetailService;
  56 + @Resource
  57 + @Lazy // 延迟加载,避免循环依赖
  58 + private WmsShipmentOrderService shipmentOrderService;
  59 + @Resource
  60 + @Lazy // 延迟加载,避免循环依赖
  61 + private WmsShipmentOrderDetailService shipmentOrderDetailService;
  62 + @Resource
  63 + @Lazy // 延迟加载,避免循环依赖
  64 + private WmsMovementOrderService movementOrderService;
  65 + @Resource
  66 + @Lazy // 延迟加载,避免循环依赖
  67 + private WmsMovementOrderDetailService movementOrderDetailService;
  68 + @Resource
  69 + @Lazy // 延迟加载,避免循环依赖
  70 + private WmsCheckOrderService checkOrderService;
  71 + @Resource
  72 + @Lazy // 延迟加载,避免循环依赖
  73 + private WmsCheckOrderDetailService checkOrderDetailService;
40 74
41 @Override 75 @Override
42 public Long createWarehouseArea(WmsWarehouseAreaSaveReqVO createReqVO) { 76 public Long createWarehouseArea(WmsWarehouseAreaSaveReqVO createReqVO) {
@@ -98,11 +132,32 @@ public class WmsWarehouseAreaServiceImpl implements WmsWarehouseAreaService { @@ -98,11 +132,32 @@ public class WmsWarehouseAreaServiceImpl implements WmsWarehouseAreaService {
98 validateAreaEnabled(); 132 validateAreaEnabled();
99 // 校验存在 133 // 校验存在
100 validateWarehouseAreaExists(id); 134 validateWarehouseAreaExists(id);
  135 + // 校验未被单据使用
  136 + validateWarehouseAreaUnused(id);
101 137
102 // 删除 138 // 删除
103 warehouseAreaMapper.deleteById(id); 139 warehouseAreaMapper.deleteById(id);
104 } 140 }
105 141
  142 + private void validateWarehouseAreaUnused(Long id) {
  143 + if (receiptOrderService.getReceiptOrderCountByAreaId(id) > 0
  144 + || receiptOrderDetailService.getReceiptOrderDetailCountByAreaId(id) > 0) {
  145 + throw exception(WAREHOUSE_AREA_HAS_ORDER, ORDER_NAME_RECEIPT);
  146 + }
  147 + if (shipmentOrderService.getShipmentOrderCountByAreaId(id) > 0
  148 + || shipmentOrderDetailService.getShipmentOrderDetailCountByAreaId(id) > 0) {
  149 + throw exception(WAREHOUSE_AREA_HAS_ORDER, ORDER_NAME_SHIPMENT);
  150 + }
  151 + if (movementOrderService.getMovementOrderCountByAreaId(id) > 0
  152 + || movementOrderDetailService.getMovementOrderDetailCountByAreaId(id) > 0) {
  153 + throw exception(WAREHOUSE_AREA_HAS_ORDER, ORDER_NAME_MOVEMENT);
  154 + }
  155 + if (checkOrderService.getCheckOrderCountByAreaId(id) > 0
  156 + || checkOrderDetailService.getCheckOrderDetailCountByAreaId(id) > 0) {
  157 + throw exception(WAREHOUSE_AREA_HAS_ORDER, ORDER_NAME_CHECK);
  158 + }
  159 + }
  160 +
106 @Override 161 @Override
107 public WmsWarehouseAreaDO validateWarehouseAreaExists(Long id) { 162 public WmsWarehouseAreaDO validateWarehouseAreaExists(Long id) {
108 validateAreaEnabled(); 163 validateAreaEnabled();
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/warehouse/WmsWarehouseServiceImpl.java
@@ -11,7 +11,12 @@ import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO; @@ -11,7 +11,12 @@ import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO;
11 import cn.iocoder.yudao.module.wms.dal.mysql.md.warehouse.WmsWarehouseAreaMapper; 11 import cn.iocoder.yudao.module.wms.dal.mysql.md.warehouse.WmsWarehouseAreaMapper;
12 import cn.iocoder.yudao.module.wms.dal.mysql.md.warehouse.WmsWarehouseMapper; 12 import cn.iocoder.yudao.module.wms.dal.mysql.md.warehouse.WmsWarehouseMapper;
13 import cn.iocoder.yudao.module.wms.framework.config.WmsProperties; 13 import cn.iocoder.yudao.module.wms.framework.config.WmsProperties;
  14 +import cn.iocoder.yudao.module.wms.service.order.check.WmsCheckOrderService;
  15 +import cn.iocoder.yudao.module.wms.service.order.movement.WmsMovementOrderService;
  16 +import cn.iocoder.yudao.module.wms.service.order.receipt.WmsReceiptOrderService;
  17 +import cn.iocoder.yudao.module.wms.service.order.shipment.WmsShipmentOrderService;
14 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
  19 +import org.springframework.context.annotation.Lazy;
15 import org.springframework.stereotype.Service; 20 import org.springframework.stereotype.Service;
16 import org.springframework.validation.annotation.Validated; 21 import org.springframework.validation.annotation.Validated;
17 22
@@ -21,6 +26,7 @@ import java.util.List; @@ -21,6 +26,7 @@ import java.util.List;
21 26
22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
23 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*; 28 import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*;
  29 +import static cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeConstants.*;
24 30
25 /** 31 /**
26 * WMS 仓库 Service 实现类 32 * WMS 仓库 Service 实现类
@@ -37,6 +43,18 @@ public class WmsWarehouseServiceImpl implements WmsWarehouseService { @@ -37,6 +43,18 @@ public class WmsWarehouseServiceImpl implements WmsWarehouseService {
37 private WmsWarehouseAreaMapper warehouseAreaMapper; 43 private WmsWarehouseAreaMapper warehouseAreaMapper;
38 @Resource 44 @Resource
39 private WmsProperties wmsProperties; 45 private WmsProperties wmsProperties;
  46 + @Resource
  47 + @Lazy // 延迟加载,避免循环依赖
  48 + private WmsReceiptOrderService receiptOrderService;
  49 + @Resource
  50 + @Lazy // 延迟加载,避免循环依赖
  51 + private WmsShipmentOrderService shipmentOrderService;
  52 + @Resource
  53 + @Lazy // 延迟加载,避免循环依赖
  54 + private WmsMovementOrderService movementOrderService;
  55 + @Resource
  56 + @Lazy // 延迟加载,避免循环依赖
  57 + private WmsCheckOrderService checkOrderService;
40 58
41 @Override 59 @Override
42 public Long createWarehouse(WmsWarehouseSaveReqVO createReqVO) { 60 public Long createWarehouse(WmsWarehouseSaveReqVO createReqVO) {
@@ -93,6 +111,8 @@ public class WmsWarehouseServiceImpl implements WmsWarehouseService { @@ -93,6 +111,8 @@ public class WmsWarehouseServiceImpl implements WmsWarehouseService {
93 public void deleteWarehouse(Long id) { 111 public void deleteWarehouse(Long id) {
94 // 校验存在 112 // 校验存在
95 validateWarehouseExists(id); 113 validateWarehouseExists(id);
  114 + // 校验未被单据使用
  115 + validateWarehouseUnused(id);
96 // 库区模式下,存在库区时不允许删除仓库 116 // 库区模式下,存在库区时不允许删除仓库
97 if (wmsProperties.isAreaEnabled() && warehouseAreaMapper.selectCountByWarehouseId(id) > 0) { 117 if (wmsProperties.isAreaEnabled() && warehouseAreaMapper.selectCountByWarehouseId(id) > 0) {
98 throw exception(WAREHOUSE_HAS_AREA); 118 throw exception(WAREHOUSE_HAS_AREA);
@@ -102,6 +122,21 @@ public class WmsWarehouseServiceImpl implements WmsWarehouseService { @@ -102,6 +122,21 @@ public class WmsWarehouseServiceImpl implements WmsWarehouseService {
102 warehouseMapper.deleteById(id); 122 warehouseMapper.deleteById(id);
103 } 123 }
104 124
  125 + private void validateWarehouseUnused(Long id) {
  126 + if (receiptOrderService.getReceiptOrderCountByWarehouseId(id) > 0) {
  127 + throw exception(WAREHOUSE_HAS_ORDER, ORDER_NAME_RECEIPT);
  128 + }
  129 + if (shipmentOrderService.getShipmentOrderCountByWarehouseId(id) > 0) {
  130 + throw exception(WAREHOUSE_HAS_ORDER, ORDER_NAME_SHIPMENT);
  131 + }
  132 + if (movementOrderService.getMovementOrderCountByWarehouseId(id) > 0) {
  133 + throw exception(WAREHOUSE_HAS_ORDER, ORDER_NAME_MOVEMENT);
  134 + }
  135 + if (checkOrderService.getCheckOrderCountByWarehouseId(id) > 0) {
  136 + throw exception(WAREHOUSE_HAS_ORDER, ORDER_NAME_CHECK);
  137 + }
  138 + }
  139 +
105 @Override 140 @Override
106 public WmsWarehouseDO validateWarehouseExists(Long id) { 141 public WmsWarehouseDO validateWarehouseExists(Long id) {
107 WmsWarehouseDO warehouse = warehouseMapper.selectById(id); 142 WmsWarehouseDO warehouse = warehouseMapper.selectById(id);
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/check/WmsCheckOrderDetailService.java
@@ -60,4 +60,20 @@ public interface WmsCheckOrderDetailService { @@ -60,4 +60,20 @@ public interface WmsCheckOrderDetailService {
60 */ 60 */
61 List<WmsCheckOrderDetailDO> validateCheckOrderDetailListExists(Long orderId); 61 List<WmsCheckOrderDetailDO> validateCheckOrderDetailListExists(Long orderId);
62 62
  63 + /**
  64 + * 获得指定 SKU 的盘库单明细数量
  65 + *
  66 + * @param skuId SKU 编号
  67 + * @return 盘库单明细数量
  68 + */
  69 + long getCheckOrderDetailCountBySkuId(Long skuId);
  70 +
  71 + /**
  72 + * 获得指定库区的盘库单明细数量
  73 + *
  74 + * @param areaId 库区编号
  75 + * @return 盘库单明细数量
  76 + */
  77 + long getCheckOrderDetailCountByAreaId(Long areaId);
  78 +
63 } 79 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/check/WmsCheckOrderDetailServiceImpl.java
@@ -111,6 +111,16 @@ public class WmsCheckOrderDetailServiceImpl implements WmsCheckOrderDetailServic @@ -111,6 +111,16 @@ public class WmsCheckOrderDetailServiceImpl implements WmsCheckOrderDetailServic
111 return details; 111 return details;
112 } 112 }
113 113
  114 + @Override
  115 + public long getCheckOrderDetailCountBySkuId(Long skuId) {
  116 + return checkOrderDetailMapper.selectCountBySkuId(skuId);
  117 + }
  118 +
  119 + @Override
  120 + public long getCheckOrderDetailCountByAreaId(Long areaId) {
  121 + return checkOrderDetailMapper.selectCountByAreaId(areaId);
  122 + }
  123 +
114 private List<WmsCheckOrderDetailDO> buildCheckOrderDetailList(WmsCheckOrderSaveReqVO reqVO) { 124 private List<WmsCheckOrderDetailDO> buildCheckOrderDetailList(WmsCheckOrderSaveReqVO reqVO) {
115 if (CollUtil.isEmpty(reqVO.getDetails())) { 125 if (CollUtil.isEmpty(reqVO.getDetails())) {
116 return ListUtil.of(); 126 return ListUtil.of();
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/check/WmsCheckOrderService.java
@@ -65,4 +65,20 @@ public interface WmsCheckOrderService { @@ -65,4 +65,20 @@ public interface WmsCheckOrderService {
65 */ 65 */
66 PageResult<WmsCheckOrderDO> getCheckOrderPage(WmsCheckOrderPageReqVO pageReqVO); 66 PageResult<WmsCheckOrderDO> getCheckOrderPage(WmsCheckOrderPageReqVO pageReqVO);
67 67
  68 + /**
  69 + * 获得指定仓库的盘库单数量
  70 + *
  71 + * @param warehouseId 仓库编号
  72 + * @return 盘库单数量
  73 + */
  74 + long getCheckOrderCountByWarehouseId(Long warehouseId);
  75 +
  76 + /**
  77 + * 获得指定库区的盘库单数量
  78 + *
  79 + * @param areaId 库区编号
  80 + * @return 盘库单数量
  81 + */
  82 + long getCheckOrderCountByAreaId(Long areaId);
  83 +
68 } 84 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/check/WmsCheckOrderServiceImpl.java
@@ -129,6 +129,16 @@ public class WmsCheckOrderServiceImpl implements WmsCheckOrderService { @@ -129,6 +129,16 @@ public class WmsCheckOrderServiceImpl implements WmsCheckOrderService {
129 return checkOrderMapper.selectPage(pageReqVO); 129 return checkOrderMapper.selectPage(pageReqVO);
130 } 130 }
131 131
  132 + @Override
  133 + public long getCheckOrderCountByWarehouseId(Long warehouseId) {
  134 + return checkOrderMapper.selectCountByWarehouseId(warehouseId);
  135 + }
  136 +
  137 + @Override
  138 + public long getCheckOrderCountByAreaId(Long areaId) {
  139 + return checkOrderMapper.selectCountByAreaId(areaId);
  140 + }
  141 +
132 private void validateCheckOrderSaveData(WmsCheckOrderSaveReqVO reqVO) { 142 private void validateCheckOrderSaveData(WmsCheckOrderSaveReqVO reqVO) {
133 // 校验盘库单号唯一 143 // 校验盘库单号唯一
134 validateCheckOrderNoUnique(reqVO.getId(), reqVO.getNo()); 144 validateCheckOrderNoUnique(reqVO.getId(), reqVO.getNo());
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/movement/WmsMovementOrderDetailService.java
@@ -60,4 +60,20 @@ public interface WmsMovementOrderDetailService { @@ -60,4 +60,20 @@ public interface WmsMovementOrderDetailService {
60 */ 60 */
61 List<WmsMovementOrderDetailDO> validateMovementOrderDetailListExists(Long orderId); 61 List<WmsMovementOrderDetailDO> validateMovementOrderDetailListExists(Long orderId);
62 62
  63 + /**
  64 + * 获得指定 SKU 的移库单明细数量
  65 + *
  66 + * @param skuId SKU 编号
  67 + * @return 移库单明细数量
  68 + */
  69 + long getMovementOrderDetailCountBySkuId(Long skuId);
  70 +
  71 + /**
  72 + * 获得指定库区的移库单明细数量
  73 + *
  74 + * @param areaId 库区编号
  75 + * @return 移库单明细数量
  76 + */
  77 + long getMovementOrderDetailCountByAreaId(Long areaId);
  78 +
63 } 79 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/movement/WmsMovementOrderDetailServiceImpl.java
@@ -112,6 +112,16 @@ public class WmsMovementOrderDetailServiceImpl implements WmsMovementOrderDetail @@ -112,6 +112,16 @@ public class WmsMovementOrderDetailServiceImpl implements WmsMovementOrderDetail
112 return details; 112 return details;
113 } 113 }
114 114
  115 + @Override
  116 + public long getMovementOrderDetailCountBySkuId(Long skuId) {
  117 + return movementOrderDetailMapper.selectCountBySkuId(skuId);
  118 + }
  119 +
  120 + @Override
  121 + public long getMovementOrderDetailCountByAreaId(Long areaId) {
  122 + return movementOrderDetailMapper.selectCountByAreaId(areaId);
  123 + }
  124 +
115 private List<WmsMovementOrderDetailDO> buildMovementOrderDetailList(WmsMovementOrderSaveReqVO reqVO) { 125 private List<WmsMovementOrderDetailDO> buildMovementOrderDetailList(WmsMovementOrderSaveReqVO reqVO) {
116 if (CollUtil.isEmpty(reqVO.getDetails())) { 126 if (CollUtil.isEmpty(reqVO.getDetails())) {
117 return ListUtil.of(); 127 return ListUtil.of();
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/movement/WmsMovementOrderService.java
@@ -65,4 +65,20 @@ public interface WmsMovementOrderService { @@ -65,4 +65,20 @@ public interface WmsMovementOrderService {
65 */ 65 */
66 PageResult<WmsMovementOrderDO> getMovementOrderPage(WmsMovementOrderPageReqVO pageReqVO); 66 PageResult<WmsMovementOrderDO> getMovementOrderPage(WmsMovementOrderPageReqVO pageReqVO);
67 67
  68 + /**
  69 + * 获得指定仓库的移库单数量
  70 + *
  71 + * @param warehouseId 仓库编号
  72 + * @return 移库单数量
  73 + */
  74 + long getMovementOrderCountByWarehouseId(Long warehouseId);
  75 +
  76 + /**
  77 + * 获得指定库区的移库单数量
  78 + *
  79 + * @param areaId 库区编号
  80 + * @return 移库单数量
  81 + */
  82 + long getMovementOrderCountByAreaId(Long areaId);
  83 +
68 } 84 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/movement/WmsMovementOrderServiceImpl.java
@@ -128,6 +128,16 @@ public class WmsMovementOrderServiceImpl implements WmsMovementOrderService { @@ -128,6 +128,16 @@ public class WmsMovementOrderServiceImpl implements WmsMovementOrderService {
128 return movementOrderMapper.selectPage(pageReqVO); 128 return movementOrderMapper.selectPage(pageReqVO);
129 } 129 }
130 130
  131 + @Override
  132 + public long getMovementOrderCountByWarehouseId(Long warehouseId) {
  133 + return movementOrderMapper.selectCountByWarehouseId(warehouseId);
  134 + }
  135 +
  136 + @Override
  137 + public long getMovementOrderCountByAreaId(Long areaId) {
  138 + return movementOrderMapper.selectCountByAreaId(areaId);
  139 + }
  140 +
131 private void validateMovementOrderSaveData(WmsMovementOrderSaveReqVO reqVO) { 141 private void validateMovementOrderSaveData(WmsMovementOrderSaveReqVO reqVO) {
132 // 校验移库单号唯一 142 // 校验移库单号唯一
133 validateMovementOrderNoUnique(reqVO.getId(), reqVO.getNo()); 143 validateMovementOrderNoUnique(reqVO.getId(), reqVO.getNo());
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/receipt/WmsReceiptOrderDetailService.java
@@ -60,4 +60,20 @@ public interface WmsReceiptOrderDetailService { @@ -60,4 +60,20 @@ public interface WmsReceiptOrderDetailService {
60 */ 60 */
61 List<WmsReceiptOrderDetailDO> validateReceiptOrderDetailListExists(Long orderId); 61 List<WmsReceiptOrderDetailDO> validateReceiptOrderDetailListExists(Long orderId);
62 62
  63 + /**
  64 + * 获得指定 SKU 的入库单明细数量
  65 + *
  66 + * @param skuId SKU 编号
  67 + * @return 入库单明细数量
  68 + */
  69 + long getReceiptOrderDetailCountBySkuId(Long skuId);
  70 +
  71 + /**
  72 + * 获得指定库区的入库单明细数量
  73 + *
  74 + * @param areaId 库区编号
  75 + * @return 入库单明细数量
  76 + */
  77 + long getReceiptOrderDetailCountByAreaId(Long areaId);
  78 +
63 } 79 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/receipt/WmsReceiptOrderDetailServiceImpl.java
@@ -111,6 +111,16 @@ public class WmsReceiptOrderDetailServiceImpl implements WmsReceiptOrderDetailSe @@ -111,6 +111,16 @@ public class WmsReceiptOrderDetailServiceImpl implements WmsReceiptOrderDetailSe
111 return details; 111 return details;
112 } 112 }
113 113
  114 + @Override
  115 + public long getReceiptOrderDetailCountBySkuId(Long skuId) {
  116 + return receiptOrderDetailMapper.selectCountBySkuId(skuId);
  117 + }
  118 +
  119 + @Override
  120 + public long getReceiptOrderDetailCountByAreaId(Long areaId) {
  121 + return receiptOrderDetailMapper.selectCountByAreaId(areaId);
  122 + }
  123 +
114 private List<WmsReceiptOrderDetailDO> buildReceiptOrderDetailList(WmsReceiptOrderSaveReqVO reqVO) { 124 private List<WmsReceiptOrderDetailDO> buildReceiptOrderDetailList(WmsReceiptOrderSaveReqVO reqVO) {
115 if (CollUtil.isEmpty(reqVO.getDetails())) { 125 if (CollUtil.isEmpty(reqVO.getDetails())) {
116 return ListUtil.of(); 126 return ListUtil.of();
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/receipt/WmsReceiptOrderService.java
@@ -65,4 +65,28 @@ public interface WmsReceiptOrderService { @@ -65,4 +65,28 @@ public interface WmsReceiptOrderService {
65 */ 65 */
66 PageResult<WmsReceiptOrderDO> getReceiptOrderPage(WmsReceiptOrderPageReqVO pageReqVO); 66 PageResult<WmsReceiptOrderDO> getReceiptOrderPage(WmsReceiptOrderPageReqVO pageReqVO);
67 67
  68 + /**
  69 + * 获得指定往来企业的入库单数量
  70 + *
  71 + * @param merchantId 往来企业编号
  72 + * @return 入库单数量
  73 + */
  74 + long getReceiptOrderCountByMerchantId(Long merchantId);
  75 +
  76 + /**
  77 + * 获得指定仓库的入库单数量
  78 + *
  79 + * @param warehouseId 仓库编号
  80 + * @return 入库单数量
  81 + */
  82 + long getReceiptOrderCountByWarehouseId(Long warehouseId);
  83 +
  84 + /**
  85 + * 获得指定库区的入库单数量
  86 + *
  87 + * @param areaId 库区编号
  88 + * @return 入库单数量
  89 + */
  90 + long getReceiptOrderCountByAreaId(Long areaId);
  91 +
68 } 92 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/receipt/WmsReceiptOrderServiceImpl.java
@@ -128,6 +128,21 @@ public class WmsReceiptOrderServiceImpl implements WmsReceiptOrderService { @@ -128,6 +128,21 @@ public class WmsReceiptOrderServiceImpl implements WmsReceiptOrderService {
128 return receiptOrderMapper.selectPage(pageReqVO); 128 return receiptOrderMapper.selectPage(pageReqVO);
129 } 129 }
130 130
  131 + @Override
  132 + public long getReceiptOrderCountByMerchantId(Long merchantId) {
  133 + return receiptOrderMapper.selectCountByMerchantId(merchantId);
  134 + }
  135 +
  136 + @Override
  137 + public long getReceiptOrderCountByWarehouseId(Long warehouseId) {
  138 + return receiptOrderMapper.selectCountByWarehouseId(warehouseId);
  139 + }
  140 +
  141 + @Override
  142 + public long getReceiptOrderCountByAreaId(Long areaId) {
  143 + return receiptOrderMapper.selectCountByAreaId(areaId);
  144 + }
  145 +
131 private void validateReceiptOrderSaveData(WmsReceiptOrderSaveReqVO reqVO) { 146 private void validateReceiptOrderSaveData(WmsReceiptOrderSaveReqVO reqVO) {
132 // 校验入库单号唯一 147 // 校验入库单号唯一
133 validateReceiptOrderNoUnique(reqVO.getId(), reqVO.getNo()); 148 validateReceiptOrderNoUnique(reqVO.getId(), reqVO.getNo());
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderDetailService.java
@@ -60,4 +60,20 @@ public interface WmsShipmentOrderDetailService { @@ -60,4 +60,20 @@ public interface WmsShipmentOrderDetailService {
60 */ 60 */
61 List<WmsShipmentOrderDetailDO> validateShipmentOrderDetailListExists(Long orderId); 61 List<WmsShipmentOrderDetailDO> validateShipmentOrderDetailListExists(Long orderId);
62 62
  63 + /**
  64 + * 获得指定 SKU 的出库单明细数量
  65 + *
  66 + * @param skuId SKU 编号
  67 + * @return 出库单明细数量
  68 + */
  69 + long getShipmentOrderDetailCountBySkuId(Long skuId);
  70 +
  71 + /**
  72 + * 获得指定库区的出库单明细数量
  73 + *
  74 + * @param areaId 库区编号
  75 + * @return 出库单明细数量
  76 + */
  77 + long getShipmentOrderDetailCountByAreaId(Long areaId);
  78 +
63 } 79 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderDetailServiceImpl.java
@@ -111,6 +111,16 @@ public class WmsShipmentOrderDetailServiceImpl implements WmsShipmentOrderDetail @@ -111,6 +111,16 @@ public class WmsShipmentOrderDetailServiceImpl implements WmsShipmentOrderDetail
111 return details; 111 return details;
112 } 112 }
113 113
  114 + @Override
  115 + public long getShipmentOrderDetailCountBySkuId(Long skuId) {
  116 + return shipmentOrderDetailMapper.selectCountBySkuId(skuId);
  117 + }
  118 +
  119 + @Override
  120 + public long getShipmentOrderDetailCountByAreaId(Long areaId) {
  121 + return shipmentOrderDetailMapper.selectCountByAreaId(areaId);
  122 + }
  123 +
114 private List<WmsShipmentOrderDetailDO> buildShipmentOrderDetailList(WmsShipmentOrderSaveReqVO reqVO) { 124 private List<WmsShipmentOrderDetailDO> buildShipmentOrderDetailList(WmsShipmentOrderSaveReqVO reqVO) {
115 if (CollUtil.isEmpty(reqVO.getDetails())) { 125 if (CollUtil.isEmpty(reqVO.getDetails())) {
116 return ListUtil.of(); 126 return ListUtil.of();
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderService.java
@@ -65,4 +65,28 @@ public interface WmsShipmentOrderService { @@ -65,4 +65,28 @@ public interface WmsShipmentOrderService {
65 */ 65 */
66 PageResult<WmsShipmentOrderDO> getShipmentOrderPage(WmsShipmentOrderPageReqVO pageReqVO); 66 PageResult<WmsShipmentOrderDO> getShipmentOrderPage(WmsShipmentOrderPageReqVO pageReqVO);
67 67
  68 + /**
  69 + * 获得指定往来企业的出库单数量
  70 + *
  71 + * @param merchantId 往来企业编号
  72 + * @return 出库单数量
  73 + */
  74 + long getShipmentOrderCountByMerchantId(Long merchantId);
  75 +
  76 + /**
  77 + * 获得指定仓库的出库单数量
  78 + *
  79 + * @param warehouseId 仓库编号
  80 + * @return 出库单数量
  81 + */
  82 + long getShipmentOrderCountByWarehouseId(Long warehouseId);
  83 +
  84 + /**
  85 + * 获得指定库区的出库单数量
  86 + *
  87 + * @param areaId 库区编号
  88 + * @return 出库单数量
  89 + */
  90 + long getShipmentOrderCountByAreaId(Long areaId);
  91 +
68 } 92 }
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderServiceImpl.java
@@ -129,6 +129,21 @@ public class WmsShipmentOrderServiceImpl implements WmsShipmentOrderService { @@ -129,6 +129,21 @@ public class WmsShipmentOrderServiceImpl implements WmsShipmentOrderService {
129 return shipmentOrderMapper.selectPage(pageReqVO); 129 return shipmentOrderMapper.selectPage(pageReqVO);
130 } 130 }
131 131
  132 + @Override
  133 + public long getShipmentOrderCountByMerchantId(Long merchantId) {
  134 + return shipmentOrderMapper.selectCountByMerchantId(merchantId);
  135 + }
  136 +
  137 + @Override
  138 + public long getShipmentOrderCountByWarehouseId(Long warehouseId) {
  139 + return shipmentOrderMapper.selectCountByWarehouseId(warehouseId);
  140 + }
  141 +
  142 + @Override
  143 + public long getShipmentOrderCountByAreaId(Long areaId) {
  144 + return shipmentOrderMapper.selectCountByAreaId(areaId);
  145 + }
  146 +
132 private void validateShipmentOrderSaveData(WmsShipmentOrderSaveReqVO reqVO) { 147 private void validateShipmentOrderSaveData(WmsShipmentOrderSaveReqVO reqVO) {
133 // 校验出库单号唯一 148 // 校验出库单号唯一
134 validateShipmentOrderNoUnique(reqVO.getId(), reqVO.getNo()); 149 validateShipmentOrderNoUnique(reqVO.getId(), reqVO.getNo());
yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/inventory/WmsInventoryServiceImplTest.java
@@ -3,9 +3,11 @@ package cn.iocoder.yudao.module.wms.service.inventory; @@ -3,9 +3,11 @@ package cn.iocoder.yudao.module.wms.service.inventory;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 4 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
5 import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryPageReqVO; 5 import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryPageReqVO;
  6 +import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO;
6 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryHistoryDO; 7 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryHistoryDO;
7 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO; 8 import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO;
8 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO; 9 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO;
  10 +import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryDetailMapper;
9 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; 11 import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO;
10 import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryHistoryMapper; 12 import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryHistoryMapper;
11 import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryMapper; 13 import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryMapper;
@@ -19,8 +21,12 @@ import org.junit.jupiter.api.Test; @@ -19,8 +21,12 @@ import org.junit.jupiter.api.Test;
19 import org.springframework.context.annotation.Import; 21 import org.springframework.context.annotation.Import;
20 22
21 import java.math.BigDecimal; 23 import java.math.BigDecimal;
  24 +import java.util.ArrayList;
22 import java.util.List; 25 import java.util.List;
23 26
  27 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  28 +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.INVENTORY_DETAIL_REMAIN_QUANTITY_NOT_ENOUGH;
  29 +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.INVENTORY_QUANTITY_NOT_ENOUGH;
24 import static org.junit.jupiter.api.Assertions.assertEquals; 30 import static org.junit.jupiter.api.Assertions.assertEquals;
25 import static org.junit.jupiter.api.Assertions.assertNotNull; 31 import static org.junit.jupiter.api.Assertions.assertNotNull;
26 32
@@ -34,6 +40,8 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { @@ -34,6 +40,8 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest {
34 @Resource 40 @Resource
35 private WmsInventoryMapper inventoryMapper; 41 private WmsInventoryMapper inventoryMapper;
36 @Resource 42 @Resource
  43 + private WmsInventoryDetailMapper inventoryDetailMapper;
  44 + @Resource
37 private WmsInventoryHistoryMapper inventoryHistoryMapper; 45 private WmsInventoryHistoryMapper inventoryHistoryMapper;
38 @Resource 46 @Resource
39 private WmsItemMapper itemMapper; 47 private WmsItemMapper itemMapper;
@@ -97,6 +105,72 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { @@ -97,6 +105,72 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest {
97 } 105 }
98 106
99 @Test 107 @Test
  108 + public void testChangeInventory_quantityNotEnough() {
  109 + // mock 数据
  110 + wmsProperties.setBatchEnable(false);
  111 + WmsItemDO item = createItem("ITEM-001", "红富士苹果");
  112 + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装");
  113 + inventoryMapper.insert(createInventory(sku.getId(), 100L, 1001L, "2.00"));
  114 + WmsInventoryChangeReqDTO reqDTO = createChangeReq(sku.getId(), 100L, 1001L, "-3.00");
  115 +
  116 + // 调用,并断言
  117 + assertServiceException(() -> inventoryService.changeInventory(reqDTO), INVENTORY_QUANTITY_NOT_ENOUGH,
  118 + new BigDecimal("2.000000"), new BigDecimal("-3.00"));
  119 + }
  120 +
  121 + @Test
  122 + public void testChangeInventory_decreaseInventoryDetail() {
  123 + // mock 数据
  124 + wmsProperties.setBatchEnable(true);
  125 + WmsItemDO item = createItem("ITEM-001", "红富士苹果");
  126 + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装");
  127 + inventoryMapper.insert(createInventory(sku.getId(), 100L, 1001L, "10.00"));
  128 + WmsInventoryDetailDO detail = createInventoryDetail(sku.getId(), 100L, 1001L, "5.00");
  129 + inventoryDetailMapper.insert(detail);
  130 + WmsInventoryChangeReqDTO reqDTO = createChangeReq(sku.getId(), 100L, 1001L, "-3.00");
  131 + reqDTO.getItems().get(0).setInventoryDetailId(detail.getId());
  132 +
  133 + // 调用
  134 + inventoryService.changeInventory(reqDTO);
  135 +
  136 + // 断言:库存余额
  137 + WmsInventoryDO inventory = inventoryMapper.selectBySkuIdAndWarehouseIdAndAreaId(sku.getId(), 100L, 1001L);
  138 + assertEquals(0, new BigDecimal("7.00").compareTo(inventory.getQuantity()));
  139 + // 断言:库存明细
  140 + WmsInventoryDetailDO dbDetail = inventoryDetailMapper.selectById(detail.getId());
  141 + assertEquals(0, new BigDecimal("2.00").compareTo(dbDetail.getRemainQuantity()));
  142 + // 断言:库存流水
  143 + List<WmsInventoryHistoryDO> histories = inventoryHistoryMapper.selectList();
  144 + assertEquals(1, histories.size());
  145 + assertEquals(0, new BigDecimal("-3.00").compareTo(histories.get(0).getQuantity()));
  146 + }
  147 +
  148 + @Test
  149 + public void testChangeInventory_decreaseInventoryDetailRemainQuantityNotEnough() {
  150 + // mock 数据
  151 + wmsProperties.setBatchEnable(true);
  152 + WmsItemDO item = createItem("ITEM-001", "红富士苹果");
  153 + WmsItemSkuDO sku = createSku(item.getId(), "SKU-001", "10kg 箱装");
  154 + inventoryMapper.insert(createInventory(sku.getId(), 100L, 1001L, "10.00"));
  155 + WmsInventoryDetailDO detail = createInventoryDetail(sku.getId(), 100L, 1001L, "5.00");
  156 + inventoryDetailMapper.insert(detail);
  157 + WmsInventoryChangeReqDTO reqDTO = createChangeReq(sku.getId(), 100L, 1001L, "-3.00");
  158 + reqDTO.getItems().get(0).setInventoryDetailId(detail.getId());
  159 + List<WmsInventoryChangeReqDTO.Item> items = new ArrayList<>(reqDTO.getItems());
  160 + items.add(new WmsInventoryChangeReqDTO.Item()
  161 + .setSkuId(sku.getId())
  162 + .setWarehouseId(100L)
  163 + .setAreaId(1001L)
  164 + .setInventoryDetailId(detail.getId())
  165 + .setQuantity(new BigDecimal("-3.00")));
  166 + reqDTO.setItems(items);
  167 +
  168 + // 调用,并断言:相同库存明细会合并后校验
  169 + assertServiceException(() -> inventoryService.changeInventory(reqDTO),
  170 + INVENTORY_DETAIL_REMAIN_QUANTITY_NOT_ENOUGH, new BigDecimal("5.000000"), new BigDecimal("6.00"));
  171 + }
  172 +
  173 + @Test
100 public void testGetInventoryPage_groupByWarehouse() { 174 public void testGetInventoryPage_groupByWarehouse() {
101 // mock 数据 175 // mock 数据
102 WmsItemDO item = createItem("ITEM-001", "红富士苹果"); 176 WmsItemDO item = createItem("ITEM-001", "红富士苹果");
@@ -191,6 +265,17 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { @@ -191,6 +265,17 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest {
191 .build(); 265 .build();
192 } 266 }
193 267
  268 + private static WmsInventoryDetailDO createInventoryDetail(Long skuId, Long warehouseId, Long areaId,
  269 + String remainQuantity) {
  270 + return WmsInventoryDetailDO.builder()
  271 + .skuId(skuId)
  272 + .warehouseId(warehouseId)
  273 + .areaId(areaId)
  274 + .quantity(new BigDecimal(remainQuantity))
  275 + .remainQuantity(new BigDecimal(remainQuantity))
  276 + .build();
  277 + }
  278 +
194 private static WmsInventoryChangeReqDTO createChangeReq(Long skuId, Long warehouseId, Long areaId, String quantity) { 279 private static WmsInventoryChangeReqDTO createChangeReq(Long skuId, Long warehouseId, Long areaId, String quantity) {
195 return new WmsInventoryChangeReqDTO() 280 return new WmsInventoryChangeReqDTO()
196 .setOrderId(1L) 281 .setOrderId(1L)