Commit a46f2b1f1aa2469a49210800f48f079f8edd73b8

Authored by YunaiV
1 parent 2f78c4df

✨ feat(mes): 新增单据状态字段,更新库存冻结状态批量处理

Showing 14 changed files with 188 additions and 78 deletions
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transfer/vo/MesWmTransferPageReqVO.java
@@ -5,11 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -5,11 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 5 import lombok.Data;
6 import lombok.EqualsAndHashCode; 6 import lombok.EqualsAndHashCode;
7 import lombok.ToString; 7 import lombok.ToString;
8 -import org.springframework.format.annotation.DateTimeFormat;  
9 -  
10 -import java.time.LocalDateTime;  
11 -  
12 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
13 8
14 @Schema(description = "管理后台 - MES 转移单分页 Request VO") 9 @Schema(description = "管理后台 - MES 转移单分页 Request VO")
15 @Data 10 @Data
@@ -26,5 +21,7 @@ public class MesWmTransferPageReqVO extends PageParam { @@ -26,5 +21,7 @@ public class MesWmTransferPageReqVO extends PageParam {
26 @Schema(description = "转移单类型", example = "1") 21 @Schema(description = "转移单类型", example = "1")
27 private Integer type; 22 private Integer type;
28 23
  24 + @Schema(description = "单据状态", example = "0")
  25 + private Integer status;
29 26
30 } 27 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
@@ -49,6 +49,11 @@ public interface MesWmMaterialStockMapper extends BaseMapperX<MesWmMaterialStock @@ -49,6 +49,11 @@ public interface MesWmMaterialStockMapper extends BaseMapperX<MesWmMaterialStock
49 return selectByIds(ids); 49 return selectByIds(ids);
50 } 50 }
51 51
  52 + default void updateByIds(Collection<Long> ids, MesWmMaterialStockDO updateObj) {
  53 + update(updateObj, new LambdaUpdateWrapper<MesWmMaterialStockDO>()
  54 + .in(MesWmMaterialStockDO::getId, ids));
  55 + }
  56 +
