Commit 5a16d8337575a8de9d9396b06ff369f713e131d1

Authored by YunaiV
1 parent 2f4dfb69

✨ feat(mes): 添加库存混放规则校验及相关错误码

新增库存混放规则校验功能,确保在指定库位存放物料和批次时遵循配置的混放规则。同时,添加了多个相关的错误码以支持业务逻辑的处理。
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -356,6 +356,13 @@ public interface ErrorCodeConstants { @@ -356,6 +356,13 @@ public interface ErrorCodeConstants {
356 ErrorCode WM_MATERIAL_STOCK_NOT_EXISTS = new ErrorCode(1_040_703_000, "库存记录不存在"); 356 ErrorCode WM_MATERIAL_STOCK_NOT_EXISTS = new ErrorCode(1_040_703_000, "库存记录不存在");
357 ErrorCode WM_MATERIAL_STOCK_INSUFFICIENT = new ErrorCode(1_040_703_001, "库存数量不足"); 357 ErrorCode WM_MATERIAL_STOCK_INSUFFICIENT = new ErrorCode(1_040_703_001, "库存数量不足");
358 ErrorCode WM_TRANSACTION_TYPE_NOT_EXISTS = new ErrorCode(1_040_703_002, "库存事务类型不存在"); 358 ErrorCode WM_TRANSACTION_TYPE_NOT_EXISTS = new ErrorCode(1_040_703_002, "库存事务类型不存在");
  359 + ErrorCode WM_TRANSACTION_WAREHOUSE_FROZEN = new ErrorCode(1_040_703_003, "仓库({})已被冻结");
  360 + ErrorCode WM_TRANSACTION_LOCATION_FROZEN = new ErrorCode(1_040_703_004, "库区({})已被冻结");
  361 + ErrorCode WM_TRANSACTION_AREA_FROZEN = new ErrorCode(1_040_703_005, "库位({})已被冻结");
  362 + ErrorCode WM_TRANSACTION_STOCK_FROZEN = new ErrorCode(1_040_703_006, "存放于({}/{}/{})下的物料已被冻结");
  363 + ErrorCode WM_TRANSACTION_BATCH_REQUIRED = new ErrorCode(1_040_703_007, "当前物料启用了批次管理,批次号不能为空");
  364 + ErrorCode WM_MATERIAL_STOCK_AREA_ITEM_MIXING_NOT_ALLOWED = new ErrorCode(1_040_703_008, "库位({})不允许物料混放,请选择其他库位");
  365 + ErrorCode WM_MATERIAL_STOCK_AREA_BATCH_MIXING_NOT_ALLOWED = new ErrorCode(1_040_703_009, "库位({})不允许批次混放,请选择其他库位");
359 366
360 // ========== MES 仓库管理-到货通知单(1-040-704-000) ========== 367 // ========== MES 仓库管理-到货通知单(1-040-704-000) ==========
361 ErrorCode WM_ARRIVAL_NOTICE_NOT_EXISTS = new ErrorCode(1_040_704_000, "到货通知单不存在"); 368 ErrorCode WM_ARRIVAL_NOTICE_NOT_EXISTS = new ErrorCode(1_040_704_000, "到货通知单不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
@@ -29,6 +29,27 @@ public interface MesWmMaterialStockService { @@ -29,6 +29,27 @@ public interface MesWmMaterialStockService {
29 MesWmMaterialStockDO getMaterialStock(Long id); 29 MesWmMaterialStockDO getMaterialStock(Long id);
30 30
31 /** 31 /**
  32 + * 按组合键查询库存记录
  33 + *
  34 + * @param itemId 物料编号
  35 + * @param warehouseId 仓库编号
  36 + * @param locationId 库区编号
  37 + * @param areaId 库位编号
  38 + * @param batchId 批次编号
  39 + * @return 库存记录,不存在返回 null
  40 + */
  41 + MesWmMaterialStockDO getMaterialStockByCompositeKey(Long itemId, Long warehouseId, Long locationId,
  42 + Long areaId, Long batchId);
  43 +
  44 + /**
  45 + * 查询指定库位的库存记录列表
  46 + *
  47 + * @param areaId 库位编号
  48 + * @return 库存记录列表
  49 + */
  50 + List<MesWmMaterialStockDO> getMaterialStockListByAreaId(Long areaId);
  51 +
  52 + /**
32 * 获得库存台账分页 53 * 获得库存台账分页
33 * 54 *
34 * @param pageReqVO 分页参数 55 * @param pageReqVO 分页参数
@@ -97,10 +118,10 @@ public interface MesWmMaterialStockService { @@ -97,10 +118,10 @@ public interface MesWmMaterialStockService {
97 * @param batchId 批次编号 118 * @param batchId 批次编号
98 * @param vendorId 供应商编号 119 * @param vendorId 供应商编号
99 * @param receiptTime 入库时间(为空则默认当前时间) 120 * @param receiptTime 入库时间(为空则默认当前时间)
100 - * @return 库存记录编号 121 + * @return 库存记录
101 */ 122 */
102 - Long getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId,  
103 - Long batchId, Long vendorId, LocalDateTime receiptTime); 123 + MesWmMaterialStockDO getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId,
  124 + Long batchId, Long vendorId, LocalDateTime receiptTime);
104 125
105 /** 126 /**
106 * 更新库存数量 127 * 更新库存数量
@@ -111,4 +132,15 @@ public interface MesWmMaterialStockService { @@ -111,4 +132,15 @@ public interface MesWmMaterialStockService {
111 */ 132 */
112 void updateMaterialStockQuantity(Long materialStockId, BigDecimal quantity, boolean checkFlag); 133 void updateMaterialStockQuantity(Long materialStockId, BigDecimal quantity, boolean checkFlag);
113 134
  135 + /**
  136 + * 库位混放规则校验
  137 + *
  138 + * 根据库位配置的物料和批次混放规则,检查是否允许在该库位存放指定物料/批次
  139 + *
  140 + * @param areaId 库位编号
  141 + * @param itemId 物料编号
  142 + * @param batchId 批次编号(可为 null)
  143 + */
  144 + void checkAreaMixingRule(Long areaId, Long itemId, Long batchId);
  145 +
114 } 146 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
1 package cn.iocoder.yudao.module.mes.service.wm.materialstock; 1 package cn.iocoder.yudao.module.mes.service.wm.materialstock;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.collection.SetUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
6 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO; 7 import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO;
@@ -9,9 +10,11 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMat @@ -9,9 +10,11 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMat
9 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; 10 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
10 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO; 11 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
11 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; 12 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
  13 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
12 import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; 14 import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
13 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; 15 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
14 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService; 16 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
  17 +import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
15 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
16 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
17 import org.springframework.validation.annotation.Validated; 20 import org.springframework.validation.annotation.Validated;
@@ -22,8 +25,7 @@ import java.util.*; @@ -22,8 +25,7 @@ import java.util.*;
22 25
23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
24 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
25 -import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_MATERIAL_STOCK_INSUFFICIENT;  
26 -import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_MATERIAL_STOCK_NOT_EXISTS; 28 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
27 29
28 /** 30 /**
29 * MES 库存台账 Service 实现类 31 * MES 库存台账 Service 实现类
@@ -37,15 +39,27 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService @@ -37,15 +39,27 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
37 39
38 @Resource 40 @Resource
39 private MesMdItemService itemService; 41 private MesMdItemService itemService;
40 -  
41 @Resource 42 @Resource
42 private MesMdItemTypeService itemTypeService; 43 private MesMdItemTypeService itemTypeService;
  44 + @Resource
  45 + private MesWmWarehouseAreaService areaService;
43 46
44 @Override 47 @Override
45 public MesWmMaterialStockDO getMaterialStock(Long id) { 48 public MesWmMaterialStockDO getMaterialStock(Long id) {
46 return materialStockMapper.selectById(id); 49 return materialStockMapper.selectById(id);
47 } 50 }
48 51
  52 + @Override
  53 + public MesWmMaterialStockDO getMaterialStockByCompositeKey(Long itemId, Long warehouseId, Long locationId,
  54 + Long areaId, Long batchId) {
  55 + return materialStockMapper.selectByCompositeKey(itemId, warehouseId, locationId, areaId, batchId);
  56 + }
  57 +
  58 + @Override
  59 + public List<MesWmMaterialStockDO> getMaterialStockListByAreaId(Long areaId) {
  60 + return materialStockMapper.selectList(MesWmMaterialStockDO::getAreaId, areaId);
  61 + }
  62 +
49 public MesWmMaterialStockDO validateMaterialStockExists(Long id) { 63 public MesWmMaterialStockDO validateMaterialStockExists(Long id) {
50 MesWmMaterialStockDO stock = materialStockMapper.selectById(id); 64 MesWmMaterialStockDO stock = materialStockMapper.selectById(id);
51 if (stock == null) { 65 if (stock == null) {
@@ -113,13 +127,13 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService @@ -113,13 +127,13 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
113 } 127 }
114 128
115 @Override 129 @Override
116 - public Long getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId,  
117 - Long batchId, Long vendorId, LocalDateTime receiptTime) { 130 + public MesWmMaterialStockDO getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId,
  131 + Long batchId, Long vendorId, LocalDateTime receiptTime) {
118 // 1. 查找已有库存记录 132 // 1. 查找已有库存记录
119 MesWmMaterialStockDO stock = materialStockMapper.selectByCompositeKey( 133 MesWmMaterialStockDO stock = materialStockMapper.selectByCompositeKey(
120 itemId, warehouseId, locationId, areaId, batchId); 134 itemId, warehouseId, locationId, areaId, batchId);
121 if (stock != null) { 135 if (stock != null) {
122 - return stock.getId(); 136 + return stock;
123 } 137 }
124 138
125 // 2. 不存在则新建 139 // 2. 不存在则新建
@@ -133,7 +147,7 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService @@ -133,7 +147,7 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
133 .frozenFlag(false) 147 .frozenFlag(false)
134 .build(); 148 .build();
135 materialStockMapper.insert(newStock); 149 materialStockMapper.insert(newStock);
136 - return newStock.getId(); 150 + return newStock;
137 } 151 }
138 152
139 @Override 153 @Override
@@ -145,4 +159,28 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService @@ -145,4 +159,28 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
145 } 159 }
146 } 160 }
147 161
  162 + @Override
  163 + public void checkAreaMixingRule(Long areaId, Long itemId, Long batchId) {
  164 + // 1.1 校验库位必须存在
  165 + MesWmWarehouseAreaDO area = areaService.validateWarehouseAreaExists(areaId);
  166 +
  167 + // 2. 查询当前库位上已有的库存记录
  168 + List<MesWmMaterialStockDO> stockList = getMaterialStockListByAreaId(areaId);
  169 + if (CollUtil.isEmpty(stockList)) {
  170 + return;
  171 + }
  172 + // 2.1 检查物料混放
  173 + if (Boolean.FALSE.equals(area.getAllowItemMixing()) && itemId != null) {
  174 + if (CollUtil.anyMatch(stockList, item -> ObjUtil.notEqual(itemId, item.getItemId()))) {
  175 + throw exception(WM_MATERIAL_STOCK_AREA_ITEM_MIXING_NOT_ALLOWED, area.getName());
  176 + }
  177 + }
  178 + // 2.2 检查批次混放
  179 + if (Boolean.FALSE.equals(area.getAllowBatchMixing()) && batchId != null) {
  180 + if (CollUtil.anyMatch(stockList, item -> ObjUtil.notEqual(batchId, item.getBatchId()))) {
  181 + throw exception(WM_MATERIAL_STOCK_AREA_BATCH_MIXING_NOT_ALLOWED, area.getName());
  182 + }
  183 + }
  184 + }
  185 +
148 } 186 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
@@ -3,13 +3,22 @@ package cn.iocoder.yudao.module.mes.service.wm.transaction; @@ -3,13 +3,22 @@ package cn.iocoder.yudao.module.mes.service.wm.transaction;
3 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
7 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; 9 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO;
  10 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
  11 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
  12 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
8 import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; 13 import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper;
9 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; 14 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
  15 +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
10 import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; 16 import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
11 import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; 17 import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
12 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; 18 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
  19 +import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
  20 +import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
  21 +import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
13 import jakarta.annotation.Resource; 22 import jakarta.annotation.Resource;
14 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
15 import org.springframework.transaction.annotation.Transactional; 24 import org.springframework.transaction.annotation.Transactional;
@@ -19,6 +28,9 @@ import java.math.BigDecimal; @@ -19,6 +28,9 @@ import java.math.BigDecimal;
19 import java.time.LocalDateTime; 28 import java.time.LocalDateTime;
20 import java.util.List; 29 import java.util.List;
21 30
  31 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  32 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
  33 +
22 /** 34 /**
23 * MES 库存事务流水 Service 实现类 35 * MES 库存事务流水 Service 实现类
24 */ 36 */
@@ -28,11 +40,18 @@ public class MesWmTransactionServiceImpl implements MesWmTransactionService { @@ -28,11 +40,18 @@ public class MesWmTransactionServiceImpl implements MesWmTransactionService {
28 40
29 @Resource 41 @Resource
30 private MesWmTransactionMapper transactionMapper; 42 private MesWmTransactionMapper transactionMapper;
31 -  
32 @Resource 43 @Resource
33 private MesWmMaterialStockService materialStockService; 44 private MesWmMaterialStockService materialStockService;
34 @Resource 45 @Resource
35 private MesWmBatchService batchService; 46 private MesWmBatchService batchService;
  47 + @Resource
  48 + private MesMdItemService itemService;
  49 + @Resource
  50 + private MesWmWarehouseService warehouseService;
  51 + @Resource
  52 + private MesWmWarehouseLocationService locationService;
  53 + @Resource
  54 + private MesWmWarehouseAreaService areaService;
36 55
37 @Override 56 @Override
38 @Transactional(rollbackFor = Exception.class) 57 @Transactional(rollbackFor = Exception.class)
@@ -42,23 +61,23 @@ public class MesWmTransactionServiceImpl implements MesWmTransactionService { @@ -42,23 +61,23 @@ public class MesWmTransactionServiceImpl implements MesWmTransactionService {
42 Assert.notNull(typeEnum, "事务类型({}) 不存在", reqDTO.getType()); 61 Assert.notNull(typeEnum, "事务类型({}) 不存在", reqDTO.getType());
43 // 1.2 校验 quantity 正负号与事务方向一致(入库必须正数,出库必须负数) 62 // 1.2 校验 quantity 正负号与事务方向一致(入库必须正数,出库必须负数)
44 boolean inbound = typeEnum.isInbound(); 63 boolean inbound = typeEnum.isInbound();
  64 + checkQuantity(reqDTO, inbound);
  65 + // 1.3 入库事务:批次号校验 + batchId/batchCode 互补
  66 + checkBatch(reqDTO, inbound);
  67 + // 1.4 入库事务:库位混放规则校验
45 if (inbound) { 68 if (inbound) {
46 - Assert.isTrue(reqDTO.getQuantity().compareTo(BigDecimal.ZERO) > 0,  
47 - "入库事务数量必须为正数,实际值: {}", reqDTO.getQuantity());  
48 - } else {  
49 - Assert.isTrue(reqDTO.getQuantity().compareTo(BigDecimal.ZERO) < 0,  
50 - "出库事务数量必须为负数,实际值: {}", reqDTO.getQuantity()); 69 + materialStockService.checkAreaMixingRule(reqDTO.getAreaId(), reqDTO.getItemId(), reqDTO.getBatchId());
51 } 70 }
52 - // 1.3 batchId / batchCode 互补  
53 - completeBatchInfo(reqDTO);  
54 71
55 // 2.1 获取或创建库存记录 72 // 2.1 获取或创建库存记录
56 - Long materialStockId = materialStockService.getOrCreateMaterialStock( 73 + MesWmMaterialStockDO materialStock = materialStockService.getOrCreateMaterialStock(
57 reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(), 74 reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(),
58 reqDTO.getBatchId(), reqDTO.getVendorId(), reqDTO.getReceiptTime()); 75 reqDTO.getBatchId(), reqDTO.getVendorId(), reqDTO.getReceiptTime());
59 - // 2.2 更新库存数量 76 + // 2.2 冻结校验(仓库、库区、库位、库存记录)
  77 + checkFrozen(reqDTO, materialStock);
  78 + // 2.3 更新库存数量
60 boolean checkFlag = ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true); 79 boolean checkFlag = ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true);
61 - materialStockService.updateMaterialStockQuantity(materialStockId, reqDTO.getQuantity(), checkFlag); 80 + materialStockService.updateMaterialStockQuantity(materialStock.getId(), reqDTO.getQuantity(), checkFlag);
62 81
63 // 3. 插入事务流水 82 // 3. 插入事务流水
64 MesWmTransactionDO transaction = MesWmTransactionDO.builder() 83 MesWmTransactionDO transaction = MesWmTransactionDO.builder()
@@ -66,7 +85,7 @@ public class MesWmTransactionServiceImpl implements MesWmTransactionService { @@ -66,7 +85,7 @@ public class MesWmTransactionServiceImpl implements MesWmTransactionService {
66 .itemId(reqDTO.getItemId()).batchId(reqDTO.getBatchId()).batchCode(reqDTO.getBatchCode()) 85 .itemId(reqDTO.getItemId()).batchId(reqDTO.getBatchId()).batchCode(reqDTO.getBatchCode())
67 .warehouseId(reqDTO.getWarehouseId()).locationId(reqDTO.getLocationId()).areaId(reqDTO.getAreaId()) 86 .warehouseId(reqDTO.getWarehouseId()).locationId(reqDTO.getLocationId()).areaId(reqDTO.getAreaId())
68 .bizType(reqDTO.getBizType()).bizId(reqDTO.getBizId()).bizCode(reqDTO.getBizCode()).bizLineId(reqDTO.getBizLineId()) 87 .bizType(reqDTO.getBizType()).bizId(reqDTO.getBizId()).bizCode(reqDTO.getBizCode()).bizLineId(reqDTO.getBizLineId())
69 - .materialStockId(materialStockId).relatedTransactionId(reqDTO.getRelatedTransactionId()) 88 + .materialStockId(materialStock.getId()).relatedTransactionId(reqDTO.getRelatedTransactionId())
70 .build(); 89 .build();
71 transactionMapper.insert(transaction); 90 transactionMapper.insert(transaction);
72 return transaction.getId(); 91 return transaction.getId();
@@ -78,30 +97,79 @@ public class MesWmTransactionServiceImpl implements MesWmTransactionService { @@ -78,30 +97,79 @@ public class MesWmTransactionServiceImpl implements MesWmTransactionService {
78 reqDTOs.forEach(this::createTransaction); 97 reqDTOs.forEach(this::createTransaction);
79 } 98 }
80 99
  100 + // ==================== 私有校验方法 ====================
  101 +
81 /** 102 /**
82 - * batchId / batchCode 互补:有 batchId 无 batchCode 时,查出 batchCode;反之亦然。  
83 - *  
84 - * @param reqDTO 事务请求 DTO 103 + * 校验数量正负号与事务方向一致
85 */ 104 */
86 - private void completeBatchInfo(MesWmTransactionSaveReqDTO reqDTO) {  
87 - if (reqDTO.getBatchId() != null && StrUtil.isNotEmpty(reqDTO.getBatchCode())) {  
88 - return; 105 + private void checkQuantity(MesWmTransactionSaveReqDTO reqDTO, boolean inbound) {
  106 + if (inbound) {
  107 + Assert.isTrue(reqDTO.getQuantity().compareTo(BigDecimal.ZERO) > 0,
  108 + "入库事务数量必须为正数,实际值: {}", reqDTO.getQuantity());
  109 + } else {
  110 + Assert.isTrue(reqDTO.getQuantity().compareTo(BigDecimal.ZERO) < 0,
  111 + "出库事务数量必须为负数,实际值: {}", reqDTO.getQuantity());
89 } 112 }
90 - // 情况一:有 batchId,补 batchCode  
91 - if (reqDTO.getBatchId() != null) { 113 + }
  114 +
  115 + /**
  116 + * 批次校验:batchId/batchCode 互补 + 入库时批次号必填检查
  117 + *
  118 + * @param reqDTO 事务请求 DTO
  119 + * @param inbound 是否入库
  120 + */
  121 + private void checkBatch(MesWmTransactionSaveReqDTO reqDTO, boolean inbound) {
  122 + // 1. batchId / batchCode 互补
  123 + if (reqDTO.getBatchId() != null && StrUtil.isEmpty(reqDTO.getBatchCode())) {
92 MesWmBatchDO batch = batchService.getBatch(reqDTO.getBatchId()); 124 MesWmBatchDO batch = batchService.getBatch(reqDTO.getBatchId());
93 if (batch != null) { 125 if (batch != null) {
94 reqDTO.setBatchCode(batch.getCode()); 126 reqDTO.setBatchCode(batch.getCode());
95 } 127 }
96 - return;  
97 - }  
98 - // 情况二:有 batchCode,补 batchId  
99 - if (StrUtil.isNotEmpty(reqDTO.getBatchCode())) { 128 + } else if (StrUtil.isNotEmpty(reqDTO.getBatchCode()) && reqDTO.getBatchId() == null) {
100 MesWmBatchDO batch = batchService.getBatchByCode(reqDTO.getBatchCode()); 129 MesWmBatchDO batch = batchService.getBatchByCode(reqDTO.getBatchCode());
101 if (batch != null) { 130 if (batch != null) {
102 reqDTO.setBatchId(batch.getId()); 131 reqDTO.setBatchId(batch.getId());
103 } 132 }
104 } 133 }
  134 + // 2. 入库时:启用批次管理的物料,必须传递批次号
  135 + if (inbound) {
  136 + MesMdItemDO item = itemService.getItem(reqDTO.getItemId());
  137 + if (item != null && Boolean.TRUE.equals(item.getBatchFlag())) {
  138 + if (StrUtil.isEmpty(reqDTO.getBatchCode()) && reqDTO.getBatchId() == null) {
  139 + throw exception(WM_TRANSACTION_BATCH_REQUIRED);
  140 + }
  141 + }
  142 + }
  143 + }
  144 +
  145 + /**
  146 + * 冻结校验:检查仓库、库区、库位、库存记录是否被冻结
  147 + *
  148 + * @param reqDTO 事务请求 DTO
  149 + * @param materialStock 库存记录
  150 + */
  151 + private void checkFrozen(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO materialStock) {
  152 + // 1.1 检查仓库冻结
  153 + MesWmWarehouseDO warehouse = warehouseService.validateWarehouseExists(reqDTO.getWarehouseId());
  154 + if (Boolean.TRUE.equals(warehouse.getFrozen())) {
  155 + throw exception(WM_TRANSACTION_WAREHOUSE_FROZEN, warehouse.getName());
  156 + }
  157 + // 1.2 检查库区冻结
  158 + MesWmWarehouseLocationDO location = locationService.validateWarehouseLocationExists(reqDTO.getLocationId());
  159 + if (Boolean.TRUE.equals(location.getFrozen())) {
  160 + throw exception(WM_TRANSACTION_LOCATION_FROZEN, location.getName());
  161 + }
  162 + // 1.3 检查库位冻结
  163 + MesWmWarehouseAreaDO area = areaService.validateWarehouseAreaExists(reqDTO.getAreaId());
  164 + if (Boolean.TRUE.equals(area.getFrozen())) {
  165 + throw exception(WM_TRANSACTION_AREA_FROZEN, area.getName());
  166 + }
  167 +
  168 + // 2. 检查库存记录冻结
  169 + if (Boolean.TRUE.equals(materialStock.getFrozenFlag())) {
  170 + throw exception(WM_TRANSACTION_STOCK_FROZEN,
  171 + warehouse.getName(), location.getName(), area.getName());
  172 + }
105 } 173 }
106 174
107 } 175 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
@@ -85,6 +85,7 @@ public class MesWmTransactionSaveReqDTO { @@ -85,6 +85,7 @@ public class MesWmTransactionSaveReqDTO {
85 /** 85 /**
86 * 库位 ID 86 * 库位 ID
87 */ 87 */
  88 + @NotNull(message = "库位 ID 不能为空")
88 private Long areaId; 89 private Long areaId;
89 90
90 // ===== 台账补充字段(入库时用于初始化 MesWmMaterialStockDO) ===== 91 // ===== 台账补充字段(入库时用于初始化 MesWmMaterialStockDO) =====
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseAreaServiceImpl.java
@@ -39,6 +39,7 @@ public class MesWmWarehouseAreaServiceImpl implements MesWmWarehouseAreaService @@ -39,6 +39,7 @@ public class MesWmWarehouseAreaServiceImpl implements MesWmWarehouseAreaService
39 @Lazy 39 @Lazy
40 private MesMdWorkstationService workstationService; 40 private MesMdWorkstationService workstationService;
41 @Resource 41 @Resource
  42 + @Lazy
42 private MesWmMaterialStockService materialStockService; 43 private MesWmMaterialStockService materialStockService;
43 @Resource 44 @Resource
44 @Lazy 45 @Lazy