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 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 160 // ==================== 拼接 VO ====================
153 161  
154 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 5 import lombok.Data;
6 6 import lombok.EqualsAndHashCode;
7 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 9 @Schema(description = "管理后台 - MES 产品收货单分页 Request VO")
15 10 @Data
... ... @@ -29,11 +24,4 @@ public class MesWmProductRecptPageReqVO extends PageParam {
29 24 @Schema(description = "产品物料编号", example = "1")
30 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 22 @NotEmpty(message = "收货单名称不能为空")
23 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 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 28 @Schema(description = "收货日期", requiredMode = Schema.RequiredMode.REQUIRED)
36 29 @NotNull(message = "收货日期不能为空")
37 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 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotNull;
  5 +import jakarta.validation.constraints.Positive;
5 6 import lombok.Data;
6 7  
7 8 import java.math.BigDecimal;
... ... @@ -23,6 +24,7 @@ public class MesWmProductRecptLineSaveReqVO {
23 24  
24 25 @Schema(description = "收货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
25 26 @NotNull(message = "收货数量不能为空")
  27 + @Positive(message = "收货数量必须大于 0")
26 28 private BigDecimal quantity;
27 29  
28 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 19 .likeIfPresent(MesWmProductRecptDO::getName, reqVO.getName())
20 20 .eqIfPresent(MesWmProductRecptDO::getWorkOrderId, reqVO.getWorkOrderId())
21 21 .eqIfPresent(MesWmProductRecptDO::getItemId, reqVO.getItemId())
22   - .eqIfPresent(MesWmProductRecptDO::getStatus, reqVO.getStatus())
23   - .betweenIfPresent(MesWmProductRecptDO::getReceiptDate, reqVO.getReceiptDate())
24 22 .orderByDesc(MesWmProductRecptDO::getId));
25 23 }
26 24  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmProductRecptStatusEnum.java
1 1 package cn.iocoder.yudao.module.mes.enums.wm;
2 2  
3 3 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import cn.iocoder.yudao.module.mes.controller.admin.wm.productrecpt.vo.MesWmProductRecptSaveReqVO;
4 5 import cn.iocoder.yudao.module.mes.enums.MesOrderStatusConstants;
  6 +import cn.iocoder.yudao.module.mes.service.wm.productrecpt.MesWmProductRecptService;
5 7 import lombok.AllArgsConstructor;
6 8 import lombok.Getter;
7 9  
8 10 import java.util.Arrays;
9 11  
10 12 /**
11   - * MES 产品收货单状态枚举
  13 + * MES 产品入库单状态枚举
12 14 *
13 15 * @author 芋道源码
14 16 */
... ... @@ -16,25 +18,34 @@ import java.util.Arrays;
16 18 @AllArgsConstructor
17 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 26 PREPARE(MesOrderStatusConstants.PREPARE, "草稿"),
24 27 /**
25 28 * 待上架
  29 + *
  30 + * 对应方法:{@link MesWmProductRecptService#submitProductRecpt(Long)}
26 31 */
27 32 APPROVING(MesOrderStatusConstants.APPROVING, "待上架"),
28 33 /**
29 34 * 待执行入库
  35 + *
  36 + * 对应方法:{@link MesWmProductRecptService#stockProductRecpt(Long)}
30 37 */
31 38 APPROVED(MesOrderStatusConstants.APPROVED, "待执行入库"),
32 39 /**
33 40 * 已完成
  41 + *
  42 + * 对应方法:{@link MesWmProductRecptService#executeProductRecpt(Long)}
34 43 */
35 44 FINISHED(MesOrderStatusConstants.FINISHED, "已完成"),
36 45 /**
37 46 * 已取消
  47 + *
  48 + * 对应方法:{@link MesWmProductRecptService#cancelProductRecpt(Long)}
38 49 */
39 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 85 */
86 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 8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
9 9 import cn.iocoder.yudao.module.mes.controller.admin.wm.productrecpt.vo.MesWmProductRecptPageReqVO;
10 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 12 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productrecpt.MesWmProductRecptDO;
12 13 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productrecpt.MesWmProductRecptDetailDO;
13 14 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productrecpt.MesWmProductRecptLineDO;
... ... @@ -44,13 +45,22 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
44 45 @Resource
45 46 private MesWmMaterialStockService materialStockService;
46 47  
  48 + @Resource
  49 + private cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService workOrderService;
  50 +
47 51 @Override
48 52 public Long createProductRecpt(MesWmProductRecptSaveReqVO createReqVO) {
49 53 // 校验编码唯一
50 54 validateCodeUnique(null, createReqVO.getCode());
  55 + // 校验工单存在并设置 itemId
  56 + MesProWorkOrderDO workOrder = createReqVO.getWorkOrderId() != null ?
  57 + workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId()) : null;
51 58  
52 59 // 插入
53 60 MesWmProductRecptDO recpt = BeanUtils.toBean(createReqVO, MesWmProductRecptDO.class);
  61 + if (workOrder != null) {
  62 + recpt.setItemId(workOrder.getProductId());
  63 + }
54 64 recpt.setStatus(MesWmProductRecptStatusEnum.PREPARE.getStatus());
55 65 productRecptMapper.insert(recpt);
56 66 return recpt.getId();
... ... @@ -62,9 +72,15 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
62 72 validateProductRecptExistsAndDraft(updateReqVO.getId());
63 73 // 校验编码唯一
64 74 validateCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
  75 + // 校验工单存在
  76 + MesProWorkOrderDO workOrder = updateReqVO.getWorkOrderId() != null ?
  77 + workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId()) : null;
65 78  
66 79 // 更新
67 80 MesWmProductRecptDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductRecptDO.class);
  81 + if (workOrder != null) {
  82 + updateObj.setItemId(workOrder.getProductId());
  83 + }
68 84 productRecptMapper.updateById(updateObj);
69 85 }
70 86  
... ... @@ -115,20 +131,24 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
115 131 if (ObjUtil.notEqual(MesWmProductRecptStatusEnum.APPROVING.getStatus(), recpt.getStatus())) {
116 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 142 List<MesWmProductRecptLineDO> lines = productRecptLineService.getProductRecptLineListByRecptId(id);
120 143 for (MesWmProductRecptLineDO line : lines) {
121 144 List<MesWmProductRecptDetailDO> details = productRecptDetailService.getProductRecptDetailListByLineId(line.getId());
122 145 BigDecimal totalDetailQty = CollectionUtils.getSumValue(details,
123 146 MesWmProductRecptDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO);
124 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 154 @Override
... ... @@ -141,6 +161,7 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
141 161 }
142 162  
143 163 // 遍历所有明细,更新库存台账
  164 + // TODO @芋艿:【后面优化】
144 165 List<MesWmProductRecptDetailDO> details = productRecptDetailService.getProductRecptDetailListByRecptId(id);
145 166 for (MesWmProductRecptDetailDO detail : details) {
146 167 materialStockService.increaseStock(
... ... @@ -164,6 +185,7 @@ public class MesWmProductRecptServiceImpl implements MesWmProductRecptService {
164 185 MesWmProductRecptStatusEnum.CANCELED.getStatus())) {
165 186 throw exception(WM_PRODUCT_RECPT_CANCEL_NOT_ALLOWED);
166 187 }
  188 +
167 189 // 取消
168 190 productRecptMapper.updateById(new MesWmProductRecptDO()
169 191 .setId(id).setStatus(MesWmProductRecptStatusEnum.CANCELED.getStatus()));
... ...