Commit e838637fd3cfef50856e52e54b320db288592901
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 | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 6 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |
| 6 | 7 | import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; |
| 7 | 8 | import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInventoryDetailPageReqVO; |
| 8 | 9 | import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO; |
| ... | ... | @@ -15,6 +16,8 @@ import java.math.BigDecimal; |
| 15 | 16 | import java.time.LocalDate; |
| 16 | 17 | import java.time.LocalDateTime; |
| 17 | 18 | import java.time.LocalTime; |
| 19 | +import java.util.Collection; | |
| 20 | +import java.util.List; | |
| 18 | 21 | |
| 19 | 22 | /** |
| 20 | 23 | * WMS 库存明细 Mapper |
| ... | ... | @@ -73,4 +76,29 @@ public interface WmsInventoryDetailMapper extends BaseMapperX<WmsInventoryDetail |
| 73 | 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<WmsCheckOrderDeta |
| 31 | 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<WmsCheckOrderDO> { |
| 37 | 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<WmsMovementOrd |
| 31 | 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<WmsMovementOrderDO> |
| 37 | 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<WmsReceiptOrder |
| 31 | 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<WmsReceiptOrderDO> { |
| 39 | 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<WmsShipmentOrd |
| 31 | 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<WmsShipmentOrderDO> |
| 39 | 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 | 12 | String RECEIPT_ORDER_TYPE = "wms_receipt_order_type"; |
| 13 | 13 | String SHIPMENT_ORDER_TYPE = "wms_shipment_order_type"; |
| 14 | 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 | 17 | ErrorCode WAREHOUSE_NAME_DUPLICATE = new ErrorCode(1_060_100_001, "仓库名称重复"); |
| 18 | 18 | ErrorCode WAREHOUSE_CODE_DUPLICATE = new ErrorCode(1_060_100_002, "仓库编号重复"); |
| 19 | 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 | 22 | // ========== WMS 基础数据-库区 1-060-101-000 ========== |
| 22 | 23 | ErrorCode WAREHOUSE_AREA_NOT_EXISTS = new ErrorCode(1_060_101_000, "库区不存在"); |
| 23 | 24 | ErrorCode WAREHOUSE_AREA_NAME_DUPLICATE = new ErrorCode(1_060_101_001, "库区名称重复"); |
| 24 | 25 | ErrorCode WAREHOUSE_AREA_CODE_DUPLICATE = new ErrorCode(1_060_101_002, "库区编号重复"); |
| 25 | 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 | 29 | // ========== WMS 基础数据-商品分类 1-060-102-000 ========== |
| 28 | 30 | ErrorCode ITEM_CATEGORY_NOT_EXISTS = new ErrorCode(1_060_102_000, "商品分类不存在"); |
| ... | ... | @@ -44,11 +46,13 @@ public interface ErrorCodeConstants { |
| 44 | 46 | ErrorCode ITEM_SKU_NAME_DUPLICATE = new ErrorCode(1_060_104_003, "商品规格名称【{}】重复"); |
| 45 | 47 | ErrorCode ITEM_SKU_NOT_EXISTS = new ErrorCode(1_060_104_004, "商品规格不存在"); |
| 46 | 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 | 51 | // ========== WMS 基础数据-往来企业 1-060-105-000 ========== |
| 49 | 52 | ErrorCode MERCHANT_NOT_EXISTS = new ErrorCode(1_060_105_000, "往来企业不存在"); |
| 50 | 53 | ErrorCode MERCHANT_NOT_SUPPLIER = new ErrorCode(1_060_105_001, "往来企业必须是供应商或客户/供应商类型"); |
| 51 | 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 | 57 | // ========== WMS 入库单 1-060-200-000 ========== |
| 54 | 58 | ErrorCode RECEIPT_ORDER_NOT_EXISTS = new ErrorCode(1_060_200_000, "入库单不存在"); |
| ... | ... | @@ -68,6 +72,25 @@ public interface ErrorCodeConstants { |
| 68 | 72 | ErrorCode SHIPMENT_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_201_005, "出库单状态不是草稿或已作废,不能删除"); |
| 69 | 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 | 94 | // ========== WMS 库存 1-060-300-000 ========== |
| 72 | 95 | ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_000, "库存不足,当前库存:{},变更数量:{}"); |
| 73 | 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 | 12 | |
| 13 | 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 | 22 | // ========== 入库单类型 [100, 200) ========== |
| 16 | 23 | |
| 17 | 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 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInventoryDetailPageReqVO; |
| 5 | 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 | 8 | import java.util.List; |
| 8 | 9 | |
| ... | ... | @@ -28,4 +29,11 @@ public interface WmsInventoryDetailService { |
| 28 | 29 | */ |
| 29 | 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 | 1 | package cn.iocoder.yudao.module.wms.service.inventory; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.util.ObjectUtil; | |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 6 | import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInventoryDetailPageReqVO; |
| 6 | 7 | import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO; |
| 7 | 8 | import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryDetailMapper; |
| 9 | +import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO; | |
| 8 | 10 | import jakarta.annotation.Resource; |
| 9 | 11 | import org.springframework.stereotype.Service; |
| 10 | 12 | import org.springframework.transaction.annotation.Transactional; |
| 11 | 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 | 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 | 28 | * WMS 库存明细 Service 实现类 |
| ... | ... | @@ -38,4 +50,47 @@ public class WmsInventoryDetailServiceImpl implements WmsInventoryDetailService |
| 38 | 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 | 19 | |
| 20 | 20 | import java.math.BigDecimal; |
| 21 | 21 | import java.util.ArrayList; |
| 22 | +import java.util.Comparator; | |
| 23 | +import java.util.IdentityHashMap; | |
| 22 | 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 | 31 | * WMS 库存 Service 实现类 |
| ... | ... | @@ -61,44 +67,72 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { |
| 61 | 67 | return; |
| 62 | 68 | } |
| 63 | 69 | |
| 64 | - // 1. 变更库存余额,并构建库存流水与批次库存明细 | |
| 70 | + // 1. 按固定顺序变更库存余额,避免多 SKU/仓库并发操作时交叉拿锁 | |
| 71 | + Map<WmsInventoryChangeReqDTO.Item, Tuple> resultMap = changeInventoryList(reqDTO.getItems()); | |
| 72 | + | |
| 73 | + // 2. 构建库存流水与批次库存明细 | |
| 65 | 74 | boolean batchEnabled = wmsProperties.isBatchEnabled(); |
| 66 | 75 | List<WmsInventoryHistoryDO> histories = new ArrayList<>(reqDTO.getItems().size()); |
| 67 | 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 | 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 | 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 | 90 | if (batchEnabled) { |
| 81 | - // 2.2 批量写入批次库存明细 | |
| 91 | + inventoryDetailService.decreaseInventoryDetailList(decreaseItems); | |
| 82 | 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 | 137 | private WmsInventoryDO getOrCreateInventory(WmsInventoryChangeReqDTO.Item item) { |
| 104 | 138 | // 1. 查询库存余额,使用 FOR UPDATE 锁定行 |
| ... | ... | @@ -147,4 +181,23 @@ public class WmsInventoryServiceImpl implements WmsInventoryService { |
| 147 | 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 | 8 | import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; |
| 9 | 9 | import cn.iocoder.yudao.module.wms.dal.mysql.md.item.WmsItemSkuMapper; |
| 10 | 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 | 15 | import cn.iocoder.yudao.module.wms.util.WmsUtils; |
| 12 | 16 | import jakarta.annotation.Resource; |
| 13 | 17 | import org.springframework.context.annotation.Lazy; |
| ... | ... | @@ -23,6 +27,7 @@ import java.util.Set; |
| 23 | 27 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 24 | 28 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| 25 | 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 | 33 | * WMS 商品 SKU Service 实现类 |
| ... | ... | @@ -38,6 +43,18 @@ public class WmsItemSkuServiceImpl implements WmsItemSkuService { |
| 38 | 43 | @Resource |
| 39 | 44 | @Lazy // 延迟加载,避免循环依赖 |
| 40 | 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 | 59 | @Override |
| 43 | 60 | @Transactional(rollbackFor = Exception.class) |
| ... | ... | @@ -146,12 +163,28 @@ public class WmsItemSkuServiceImpl implements WmsItemSkuService { |
| 146 | 163 | return; |
| 147 | 164 | } |
| 148 | 165 | for (WmsItemSkuDO sku : skus) { |
| 166 | + validateItemSkuOrderUnused(sku); | |
| 149 | 167 | if (inventoryService.getInventoryCountBySkuId(sku.getId()) > 0) { |
| 150 | 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 | 10 | import cn.iocoder.yudao.module.wms.dal.dataobject.md.merchant.WmsMerchantDO; |
| 11 | 11 | import cn.iocoder.yudao.module.wms.dal.mysql.md.merchant.WmsMerchantMapper; |
| 12 | 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 | 15 | import jakarta.annotation.Resource; |
| 16 | +import org.springframework.context.annotation.Lazy; | |
| 14 | 17 | import org.springframework.stereotype.Service; |
| 15 | 18 | import org.springframework.validation.annotation.Validated; |
| 16 | 19 | |
| ... | ... | @@ -19,6 +22,7 @@ import java.util.List; |
| 19 | 22 | |
| 20 | 23 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 21 | 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 | 28 | * WMS 往来企业 Service 实现类 |
| ... | ... | @@ -31,6 +35,12 @@ public class WmsMerchantServiceImpl implements WmsMerchantService { |
| 31 | 35 | |
| 32 | 36 | @Resource |
| 33 | 37 | private WmsMerchantMapper merchantMapper; |
| 38 | + @Resource | |
| 39 | + @Lazy // 延迟加载,避免循环依赖 | |
| 40 | + private WmsReceiptOrderService receiptOrderService; | |
| 41 | + @Resource | |
| 42 | + @Lazy // 延迟加载,避免循环依赖 | |
| 43 | + private WmsShipmentOrderService shipmentOrderService; | |
| 34 | 44 | |
| 35 | 45 | @Override |
| 36 | 46 | public Long createMerchant(WmsMerchantSaveReqVO createReqVO) { |
| ... | ... | @@ -53,12 +63,22 @@ public class WmsMerchantServiceImpl implements WmsMerchantService { |
| 53 | 63 | public void deleteMerchant(Long id) { |
| 54 | 64 | // 校验存在 |
| 55 | 65 | validateMerchantExists(id); |
| 56 | - // TODO 入库/出库单实现后,校验往来企业不存在业务关联后再删除 | |
| 66 | + // 校验未被单据使用 | |
| 67 | + validateMerchantUnused(id); | |
| 57 | 68 | |
| 58 | 69 | // 删除 |
| 59 | 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 | 82 | @Override |
| 63 | 83 | public WmsMerchantDO validateMerchantExists(Long id) { |
| 64 | 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 | 11 | import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseAreaDO; |
| 12 | 12 | import cn.iocoder.yudao.module.wms.dal.mysql.md.warehouse.WmsWarehouseAreaMapper; |
| 13 | 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 | 22 | import jakarta.annotation.Resource; |
| 23 | +import org.springframework.context.annotation.Lazy; | |
| 15 | 24 | import org.springframework.stereotype.Service; |
| 16 | 25 | import org.springframework.validation.annotation.Validated; |
| 17 | 26 | |
| ... | ... | @@ -21,6 +30,7 @@ import java.util.List; |
| 21 | 30 | |
| 22 | 31 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 23 | 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 | 36 | * WMS 库区 Service 实现类 |
| ... | ... | @@ -37,6 +47,30 @@ public class WmsWarehouseAreaServiceImpl implements WmsWarehouseAreaService { |
| 37 | 47 | private WmsWarehouseService warehouseService; |
| 38 | 48 | @Resource |
| 39 | 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 | 75 | @Override |
| 42 | 76 | public Long createWarehouseArea(WmsWarehouseAreaSaveReqVO createReqVO) { |
| ... | ... | @@ -98,11 +132,32 @@ public class WmsWarehouseAreaServiceImpl implements WmsWarehouseAreaService { |
| 98 | 132 | validateAreaEnabled(); |
| 99 | 133 | // 校验存在 |
| 100 | 134 | validateWarehouseAreaExists(id); |
| 135 | + // 校验未被单据使用 | |
| 136 | + validateWarehouseAreaUnused(id); | |
| 101 | 137 | |
| 102 | 138 | // 删除 |
| 103 | 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 | 161 | @Override |
| 107 | 162 | public WmsWarehouseAreaDO validateWarehouseAreaExists(Long id) { |
| 108 | 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 | 11 | import cn.iocoder.yudao.module.wms.dal.mysql.md.warehouse.WmsWarehouseAreaMapper; |
| 12 | 12 | import cn.iocoder.yudao.module.wms.dal.mysql.md.warehouse.WmsWarehouseMapper; |
| 13 | 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 | 18 | import jakarta.annotation.Resource; |
| 19 | +import org.springframework.context.annotation.Lazy; | |
| 15 | 20 | import org.springframework.stereotype.Service; |
| 16 | 21 | import org.springframework.validation.annotation.Validated; |
| 17 | 22 | |
| ... | ... | @@ -21,6 +26,7 @@ import java.util.List; |
| 21 | 26 | |
| 22 | 27 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 23 | 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 | 32 | * WMS 仓库 Service 实现类 |
| ... | ... | @@ -37,6 +43,18 @@ public class WmsWarehouseServiceImpl implements WmsWarehouseService { |
| 37 | 43 | private WmsWarehouseAreaMapper warehouseAreaMapper; |
| 38 | 44 | @Resource |
| 39 | 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 | 59 | @Override |
| 42 | 60 | public Long createWarehouse(WmsWarehouseSaveReqVO createReqVO) { |
| ... | ... | @@ -93,6 +111,8 @@ public class WmsWarehouseServiceImpl implements WmsWarehouseService { |
| 93 | 111 | public void deleteWarehouse(Long id) { |
| 94 | 112 | // 校验存在 |
| 95 | 113 | validateWarehouseExists(id); |
| 114 | + // 校验未被单据使用 | |
| 115 | + validateWarehouseUnused(id); | |
| 96 | 116 | // 库区模式下,存在库区时不允许删除仓库 |
| 97 | 117 | if (wmsProperties.isAreaEnabled() && warehouseAreaMapper.selectCountByWarehouseId(id) > 0) { |
| 98 | 118 | throw exception(WAREHOUSE_HAS_AREA); |
| ... | ... | @@ -102,6 +122,21 @@ public class WmsWarehouseServiceImpl implements WmsWarehouseService { |
| 102 | 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 | 140 | @Override |
| 106 | 141 | public WmsWarehouseDO validateWarehouseExists(Long id) { |
| 107 | 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 | 60 | */ |
| 61 | 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 | 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 | 124 | private List<WmsCheckOrderDetailDO> buildCheckOrderDetailList(WmsCheckOrderSaveReqVO reqVO) { |
| 115 | 125 | if (CollUtil.isEmpty(reqVO.getDetails())) { |
| 116 | 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 | 65 | */ |
| 66 | 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 | 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 | 142 | private void validateCheckOrderSaveData(WmsCheckOrderSaveReqVO reqVO) { |
| 133 | 143 | // 校验盘库单号唯一 |
| 134 | 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 | 60 | */ |
| 61 | 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 | 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 | 125 | private List<WmsMovementOrderDetailDO> buildMovementOrderDetailList(WmsMovementOrderSaveReqVO reqVO) { |
| 116 | 126 | if (CollUtil.isEmpty(reqVO.getDetails())) { |
| 117 | 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 | 65 | */ |
| 66 | 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 | 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 | 141 | private void validateMovementOrderSaveData(WmsMovementOrderSaveReqVO reqVO) { |
| 132 | 142 | // 校验移库单号唯一 |
| 133 | 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 | 60 | */ |
| 61 | 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 | 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 | 124 | private List<WmsReceiptOrderDetailDO> buildReceiptOrderDetailList(WmsReceiptOrderSaveReqVO reqVO) { |
| 115 | 125 | if (CollUtil.isEmpty(reqVO.getDetails())) { |
| 116 | 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 | 65 | */ |
| 66 | 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 | 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 | 146 | private void validateReceiptOrderSaveData(WmsReceiptOrderSaveReqVO reqVO) { |
| 132 | 147 | // 校验入库单号唯一 |
| 133 | 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 | 60 | */ |
| 61 | 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 | 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 | 124 | private List<WmsShipmentOrderDetailDO> buildShipmentOrderDetailList(WmsShipmentOrderSaveReqVO reqVO) { |
| 115 | 125 | if (CollUtil.isEmpty(reqVO.getDetails())) { |
| 116 | 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 | 65 | */ |
| 66 | 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 | 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 | 147 | private void validateShipmentOrderSaveData(WmsShipmentOrderSaveReqVO reqVO) { |
| 133 | 148 | // 校验出库单号唯一 |
| 134 | 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 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| 5 | 5 | import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.WmsInventoryPageReqVO; |
| 6 | +import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO; | |
| 6 | 7 | import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryHistoryDO; |
| 7 | 8 | import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDO; |
| 8 | 9 | import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO; |
| 10 | +import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryDetailMapper; | |
| 9 | 11 | import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; |
| 10 | 12 | import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryHistoryMapper; |
| 11 | 13 | import cn.iocoder.yudao.module.wms.dal.mysql.inventory.WmsInventoryMapper; |
| ... | ... | @@ -19,8 +21,12 @@ import org.junit.jupiter.api.Test; |
| 19 | 21 | import org.springframework.context.annotation.Import; |
| 20 | 22 | |
| 21 | 23 | import java.math.BigDecimal; |
| 24 | +import java.util.ArrayList; | |
| 22 | 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 | 30 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 25 | 31 | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| 26 | 32 | |
| ... | ... | @@ -34,6 +40,8 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { |
| 34 | 40 | @Resource |
| 35 | 41 | private WmsInventoryMapper inventoryMapper; |
| 36 | 42 | @Resource |
| 43 | + private WmsInventoryDetailMapper inventoryDetailMapper; | |
| 44 | + @Resource | |
| 37 | 45 | private WmsInventoryHistoryMapper inventoryHistoryMapper; |
| 38 | 46 | @Resource |
| 39 | 47 | private WmsItemMapper itemMapper; |
| ... | ... | @@ -97,6 +105,72 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { |
| 97 | 105 | } |
| 98 | 106 | |
| 99 | 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 | 174 | public void testGetInventoryPage_groupByWarehouse() { |
| 101 | 175 | // mock 数据 |
| 102 | 176 | WmsItemDO item = createItem("ITEM-001", "红富士苹果"); |
| ... | ... | @@ -191,6 +265,17 @@ public class WmsInventoryServiceImplTest extends BaseDbUnitTest { |
| 191 | 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 | 279 | private static WmsInventoryChangeReqDTO createChangeReq(Long skuId, Long warehouseId, Long areaId, String quantity) { |
| 195 | 280 | return new WmsInventoryChangeReqDTO() |
| 196 | 281 | .setOrderId(1L) | ... | ... |