52 /** 57 /**
53 * 增量更新库存数量 58 * 增量更新库存数量
54 * 59 *
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/transfer/MesWmTransferMapper.java
@@ -18,6 +18,7 @@ public interface MesWmTransferMapper extends BaseMapperX&lt;MesWmTransferDO&gt; { @@ -18,6 +18,7 @@ public interface MesWmTransferMapper extends BaseMapperX&lt;MesWmTransferDO&gt; {
18 .likeIfPresent(MesWmTransferDO::getCode, reqVO.getCode()) 18 .likeIfPresent(MesWmTransferDO::getCode, reqVO.getCode())
19 .likeIfPresent(MesWmTransferDO::getName, reqVO.getName()) 19 .likeIfPresent(MesWmTransferDO::getName, reqVO.getName())
20 .eqIfPresent(MesWmTransferDO::getType, reqVO.getType()) 20 .eqIfPresent(MesWmTransferDO::getType, reqVO.getType())
  21 + .eqIfPresent(MesWmTransferDO::getStatus, reqVO.getStatus())
21 .orderByDesc(MesWmTransferDO::getId)); 22 .orderByDesc(MesWmTransferDO::getId));
22 } 23 }
23 24
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -453,6 +453,7 @@ public interface ErrorCodeConstants { @@ -453,6 +453,7 @@ public interface ErrorCodeConstants {
453 ErrorCode WM_TRANSFER_LINE_NOT_EXISTS = new ErrorCode(1_040_710_100, "转移单行不存在"); 453 ErrorCode WM_TRANSFER_LINE_NOT_EXISTS = new ErrorCode(1_040_710_100, "转移单行不存在");
454 ErrorCode WM_TRANSFER_DETAIL_NOT_EXISTS = new ErrorCode(1_040_710_200, "调拨明细不存在"); 454 ErrorCode WM_TRANSFER_DETAIL_NOT_EXISTS = new ErrorCode(1_040_710_200, "调拨明细不存在");
455 ErrorCode WM_TRANSFER_DETAIL_QUANTITY_EXCEED = new ErrorCode(1_040_710_201, "调拨明细总数量不能超过调拨单行数量"); 455 ErrorCode WM_TRANSFER_DETAIL_QUANTITY_EXCEED = new ErrorCode(1_040_710_201, "调拨明细总数量不能超过调拨单行数量");
  456 + ErrorCode WM_TRANSFER_DETAIL_MIXED_GOODS = new ErrorCode(1_040_710_202, "同一目标仓位下已存在其他物料的明细,不允许混货");
456 457
457 // ========== MES 仓库管理-生产退料单(1-040-711-000) ========== 458 // ========== MES 仓库管理-生产退料单(1-040-711-000) ==========
458 ErrorCode WM_RETURN_ISSUE_NOT_EXISTS = new ErrorCode(1_040_710_000, "生产退料单不存在"); 459 ErrorCode WM_RETURN_ISSUE_NOT_EXISTS = new ErrorCode(1_040_710_000, "生产退料单不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/md/autocode/MesMdAutoCodeRuleCodeEnum.java
@@ -33,7 +33,8 @@ public enum MesMdAutoCodeRuleCodeEnum { @@ -33,7 +33,8 @@ public enum MesMdAutoCodeRuleCodeEnum {
33 WM_LOCATION_CODE("WM_LOCATION_CODE", "库区编码"), 33 WM_LOCATION_CODE("WM_LOCATION_CODE", "库区编码"),
34 WM_AREA_CODE("WM_AREA_CODE", "库位编码"), 34 WM_AREA_CODE("WM_AREA_CODE", "库位编码"),
35 WM_PRODUCT_SALES_CODE("WM_PRODUCT_SALES_CODE", "销售出库单编码"), 35 WM_PRODUCT_SALES_CODE("WM_PRODUCT_SALES_CODE", "销售出库单编码"),
36 - WM_MISC_RECEIPT_CODE("WM_MISC_RECEIPT_CODE", "杂项入库单编码"); 36 + WM_MISC_RECEIPT_CODE("WM_MISC_RECEIPT_CODE", "杂项入库单编码"),
  37 + WM_TRANSFER_CODE("WM_TRANSFER_CODE", "转移调拨单编码");
37 38
38 /** 39 /**
39 * 规则代码 40 * 规则代码
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmProductProduceStatusEnum.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mes.enums.wm; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mes.enums.wm;
2 2
3 import cn.iocoder.yudao.framework.common.core.ArrayValuable; 3 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
4 import cn.iocoder.yudao.module.mes.enums.MesOrderStatusConstants; 4 import cn.iocoder.yudao.module.mes.enums.MesOrderStatusConstants;
  5 +import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService;
5 import lombok.AllArgsConstructor; 6 import lombok.AllArgsConstructor;
6 import lombok.Getter; 7 import lombok.Getter;
7 8
@@ -16,13 +17,16 @@ import java.util.Arrays; @@ -16,13 +17,16 @@ import java.util.Arrays;
16 @AllArgsConstructor 17 @AllArgsConstructor
17 public enum MesWmProductProduceStatusEnum implements ArrayValuable<Integer> { 18 public enum MesWmProductProduceStatusEnum implements ArrayValuable<Integer> {
18 19
19 - // TODO @AI:对应的方法;参考别的 MesWmProductIssueStatusEnum  
20 /** 20 /**
21 * 草稿 21 * 草稿
  22 + *
  23 + * 对应方法:{@link MesWmProductProduceService#generateProductProduce}
22 */ 24 */
23 PREPARE(MesOrderStatusConstants.PREPARE, "草稿"), 25 PREPARE(MesOrderStatusConstants.PREPARE, "草稿"),
24 /** 26 /**
25 * 已完成 27 * 已完成
  28 + *
  29 + * 对应方法:{@link MesWmProductProduceService#finishProductProduce(Long)}
26 */ 30 */
27 FINISHED(MesOrderStatusConstants.FINISHED, "已完成"), 31 FINISHED(MesOrderStatusConstants.FINISHED, "已完成"),
28 /** 32 /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmTransferTypeEnum.java
@@ -6,7 +6,6 @@ import lombok.Getter; @@ -6,7 +6,6 @@ import lombok.Getter;
6 6
7 import java.util.Arrays; 7 import java.util.Arrays;
8 8
9 -// TODO @AI:没有 IntArrayValuable,你检查下;应该是另外个  
10 /** 9 /**
11 * MES 转移单类型枚举 10 * MES 转移单类型枚举
12 * 11 *
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
@@ -11,9 +11,6 @@ import java.math.BigDecimal; @@ -11,9 +11,6 @@ import java.math.BigDecimal;
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 import java.util.Collection; 12 import java.util.Collection;
13 import java.util.List; 13 import java.util.List;
14 -import java.util.Map;  
15 -  
16 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;  
17 14
18 /** 15 /**
19 * MES 库存台账 Service 接口 16 * MES 库存台账 Service 接口
@@ -52,6 +49,14 @@ public interface MesWmMaterialStockService { @@ -52,6 +49,14 @@ public interface MesWmMaterialStockService {
52 void updateMaterialStockFrozen(@Valid MesWmMaterialStockFreezeReqVO updateReqVO); 49 void updateMaterialStockFrozen(@Valid MesWmMaterialStockFreezeReqVO updateReqVO);
53 50
54 /** 51 /**
  52 + * 批量更新库存冻结状态
  53 + *
  54 + * @param ids 库存记录编号列表
  55 + * @param frozen 是否冻结
  56 + */
  57 + void updateMaterialStockFrozen(Collection<Long> ids, boolean frozen);
  58 +
  59 + /**
55 * 获得仓库下库存记录数量 60 * 获得仓库下库存记录数量
56 * 61 *
57 * @param warehouseId 仓库编号 62 * @param warehouseId 仓库编号
@@ -91,10 +96,6 @@ public interface MesWmMaterialStockService { @@ -91,10 +96,6 @@ public interface MesWmMaterialStockService {
91 */ 96 */
92 List<MesWmMaterialStockDO> getMaterialStockList(MesWmMaterialStockListReqVO reqVO); 97 List<MesWmMaterialStockDO> getMaterialStockList(MesWmMaterialStockListReqVO reqVO);
93 98
94 - default Map<Long, MesWmMaterialStockDO> getMaterialStockMap(Collection<Long> ids) {  
95 - return convertMap(getMaterialStockList(ids), MesWmMaterialStockDO::getId);  
96 - }  
97 -  
98 /** 99 /**
99 * 获得库存精简列表(用于前端下拉选择) 100 * 获得库存精简列表(用于前端下拉选择)
100 * 101 *
@@ -122,11 +123,11 @@ public interface MesWmMaterialStockService { @@ -122,11 +123,11 @@ public interface MesWmMaterialStockService {
122 /** 123 /**
123 * 更新库存数量 124 * 更新库存数量
124 * 125 *
125 - * @param materialStockId 库存记录编号 126 + * @param id 库存记录编号
126 * @param quantity 变动数量(正数=增加,负数=扣减) 127 * @param quantity 变动数量(正数=增加,负数=扣减)
127 * @param checkFlag 是否校验库存充足(为 true 且扣减后为负则报错) 128 * @param checkFlag 是否校验库存充足(为 true 且扣减后为负则报错)
128 */ 129 */
129 - void updateMaterialStockQuantity(Long materialStockId, BigDecimal quantity, boolean checkFlag); 130 + void updateMaterialStockQuantity(Long id, BigDecimal quantity, boolean checkFlag);
130 131
131 /** 132 /**
132 * 库位混放规则校验 133 * 库位混放规则校验
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -93,6 +93,14 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService @@ -93,6 +93,14 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
93 } 93 }
94 94
95 @Override 95 @Override
  96 + public void updateMaterialStockFrozen(Collection<Long> ids, boolean frozen) {
  97 + if (CollUtil.isEmpty(ids)) {
  98 + return;
  99 + }
  100 + materialStockMapper.updateByIds(ids, new MesWmMaterialStockDO().setFrozen(frozen));
  101 + }
  102 +
  103 + @Override
96 public Long getMaterialStockCountByWarehouseId(Long warehouseId) { 104 public Long getMaterialStockCountByWarehouseId(Long warehouseId) {
97 return materialStockMapper.selectCountByWarehouseId(warehouseId); 105 return materialStockMapper.selectCountByWarehouseId(warehouseId);
98 } 106 }
@@ -153,9 +161,9 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService @@ -153,9 +161,9 @@ public class MesWmMaterialStockServiceImpl implements MesWmMaterialStockService
153 } 161 }
154 162
155 @Override 163 @Override
156 - public void updateMaterialStockQuantity(Long materialStockId, BigDecimal quantity, boolean checkFlag) { 164 + public void updateMaterialStockQuantity(Long id, BigDecimal quantity, boolean checkFlag) {
157 // 更新库存数量(Mapper 层 CAS 防负库存;checkFlag=false 时允许负数) 165 // 更新库存数量(Mapper 层 CAS 防负库存;checkFlag=false 时允许负数)
158 - int updatedRows = materialStockMapper.updateQuantity(materialStockId, quantity, checkFlag); 166 + int updatedRows = materialStockMapper.updateQuantity(id, quantity, checkFlag);
159 if (updatedRows == 0) { 167 if (updatedRows == 0) {
160 throw exception(WM_MATERIAL_STOCK_INSUFFICIENT); 168 throw exception(WM_MATERIAL_STOCK_INSUFFICIENT);
161 } 169 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java
@@ -45,11 +45,8 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ @@ -45,11 +45,8 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ
45 45
46 @Override 46 @Override
47 public Long createSalesNoticeLine(MesWmSalesNoticeLineSaveReqVO createReqVO) { 47 public Long createSalesNoticeLine(MesWmSalesNoticeLineSaveReqVO createReqVO) {
48 - // TODO @AI:增加 validateXXXSaveData 方法  
49 - // 校验父单据存在且为草稿状态  
50 - validateNoticeStatusDraft(createReqVO.getNoticeId());  
51 - // 校验物料存在  
52 - itemService.validateItemExists(createReqVO.getItemId()); 48 + // 校验数据
  49 + validateLineSaveData(createReqVO);
53 50
54 // 插入 51 // 插入
55 MesWmSalesNoticeLineDO line = BeanUtils.toBean(createReqVO, MesWmSalesNoticeLineDO.class); 52 MesWmSalesNoticeLineDO line = BeanUtils.toBean(createReqVO, MesWmSalesNoticeLineDO.class);
@@ -62,13 +59,9 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ @@ -62,13 +59,9 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ
62 public void updateSalesNoticeLine(MesWmSalesNoticeLineSaveReqVO updateReqVO) { 59 public void updateSalesNoticeLine(MesWmSalesNoticeLineSaveReqVO updateReqVO) {
63 // 校验存在 60 // 校验存在
64 MesWmSalesNoticeLineDO line = validateSalesNoticeLineExists(updateReqVO.getId()); 61 MesWmSalesNoticeLineDO line = validateSalesNoticeLineExists(updateReqVO.getId());
65 - // TODO @AI:增加 validateXXXSaveData 方法  
66 - // 校验父单据存在且为草稿状态  
67 - validateNoticeStatusDraft(line.getNoticeId());  
68 - // 校验物料存在  
69 - if (updateReqVO.getItemId() != null) {  
70 - itemService.validateItemExists(updateReqVO.getItemId());  
71 - } 62 + // 校验数据
  63 + updateReqVO.setNoticeId(line.getNoticeId());
  64 + validateLineSaveData(updateReqVO);
72 65
73 // 更新 66 // 更新
74 MesWmSalesNoticeLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmSalesNoticeLineDO.class); 67 MesWmSalesNoticeLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmSalesNoticeLineDO.class);
@@ -115,6 +108,15 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ @@ -115,6 +108,15 @@ public class MesWmSalesNoticeLineServiceImpl implements MesWmSalesNoticeLineServ
115 return line; 108 return line;
116 } 109 }
117 110
  111 + private void validateLineSaveData(MesWmSalesNoticeLineSaveReqVO reqVO) {
  112 + // 校验父单据存在且为草稿状态
  113 + validateNoticeStatusDraft(reqVO.getNoticeId());
  114 + // 校验物料存在
  115 + if (reqVO.getItemId() != null) {
  116 + itemService.validateItemExists(reqVO.getItemId());
  117 + }
  118 + }
  119 +
118 /** 120 /**
119 * 校验父发货通知单存在且为草稿状态 121 * 校验父发货通知单存在且为草稿状态
120 */ 122 */
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferDetailService.java
@@ -64,4 +64,11 @@ public interface MesWmTransferDetailService { @@ -64,4 +64,11 @@ public interface MesWmTransferDetailService {
64 */ 64 */
65 void deleteTransferDetailByTransferId(Long transferId); 65 void deleteTransferDetailByTransferId(Long transferId);
66 66
  67 + /**
  68 + * 根据行 ID 删除明细
  69 + *
  70 + * @param lineId 行 ID
  71 + */
  72 + void deleteTransferDetailByLineId(Long lineId);
  73 +
67 } 74 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferDetailServiceImpl.java
1 package cn.iocoder.yudao.module.mes.service.wm.transfer; 1 package cn.iocoder.yudao.module.mes.service.wm.transfer;
2 2
  3 +import cn.hutool.core.util.ObjUtil;
3 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 import cn.iocoder.yudao.module.mes.controller.admin.wm.transfer.vo.detail.MesWmTransferDetailSaveReqVO; 6 import cn.iocoder.yudao.module.mes.controller.admin.wm.transfer.vo.detail.MesWmTransferDetailSaveReqVO;
@@ -18,6 +19,7 @@ import java.util.List; @@ -18,6 +19,7 @@ import java.util.List;
18 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
19 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_TRANSFER_DETAIL_NOT_EXISTS; 20 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_TRANSFER_DETAIL_NOT_EXISTS;
20 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_TRANSFER_DETAIL_QUANTITY_EXCEED; 21 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_TRANSFER_DETAIL_QUANTITY_EXCEED;
  22 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_TRANSFER_DETAIL_MIXED_GOODS;
21 23
22 /** 24 /**
23 * MES 调拨明细 Service 实现类 25 * MES 调拨明细 Service 实现类
@@ -31,6 +33,9 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic @@ -31,6 +33,9 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic
31 33
32 @Resource 34 @Resource
33 @Lazy 35 @Lazy
  36 + private MesWmTransferService transferService;
  37 + @Resource
  38 + @Lazy
34 private MesWmTransferLineService transferLineService; 39 private MesWmTransferLineService transferLineService;
35 40
36 @Resource 41 @Resource
@@ -38,14 +43,8 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic @@ -38,14 +43,8 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic
38 43
39 @Override 44 @Override
40 public Long createTransferDetail(MesWmTransferDetailSaveReqVO createReqVO) { 45 public Long createTransferDetail(MesWmTransferDetailSaveReqVO createReqVO) {
41 - // 校验父数据存在  
42 - MesWmTransferLineDO line = transferLineService.validateTransferLineExists(createReqVO.getLineId());  
43 - // 校验仓库、库区、库位的关联关系  
44 - warehouseAreaService.validateWarehouseAreaExists(  
45 - createReqVO.getToWarehouseId(), createReqVO.getToLocationId(), createReqVO.getToAreaId());  
46 - // 校验明细总数量不超过行数量  
47 - validateDetailQuantityNotExceed(createReqVO.getLineId(), createReqVO.getQuantity(), null, line);  
48 - // TODO @芋艿:不能混货 46 + // 校验
  47 + validateDetailSaveData(createReqVO, null);
49 48
50 // 插入 49 // 插入
51 MesWmTransferDetailDO detail = BeanUtils.toBean(createReqVO, MesWmTransferDetailDO.class); 50 MesWmTransferDetailDO detail = BeanUtils.toBean(createReqVO, MesWmTransferDetailDO.class);
@@ -57,14 +56,8 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic @@ -57,14 +56,8 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic
57 public void updateTransferDetail(MesWmTransferDetailSaveReqVO updateReqVO) { 56 public void updateTransferDetail(MesWmTransferDetailSaveReqVO updateReqVO) {
58 // 校验存在 57 // 校验存在
59 validateTransferDetailExists(updateReqVO.getId()); 58 validateTransferDetailExists(updateReqVO.getId());
60 - // 校验父数据存在  
61 - MesWmTransferLineDO line = transferLineService.validateTransferLineExists(updateReqVO.getLineId());  
62 - // 校验仓库、库区、库位的关联关系  
63 - warehouseAreaService.validateWarehouseAreaExists(  
64 - updateReqVO.getToWarehouseId(), updateReqVO.getToLocationId(), updateReqVO.getToAreaId());  
65 - // 校验明细总数量不超过行数量(排除自身)  
66 - validateDetailQuantityNotExceed(updateReqVO.getLineId(), updateReqVO.getQuantity(), updateReqVO.getId(), line);  
67 - // TODO @芋艿:不能混货 59 + // 校验
  60 + validateDetailSaveData(updateReqVO, updateReqVO.getId());
68 61
69 // 更新 62 // 更新
70 MesWmTransferDetailDO updateObj = BeanUtils.toBean(updateReqVO, MesWmTransferDetailDO.class); 63 MesWmTransferDetailDO updateObj = BeanUtils.toBean(updateReqVO, MesWmTransferDetailDO.class);
@@ -74,7 +67,13 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic @@ -74,7 +67,13 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic
74 @Override 67 @Override
75 public void deleteTransferDetail(Long id) { 68 public void deleteTransferDetail(Long id) {
76 // 校验存在 69 // 校验存在
77 - validateTransferDetailExists(id); 70 + MesWmTransferDetailDO detail = transferDetailMapper.selectById(id);
  71 + if (detail == null) {
  72 + throw exception(WM_TRANSFER_DETAIL_NOT_EXISTS);
  73 + }
  74 + // 校验父转移单可编辑(通过 line 找到 transfer)
  75 + MesWmTransferLineDO line = transferLineService.validateTransferLineExists(detail.getLineId());
  76 + transferService.validateTransferEditable(line.getTransferId());
78 // 删除 77 // 删除
79 transferDetailMapper.deleteById(id); 78 transferDetailMapper.deleteById(id);
80 } 79 }
@@ -100,6 +99,13 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic @@ -100,6 +99,13 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic
100 .forEach(detail -> transferDetailMapper.deleteById(detail.getId())); 99 .forEach(detail -> transferDetailMapper.deleteById(detail.getId()));
101 } 100 }
102 101
  102 + @Override
  103 + public void deleteTransferDetailByLineId(Long lineId) {
  104 + // TODO @AI:不要 get;直接去 deletebylineId;
  105 + getTransferDetailListByLineId(lineId)
  106 + .forEach(detail -> transferDetailMapper.deleteById(detail.getId()));
  107 + }
  108 +
103 private void validateTransferDetailExists(Long id) { 109 private void validateTransferDetailExists(Long id) {
104 if (transferDetailMapper.selectById(id) == null) { 110 if (transferDetailMapper.selectById(id) == null) {
105 throw exception(WM_TRANSFER_DETAIL_NOT_EXISTS); 111 throw exception(WM_TRANSFER_DETAIL_NOT_EXISTS);
@@ -107,6 +113,25 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic @@ -107,6 +113,25 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic
107 } 113 }
108 114
109 /** 115 /**
  116 + * 校验明细保存数据
  117 + *
  118 + * @param reqVO 保存请求
  119 + * @param excludeDetailId 排除的明细 ID(更新时排除自身,新增时传 null)
  120 + */
  121 + private void validateDetailSaveData(MesWmTransferDetailSaveReqVO reqVO, Long excludeDetailId) {
  122 + // 校验父数据存在
  123 + MesWmTransferLineDO line = transferLineService.validateTransferLineExists(reqVO.getLineId());
  124 + // 校验仓库、库区、库位的关联关系
  125 + warehouseAreaService.validateWarehouseAreaExists(
  126 + reqVO.getToWarehouseId(), reqVO.getToLocationId(), reqVO.getToAreaId());
  127 + // 校验明细总数量不超过行数量
  128 + validateDetailQuantityNotExceed(reqVO.getLineId(), reqVO.getQuantity(), excludeDetailId, line);
  129 + // 校验不能混货:同一个目标仓位下,不能存在不同物料的明细
  130 + validateNoMixedGoods(reqVO.getLineId(), reqVO.getToWarehouseId(),
  131 + reqVO.getToLocationId(), reqVO.getToAreaId(), line.getItemId(), excludeDetailId);
  132 + }
  133 +
  134 + /**
110 * 校验明细总数量不超过行数量 135 * 校验明细总数量不超过行数量
111 * 136 *
112 * @param lineId 行 ID 137 * @param lineId 行 ID
@@ -128,4 +153,45 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic @@ -128,4 +153,45 @@ public class MesWmTransferDetailServiceImpl implements MesWmTransferDetailServic
128 } 153 }
129 } 154 }
130 155
  156 + /**
  157 + * 校验不能混货:同一转移单下,同一个目标仓位(仓库+库区+库位)只能放同一个物料
  158 + *
  159 + * @param lineId 当前行 ID(用于排除同行明细,因为同行物料相同)
  160 + * @param toWarehouseId 目标仓库 ID
  161 + * @param toLocationId 目标库区 ID
  162 + * @param toAreaId 目标库位 ID
  163 + * @param itemId 当前物料 ID
  164 + * @param excludeDetailId 排除的明细 ID(更新时排除自身)
  165 + */
  166 + private void validateNoMixedGoods(Long lineId, Long toWarehouseId, Long toLocationId,
  167 + Long toAreaId, Long itemId, Long excludeDetailId) {
  168 + // 1. 获取当前转移单下的所有明细
  169 + // 说明:因为无法直接从参数拿到 transferId,所以先通过 lineId 查询当前行,进而获取 transferId 和所有明细
  170 + MesWmTransferLineDO currentLine = transferLineService.validateTransferLineExists(lineId);
  171 + List<MesWmTransferDetailDO> allDetails = transferDetailMapper.selectListByTransferId(currentLine.getTransferId());
  172 +
  173 + // 2. 遍历所有明细,校验同一个目标仓位不能有不同的物料
  174 + for (MesWmTransferDetailDO detail : allDetails) {
  175 + // 2.1 排除当前正在编辑的明细(更新时)
  176 + if (excludeDetailId != null && excludeDetailId.equals(detail.getId())) {
  177 + continue;
  178 + }
  179 + // 2.2 排除同行的明细(同行属于同一个转移单行,物料必定相同,无需校验)
  180 + if (detail.getLineId().equals(lineId)) {
  181 + continue;
  182 + }
  183 + // 2.3 目标仓位不同,则不会混货,直接跳过
  184 + if (ObjUtil.notEqual(detail.getToWarehouseId(), toWarehouseId)
  185 + || ObjUtil.notEqual(detail.getToLocationId(), toLocationId)
  186 + || ObjUtil.notEqual(detail.getToAreaId(), toAreaId)) {
  187 + continue;
  188 + }
  189 + // 2.4 重点:如果目标仓位相同,则必须保证物料也相同
  190 + MesWmTransferLineDO otherLine = transferLineService.validateTransferLineExists(detail.getLineId());
  191 + if (ObjUtil.notEqual(otherLine.getItemId(), itemId)) {
  192 + throw exception(WM_TRANSFER_DETAIL_MIXED_GOODS);
  193 + }
  194 + }
  195 + }
  196 +
131 } 197 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferLineServiceImpl.java
@@ -5,15 +5,18 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.transfer.vo.line.MesWmTra @@ -5,15 +5,18 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.transfer.vo.line.MesWmTra
5 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transfer.MesWmTransferLineDO; 5 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transfer.MesWmTransferLineDO;
6 import cn.iocoder.yudao.module.mes.dal.mysql.wm.transfer.MesWmTransferLineMapper; 6 import cn.iocoder.yudao.module.mes.dal.mysql.wm.transfer.MesWmTransferLineMapper;
7 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; 7 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
  8 +import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
