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 33 @Schema(description = "来源单据 ID", example = "200")
34 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 39 @Schema(description = "来源单据行 ID", example = "300")
43 40 private Long sourceLineId;
... ... @@ -87,15 +84,15 @@ public class MesQcIqcRespVO {
87 84  
88 85 @Schema(description = "本次检测数量", example = "10")
89 86 @ExcelProperty("检测数量")
90   - private Integer checkQuantity;
  87 + private BigDecimal checkQuantity;
91 88  
92 89 @Schema(description = "合格品数量", example = "9")
93 90 @ExcelProperty("合格品数量")
94   - private Integer qualifiedQuantity;
  91 + private BigDecimal qualifiedQuantity;
95 92  
96 93 @Schema(description = "不合格品数量", example = "1")
97 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 32 @Schema(description = "来源单据 ID", example = "200")
33 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 38 @Schema(description = "来源单据行 ID", example = "300")
42 39 private Long sourceLineId;
... ... @@ -69,13 +66,13 @@ public class MesQcIqcSaveReqVO {
69 66 private BigDecimal receivedQuantity;
70 67  
71 68 @Schema(description = "本次检测数量", example = "10")
72   - private Integer checkQuantity;
  69 + private BigDecimal checkQuantity;
73 70  
74 71 @Schema(description = "合格品数量", example = "9")
75   - private Integer qualifiedQuantity;
  72 + private BigDecimal qualifiedQuantity;
76 73  
77 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 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 115 @GetMapping("/simple-list")
125 116 @Operation(summary = "获得到货通知单精简列表")
126 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 4 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
5 5 import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
6 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 10 import com.baomidou.mybatisplus.annotation.KeySequence;
8 11 import com.baomidou.mybatisplus.annotation.TableId;
9 12 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -47,24 +50,26 @@ public class MesQcIqcDO extends BaseDO {
47 50 */
48 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 69 * 来源单据行 ID
  70 + *
  71 + * 关联:根据 {@link #sourceDocType} 不同:
  72 + * 1. {@link MesQcSourceDocTypeEnum#ARRIVAL_NOTICE} 时,关联 {@link MesWmArrivalNoticeLineDO#getId()}
68 73 */
69 74 private Long sourceLineId;
70 75  
... ... @@ -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 36 */
37 37 @TableId
38 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 48 * 关联 {@link MesMdItemDO#getId()}
51 49 */
52 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 84 * 关联 {@link MesMdVendorDO#getId()}
89 85 */
90 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 1 package cn.iocoder.yudao.module.mes.enums.qc;
2 2  
3 3 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
4 5 import lombok.AllArgsConstructor;
5 6 import lombok.Getter;
6 7  
... ... @@ -15,10 +16,10 @@ import java.util.Arrays;
15 16 @AllArgsConstructor
16 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 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 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
12 12 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
13 13 import cn.iocoder.yudao.module.mes.dal.mysql.qc.lqc.MesQcIqcMapper;
  14 +import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
14 15 import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
15 16 import cn.iocoder.yudao.module.mes.enums.qc.MesQcIqcStatusEnum;
16 17 import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
17 18 import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
18 19 import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateService;
  20 +import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
19 21 import jakarta.annotation.Resource;
20 22 import org.springframework.stereotype.Service;
21 23 import org.springframework.transaction.annotation.Transactional;
... ... @@ -48,6 +50,8 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
48 50 private MesQcIqcLineService iqcLineService;
49 51 @Resource
50 52 private MesQcDefectRecordService defectRecordService;
  53 + @Resource
  54 + private MesWmArrivalNoticeService arrivalNoticeService;
51 55  
52 56 @Override
53 57 @Transactional(rollbackFor = Exception.class)
... ... @@ -99,10 +103,10 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
99 103 // 1.1 校验存在 + 草稿状态
100 104 MesQcIqcDO iqc = validateIqcStatusPrepare(id);
101 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 110 throw exception(QC_IQC_QUANTITY_MISMATCH);
107 111 }
108 112 }
... ... @@ -112,7 +116,31 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
112 116 .setId(id).setStatus(MesQcIqcStatusEnum.FINISHED.getType());
113 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 146 @Override
... ... @@ -196,8 +224,8 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
196 224 BigDecimal criticalRate = BigDecimal.ZERO;
197 225 BigDecimal majorRate = BigDecimal.ZERO;
198 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 229 criticalRate = BigDecimal.valueOf(totalCritical).multiply(BigDecimal.valueOf(100))
202 230 .divide(checkQty, 2, RoundingMode.HALF_UP);
203 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 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
7 7 import jakarta.validation.Valid;
8 8  
  9 +import java.math.BigDecimal;
9 10 import java.util.List;
10 11  
11 12 /**
... ... @@ -66,4 +67,13 @@ public interface MesWmArrivalNoticeLineService {
66 67 */
67 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 15 import org.springframework.stereotype.Service;
16 16 import org.springframework.validation.annotation.Validated;
17 17  
  18 +import java.math.BigDecimal;
18 19 import java.util.List;
19 20  
20 21 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -41,11 +42,7 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine
41 42  
42 43 // 插入
43 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 46 arrivalNoticeLineMapper.insert(line);
50 47 return line.getId();
51 48 }
... ... @@ -59,13 +56,21 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine
59 56  
60 57 // 更新
61 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 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 74 @Override
70 75 public void deleteArrivalNoticeLine(Long id) {
71 76 // 校验存在
... ... @@ -94,6 +99,15 @@ public class MesWmArrivalNoticeLineServiceImpl implements MesWmArrivalNoticeLine
94 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 111 private MesWmArrivalNoticeLineDO validateArrivalNoticeLineExists(Long id) {
98 112 MesWmArrivalNoticeLineDO line = arrivalNoticeLineMapper.selectById(id);
99 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 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
7 7 import jakarta.validation.Valid;
8 8  
  9 +import java.math.BigDecimal;
9 10 import java.util.Collection;
10 11 import java.util.List;
11 12 import java.util.Map;
... ... @@ -63,11 +64,14 @@ public interface MesWmArrivalNoticeService {
63 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 12 import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
13 13 import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
14 14 import jakarta.annotation.Resource;
  15 +import lombok.extern.slf4j.Slf4j;
15 16 import org.springframework.stereotype.Service;
16 17 import org.springframework.transaction.annotation.Transactional;
17 18 import org.springframework.validation.annotation.Validated;
18 19  
  20 +import java.math.BigDecimal;
19 21 import java.util.Collection;
20 22 import java.util.Collections;
21 23 import java.util.List;
... ... @@ -28,6 +30,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
28 30 */
29 31 @Service
30 32 @Validated
  33 +@Slf4j
31 34 public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService {
32 35  
33 36 @Resource
... ... @@ -107,25 +110,28 @@ public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService
107 110 }
108 111 }
109 112  
110   - // TODO DONE @AI:确认由 IQC 模块在检验完成后调用。前端审批按钮已移除,仅保留 Service 方法供内部调用
111 113 @Override
112 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 117 MesWmArrivalNoticeDO notice = validateArrivalNoticeExists(id);
116   - // 1.2 校验状态:只有待质检才允许审批
  118 + // 1.2 校验状态为待质检
117 119 if (ObjUtil.notEqual(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus(), notice.getStatus())) {
118 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 127 List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id);
122 128 boolean hasUnchecked = CollectionUtils.anyMatch(lines,
123 129 line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null);
124 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 135 arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO()
130 136 .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus()));
131 137 }
... ...