Commit 7d45f862fcf531b85fbce7f8717dea3d58b8d309

Authored by YunaiV
1 parent 26507997

refactor(mes): 优化 RQC 模块,添加主表缺陷统计

  主要变更:
  - RQC 主表添加缺陷统计字段(致命/严重/轻微缺陷数量和缺陷率)
  - 实现主表缺陷统计逻辑,对齐 IQC 实现方式
  - 删除 sourceDocCode 字段,rqcType 重命名为 type
  - RQC Line 表 tool 字段改为 String 类型(冗余存储)
  - 删除 MesQcTemplateDetailService 中未使用的方法
  - 修复 OQC Mapper 查询条件错误
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/MesQcRqcLineController.java
... ... @@ -85,13 +85,6 @@ public class MesQcRqcLineController {
85 85 .setIndicatorType(indicator.getType()));
86 86 findAndThen(unitMeasureMap, vo.getUnitMeasureId(),
87 87 unit -> vo.setUnitMeasureName(unit.getName()));
88   - // 逐个查询工具名称(工具数量通常较少)
89   - if (vo.getToolId() != null) {
90   - MesTmToolDO tool = toolService.getTool(vo.getToolId());
91   - if (tool != null) {
92   - vo.setToolName(tool.getName());
93   - }
94   - }
95 88 });
96 89 }
97 90  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcPageReqVO.java
... ... @@ -23,14 +23,11 @@ public class MesQcRqcPageReqVO extends PageParam {
23 23 @Schema(description = "来源单据类型", example = "RTISSUE")
24 24 private String sourceDocType;
25 25  
26   - @Schema(description = "来源单据编号", example = "RT2025")
27   - private String sourceDocCode;
28   -
29 26 @Schema(description = "产品物料 ID", example = "20")
30 27 private Long itemId;
31 28  
32   - @Schema(description = "检验类型", example = "1")
33   - private Integer rqcType;
  29 + @Schema(description = "批次号", example = "BATCH001")
  30 + private String batchCode;
34 31  
35 32 @Schema(description = "检测结果", example = "1")
36 33 private Integer checkResult;
... ... @@ -41,8 +38,4 @@ public class MesQcRqcPageReqVO extends PageParam {
41 38 @Schema(description = "检测人员用户 ID", example = "1")
42 39 private Long inspectorUserId;
43 40  
44   - @Schema(description = "检测日期")
45   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
46   - private LocalDateTime[] inspectDate;
47   -
48 41 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcRespVO.java
... ... @@ -36,9 +36,6 @@ public class MesQcRqcRespVO {
36 36 @Schema(description = "来源单据类型", example = "RTISSUE")
37 37 private String sourceDocType;
38 38  
39   - @Schema(description = "来源单据编号", example = "RT20250101001")
40   - private String sourceDocCode;
41   -
42 39 @Schema(description = "来源单据行 ID", example = "300")
43 40 private Long sourceLineId;
44 41  
... ... @@ -46,7 +43,7 @@ public class MesQcRqcRespVO {
46 43  
47 44 @Schema(description = "检验类型", example = "1")
48 45 @ExcelProperty("检验类型")
49   - private Integer rqcType;
  46 + private Integer type;
50 47  
51 48 // ========== 物料 ==========
52 49  
... ... @@ -110,6 +107,26 @@ public class MesQcRqcRespVO {
110 107 @ExcelProperty("备注")
111 108 private String remark;
112 109  
  110 + // ========== 缺陷统计 ==========
  111 +
  112 + @Schema(description = "致命缺陷数量", example = "5")
  113 + private Integer criticalQuantity;
  114 +
  115 + @Schema(description = "严重缺陷数量", example = "10")
  116 + private Integer majorQuantity;
  117 +
  118 + @Schema(description = "轻微缺陷数量", example = "15")
  119 + private Integer minorQuantity;
  120 +
  121 + @Schema(description = "致命缺陷率(%)", example = "5.00")
  122 + private BigDecimal criticalRate;
  123 +
  124 + @Schema(description = "严重缺陷率(%)", example = "10.00")
  125 + private BigDecimal majorRate;
  126 +
  127 + @Schema(description = "轻微缺陷率(%)", example = "15.00")
  128 + private BigDecimal minorRate;
  129 +
113 130 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
114 131 @ExcelProperty("创建时间")
115 132 private LocalDateTime createTime;
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/MesQcRqcSaveReqVO.java
1 1 package cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo;
2 2  
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import com.fasterxml.jackson.annotation.JsonIgnore;
3 5 import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.AssertTrue;
  7 +import jakarta.validation.constraints.DecimalMin;
4 8 import jakarta.validation.constraints.NotEmpty;
5 9 import jakarta.validation.constraints.NotNull;
6 10 import lombok.Data;
... ... @@ -23,8 +27,7 @@ public class MesQcRqcSaveReqVO {
23 27 @NotEmpty(message = "检验单名称不能为空")
24 28 private String name;
25 29  
26   - @Schema(description = "检验模板 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
27   - @NotNull(message = "检验模板不能为空")
  30 + @Schema(description = "检验模板 ID", example = "100")
28 31 private Long templateId;
29 32  
30 33 // ========== 来源单据 ==========
... ... @@ -35,16 +38,14 @@ public class MesQcRqcSaveReqVO {
35 38 @Schema(description = "来源单据类型", example = "RTISSUE")
36 39 private String sourceDocType;
37 40  
38   - @Schema(description = "来源单据编号", example = "RT20250101001")
39   - private String sourceDocCode;
40   -
41 41 @Schema(description = "来源单据行 ID", example = "300")
42 42 private Long sourceLineId;
43 43  
44 44 // ========== 检验类型 ==========
45 45  
46   - @Schema(description = "检验类型", example = "1")
47   - private Integer rqcType;
  46 + @Schema(description = "检验类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  47 + @NotNull(message = "检验类型不能为空")
  48 + private Integer type;
48 49  
49 50 // ========== 物料 ==========
50 51  
... ... @@ -57,15 +58,19 @@ public class MesQcRqcSaveReqVO {
57 58  
58 59 // ========== 数量 ==========
59 60  
60   - // TODO @AI:参考 iqc 的参数校验;
61   -
62   - @Schema(description = "检测数量", example = "100")
  61 + @Schema(description = "检测数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  62 + @NotNull(message = "检测数量不能为空")
  63 + @DecimalMin(value = "0", message = "检测数量不能小于 0")
63 64 private BigDecimal checkQuantity;
64 65  
65   - @Schema(description = "合格品数量", example = "90")
  66 + @Schema(description = "合格品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "90")
  67 + @NotNull(message = "合格品数量不能为空")
  68 + @DecimalMin(value = "0", message = "合格品数量不能小于 0")
66 69 private BigDecimal qualifiedQuantity;
67 70  
68   - @Schema(description = "不合格数量", example = "10")
  71 + @Schema(description = "不合格数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  72 + @NotNull(message = "不合格数量不能为空")
  73 + @DecimalMin(value = "0", message = "不合格数量不能小于 0")
69 74 private BigDecimal unqualifiedQuantity;
70 75  
71 76 // ========== 检验 ==========
... ... @@ -73,13 +78,24 @@ public class MesQcRqcSaveReqVO {
73 78 @Schema(description = "检测结果", example = "1")
74 79 private Integer checkResult;
75 80  
76   - @Schema(description = "检测日期")
  81 + @Schema(description = "检测日期", requiredMode = Schema.RequiredMode.REQUIRED)
  82 + @NotNull(message = "检测日期不能为空")
77 83 private LocalDateTime inspectDate;
78 84  
79   - @Schema(description = "检测人员用户 ID", example = "1")
  85 + @Schema(description = "检测人员用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  86 + @NotNull(message = "检测人员不能为空")
80 87 private Long inspectorUserId;
81 88  
82 89 @Schema(description = "备注", example = "备注")
83 90 private String remark;
84 91  
  92 + @AssertTrue(message = "检测数量必须等于合格品数量与不合格品数量之和")
  93 + @JsonIgnore
  94 + public boolean isQuantityValid() {
  95 + if (ObjUtil.hasNull(checkQuantity, qualifiedQuantity, unqualifiedQuantity)) {
  96 + return true; // @NotNull 会处理 null
  97 + }
  98 + return checkQuantity.compareTo(qualifiedQuantity.add(unqualifiedQuantity)) == 0;
  99 + }
  100 +
85 101 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/rqc/vo/line/MesQcRqcLineRespVO.java
... ... @@ -30,11 +30,8 @@ public class MesQcRqcLineRespVO {
30 30 @Schema(description = "检测指标类型", example = "QUANTITATIVE")
31 31 private String indicatorType;
32 32  
33   - @Schema(description = "检测工具 ID", example = "5")
34   - private Long toolId;
35   -
36   - @Schema(description = "检测工具名称", example = "卡尺")
37   - private String toolName;
  33 + @Schema(description = "检测工具", example = "卡尺")
  34 + private String tool;
38 35  
39 36 @Schema(description = "检测方法", example = "目测")
40 37 private String checkMethod;
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/rqc/MesQcRqcDO.java
... ... @@ -58,10 +58,6 @@ public class MesQcRqcDO extends BaseDO {
58 58 */
59 59 private String sourceDocType;
60 60 /**
61   - * 来源单据编号
62   - */
63   - private String sourceDocCode;
64   - /**
65 61 * 来源单据行 ID
66 62 */
67 63 private Long sourceLineId;
... ... @@ -73,7 +69,7 @@ public class MesQcRqcDO extends BaseDO {
73 69 *
74 70 * 字典 mes_rqc_type
75 71 */
76   - private Integer rqcType;
  72 + private Integer type;
77 73  
78 74 // ========== 物料 ==========
79 75  
... ... @@ -133,6 +129,33 @@ public class MesQcRqcDO extends BaseDO {
133 129 */
134 130 private String remark;
135 131  
  132 + // ========== 缺陷统计 ==========
  133 +
  134 + /**
  135 + * 致命缺陷数量
  136 + */
  137 + private Integer criticalQuantity;
  138 + /**
  139 + * 严重缺陷数量
  140 + */
  141 + private Integer majorQuantity;
  142 + /**
  143 + * 轻微缺陷数量
  144 + */
  145 + private Integer minorQuantity;
  146 + /**
  147 + * 致命缺陷率(%)
  148 + */
  149 + private BigDecimal criticalRate;
  150 + /**
  151 + * 严重缺陷率(%)
  152 + */
  153 + private BigDecimal majorRate;
  154 + /**
  155 + * 轻微缺陷率(%)
  156 + */
  157 + private BigDecimal minorRate;
  158 +
136 159 // ========== 预留字段 ==========
137 160  
138 161 /**
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/rqc/MesQcRqcLineDO.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc;
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 4 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
5 5 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
6   -import cn.iocoder.yudao.module.mes.dal.dataobject.tm.tool.MesTmToolDO;
7 6 import com.baomidou.mybatisplus.annotation.KeySequence;
8 7 import com.baomidou.mybatisplus.annotation.TableId;
9 8 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -44,11 +43,11 @@ public class MesQcRqcLineDO extends BaseDO {
44 43 */
45 44 private Long indicatorId;
46 45 /**
47   - * 检测工具 ID
  46 + * 检测工具
48 47 *
49   - * 关联 {@link MesTmToolDO#getId()}
  48 + * 冗余 {@link MesQcIndicatorDO#getTool()}
50 49 */
51   - private Long toolId;
  50 + private String tool;
52 51 /**
53 52 * 检测方法
54 53 */
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/oqc/MesQcOqcMapper.java
... ... @@ -23,8 +23,6 @@ public interface MesQcOqcMapper extends BaseMapperX<MesQcOqcDO> {
23 23 .eqIfPresent(MesQcOqcDO::getItemId, reqVO.getItemId())
24 24 .eqIfPresent(MesQcOqcDO::getCheckResult, reqVO.getCheckResult())
25 25 .eqIfPresent(MesQcOqcDO::getStatus, reqVO.getStatus())
26   - .betweenIfPresent(MesQcOqcDO::getOutDate, reqVO.getOutDate())
27   - .betweenIfPresent(MesQcOqcDO::getInspectDate, reqVO.getInspectDate())
28 26 .eqIfPresent(MesQcOqcDO::getInspectorUserId, reqVO.getInspectorUserId())
29 27 .orderByDesc(MesQcOqcDO::getId));
30 28 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/rqc/MesQcRqcMapper.java
... ... @@ -19,12 +19,10 @@ public interface MesQcRqcMapper extends BaseMapperX<MesQcRqcDO> {
19 19 return selectPage(reqVO, new LambdaQueryWrapperX<MesQcRqcDO>()
20 20 .likeIfPresent(MesQcRqcDO::getCode, reqVO.getCode())
21 21 .eqIfPresent(MesQcRqcDO::getSourceDocType, reqVO.getSourceDocType())
22   - .likeIfPresent(MesQcRqcDO::getSourceDocCode, reqVO.getSourceDocCode())
23 22 .eqIfPresent(MesQcRqcDO::getItemId, reqVO.getItemId())
24   - .eqIfPresent(MesQcRqcDO::getRqcType, reqVO.getRqcType())
  23 + .likeIfPresent(MesQcRqcDO::getBatchCode, reqVO.getBatchCode())
25 24 .eqIfPresent(MesQcRqcDO::getCheckResult, reqVO.getCheckResult())
26 25 .eqIfPresent(MesQcRqcDO::getStatus, reqVO.getStatus())
27   - .betweenIfPresent(MesQcRqcDO::getInspectDate, reqVO.getInspectDate())
28 26 .eqIfPresent(MesQcRqcDO::getInspectorUserId, reqVO.getInspectorUserId())
29 27 .orderByDesc(MesQcRqcDO::getId));
30 28 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcLineServiceImpl.java
... ... @@ -5,21 +5,25 @@ import cn.hutool.core.util.ObjUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.line.MesQcRqcLinePageReqVO;
7 7 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
8 9 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcLineDO;
9 10 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
10 11 import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcLineMapper;
11 12 import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateIndicatorMapper;
12 13 import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
  14 +import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
13 15 import jakarta.annotation.Resource;
14 16 import org.springframework.stereotype.Service;
15 17 import org.springframework.validation.annotation.Validated;
16 18  
17 19 import java.util.ArrayList;
18 20 import java.util.List;
  21 +import java.util.Map;
19 22 import java.util.Objects;
20 23  
21 24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
22 25 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
23 27 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
24 28  
25 29 /**
... ... @@ -36,6 +40,9 @@ public class MesQcRqcLineServiceImpl implements MesQcRqcLineService {
36 40 @Resource
37 41 private MesQcTemplateIndicatorMapper templateIndicatorMapper;
38 42  
  43 + @Resource
  44 + private MesQcIndicatorService indicatorService;
  45 +
39 46 @Override
40 47 public MesQcRqcLineDO validateRqcLineExists(Long id) {
41 48 MesQcRqcLineDO line = rqcLineMapper.selectById(id);
... ... @@ -57,14 +64,22 @@ public class MesQcRqcLineServiceImpl implements MesQcRqcLineService {
57 64  
58 65 @Override
59 66 public void createLinesFromTemplate(Long rqcId, Long templateId) {
60   - List<MesQcTemplateIndicatorDO> indicators = templateIndicatorMapper.selectListByTemplateId(templateId);
61   - List<MesQcRqcLineDO> lines = convertList(indicators, indicator -> new MesQcRqcLineDO()
62   - .setRqcId(rqcId).setIndicatorId(indicator.getIndicatorId())
63   - .setToolId(null) // TODO @芋艿:模板指标暂无 toolId,后续可扩展;todo @AI:改成 tool;参考 iqc
64   - .setCheckMethod(indicator.getCheckMethod())
65   - .setStandardValue(indicator.getStandardValue()).setUnitMeasureId(indicator.getUnitMeasureId())
66   - .setMaxThreshold(indicator.getThresholdMax()).setMinThreshold(indicator.getThresholdMin())
67   - .setCriticalQuantity(0).setMajorQuantity(0).setMinorQuantity(0));
  67 + List<MesQcTemplateIndicatorDO> templateIndicators = templateIndicatorMapper.selectListByTemplateId(templateId);
  68 + if (CollUtil.isEmpty(templateIndicators)) {
  69 + return;
  70 + }
  71 + Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap(
  72 + convertSet(templateIndicators, MesQcTemplateIndicatorDO::getIndicatorId));
  73 + List<MesQcRqcLineDO> lines = convertList(templateIndicators, templateIndicator -> {
  74 + MesQcIndicatorDO indicator = indicatorMap.get(templateIndicator.getIndicatorId());
  75 + return new MesQcRqcLineDO()
  76 + .setRqcId(rqcId).setIndicatorId(templateIndicator.getIndicatorId())
  77 + .setTool(indicator != null ? indicator.getTool() : null)
  78 + .setCheckMethod(templateIndicator.getCheckMethod())
  79 + .setStandardValue(templateIndicator.getStandardValue()).setUnitMeasureId(templateIndicator.getUnitMeasureId())
  80 + .setMaxThreshold(templateIndicator.getThresholdMax()).setMinThreshold(templateIndicator.getThresholdMin())
  81 + .setCriticalQuantity(0).setMajorQuantity(0).setMinorQuantity(0);
  82 + });
68 83 rqcLineMapper.insertBatch(lines);
69 84 }
70 85  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/rqc/MesQcRqcServiceImpl.java
1 1 package cn.iocoder.yudao.module.mes.service.qc.rqc;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.hutool.core.util.ObjUtil;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
... ... @@ -8,12 +7,15 @@ import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.MesQcRqcPageReqVO;
8 7 import cn.iocoder.yudao.module.mes.controller.admin.qc.rqc.vo.MesQcRqcSaveReqVO;
9 8 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
10 9 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcDO;
11   -import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
  10 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
12 11 import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcMapper;
13 12 import cn.iocoder.yudao.module.mes.enums.MesOrderStatusEnum;
  13 +import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
14 14 import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
  15 +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
15 16 import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
16   -import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateService;
  17 +import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateDetailService;
  18 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
17 19 import jakarta.annotation.Resource;
18 20 import org.springframework.context.annotation.Lazy;
19 21 import org.springframework.stereotype.Service;
... ... @@ -21,7 +23,9 @@ import org.springframework.transaction.annotation.Transactional;
21 23 import org.springframework.validation.annotation.Validated;
22 24  
23 25 import java.math.BigDecimal;
  26 +import java.math.RoundingMode;
24 27 import java.util.List;
  28 +import java.util.Objects;
25 29  
26 30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
27 31 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
... ... @@ -38,29 +42,35 @@ public class MesQcRqcServiceImpl implements MesQcRqcService {
38 42 @Resource
39 43 private MesQcRqcMapper rqcMapper;
40 44 @Resource
41   - private MesQcTemplateService templateService;
  45 + private MesQcTemplateDetailService templateDetailService;
42 46 @Resource
43 47 private MesQcRqcLineService rqcLineService;
44 48 @Resource
45 49 @Lazy
  50 + private MesMdItemService itemService;
  51 + @Resource
  52 + @Lazy
46 53 private MesQcDefectRecordService defectRecordService;
47 54  
  55 + @Resource
  56 + private AdminUserApi adminUserApi;
  57 +
48 58 @Override
49 59 @Transactional(rollbackFor = Exception.class)
50 60 public Long createRqc(MesQcRqcSaveReqVO createReqVO) {
51 61 // 1.1 校验编号唯一
52 62 validateRqcCodeUnique(null, createReqVO.getCode());
53   - // TODO @AI:关联的物资等存在的校验;
54   - // 1.2 校验检验模板存在且支持 RQC 类型
55   - Long templateId = createReqVO.getTemplateId();
56   - // TODO @AI:参考 iqc 获取 templateId;不在依赖前端传递;
57   - MesQcTemplateDO template = templateService.validateTemplateExists(templateId);
58   - if (!CollUtil.contains(template.getTypes(), MesQcTypeEnum.RQC.getType())) {
59   - throw exception(QC_RQC_NO_TEMPLATE);
60   - }
  63 + // 1.2 校验物料、检测人员存在
  64 + itemService.validateItemExists(createReqVO.getItemId());
  65 + adminUserApi.validateUser(createReqVO.getInspectorUserId());
  66 + // 1.3 根据物料 + 检验类型自动匹配模板
  67 + MesQcTemplateItemDO templateItem = templateDetailService.getRequiredTemplateByItemIdAndType(
  68 + createReqVO.getItemId(), createReqVO.getType());
  69 + Long templateId = templateItem.getTemplateId();
61 70  
62 71 // 2. 插入主表
63 72 MesQcRqcDO rqc = BeanUtils.toBean(createReqVO, MesQcRqcDO.class)
  73 + .setTemplateId(templateId)
64 74 .setStatus(MesOrderStatusEnum.DRAFT.getType());
65 75 rqcMapper.insert(rqc);
66 76  
... ... @@ -75,6 +85,9 @@ public class MesQcRqcServiceImpl implements MesQcRqcService {
75 85 validateRqcStatusPrepare(updateReqVO.getId());
76 86 // 1.2 校验编号唯一
77 87 validateRqcCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
  88 + // 1.3 校验物料、检测人员存在
  89 + itemService.validateItemExists(updateReqVO.getItemId());
  90 + adminUserApi.validateUser(updateReqVO.getInspectorUserId());
78 91  
79 92 // 2. 更新
80 93 MesQcRqcDO updateObj = BeanUtils.toBean(updateReqVO, MesQcRqcDO.class);
... ... @@ -160,10 +173,44 @@ public class MesQcRqcServiceImpl implements MesQcRqcService {
160 173  
161 174 @Override
162 175 public void recalculateDefectStats(Long rqcId, List<MesQcDefectRecordDO> records) {
163   - // RQC 只更新行级缺陷统计,主表不存储缺陷统计字段
  176 + MesQcRqcDO rqc = validateRqcExists(rqcId);
  177 + // 1. 行级缺陷统计
164 178 rqcLineService.recalculateLineDefectStats(rqcId, records);
165 179  
166   - // TODO @AI:还是对齐其他的
  180 + // 2.1 汇总主表的缺陷数量
  181 + int totalCritical = 0;
  182 + int totalMajor = 0;
  183 + int totalMinor = 0;
  184 + for (MesQcDefectRecordDO record : records) {
  185 + int quantity = ObjUtil.defaultIfNull(record.getQuantity(), 1);
  186 + if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.CRITICAL.getType())) {
  187 + totalCritical += quantity;
  188 + } else if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.MAJOR.getType())) {
  189 + totalMajor += quantity;
  190 + } else if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.MINOR.getType())) {
  191 + totalMinor += quantity;
  192 + } else {
  193 + throw exception(QC_DEFECT_RECORD_LEVEL_UNKNOWN);
  194 + }
  195 + }
  196 + // 2.2 计算缺陷率
  197 + BigDecimal criticalRate = BigDecimal.ZERO;
  198 + BigDecimal majorRate = BigDecimal.ZERO;
  199 + BigDecimal minorRate = BigDecimal.ZERO;
  200 + if (rqc.getCheckQuantity() != null && rqc.getCheckQuantity().compareTo(BigDecimal.ZERO) > 0) {
  201 + BigDecimal checkQty = rqc.getCheckQuantity();
  202 + criticalRate = BigDecimal.valueOf(totalCritical).multiply(BigDecimal.valueOf(100))
  203 + .divide(checkQty, 2, RoundingMode.HALF_UP);
  204 + majorRate = BigDecimal.valueOf(totalMajor).multiply(BigDecimal.valueOf(100))
  205 + .divide(checkQty, 2, RoundingMode.HALF_UP);
  206 + minorRate = BigDecimal.valueOf(totalMinor).multiply(BigDecimal.valueOf(100))
  207 + .divide(checkQty, 2, RoundingMode.HALF_UP);
  208 + }
  209 + // 2.3 更新主表
  210 + MesQcRqcDO updateRqc = new MesQcRqcDO().setId(rqcId)
  211 + .setCriticalQuantity(totalCritical).setMajorQuantity(totalMajor).setMinorQuantity(totalMinor)
  212 + .setCriticalRate(criticalRate).setMajorRate(majorRate).setMinorRate(minorRate);
  213 + rqcMapper.updateById(updateRqc);
167 214 }
168 215  
169 216 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateDetailService.java
... ... @@ -5,13 +5,12 @@ import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator.Mes
5 5 import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator.MesQcTemplateIndicatorSaveReqVO;
6 6 import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemPageReqVO;
7 7 import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.item.MesQcTemplateItemSaveReqVO;
8   -import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
9 8 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
10 9 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
11 10 import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
12 11 import jakarta.validation.Valid;
13 12  
14   -// TODO @AI:还是按照表,拆分。而不是 detail;
  13 +// TODO @AI:还是按照表,拆分。一个 MesQcTemplateIndicatorDO、MesQcTemplateItemDO;
15 14 /**
16 15 * MES 质检方案明细 Service 接口
17 16 *
... ... @@ -99,29 +98,6 @@ public interface MesQcTemplateDetailService {
99 98 */
100 99 PageResult<MesQcTemplateItemDO> getTemplateItemPage(MesQcTemplateItemPageReqVO pageReqVO);
101 100  
102   - // ========== 复杂查询 ==========
103   -
104   - /**
105   - * 根据方案 ID + 产品 ID 获取产品关联
106   - *
107   - * @param templateId 方案 ID
108   - * @param itemId 产品 ID
109   - * @return 产品关联
110   - */
111   - MesQcTemplateItemDO getTemplateItemByTemplateIdAndItemId(Long templateId, Long itemId);
112   -
113   - // TODO @芋艿:【不要删除】后续这个方法,会慢慢被替代掉;
114   - /**
115   - * 根据产品物料 ID 和 QC 类型查找检验模板
116   - *
117   - * 查找逻辑:在 template_item 中找到匹配 itemId 的模板,再校验该模板的 types 包含给定的 qcType
118   - *
119   - * @param itemId 产品物料 ID
120   - * @param qcType 检测种类(枚举 {@link MesQcTypeEnum})
121   - * @return 匹配的检验模板(可能为 null)
122   - */
123   - MesQcTemplateDO getTemplateByItemIdAndType(Long itemId, Integer qcType);
124   -
125 101 /**
126 102 * 根据产品物料 ID 和 QC 类型查找检验模板的产品关联(必须存在,否则抛异常)
127 103 *
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/template/MesQcTemplateDetailServiceImpl.java
... ... @@ -159,25 +159,6 @@ public class MesQcTemplateDetailServiceImpl implements MesQcTemplateDetailServic
159 159 // ========== 复杂查询 ==========
160 160  
161 161 @Override
162   - public MesQcTemplateItemDO getTemplateItemByTemplateIdAndItemId(Long templateId, Long itemId) {
163   - return templateItemMapper.selectByTemplateIdAndItemId(templateId, itemId);
164   - }
165   -
166   - @Override
167   - public MesQcTemplateDO getTemplateByItemIdAndType(Long itemId, Integer qcType) {
168   - // 1.1 查出 itemId 关联的所有 templateId
169   - List<MesQcTemplateItemDO> templateItems = templateItemMapper.selectListByItemId(itemId);
170   - if (CollUtil.isEmpty(templateItems)) {
171   - return null;
172   - }
173   - // 1.2 批量查模板,筛选 types 包含给定 qcType 的第一个
174   - List<MesQcTemplateDO> templates = templateMapper.selectByIds(
175   - convertSet(templateItems, MesQcTemplateItemDO::getTemplateId));
176   - // 2. 筛选 types 包含给定 qcType 的第一个
177   - return findFirst(templates, template -> CollUtil.contains(template.getTypes(), qcType));
178   - }
179   -
180   - @Override
181 162 public MesQcTemplateItemDO getRequiredTemplateByItemIdAndType(Long itemId, Integer qcType) {
182 163 // 1. 查出 itemId 关联的所有 templateId
183 164 List<MesQcTemplateItemDO> templateItems = templateItemMapper.selectListByItemId(itemId);
... ...