Commit 81193982eea08877a04cd55c942643a8a2fcbe65

Authored by YunaiV
1 parent 724c527a

✨ feat(mes): 添加产品收货单明细数量校验功能

新增校验产品收货单明细数量的接口,确保每行明细数量之和与行收货数量一致。此功能提升了数据的准确性和完整性,避免了潜在的入库错误。

- 添加 checkProductRecptQuantity 方法
- 更新相关接口以支持数量校验
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productrecpt/MesWmProductRecptController.java
@@ -149,6 +149,14 @@ public class MesWmProductRecptController { @@ -149,6 +149,14 @@ public class MesWmProductRecptController {
149 return success(true); 149 return success(true);
150 } 150 }
151 151
  152 + @GetMapping("/check-quantity")
  153 + @Operation(summary = "校验产品收货单明细数量")
  154 + @Parameter(name = "id", description = "编号", required = true)
  155 + @PreAuthorize("@ss.hasPermission('mes:wm-product-recpt:query')")
  156 + public CommonResult<Boolean> checkProductRecptQuantity(@RequestParam("id") Long id) {
  157 + return success(productRecptService.checkProductRecptQuantity(id));
  158 + }
  159 +
152 // ==================== 拼接 VO ==================== 160 // ==================== 拼接 VO ====================
153 161
154 private List<MesWmProductRecptRespVO> buildRespVOList(List<MesWmProductRecptDO> list) { 162 private List<MesWmProductRecptRespVO> buildRespVOList(List<MesWmProductRecptDO> list) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productrecpt/vo/MesWmProductRecptPageReqVO.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
@@ -29,11 +24,4 @@ public class MesWmProductRecptPageReqVO extends PageParam { @@ -29,11 +24,4 @@ public class MesWmProductRecptPageReqVO extends PageParam {
29 @Schema(description = "产品物料编号", example = "1") 24 @Schema(description = "产品物料编号", example = "1")
30 private Long itemId; 25 private Long itemId;
31 26
32 - @Schema(description = "状态", example = "0")  
33 - private Integer status;  
34 -  
35 - @Schema(description = "收货日期")  
36 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
37 - private LocalDateTime[] receiptDate;  
38 -  
39 } 27 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productrecpt/vo/MesWmProductRecptSaveReqVO.java
@@ -22,16 +22,9 @@ public class MesWmProductRecptSaveReqVO { @@ -22,16 +22,9 @@ public class MesWmProductRecptSaveReqVO {
22 @NotEmpty(message = "收货单名称不能为空") 22 @NotEmpty(message = "收货单名称不能为空")
23 private String name; 23 private String name;
24 24
25 - @Schema(description = "生产工单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
26 - // TODO @AI:workOrderId 必须要非必传  
27 - @NotNull(message = "生产工单不能为空") 25 + @Schema(description = "生产工单编号", example = "1")
28 private Long workOrderId; 26 private Long workOrderId;
29 27
30 - @Schema(description = "产品物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
31 - @NotNull(message = "产品物料不能为空")  
32 - // TODO @AI:workOrderId 必须要非必传  
33 - private Long itemId;  
34 -  
35 @Schema(description = "收货日期", requiredMode = Schema.RequiredMode.REQUIRED) 28 @Schema(description = "收货日期", requiredMode = Schema.RequiredMode.REQUIRED)
36 @NotNull(message = "收货日期不能为空") 29 @NotNull(message = "收货日期不能为空")
37 private LocalDateTime receiptDate; 30 private LocalDateTime receiptDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productrecpt/vo/line/MesWmProductRecptLineSaveReqVO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.productrecpt.vo.line; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.productrecpt.vo.line;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
  5 +import jakarta.validation.constraints.Positive;
5 import lombok.Data; 6 import lombok.Data;
6 7
7 import java.math.BigDecimal; 8 import java.math.BigDecimal;
@@ -23,6 +24,7 @@ public class MesWmProductRecptLineSaveReqVO { @@ -23,6 +24,7 @@ public class MesWmProductRecptLineSaveReqVO {
23 24
24 @Schema(description = "收货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") 25 @Schema(description = "收货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
25 @NotNull(message = "收货数量不能为空") 26 @NotNull(message = "收货数量不能为空")
  27 + @Positive(message = "收货数量必须大于 0")
26 private BigDecimal quantity; 28 private BigDecimal quantity;
27 29
28 @Schema(description = "批次编号", example = "1") 30 @Schema(description = "批次编号", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productrecpt/MesWmProductRecptMapper.java
@@ -19,8 +19,6 @@ public interface MesWmProductRecptMapper extends BaseMapperX&lt;MesWmProductRecptDO @@ -19,8 +19,6 @@ public interface MesWmProductRecptMapper extends BaseMapperX&lt;MesWmProductRecptDO
19 .likeIfPresent(MesWmProductRecptDO::getName, reqVO.getName()) 19 .likeIfPresent(MesWmProductRecptDO::getName, reqVO.getName())
20 .eqIfPresent(MesWmProductRecptDO::getWorkOrderId, reqVO.getWorkOrderId()) 20 .eqIfPresent(MesWmProductRecptDO::getWorkOrderId, reqVO.getWorkOrderId())
21 .eqIfPresent(MesWmProductRecptDO::getItemId, reqVO.getItemId()) 21 .eqIfPresent(MesWmProductRecptDO::getItemId, reqVO.getItemId())
22 - .eqIfPresent(MesWmProductRecptDO::getStatus, reqVO.getStatus())  
23 - .betweenIfPresent(MesWmProductRecptDO::getReceiptDate, reqVO.getReceiptDate())  
24 .orderByDesc(MesWmProductRecptDO::getId)); 22 .orderByDesc(MesWmProductRecptDO::getId));
25 } 23 }
26 24
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmProductRecptStatusEnum.java
1 package cn.iocoder.yudao.module.mes.enums.wm; 1 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.controller.admin.wm.productrecpt.vo.MesWmProductRecptSaveReqVO;
4 import cn.iocoder.yudao.module.mes.enums.MesOrderStatusConstants; 5 import cn.iocoder.yudao.module.mes.enums.MesOrderStatusConstants;
  6 +import cn.iocoder.yudao.module.mes.service.wm.productrecpt.MesWmProductRecptService;
5 import lombok.AllArgsConstructor; 7 import lombok.AllArgsConstructor;
6 import lombok.Getter; 8 import lombok.Getter;
7 9
8 import java.util.Arrays; 10 import java.util.Arrays;
9 11
10 /** 12 /**
11 - * MES 产品收货单状态枚举 13 + * MES 产品入库单状态枚举
12 * 14 *
13 * @author 芋道源码 15 * @author 芋道源码
14 */ 16 */
@@ -16,25 +18,34 @@ import java.util.Arrays; @@ -16,25 +18,34 @@ import java.util.Arrays;
16 @AllArgsConstructor 18 @AllArgsConstructor
17 public enum MesWmProductRecptStatusEnum implements ArrayValuable<Integer> { 19 public enum MesWmProductRecptStatusEnum implements ArrayValuable<Integer> {
18 20
19 - // TODO @AI:对应的方法;参考别的 MesWmProductionIssueStatusEnum  
20 /** 21 /**
21 * 草稿 22 * 草稿
  23 + *
  24 + * 对应方法:{@link MesWmProductRecptService#createProductRecpt(MesWmProductRecptSaveReqVO)}
22 */ 25 */
23 PREPARE(MesOrderStatusConstants.PREPARE, "草稿"), 26 PREPARE(MesOrderStatusConstants.PREPARE, "草稿"),
24 /** 27 /**
25 * 待上架 28 * 待上架
  29 + *
  30 + * 对应方法:{@link MesWmProductRecptService#submitProductRecpt(Long)}
26 */ 31 */
27 APPROVING(MesOrderStatusConstants.APPROVING, "待上架"), 32 APPROVING(MesOrderStatusConstants.APPROVING, "待上架"),
28 /** 33 /**
29 * 待执行入库 34 * 待执行入库
  35 + *
  36 + * 对应方法:{@link MesWmProductRecptService#stockProductRecpt(Long)}
30 */ 37 */
31 APPROVED(MesOrderStatusConstants.APPROVED, "待执行入库"), 38 APPROVED(MesOrderStatusConstants.APPROVED, "待执行入库"),
32 /** 39 /**
33 * 已完成 40 * 已完成
  41 + *
  42 + * 对应方法:{@link MesWmProductRecptService#executeProductRecpt(Long)}
34 */ 43 */
35 FINISHED(MesOrderStatusConstants.FINISHED, "已完成"), 44 FINISHED(MesOrderStatusConstants.FINISHED, "已完成"),
36 /** 45 /**
37 * 已取消 46 * 已取消
  47 + *
  48 + * 对应方法:{@link MesWmProductRecptService#cancelProductRecpt(Long)}
38 */ 49 */
39 CANCELED(MesOrderStatusConstants.CANCELLED, "已取消"); 50 CANCELED(MesOrderStatusConstants.CANCELLED, "已取消");
40 51
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productrecpt/MesWmProductRecptService.java
@@ -85,4 +85,12 @@ public interface MesWmProductRecptService { @@ -85,4 +85,12 @@ public interface MesWmProductRecptService {
85 */ 85 */
86 MesWmProductRecptDO validateProductRecptEditable(Long id); 86 MesWmProductRecptDO validateProductRecptEditable(Long id);
87 87
  88 + /**
  89 + * 校验产品收货单明细数量(每行明细数量之和是否等于行收货数量)
  90 + *
  91 + * @param id 编号
  92 + * @return 是否匹配
  93 + */
  94 + Boolean checkProductRecptQuantity(Long id);
  95 +
88 } 96 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productrecpt/MesWmProductRecptServiceImpl.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
9 import cn.iocoder.yudao.module.mes.controller.admin.wm.productrecpt.vo.MesWmProductRecptPageReqVO; 9 import cn.iocoder.yudao.module.mes.controller.admin.wm.productrecpt.vo.MesWmProductRecptPageReqVO;
10 import cn.iocoder.yudao.module.mes.controller.admin.wm.productrecpt.vo.MesWmProductRecptSaveReqVO; 10 import cn.iocoder.yudao.module.mes.controller.admin.wm.productrecpt.vo.MesWmProductRecptSaveReqVO;
  11 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
11 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productrecpt.MesWmProductRecptDO; 12 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productrecpt.MesWmProductRecptDO;
12 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productrecpt.MesWmProductRecptDetailDO; 13 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productrecpt.MesWmProductRecptDetailDO;
13 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productrecpt.MesWmProductRecptLineDO; 14 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productrecpt.MesWmProductRecptLineDO;
@@ -44,13 +45,22 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService { @@ -44,13 +45,22 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
44 @Resource 45 @Resource
45 private MesWmMaterialStockService materialStockService; 46 private MesWmMaterialStockService materialStockService;
46 47
  48 + @Resource
  49 + private cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService workOrderService;
  50 +
47 @Override 51 @Override
48 public Long createProductRecpt(MesWmProductRecptSaveReqVO createReqVO) { 52 public Long createProductRecpt(MesWmProductRecptSaveReqVO createReqVO) {
49 // 校验编码唯一 53 // 校验编码唯一
50 validateCodeUnique(null, createReqVO.getCode()); 54 validateCodeUnique(null, createReqVO.getCode());
  55 + // 校验工单存在并设置 itemId
  56 + MesProWorkOrderDO workOrder = createReqVO.getWorkOrderId() != null ?
  57 + workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId()) : null;
51 58
52 // 插入 59 // 插入
53 MesWmProductRecptDO recpt = BeanUtils.toBean(createReqVO, MesWmProductRecptDO.class); 60 MesWmProductRecptDO recpt = BeanUtils.toBean(createReqVO, MesWmProductRecptDO.class);
  61 + if (workOrder != null) {
  62 + recpt.setItemId(workOrder.getProductId());
  63 + }
54 recpt.setStatus(MesWmProductRecptStatusEnum.PREPARE.getStatus()); 64 recpt.setStatus(MesWmProductRecptStatusEnum.PREPARE.getStatus());
55 productRecptMapper.insert(recpt); 65 productRecptMapper.insert(recpt);
56 return recpt.getId(); 66 return recpt.getId();
@@ -62,9 +72,15 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService { @@ -62,9 +72,15 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
62 validateProductRecptExistsAndDraft(updateReqVO.getId()); 72 validateProductRecptExistsAndDraft(updateReqVO.getId());
63 // 校验编码唯一 73 // 校验编码唯一
64 validateCodeUnique(updateReqVO.getId(), updateReqVO.getCode()); 74 validateCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
  75 + // 校验工单存在
  76 + MesProWorkOrderDO workOrder = updateReqVO.getWorkOrderId() != null ?
  77 + workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId()) : null;
65 78
66 // 更新 79 // 更新
67 MesWmProductRecptDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductRecptDO.class); 80 MesWmProductRecptDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductRecptDO.class);
  81 + if (workOrder != null) {
  82 + updateObj.setItemId(workOrder.getProductId());
  83 + }
68 productRecptMapper.updateById(updateObj); 84 productRecptMapper.updateById(updateObj);
69 } 85 }
70 86
@@ -115,20 +131,24 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService { @@ -115,20 +131,24 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
115 if (ObjUtil.notEqual(MesWmProductRecptStatusEnum.APPROVING.getStatus(), recpt.getStatus())) { 131 if (ObjUtil.notEqual(MesWmProductRecptStatusEnum.APPROVING.getStatus(), recpt.getStatus())) {
116 throw exception(WM_PRODUCT_RECPT_STATUS_ERROR); 132 throw exception(WM_PRODUCT_RECPT_STATUS_ERROR);
117 } 133 }
118 - // 校验每行明细数量之和是否等于行收货数量 134 +
  135 + // 执行上架(待上架 → 待入库)
  136 + productRecptMapper.updateById(new MesWmProductRecptDO()
  137 + .setId(id).setStatus(MesWmProductRecptStatusEnum.APPROVED.getStatus()));
  138 + }
  139 +
  140 + @Override
  141 + public Boolean checkProductRecptQuantity(Long id) {
119 List<MesWmProductRecptLineDO> lines = productRecptLineService.getProductRecptLineListByRecptId(id); 142 List<MesWmProductRecptLineDO> lines = productRecptLineService.getProductRecptLineListByRecptId(id);
120 for (MesWmProductRecptLineDO line : lines) { 143 for (MesWmProductRecptLineDO line : lines) {
121 List<MesWmProductRecptDetailDO> details = productRecptDetailService.getProductRecptDetailListByLineId(line.getId()); 144 List<MesWmProductRecptDetailDO> details = productRecptDetailService.getProductRecptDetailListByLineId(line.getId());
122 BigDecimal totalDetailQty = CollectionUtils.getSumValue(details, 145 BigDecimal totalDetailQty = CollectionUtils.getSumValue(details,
123 MesWmProductRecptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO); 146 MesWmProductRecptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
124 if (line.getQuantity() != null && totalDetailQty.compareTo(line.getQuantity()) != 0) { 147 if (line.getQuantity() != null && totalDetailQty.compareTo(line.getQuantity()) != 0) {
125 - throw exception(WM_PRODUCT_RECPT_DETAIL_QUANTITY_MISMATCH); 148 + return false;
126 } 149 }
127 } 150 }
128 -  
129 - // 执行上架(待上架 → 待入库)  
130 - productRecptMapper.updateById(new MesWmProductRecptDO()  
131 - .setId(id).setStatus(MesWmProductRecptStatusEnum.APPROVED.getStatus())); 151 + return true;
132 } 152 }
133 153
134 @Override 154 @Override
@@ -141,6 +161,7 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService { @@ -141,6 +161,7 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
141 } 161 }
142 162
143 // 遍历所有明细,更新库存台账 163 // 遍历所有明细,更新库存台账
  164 + // TODO @芋艿:【后面优化】
144 List<MesWmProductRecptDetailDO> details = productRecptDetailService.getProductRecptDetailListByRecptId(id); 165 List<MesWmProductRecptDetailDO> details = productRecptDetailService.getProductRecptDetailListByRecptId(id);
145 for (MesWmProductRecptDetailDO detail : details) { 166 for (MesWmProductRecptDetailDO detail : details) {
146 materialStockService.increaseStock( 167 materialStockService.increaseStock(
@@ -164,6 +185,7 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService { @@ -164,6 +185,7 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
164 MesWmProductRecptStatusEnum.CANCELED.getStatus())) { 185 MesWmProductRecptStatusEnum.CANCELED.getStatus())) {
165 throw exception(WM_PRODUCT_RECPT_CANCEL_NOT_ALLOWED); 186 throw exception(WM_PRODUCT_RECPT_CANCEL_NOT_ALLOWED);
166 } 187 }
  188 +
167 // 取消 189 // 取消
168 productRecptMapper.updateById(new MesWmProductRecptDO() 190 productRecptMapper.updateById(new MesWmProductRecptDO()
169 .setId(id).setStatus(MesWmProductRecptStatusEnum.CANCELED.getStatus())); 191 .setId(id).setStatus(MesWmProductRecptStatusEnum.CANCELED.getStatus()));