8 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; 9 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
9 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
10 import org.springframework.context.annotation.Lazy; 11 import org.springframework.context.annotation.Lazy;
11 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
  13 +import org.springframework.transaction.annotation.Transactional;
12 import org.springframework.validation.annotation.Validated; 14 import org.springframework.validation.annotation.Validated;
13 15
14 import java.util.List; 16 import java.util.List;
15 17
16 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 18 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  19 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_MATERIAL_STOCK_NOT_EXISTS;
17 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_TRANSFER_LINE_NOT_EXISTS; 20 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_TRANSFER_LINE_NOT_EXISTS;
18 21
19 /** 22 /**
@@ -30,19 +33,19 @@ public class MesWmTransferLineServiceImpl implements MesWmTransferLineService { @@ -30,19 +33,19 @@ public class MesWmTransferLineServiceImpl implements MesWmTransferLineService {
30 @Lazy 33 @Lazy
31 private MesWmTransferService transferService; 34 private MesWmTransferService transferService;
32 @Resource 35 @Resource
  36 + @Lazy
  37 + private MesWmTransferDetailService transferDetailService;
  38 + @Resource
33 private MesMdItemService itemService; 39 private MesMdItemService itemService;
34 @Resource 40 @Resource
35 private MesWmWarehouseAreaService warehouseAreaService; 41 private MesWmWarehouseAreaService warehouseAreaService;
  42 + @Resource
  43 + private MesWmMaterialStockService materialStockService;
36 44
37 @Override 45 @Override
38 public Long createTransferLine(MesWmTransferLineSaveReqVO createReqVO) { 46 public Long createTransferLine(MesWmTransferLineSaveReqVO createReqVO) {
39 - // 校验父数据可编辑  
40 - transferService.validateTransferEditable(createReqVO.getTransferId());  
41 - // 校验产品存在  
42 - itemService.validateItemExists(createReqVO.getItemId());  
43 - // 校验来源仓库、库区、库位的关联关系  
44 - warehouseAreaService.validateWarehouseAreaExists(createReqVO.getFromWarehouseId(),  
45 - createReqVO.getFromLocationId(), createReqVO.getFromAreaId()); 47 + // 校验
  48 + validateLineSaveData(createReqVO);
46 49
47 // 插入 50 // 插入
48 MesWmTransferLineDO line = BeanUtils.toBean(createReqVO, MesWmTransferLineDO.class); 51 MesWmTransferLineDO line = BeanUtils.toBean(createReqVO, MesWmTransferLineDO.class);
@@ -54,13 +57,8 @@ public class MesWmTransferLineServiceImpl implements MesWmTransferLineService { @@ -54,13 +57,8 @@ public class MesWmTransferLineServiceImpl implements MesWmTransferLineService {
54 public void updateTransferLine(MesWmTransferLineSaveReqVO updateReqVO) { 57 public void updateTransferLine(MesWmTransferLineSaveReqVO updateReqVO) {
55 // 校验存在 58 // 校验存在
56 validateTransferLineExists(updateReqVO.getId()); 59 validateTransferLineExists(updateReqVO.getId());
57 - // 校验父数据可编辑  
58 - transferService.validateTransferEditable(updateReqVO.getTransferId());  
59 - // 校验产品存在  
60 - itemService.validateItemExists(updateReqVO.getItemId());  
61 - // 校验来源仓库、库区、库位的关联关系  
62 - warehouseAreaService.validateWarehouseAreaExists(updateReqVO.getFromWarehouseId(),  
63 - updateReqVO.getFromLocationId(), updateReqVO.getFromAreaId()); 60 + // 校验
  61 + validateLineSaveData(updateReqVO);
64 62
65 // 更新 63 // 更新
66 MesWmTransferLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmTransferLineDO.class); 64 MesWmTransferLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmTransferLineDO.class);
@@ -68,10 +66,16 @@ public class MesWmTransferLineServiceImpl implements MesWmTransferLineService { @@ -68,10 +66,16 @@ public class MesWmTransferLineServiceImpl implements MesWmTransferLineService {
68 } 66 }
69 67
70 @Override 68 @Override
  69 + @Transactional(rollbackFor = Exception.class)
71 public void deleteTransferLine(Long id) { 70 public void deleteTransferLine(Long id) {
72 // 校验存在 71 // 校验存在
73 - validateTransferLineExists(id);  
74 - // 删除 72 + MesWmTransferLineDO line = validateTransferLineExists(id);
  73 + // 校验父数据可编辑
  74 + transferService.validateTransferEditable(line.getTransferId());
  75 +
  76 + // 级联删除该行下所有明细
  77 + transferDetailService.deleteTransferDetailByLineId(id);
  78 + // 删除行
75 transferLineMapper.deleteById(id); 79 transferLineMapper.deleteById(id);
76 } 80 }
77 81
@@ -100,4 +104,19 @@ public class MesWmTransferLineServiceImpl implements MesWmTransferLineService { @@ -100,4 +104,19 @@ public class MesWmTransferLineServiceImpl implements MesWmTransferLineService {
100 return line; 104 return line;
101 } 105 }
102 106
  107 + private void validateLineSaveData(MesWmTransferLineSaveReqVO reqVO) {
  108 + // 校验父数据可编辑
  109 + transferService.validateTransferEditable(reqVO.getTransferId());
  110 + // 校验产品存在
  111 + itemService.validateItemExists(reqVO.getItemId());
  112 + // 校验来源仓库、库区、库位的关联关系
  113 + warehouseAreaService.validateWarehouseAreaExists(reqVO.getFromWarehouseId(),
  114 + reqVO.getFromLocationId(), reqVO.getFromAreaId());
  115 + // 校验库存记录存在
  116 + if (reqVO.getMaterialStockId() != null
  117 + && materialStockService.getMaterialStock(reqVO.getMaterialStockId()) == null) {
  118 + throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
  119 + }
  120 + }
  121 +
103 } 122 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transfer/MesWmTransferServiceImpl.java
@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil; @@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 -import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockFreezeReqVO;  
8 import cn.iocoder.yudao.module.mes.controller.admin.wm.transfer.vo.MesWmTransferPageReqVO; 7 import cn.iocoder.yudao.module.mes.controller.admin.wm.transfer.vo.MesWmTransferPageReqVO;
9 import cn.iocoder.yudao.module.mes.controller.admin.wm.transfer.vo.MesWmTransferSaveReqVO; 8 import cn.iocoder.yudao.module.mes.controller.admin.wm.transfer.vo.MesWmTransferSaveReqVO;
10 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transfer.MesWmTransferDO; 9 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transfer.MesWmTransferDO;
@@ -27,6 +26,7 @@ import java.util.List; @@ -27,6 +26,7 @@ import java.util.List;
27 import java.util.Map; 26 import java.util.Map;
28 27
29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 28 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
30 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; 30 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
31 31
32 /** 32 /**
@@ -109,7 +109,7 @@ public class MesWmTransferServiceImpl implements MesWmTransferService { @@ -109,7 +109,7 @@ public class MesWmTransferServiceImpl implements MesWmTransferService {
109 if (Boolean.TRUE.equals(transfer.getDeliveryFlag()) && !Boolean.TRUE.equals(transfer.getConfirmFlag())) { 109 if (Boolean.TRUE.equals(transfer.getDeliveryFlag()) && !Boolean.TRUE.equals(transfer.getConfirmFlag())) {
110 transfer.setStatus(MesWmTransferStatusEnum.UNCONFIRMED.getStatus()).setConfirmFlag(false); 110 transfer.setStatus(MesWmTransferStatusEnum.UNCONFIRMED.getStatus()).setConfirmFlag(false);
111 // DONE @AI:冻结库存属于主单状态流转的一部分,保留在主单 service 统一处理 111 // DONE @AI:冻结库存属于主单状态流转的一部分,保留在主单 service 统一处理
112 - freezeTransferLineStocks(lines, true); 112 + freezeTransferLineStocks(id, true);
113 } else { 113 } else {
114 transfer.setStatus(MesWmTransferStatusEnum.APPROVING.getStatus()); 114 transfer.setStatus(MesWmTransferStatusEnum.APPROVING.getStatus());
115 } 115 }
@@ -171,7 +171,7 @@ public class MesWmTransferServiceImpl implements MesWmTransferService { @@ -171,7 +171,7 @@ public class MesWmTransferServiceImpl implements MesWmTransferService {
171 171
172 // 4. 配送模式下,执行完成后解除来源库存冻结 172 // 4. 配送模式下,执行完成后解除来源库存冻结
173 if (Boolean.TRUE.equals(transfer.getDeliveryFlag())) { 173 if (Boolean.TRUE.equals(transfer.getDeliveryFlag())) {
174 - freezeTransferLineStocks(lines, false); 174 + freezeTransferLineStocks(id, false);
175 } 175 }
176 } 176 }
177 177
@@ -215,6 +215,11 @@ public class MesWmTransferServiceImpl implements MesWmTransferService { @@ -215,6 +215,11 @@ public class MesWmTransferServiceImpl implements MesWmTransferService {
215 // 校验存在 + 非已完成/已取消状态 215 // 校验存在 + 非已完成/已取消状态
216 MesWmTransferDO transfer = validateTransferExistsAndNotFinished(id); 216 MesWmTransferDO transfer = validateTransferExistsAndNotFinished(id);
217 217
  218 + // 配送模式下,取消时需解除冻结
  219 + if (Boolean.TRUE.equals(transfer.getDeliveryFlag())) {
  220 + freezeTransferLineStocks(id, false);
  221 + }
  222 +
218 // 更新状态 -> 已取消 223 // 更新状态 -> 已取消
219 transfer.setStatus(MesWmTransferStatusEnum.CANCELED.getStatus()); 224 transfer.setStatus(MesWmTransferStatusEnum.CANCELED.getStatus());
220 transferMapper.updateById(transfer); 225 transferMapper.updateById(transfer);
@@ -305,20 +310,14 @@ public class MesWmTransferServiceImpl implements MesWmTransferService { @@ -305,20 +310,14 @@ public class MesWmTransferServiceImpl implements MesWmTransferService {
305 return transfer; 310 return transfer;
306 } 311 }
307 312
308 - // TODO @芋艿:【后续弄】materialStockService 封装一个方法,传入 transferId + frozen 统一处理了;不用判断 line 的 getMaterialStockId  
309 - private void freezeTransferLineStocks(List<MesWmTransferLineDO> lines, boolean frozen) { 313 + private void freezeTransferLineStocks(Long transferId, boolean frozen) {
  314 + List<MesWmTransferLineDO> lines = transferLineService.getTransferLineListByTransferId(transferId);
310 if (CollUtil.isEmpty(lines)) { 315 if (CollUtil.isEmpty(lines)) {
311 return; 316 return;
312 } 317 }
313 - for (MesWmTransferLineDO line : lines) {  
314 - if (line.getMaterialStockId() == null) {  
315 - continue;  
316 - }  
317 - MesWmMaterialStockFreezeReqVO freezeReqVO = new MesWmMaterialStockFreezeReqVO();  
318 - freezeReqVO.setId(line.getMaterialStockId());  
319 - freezeReqVO.setFrozen(frozen);  
320 - materialStockService.updateMaterialStockFrozen(freezeReqVO);  
321 - } 318 + List<Long> materialStockIds = convertList(lines,
  319 + MesWmTransferLineDO::getMaterialStockId, line -> line.getMaterialStockId() != null);
  320 + materialStockService.updateMaterialStockFrozen(materialStockIds, frozen);
322 } 321 }
323 322
324 } 323 }