Commit 7d2bf8e36bc41427c5e0156a5cf684f72310b8e9

Authored by YunaiV
1 parent ebad1419

feat(mes): IQC 与到货通知单集成重构

1. 新增 MesBizTypeConstants 集中管理业务类型常量
2. IQC sourceDocType 从 String 改 Integer,删除冗余 sourceDocCode
3. IQC checkQuantity/qualifiedQuantity/unqualifiedQuantity 从 Integer 改 BigDecimal
4. IQC 完成后回写逻辑提取为 writeBackSourceDoc(),按 sourceDocType 分发
5. IQC 完成回调从 arrivalNoticeLineService 上移至 arrivalNoticeService.approveArrivalNoticeWhenIqcComplete()
6. 到货通知单行 updateByIqcComplete() 简化为纯更新 + 校验存在
7. 到货通知单新增 approveArrivalNoticeWhenIqcComplete(),含状态校验 + 全行检验完成判断
8. 删除到货通知单 PUT /approve 控制器端点
9. 提取 initQualifiedQuantityIfNoIqc() 复用创建/更新逻辑
Showing 14 changed files with 201 additions and 72 deletions
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcRespVO.java
@@ -33,11 +33,8 @@ public class MesQcIqcRespVO { @@ -33,11 +33,8 @@ public class MesQcIqcRespVO {
33 @Schema(description = "来源单据 ID", example = "200") 33 @Schema(description = "来源单据 ID", example = "200")
34 private Long sourceDocId; 34 private Long sourceDocId;
35 35
36 - @Schema(description = "来源单据类型", example = "ARRIVAL")  
37 - private String sourceDocType;  
38 -  
39 - @Schema(description = "来源单据编号", example = "ARV20250101001")  
40 - private String sourceDocCode; 36 + @Schema(description = "来源单据类型", example = "100")
  37 + private Integer sourceDocType;
41 38
42 @Schema(description = "来源单据行 ID", example = "300") 39 @Schema(description = "来源单据行 ID", example = "300")
43 private Long sourceLineId; 40 private Long sourceLineId;
@@ -87,15 +84,15 @@ public class MesQcIqcRespVO { @@ -87,15 +84,15 @@ public class MesQcIqcRespVO {
87 84
88 @Schema(description = "本次检测数量", example = "10") 85 @Schema(description = "本次检测数量", example = "10")
89 @ExcelProperty("检测数量") 86 @ExcelProperty("检测数量")
90 - private Integer checkQuantity; 87 + private BigDecimal checkQuantity;
91 88
92 @Schema(description = "合格品数量", example = "9") 89 @Schema(description = "合格品数量", example = "9")
93 @ExcelProperty("合格品数量") 90 @ExcelProperty("合格品数量")
94 - private Integer qualifiedQuantity; 91 + private BigDecimal qualifiedQuantity;
95 92
96 @Schema(description = "不合格品数量", example = "1") 93 @Schema(description = "不合格品数量", example = "1")
97 @ExcelProperty("不合格品数量") 94 @ExcelProperty("不合格品数量")
98 - private Integer unqualifiedQuantity; 95 + private BigDecimal unqualifiedQuantity;
99 96
100 // ========== 缺陷统计 ========== 97 // ========== 缺陷统计 ==========
101 98
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcSaveReqVO.java
@@ -32,11 +32,8 @@ public class MesQcIqcSaveReqVO { @@ -32,11 +32,8 @@ public class MesQcIqcSaveReqVO {
32 @Schema(description = "来源单据 ID", example = "200") 32 @Schema(description = "来源单据 ID", example = "200")
33 private Long sourceDocId; 33 private Long sourceDocId;
34 34
35 - @Schema(description = "来源单据类型", example = "ARRIVAL")  
36 - private String sourceDocType;  
37 -  
38 - @Schema(description = "来源单据编号", example = "ARV20250101001")  
39 - private String sourceDocCode; 35 + @Schema(description = "来源单据类型", example = "100")
  36 + private Integer sourceDocType;
40 37
41 @Schema(description = "来源单据行 ID", example = "300") 38 @Schema(description = "来源单据行 ID", example = "300")
42 private Long sourceLineId; 39 private Long sourceLineId;
@@ -69,13 +66,13 @@ public class MesQcIqcSaveReqVO { @@ -69,13 +66,13 @@ public class MesQcIqcSaveReqVO {
69 private BigDecimal receivedQuantity; 66 private BigDecimal receivedQuantity;
70 67
71 @Schema(description = "本次检测数量", example = "10") 68 @Schema(description = "本次检测数量", example = "10")
72 - private Integer checkQuantity; 69 + private BigDecimal checkQuantity;
73 70
74 @Schema(description = "合格品数量", example = "9") 71 @Schema(description = "合格品数量", example = "9")
75 - private Integer qualifiedQuantity; 72 + private BigDecimal qualifiedQuantity;
76 73
77 @Schema(description = "不合格品数量", example = "1") 74 @Schema(description = "不合格品数量", example = "1")
78 - private Integer unqualifiedQuantity; 75 + private BigDecimal unqualifiedQuantity;
79 76
80 // ========== 检验 ========== 77 // ========== 检验 ==========
81 78
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java
@@ -112,15 +112,6 @@ public class MesWmArrivalNoticeController { @@ -112,15 +112,6 @@ public class MesWmArrivalNoticeController {
112 return success(true); 112 return success(true);
113 } 113 }
114 114
115 - @PutMapping("/approve")  
116 - @Operation(summary = "审批到货通知单")  
117 - @Parameter(name = "id", description = "编号", required = true)  
118 - @PreAuthorize("@ss.hasPermission('mes:wm-arrival-notice:update')")  
119 - public CommonResult<Boolean> approveArrivalNotice(@RequestParam("id") Long id) {  
120 - arrivalNoticeService.approveArrivalNotice(id);  
121 - return success(true);  
122 - }  
123 -  
124 @GetMapping("/simple-list") 115 @GetMapping("/simple-list")
125 @Operation(summary = "获得到货通知单精简列表") 116 @Operation(summary = "获得到货通知单精简列表")
126 @Parameter(name = "status", description = "状态", example = "2") 117 @Parameter(name = "status", description = "状态", example = "2")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/lqc/MesQcIqcDO.java
@@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; @@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; 4 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
5 import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO; 5 import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
6 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
  9 +import cn.iocoder.yudao.module.mes.enums.qc.MesQcSourceDocTypeEnum;
7 import com.baomidou.mybatisplus.annotation.KeySequence; 10 import com.baomidou.mybatisplus.annotation.KeySequence;
8 import com.baomidou.mybatisplus.annotation.TableId; 11 import com.baomidou.mybatisplus.annotation.TableId;
9 import com.baomidou.mybatisplus.annotation.TableName; 12 import com.baomidou.mybatisplus.annotation.TableName;
@@ -47,24 +50,26 @@ public class MesQcIqcDO extends BaseDO { @@ -47,24 +50,26 @@ public class MesQcIqcDO extends BaseDO {
47 */ 50 */
48 private Long templateId; 51 private Long templateId;
49 52
50 - // ========== 来源单据(TODO @芋艿:WM 模块迁移后接入) ========== 53 + // ========== 来源单据 ==========
51 54
52 /** 55 /**
53 - * 来源单据 ID  
54 - */  
55 - private Long sourceDocId;  
56 - /**  
57 * 来源单据类型 56 * 来源单据类型
58 * 57 *
59 - * 字典 mes_qc_source_doc_type 58 + * 枚举 {@link MesQcSourceDocTypeEnum}
60 */ 59 */
61 - private String sourceDocType; 60 + private Integer sourceDocType;
62 /** 61 /**
63 - * 来源单据编号 62 + * 来源单据 ID
  63 + *
  64 + * 关联:根据 {@link #sourceDocType} 不同:
  65 + * 1. {@link MesQcSourceDocTypeEnum#ARRIVAL_NOTICE} 时,关联 {@link MesWmArrivalNoticeDO#getId()}
64 */ 66 */
65 - private String sourceDocCode; 67 + private Long sourceDocId;
66 /** 68 /**
67 * 来源单据行 ID 69 * 来源单据行 ID
  70 + *
  71 + * 关联:根据 {@link #sourceDocType} 不同:
  72 + * 1. {@link MesQcSourceDocTypeEnum#ARRIVAL_NOTICE} 时,关联 {@link MesWmArrivalNoticeLineDO#getId()}
68 */ 73 */
69 private Long sourceLineId; 74 private Long sourceLineId;
70 75
@@ -107,15 +112,15 @@ public class MesQcIqcDO extends BaseDO { @@ -107,15 +112,15 @@ public class MesQcIqcDO extends BaseDO {
107 /** 112 /**
108 * 本次检测数量 113 * 本次检测数量
109 */ 114 */
110 - private Integer checkQuantity; 115 + private BigDecimal checkQuantity;
111 /** 116 /**
112 * 合格品数量 117 * 合格品数量
113 */ 118 */
114 - private Integer qualifiedQuantity; 119 + private BigDecimal qualifiedQuantity;
115 /** 120 /**
116 * 不合格品数量 121 * 不合格品数量
117 */ 122 */
118 - private Integer unqualifiedQuantity; 123 + private BigDecimal unqualifiedQuantity;
119 124
120 // ========== 缺陷统计 ========== 125 // ========== 缺陷统计 ==========
121 126
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/materialstock/MesWmMaterialStockDO.java
@@ -36,8 +36,6 @@ public class MesWmMaterialStockDO extends BaseDO { @@ -36,8 +36,6 @@ public class MesWmMaterialStockDO extends BaseDO {
36 */ 36 */
37 @TableId 37 @TableId
38 private Long id; 38 private Long id;
39 - // TODO DONE @AI:保留冗余字段。库存台账查询频繁,冗余存储可避免关联 item 表,提升查询性能  
40 - // TODO @AI:还是不记录;因为它没有筛选的诉求;并且,它可以通过 itemId 间接获取;冗余存储反而增加了维护成本;  
41 /** 39 /**
42 * 物料分类编号 40 * 物料分类编号
43 * 41 *
@@ -50,8 +48,6 @@ public class MesWmMaterialStockDO extends BaseDO { @@ -50,8 +48,6 @@ public class MesWmMaterialStockDO extends BaseDO {
50 * 关联 {@link MesMdItemDO#getId()} 48 * 关联 {@link MesMdItemDO#getId()}
51 */ 49 */
52 private Long itemId; 50 private Long itemId;
53 - // TODO DONE @AI:保留冗余字段。库存台账查询频繁,冗余存储可避免关联 item 表,提升查询性能  
54 - // TODO @AI:还是不记录;因为它没有筛选的诉求;并且,它可以通过 itemId 间接获取;冗余存储反而增加了维护成本;  
55 /** 51 /**
56 * 计量单位编号 52 * 计量单位编号
57 * 53 *
@@ -88,6 +84,7 @@ public class MesWmMaterialStockDO extends BaseDO { @@ -88,6 +84,7 @@ public class MesWmMaterialStockDO extends BaseDO {
88 * 关联 {@link MesMdVendorDO#getId()} 84 * 关联 {@link MesMdVendorDO#getId()}
89 */ 85 */
90 private Long vendorId; 86 private Long vendorId;
  87 + // TODO @AI:这个字段,应该没有的。
91 /** 88 /**
92 * 生产工单编号 89 * 生产工单编号
93 * 90 *
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.enums;
  2 +
  3 +/**
  4 + * MES 业务类型常量
  5 + *
  6 + * 集中管理业务类型枚举的编号,按业务域分段。
  7 + * 各枚举类引用此处常量,避免硬编码数字。(也避免冲突!!!)
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +public final class MesBizTypeConstants {
  12 +
  13 + private MesBizTypeConstants() {}
  14 +
  15 + // ========== QC 质检模块 [1, 100) ==========
  16 +
  17 + public static final int QC_IQC = 1; // 来料检验:MesQcIqcDO
  18 + public static final int QC_IPQC = 2; // 过程检验:MesQcIpqcDO
  19 + public static final int QC_OQC = 3; // 出货检验:MesQcOqcDO
  20 + public static final int QC_RQC = 4; // 退货检验:MesQcRqcDO
  21 +
  22 + // ========== WM 仓库模块 [100, 200) ==========
  23 +
  24 + public static final int WM_ARRIVAL_NOTICE = 100; // 到货通知单:MesWmArrivalNoticeDO
  25 + // public static final int WM_OUTSOURCE_RECPT = 101; // TODO 外协入库单:未实现,占位
  26 +
  27 + // ========== CAL 排班模块 [200, 300) ==========
  28 +
  29 + // TODO @芋艿【暂时忽略】:MesProWorkOrderTypeEnum
  30 + // TODO @芋艿【暂时忽略】:MesProWorkOrderSourceTypeEnum
  31 +
  32 + // ========== PRO 生产模块 [300, 400) ==========
  33 +
  34 + // ========== DV 设备模块 [400, 500) ==========
  35 +
  36 + // ========== TM 工装夹具模块 [500, 600) ==========
  37 +
  38 + // ========== MD 主数据模块 [600, 700) ==========
  39 +
  40 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkorderSourceTypeEnum.java renamed to yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProWorkOrderSourceTypeEnum.java
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcSourceDocTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.enums.qc;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * MES 质检来源单据类型枚举
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Getter
  16 +@AllArgsConstructor
  17 +public enum MesQcSourceDocTypeEnum implements ArrayValuable<Integer> {
  18 +
  19 + ARRIVAL_NOTICE(MesBizTypeConstants.WM_ARRIVAL_NOTICE, "到货通知单"),
  20 + // OUTSOURCE_RECPT(MesBizTypeConstants.WM_OUTSOURCE_RECPT, "外协入库单"), // TODO 未实现,占位
  21 + ;
  22 +
  23 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesQcSourceDocTypeEnum::getType).toArray(Integer[]::new);
  24 +
  25 + /**
  26 + * 类型值
  27 + */
  28 + private final Integer type;
  29 + /**
  30 + * 类型名
  31 + */
  32 + private final String name;
  33 +
  34 + @Override
  35 + public Integer[] array() {
  36 + return ARRAYS;
  37 + }
  38 +
  39 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcTypeEnum.java
1 package cn.iocoder.yudao.module.mes.enums.qc; 1 package cn.iocoder.yudao.module.mes.enums.qc;
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.MesBizTypeConstants;
4 import lombok.AllArgsConstructor; 5 import lombok.AllArgsConstructor;
5 import lombok.Getter; 6 import lombok.Getter;
6 7
@@ -15,10 +16,10 @@ import java.util.Arrays; @@ -15,10 +16,10 @@ import java.util.Arrays;
15 @AllArgsConstructor 16 @AllArgsConstructor
16 public enum MesQcTypeEnum implements ArrayValuable<Integer> { 17 public enum MesQcTypeEnum implements ArrayValuable<Integer> {
17 18
18 - IQC(1, "来料检验"),  
19 - IPQC(2, "过程检验"),  
20 - OQC(3, "出货检验"),  
21 - RQC(4, "退货检验"); 19 + IQC(MesBizTypeConstants.QC_IQC, "来料检验"),
  20 + IPQC(MesBizTypeConstants.QC_IPQC, "过程检验"),
  21 + OQC(MesBizTypeConstants.QC_OQC, "出货检验"),
  22 + RQC(MesBizTypeConstants.QC_RQC, "退货检验");
22 23
23 public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesQcTypeEnum::getType).toArray(Integer[]::new); 24 public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesQcTypeEnum::getType).toArray(Integer[]::new);
24 25
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/lqc/MesQcIqcServiceImpl.java
@@ -11,11 +11,13 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.lqc.MesQcIqcDO; @@ -11,11 +11,13 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.lqc.MesQcIqcDO;
11 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO; 11 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
12 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO; 12 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
13 import cn.iocoder.yudao.module.mes.dal.mysql.qc.lqc.MesQcIqcMapper; 13 import cn.iocoder.yudao.module.mes.dal.mysql.qc.lqc.MesQcIqcMapper;
  14 +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
14 import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum; 15 import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
15 import cn.iocoder.yudao.module.mes.enums.qc.MesQcIqcStatusEnum; 16 import cn.iocoder.yudao.module.mes.enums.qc.MesQcIqcStatusEnum;
16 import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; 17 import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
17 import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService; 18 import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
18 import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateService; 19 import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateService;
  20 +import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
19 import jakarta.annotation.Resource; 21 import jakarta.annotation.Resource;
20 import org.springframework.stereotype.Service; 22 import org.springframework.stereotype.Service;
21 import org.springframework.transaction.annotation.Transactional; 23 import org.springframework.transaction.annotation.Transactional;
@@ -48,6 +50,8 @@ public class MesQcIqcServiceImpl implements MesQcIqcService { @@ -48,6 +50,8 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
48 private MesQcIqcLineService iqcLineService; 50 private MesQcIqcLineService iqcLineService;
49 @Resource 51 @Resource
50 private MesQcDefectRecordService defectRecordService; 52 private MesQcDefectRecordService defectRecordService;
  53 + @Resource
  54 + private MesWmArrivalNoticeService arrivalNoticeService;
51 55
52 @Override 56 @Override
53 @Transactional(rollbackFor = Exception.class) 57 @Transactional(rollbackFor = Exception.class)
@@ -99,10 +103,10 @@ public class MesQcIqcServiceImpl implements MesQcIqcService { @@ -99,10 +103,10 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
99 // 1.1 校验存在 + 草稿状态 103 // 1.1 校验存在 + 草稿状态
100 MesQcIqcDO iqc = validateIqcStatusPrepare(id); 104 MesQcIqcDO iqc = validateIqcStatusPrepare(id);
101 // 1.2 校验合格品 + 不合格品 = 检测数量 105 // 1.2 校验合格品 + 不合格品 = 检测数量
102 - if (iqc.getCheckQuantity() != null && iqc.getCheckQuantity() > 0) {  
103 - int total = (iqc.getQualifiedQuantity() != null ? iqc.getQualifiedQuantity() : 0)  
104 - + (iqc.getUnqualifiedQuantity() != null ? iqc.getUnqualifiedQuantity() : 0);  
105 - if (total != iqc.getCheckQuantity()) { 106 + if (iqc.getCheckQuantity() != null && iqc.getCheckQuantity().compareTo(BigDecimal.ZERO) > 0) {
  107 + BigDecimal total = ObjUtil.defaultIfNull(iqc.getQualifiedQuantity(), BigDecimal.ZERO)
  108 + .add(ObjUtil.defaultIfNull(iqc.getUnqualifiedQuantity(), BigDecimal.ZERO));
  109 + if (total.compareTo(iqc.getCheckQuantity()) != 0) {
106 throw exception(QC_IQC_QUANTITY_MISMATCH); 110 throw exception(QC_IQC_QUANTITY_MISMATCH);
107 } 111 }
108 } 112 }
@@ -112,7 +116,31 @@ public class MesQcIqcServiceImpl implements MesQcIqcService { @@ -112,7 +116,31 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
112 .setId(id).setStatus(MesQcIqcStatusEnum.FINISHED.getType()); 116 .setId(id).setStatus(MesQcIqcStatusEnum.FINISHED.getType());
113 iqcMapper.updateById(updateObj); 117 iqcMapper.updateById(updateObj);
114 118
115 - // TODO @芋艿:WM 模块迁移后,更新到货通知单行/外协入库单行的检验结果 119 + // 3. 回写来源单据
  120 + writeBackSourceDoc(iqc);
  121 + }
  122 +
  123 + /**
  124 + * 回写来源单据(IQC 完成后)
  125 + *
  126 + * @param iqc 来料检验单
  127 + */
  128 + private void writeBackSourceDoc(MesQcIqcDO iqc) {
  129 + // 校验:sourceDocType 非空时,lineId 和 docId 必须非空,否则为脏数据
  130 + if (iqc.getSourceDocType() == null) {
  131 + return;
  132 + }
  133 + if (iqc.getSourceLineId() == null || iqc.getSourceDocId() == null) {
  134 + throw new IllegalArgumentException(
  135 + "IQC 单[" + iqc.getId() + "] sourceDocType 非空但 sourceLineId 或 sourceDocId 为空");
  136 + }
  137 +
  138 + // 根据不同的来源单据类型,回写不同的单据
  139 + if (Objects.equals(iqc.getSourceDocType(), MesBizTypeConstants.WM_ARRIVAL_NOTICE)) {
  140 + arrivalNoticeService.approveArrivalNoticeWhenIqcComplete(iqc.getSourceDocId(), iqc.getSourceLineId(),
  141 + iqc.getId(), iqc.getQualifiedQuantity());
  142 + }
  143 + // TODO 外协入库单:未实现,占位
116 } 144 }
117 145
118 @Override 146 @Override
@@ -196,8 +224,8 @@ public class MesQcIqcServiceImpl implements MesQcIqcService { @@ -196,8 +224,8 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
196 BigDecimal criticalRate = BigDecimal.ZERO; 224 BigDecimal criticalRate = BigDecimal.ZERO;
197 BigDecimal majorRate = BigDecimal.ZERO; 225 BigDecimal majorRate = BigDecimal.ZERO;
198 BigDecimal minorRate = BigDecimal.ZERO; 226 BigDecimal minorRate = BigDecimal.ZERO;
199 - if (iqc.getCheckQuantity() != null && iqc.getCheckQuantity() > 0) {  
200 - BigDecimal checkQty = BigDecimal.valueOf(iqc.getCheckQuantity()); 227 + if (iqc.getCheckQuantity() != null && iqc.getCheckQuantity().compareTo(BigDecimal.ZERO) > 0) {
  228 + BigDecimal checkQty = iqc.getCheckQuantity();
201 criticalRate = BigDecimal.valueOf(totalCritical).multiply(BigDecimal.valueOf(100)) 229 criticalRate = BigDecimal.valueOf(totalCritical).multiply(BigDecimal.valueOf(100))
202 .divide(checkQty, 2, RoundingMode.HALF_UP); 230 .divide(checkQty, 2, RoundingMode.HALF_UP);
203 majorRate = BigDecimal.valueOf(totalMajor).multiply(BigDecimal.valueOf(100)) 231 majorRate = BigDecimal.valueOf(totalMajor).multiply(BigDecimal.valueOf(100))
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.line.Mes @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.line.Mes
6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
  9 +import java.math.BigDecimal;
9 import java.util.List; 10 import java.util.List;
10 11
11 /** 12 /**
@@ -66,4 +67,13 @@ public interface MesWmArrivalNoticeLineService { @@ -66,4 +67,13 @@ public interface MesWmArrivalNoticeLineService {
66 */ 67 */
67 void deleteArrivalNoticeLineByNoticeId(Long noticeId); 68 void deleteArrivalNoticeLineByNoticeId(Long noticeId);
68 69
  70 + /**
  71 + * IQC 检验完成后回写行数据
  72 + *
  73 + * @param lineId 行编号
  74 + * @param iqcId IQC 检验单编号
  75 + * @param qualifiedQuantity 合格数量
  76 + */
  77 + void updateByIqcComplete(Long lineId, Long iqcId, BigDecimal qualifiedQuantity);
  78 +
69 } 79 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java
@@ -15,6 +15,7 @@ import org.springframework.context.annotation.Lazy; @@ -15,6 +15,7 @@ import org.springframework.context.annotation.Lazy;
15 import org.springframework.stereotype.Service; 15 import org.springframework.stereotype.Service;
16 import org.springframework.validation.annotation.Validated; 16 import org.springframework.validation.annotation.Validated;
17 17
  18 +import java.math.BigDecimal;
18 import java.util.List; 19 import java.util.List;
19 20
20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 21 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -41,11 +42,7 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine @@ -41,11 +42,7 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine
41 42
42 // 插入 43 // 插入
43 MesWmArrivalNoticeLineDO line = BeanUtils.toBean(createReqVO, MesWmArrivalNoticeLineDO.class); 44 MesWmArrivalNoticeLineDO line = BeanUtils.toBean(createReqVO, MesWmArrivalNoticeLineDO.class);
44 - // 如果不需要检验,则合格品数量直接等于到货数量  
45 - // TODO @AI:抽个通用方法,给 create 和 update 方法使用;然后最好注释下:“如果不需要 iqc 检验,则合格品数量直接 = 接收数量;否则,合格品数量就由对应的 iqc 检验单负责更新(对应方法)”  
46 - if (BooleanUtil.isFalse(line.getIqcCheckFlag())) {  
47 - line.setQualifiedQuantity(line.getArrivalQuantity());  
48 - } 45 + initQualifiedQuantityIfNoIqc(line);
49 arrivalNoticeLineMapper.insert(line); 46 arrivalNoticeLineMapper.insert(line);
50 return line.getId(); 47 return line.getId();
51 } 48 }
@@ -59,13 +56,21 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine @@ -59,13 +56,21 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine
59 56
60 // 更新 57 // 更新
61 MesWmArrivalNoticeLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmArrivalNoticeLineDO.class); 58 MesWmArrivalNoticeLineDO updateObj = BeanUtils.toBean(updateReqVO, MesWmArrivalNoticeLineDO.class);
62 - // 如果不需要检验,则合格品数量直接等于到货数量  
63 - if (BooleanUtil.isFalse(updateObj.getIqcCheckFlag())) {  
64 - updateObj.setQualifiedQuantity(updateObj.getArrivalQuantity());  
65 - } 59 + initQualifiedQuantityIfNoIqc(updateObj);
66 arrivalNoticeLineMapper.updateById(updateObj); 60 arrivalNoticeLineMapper.updateById(updateObj);
67 } 61 }
68 62
  63 + /**
  64 + * 初始化合格品数量:
  65 + * 1. 如果不需要 IQC 检验,则合格品数量直接 = 接收数量;
  66 + * 2. 否则,合格品数量由对应的 IQC 检验单负责更新(见 {@link #updateByIqcComplete})
  67 + */
  68 + private void initQualifiedQuantityIfNoIqc(MesWmArrivalNoticeLineDO line) {
  69 + if (BooleanUtil.isFalse(line.getIqcCheckFlag())) {
  70 + line.setQualifiedQuantity(line.getArrivalQuantity());
  71 + }
  72 + }
  73 +
69 @Override 74 @Override
70 public void deleteArrivalNoticeLine(Long id) { 75 public void deleteArrivalNoticeLine(Long id) {
71 // 校验存在 76 // 校验存在
@@ -94,6 +99,15 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine @@ -94,6 +99,15 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine
94 arrivalNoticeLineMapper.deleteByNoticeId(noticeId); 99 arrivalNoticeLineMapper.deleteByNoticeId(noticeId);
95 } 100 }
96 101
  102 + @Override
  103 + public void updateByIqcComplete(Long lineId, Long iqcId, BigDecimal qualifiedQuantity) {
  104 + // 校验行存在
  105 + validateArrivalNoticeLineExists(lineId);
  106 + // 更新
  107 + arrivalNoticeLineMapper.updateById(new MesWmArrivalNoticeLineDO()
  108 + .setId(lineId).setIqcId(iqcId).setQualifiedQuantity(qualifiedQuantity));
  109 + }
  110 +
97 private MesWmArrivalNoticeLineDO validateArrivalNoticeLineExists(Long id) { 111 private MesWmArrivalNoticeLineDO validateArrivalNoticeLineExists(Long id) {
98 MesWmArrivalNoticeLineDO line = arrivalNoticeLineMapper.selectById(id); 112 MesWmArrivalNoticeLineDO line = arrivalNoticeLineMapper.selectById(id);
99 if (line == null) { 113 if (line == null) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.MesWmArr @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.MesWmArr
6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
  9 +import java.math.BigDecimal;
9 import java.util.Collection; 10 import java.util.Collection;
10 import java.util.List; 11 import java.util.List;
11 import java.util.Map; 12 import java.util.Map;
@@ -63,11 +64,14 @@ public interface MesWmArrivalNoticeService { @@ -63,11 +64,14 @@ public interface MesWmArrivalNoticeService {
63 void submitArrivalNotice(Long id); 64 void submitArrivalNotice(Long id);
64 65
65 /** 66 /**
66 - * 审批到货通知单(待质检 → 待入库) 67 + * IQC 检验完成后回写到货通知单(更新行 + 尝试推进主表状态)
67 * 68 *
68 - * @param id 编号 69 + * @param id 到货通知单编号
  70 + * @param lineId 到货通知单行编号
  71 + * @param iqcId IQC 检验单编号
  72 + * @param qualifiedQuantity 合格数量
69 */ 73 */
70 - void approveArrivalNotice(Long id); 74 + void approveArrivalNoticeWhenIqcComplete(Long id, Long lineId, Long iqcId, BigDecimal qualifiedQuantity);
71 75
72 /** 76 /**
73 * 完成到货通知单(待入库 → 已完成),内部调用 77 * 完成到货通知单(待入库 → 已完成),内部调用
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
@@ -12,10 +12,12 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalN @@ -12,10 +12,12 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalN
12 import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper; 12 import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
13 import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum; 13 import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
14 import jakarta.annotation.Resource; 14 import jakarta.annotation.Resource;
  15 +import lombok.extern.slf4j.Slf4j;
15 import org.springframework.stereotype.Service; 16 import org.springframework.stereotype.Service;
16 import org.springframework.transaction.annotation.Transactional; 17 import org.springframework.transaction.annotation.Transactional;
17 import org.springframework.validation.annotation.Validated; 18 import org.springframework.validation.annotation.Validated;
18 19
  20 +import java.math.BigDecimal;
19 import java.util.Collection; 21 import java.util.Collection;
20 import java.util.Collections; 22 import java.util.Collections;
21 import java.util.List; 23 import java.util.List;
@@ -28,6 +30,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; @@ -28,6 +30,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
28 */ 30 */
29 @Service 31 @Service
30 @Validated 32 @Validated
  33 +@Slf4j
31 public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService { 34 public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService {
32 35
33 @Resource 36 @Resource
@@ -107,25 +110,28 @@ public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService @@ -107,25 +110,28 @@ public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService
107 } 110 }
108 } 111 }
109 112
110 - // TODO DONE @AI:确认由 IQC 模块在检验完成后调用。前端审批按钮已移除,仅保留 Service 方法供内部调用  
111 @Override 113 @Override
112 @Transactional(rollbackFor = Exception.class) 114 @Transactional(rollbackFor = Exception.class)
113 - public void approveArrivalNotice(Long id) {  
114 - // 1.1 校验存在 115 + public void approveArrivalNoticeWhenIqcComplete(Long id, Long lineId, Long iqcId, BigDecimal qualifiedQuantity) {
  116 + // 1.1 校验到货通知单存在
115 MesWmArrivalNoticeDO notice = validateArrivalNoticeExists(id); 117 MesWmArrivalNoticeDO notice = validateArrivalNoticeExists(id);
116 - // 1.2 校验状态:只有待质检才允许审批 118 + // 1.2 校验状态为待质检
117 if (ObjUtil.notEqual(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus(), notice.getStatus())) { 119 if (ObjUtil.notEqual(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus(), notice.getStatus())) {
118 throw exception(WM_ARRIVAL_NOTICE_STATUS_NOT_PENDING_QC); 120 throw exception(WM_ARRIVAL_NOTICE_STATUS_NOT_PENDING_QC);
119 } 121 }
120 - // 1.3 校验所有 iqcCheckFlag=true 的行必须 iqcId 不为空 122 +
  123 + // 2. 更新到货通知单行:绑定 iqcId + 合格数量
  124 + arrivalNoticeLineService.updateByIqcComplete(lineId, iqcId, qualifiedQuantity);
  125 +
  126 + // 3.1 判断是否所有需检行都已完成
121 List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id); 127 List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id);
122 boolean hasUnchecked = CollectionUtils.anyMatch(lines, 128 boolean hasUnchecked = CollectionUtils.anyMatch(lines,
123 line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null); 129 line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null);
124 if (hasUnchecked) { 130 if (hasUnchecked) {
125 - throw exception(WM_ARRIVAL_NOTICE_IQC_PENDING); 131 + log.info("[updateWhenIqcComplete][到货通知单({}) 还有未完成检验的行,暂不更新状态]", id);
  132 + return;
126 } 133 }
127 -  
128 - // 2. 审批通过 134 + // 3.2 所有行检验完成,更新主表状态 PENDING_QC → PENDING_RECEIPT
129 arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO() 135 arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO()
130 .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus())); 136 .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus()));
131 } 137 }