Commit b567632e121ee7f6ce95f3aa3a8329dd7ba9a160

Authored by YunaiV
1 parent 1f53c39c

refactor(mes-qc): 优化 IPQC/IQC 质检模块

  IPQC 优化:
  - 修复 TODO AI 项:模板检索方法、tool 字段重构、删除冗余字段
  - 新增废品数量字段(工废/料废/其他废品)及验证
  - 添加检测人员和物料的存在性验证

  IQC 优化:
  - 检测人员由后端自动设置改为前端传递
  - 添加检测人员存在性验证
Showing 14 changed files with 131 additions and 87 deletions
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/MesQcIpqcLineController.java
... ... @@ -85,13 +85,6 @@ public class MesQcIpqcLineController {
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/ipqc/vo/MesQcIpqcPageReqVO.java
... ... @@ -26,9 +26,6 @@ public class MesQcIpqcPageReqVO extends PageParam {
26 26 @Schema(description = "生产工单 ID", example = "10")
27 27 private Long workOrderId;
28 28  
29   - @Schema(description = "工位 ID", example = "30")
30   - private Long workstationId;
31   -
32 29 @Schema(description = "产品物料 ID", example = "50")
33 30 private Long itemId;
34 31  
... ... @@ -41,8 +38,4 @@ public class MesQcIpqcPageReqVO 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/ipqc/vo/MesQcIpqcSaveReqVO.java
1 1 package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.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;
... ... @@ -38,10 +42,6 @@ public class MesQcIpqcSaveReqVO {
38 42 @Schema(description = "来源单据 ID", example = "200")
39 43 private Long sourceDocId;
40 44  
41   - // TODO @AI:
42   - @Schema(description = "来源单据编号", example = "FB20250101001")
43   - private String sourceDocCode;
44   -
45 45 @Schema(description = "来源单据行 ID", example = "300")
46 46 private Long sourceLineId;
47 47  
... ... @@ -51,6 +51,7 @@ public class MesQcIpqcSaveReqVO {
51 51 @NotNull(message = "生产工单不能为空")
52 52 private Long workOrderId;
53 53  
  54 + // TODO @芋艿:【不确定】这里要怎么弄
54 55 @Schema(description = "生产任务 ID", example = "20")
55 56 private Long taskId;
56 57  
... ... @@ -58,6 +59,7 @@ public class MesQcIpqcSaveReqVO {
58 59 @NotNull(message = "工位不能为空")
59 60 private Long workstationId;
60 61  
  62 + // TODO @芋艿:【不确定】这里要怎么弄
61 63 @Schema(description = "工序 ID", example = "40")
62 64 private Long processId;
63 65  
... ... @@ -68,22 +70,34 @@ public class MesQcIpqcSaveReqVO {
68 70  
69 71 // ========== 数量 ==========
70 72  
71   - @Schema(description = "检测数量", example = "100")
  73 + @Schema(description = "检测数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  74 + @NotNull(message = "检测数量不能为空")
  75 + @DecimalMin(value = "0", message = "检测数量不能小于 0")
72 76 private BigDecimal checkQuantity;
73 77  
74   - @Schema(description = "合格品数量", example = "95")
  78 + @Schema(description = "合格品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "95")
  79 + @NotNull(message = "合格品数量不能为空")
  80 + @DecimalMin(value = "0", message = "合格品数量不能小于 0")
75 81 private BigDecimal qualifiedQuantity;
76 82  
77   - @Schema(description = "不合格品数量", example = "5")
  83 + @Schema(description = "不合格品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  84 + @NotNull(message = "不合格品数量不能为空")
  85 + @DecimalMin(value = "0", message = "不合格品数量不能小于 0")
78 86 private BigDecimal unqualifiedQuantity;
79 87  
80   - @Schema(description = "工废数量", example = "2")
  88 + @Schema(description = "工废数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  89 + @NotNull(message = "工废数量不能为空")
  90 + @DecimalMin(value = "0", message = "工废数量不能小于 0")
81 91 private BigDecimal laborScrapQuantity;
82 92  
83   - @Schema(description = "料废数量", example = "2")
  93 + @Schema(description = "料废数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  94 + @NotNull(message = "料废数量不能为空")
  95 + @DecimalMin(value = "0", message = "料废数量不能小于 0")
84 96 private BigDecimal materialScrapQuantity;
85 97  
86   - @Schema(description = "其他废品数量", example = "1")
  98 + @Schema(description = "其他废品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  99 + @NotNull(message = "其他废品数量不能为空")
  100 + @DecimalMin(value = "0", message = "其他废品数量不能小于 0")
87 101 private BigDecimal otherScrapQuantity;
88 102  
89 103 // ========== 检验 ==========
... ... @@ -100,4 +114,23 @@ public class MesQcIpqcSaveReqVO {
100 114 @Schema(description = "备注", example = "备注")
101 115 private String remark;
102 116  
  117 + @AssertTrue(message = "检测数量必须等于合格品数量与不合格品数量之和")
  118 + @JsonIgnore
  119 + public boolean isQuantityValid() {
  120 + if (ObjUtil.hasNull(checkQuantity, qualifiedQuantity, unqualifiedQuantity)) {
  121 + return true; // @NotNull 会处理 null
  122 + }
  123 + return checkQuantity.compareTo(qualifiedQuantity.add(unqualifiedQuantity)) == 0;
  124 + }
  125 +
  126 + @AssertTrue(message = "工废、料废、其他废品数量之和不能超过不合格品数量")
  127 + @JsonIgnore
  128 + public boolean isScrapQuantityValid() {
  129 + if (ObjUtil.hasNull(unqualifiedQuantity, laborScrapQuantity, materialScrapQuantity, otherScrapQuantity)) {
  130 + return true; // @NotNull 会处理 null
  131 + }
  132 + BigDecimal scrapSum = laborScrapQuantity.add(materialScrapQuantity).add(otherScrapQuantity);
  133 + return scrapSum.compareTo(unqualifiedQuantity) <= 0;
  134 + }
  135 +
103 136 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/line/MesQcIpqcLineRespVO.java
... ... @@ -30,11 +30,8 @@ public class MesQcIpqcLineRespVO {
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/controller/admin/qc/iqc/MesQcIqcController.java
... ... @@ -63,14 +63,14 @@ public class MesQcIqcController {
63 63 @Operation(summary = "创建来料检验单")
64 64 @PreAuthorize("@ss.hasPermission('mes:qc-iqc:create')")
65 65 public CommonResult<Long> createIqc(@Valid @RequestBody MesQcIqcSaveReqVO createReqVO) {
66   - return success(iqcService.createIqc(createReqVO, getLoginUserId()));
  66 + return success(iqcService.createIqc(createReqVO));
67 67 }
68 68  
69 69 @PutMapping("/update")
70 70 @Operation(summary = "更新来料检验单")
71 71 @PreAuthorize("@ss.hasPermission('mes:qc-iqc:update')")
72 72 public CommonResult<Boolean> updateIqc(@Valid @RequestBody MesQcIqcSaveReqVO updateReqVO) {
73   - iqcService.updateIqc(updateReqVO, getLoginUserId());
  73 + iqcService.updateIqc(updateReqVO);
74 74 return success(true);
75 75 }
76 76  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/iqc/vo/MesQcIqcSaveReqVO.java
... ... @@ -71,6 +71,14 @@ public class MesQcIqcSaveReqVO {
71 71  
72 72 // ========== 检验 ==========
73 73  
  74 + @Schema(description = "检测人员用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  75 + @NotNull(message = "检测人员不能为空")
  76 + private Long inspectorUserId;
  77 +
  78 + @Schema(description = "检测日期", requiredMode = Schema.RequiredMode.REQUIRED)
  79 + @NotNull(message = "检测日期不能为空")
  80 + private LocalDateTime inspectDate;
  81 +
74 82 @Schema(description = "检测结果", example = "1")
75 83 private Integer checkResult;
76 84  
... ... @@ -78,10 +86,6 @@ public class MesQcIqcSaveReqVO {
78 86 @NotNull(message = "来料日期不能为空")
79 87 private LocalDateTime receiveDate;
80 88  
81   - @Schema(description = "检测日期", requiredMode = Schema.RequiredMode.REQUIRED)
82   - @NotNull(message = "检测日期不能为空")
83   - private LocalDateTime inspectDate;
84   -
85 89 @Schema(description = "备注", example = "备注")
86 90 private String remark;
87 91  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/ipqc/MesQcIpqcDO.java
... ... @@ -65,11 +65,6 @@ public class MesQcIpqcDO extends BaseDO {
65 65 * 来源单据类型
66 66 */
67 67 private String sourceDocType;
68   - // TODO @AI:sourceDocCode 是不是不用存储?(晚点在处理,对应模块还没搞完)
69   - /**
70   - * 来源单据编号
71   - */
72   - private String sourceDocCode;
73 68 /**
74 69 * 来源单据行 ID
75 70 */
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/ipqc/MesQcIpqcLineDO.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc;
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 MesQcIpqcLineDO 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/ipqc/MesQcIpqcMapper.java
... ... @@ -20,11 +20,9 @@ public interface MesQcIpqcMapper extends BaseMapperX&lt;MesQcIpqcDO&gt; {
20 20 .likeIfPresent(MesQcIpqcDO::getCode, reqVO.getCode())
21 21 .eqIfPresent(MesQcIpqcDO::getType, reqVO.getType())
22 22 .eqIfPresent(MesQcIpqcDO::getWorkOrderId, reqVO.getWorkOrderId())
23   - .eqIfPresent(MesQcIpqcDO::getWorkstationId, reqVO.getWorkstationId())
24 23 .eqIfPresent(MesQcIpqcDO::getItemId, reqVO.getItemId())
25 24 .eqIfPresent(MesQcIpqcDO::getCheckResult, reqVO.getCheckResult())
26 25 .eqIfPresent(MesQcIpqcDO::getStatus, reqVO.getStatus())
27   - .betweenIfPresent(MesQcIpqcDO::getInspectDate, reqVO.getInspectDate())
28 26 .eqIfPresent(MesQcIpqcDO::getInspectorUserId, reqVO.getInspectorUserId())
29 27 .orderByDesc(MesQcIpqcDO::getId));
30 28 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
... ... @@ -261,6 +261,7 @@ public interface ErrorCodeConstants {
261 261 ErrorCode QC_IPQC_NOT_PREPARE = new ErrorCode(1_040_604_002, "只有草稿状态的检验单才可操作");
262 262 ErrorCode QC_IPQC_QUANTITY_MISMATCH = new ErrorCode(1_040_604_004, "合格品与不合格品数量之和须等于检测数量");
263 263 ErrorCode QC_IPQC_NO_TEMPLATE = new ErrorCode(1_040_604_005, "当前产品未配置 IPQC 检测模板");
  264 + ErrorCode QC_IPQC_CHECK_RESULT_EMPTY = new ErrorCode(1_040_604_006, "完成检验单前,检测结果必须填写");
264 265 // ========== MES 质量管理-过程检验行(1-040-604-100) ==========
265 266 ErrorCode QC_IPQC_LINE_NOT_EXISTS = new ErrorCode(1_040_604_100, "过程检验行不存在");
266 267  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineServiceImpl.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.ipqc.vo.line.MesQcIpqcLinePageReqVO;
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.ipqc.MesQcIpqcLineDO;
9 10 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
10 11 import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcLineMapper;
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 MesQcIpqcLineServiceImpl implements MesQcIpqcLineService {
36 40 @Resource
37 41 private MesQcTemplateIndicatorMapper templateIndicatorMapper;
38 42  
  43 + @Resource
  44 + private MesQcIndicatorService indicatorService;
  45 +
39 46 @Override
40 47 public MesQcIpqcLineDO getIpqcLine(Long id) {
41 48 return ipqcLineMapper.selectById(id);
... ... @@ -53,14 +60,22 @@ public class MesQcIpqcLineServiceImpl implements MesQcIpqcLineService {
53 60  
54 61 @Override
55 62 public void createLinesFromTemplate(Long ipqcId, Long templateId) {
56   - List<MesQcTemplateIndicatorDO> indicators = templateIndicatorMapper.selectListByTemplateId(templateId);
57   - List<MesQcIpqcLineDO> lines = convertList(indicators, indicator -> new MesQcIpqcLineDO()
58   - .setIpqcId(ipqcId).setIndicatorId(indicator.getIndicatorId())
59   - .setToolId(null) // TODO @芋艿:模板指标暂无 toolId,后续可扩展
60   - .setCheckMethod(indicator.getCheckMethod())
61   - .setStandardValue(indicator.getStandardValue()).setUnitMeasureId(indicator.getUnitMeasureId())
62   - .setMaxThreshold(indicator.getThresholdMax()).setMinThreshold(indicator.getThresholdMin())
63   - .setCriticalQuantity(0).setMajorQuantity(0).setMinorQuantity(0));
  63 + List<MesQcTemplateIndicatorDO> templateIndicators = templateIndicatorMapper.selectListByTemplateId(templateId);
  64 + if (CollUtil.isEmpty(templateIndicators)) {
  65 + return;
  66 + }
  67 + Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap(
  68 + convertSet(templateIndicators, MesQcTemplateIndicatorDO::getIndicatorId));
  69 + List<MesQcIpqcLineDO> lines = convertList(templateIndicators, templateIndicator -> {
  70 + MesQcIndicatorDO indicator = indicatorMap.get(templateIndicator.getIndicatorId());
  71 + return new MesQcIpqcLineDO()
  72 + .setIpqcId(ipqcId).setIndicatorId(templateIndicator.getIndicatorId())
  73 + .setTool(indicator != null ? indicator.getTool() : null)
  74 + .setCheckMethod(templateIndicator.getCheckMethod())
  75 + .setStandardValue(templateIndicator.getStandardValue()).setUnitMeasureId(templateIndicator.getUnitMeasureId())
  76 + .setMaxThreshold(templateIndicator.getThresholdMax()).setMinThreshold(templateIndicator.getThresholdMin())
  77 + .setCriticalQuantity(0).setMajorQuantity(0).setMinorQuantity(0);
  78 + });
64 79 ipqcLineMapper.insertBatch(lines);
65 80 }
66 81  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java
... ... @@ -8,15 +8,17 @@ import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqV
8 8 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
9 9 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
10 10 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
11   -import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
  11 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
12 12 import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper;
13 13 import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
14 14 import cn.iocoder.yudao.module.mes.enums.MesOrderStatusEnum;
15 15 import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
  16 +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
16 17 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
17 18 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
18 19 import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
19 20 import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateDetailService;
  21 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
20 22 import jakarta.annotation.Resource;
21 23 import org.springframework.context.annotation.Lazy;
22 24 import org.springframework.stereotype.Service;
... ... @@ -55,8 +57,14 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
55 57 private MesMdWorkstationService workstationService;
56 58 @Resource
57 59 @Lazy
  60 + private MesMdItemService itemService;
  61 + @Resource
  62 + @Lazy
58 63 private MesQcDefectRecordService defectRecordService;
59 64  
  65 + @Resource
  66 + private AdminUserApi adminUserApi;
  67 +
60 68 @Override
61 69 @Transactional(rollbackFor = Exception.class)
62 70 public Long createIpqc(MesQcIpqcSaveReqVO createReqVO) {
... ... @@ -66,23 +74,24 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
66 74 MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId());
67 75 // 1.3 校验工位存在
68 76 workstationService.validateWorkstationExists(createReqVO.getWorkstationId());
69   - // 1.4 根据产品 + 检验类型自动匹配模板
70   - // TODO @AI:参考 iqc,应该通过 getRequiredTemplatesByItemIdAndType 方法;
71   - MesQcTemplateDO template = templateDetailService.getTemplateByItemIdAndType(
  77 + // 1.4 校验检测人员存在
  78 + adminUserApi.validateUser(createReqVO.getInspectorUserId());
  79 + // 1.5 校验物料存在
  80 + itemService.validateItemExists(createReqVO.getItemId());
  81 + // 1.6 根据产品 + 检验类型自动匹配模板
  82 + MesQcTemplateItemDO templateItem = templateDetailService.getRequiredTemplateByItemIdAndType(
72 83 workOrder.getProductId(), MesQcTypeEnum.IPQC.getType());
73   - if (template == null) {
74   - throw exception(QC_IPQC_NO_TEMPLATE);
75   - }
  84 + Long templateId = templateItem.getTemplateId();
76 85 // TODO @AI:processId 不应该是前端传递,而是通过 workstation + work order + route 表,联合查询出来的;
77 86  
78 87 // 2. 插入主表
79 88 MesQcIpqcDO ipqc = BeanUtils.toBean(createReqVO, MesQcIpqcDO.class);
80 89 ipqc.setItemId(workOrder.getProductId());
81   - ipqc.setTemplateId(template.getId()).setStatus(MesOrderStatusEnum.DRAFT.getType());
  90 + ipqc.setTemplateId(templateId).setStatus(MesOrderStatusEnum.DRAFT.getType());
82 91 ipqcMapper.insert(ipqc);
83 92  
84 93 // 3. 从模板指标自动生成检验行
85   - ipqcLineService.createLinesFromTemplate(ipqc.getId(), template.getId());
  94 + ipqcLineService.createLinesFromTemplate(ipqc.getId(), templateId);
86 95 return ipqc.getId();
87 96 }
88 97  
... ... @@ -92,6 +101,14 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
92 101 validateIpqcStatusPrepare(updateReqVO.getId());
93 102 // 1.2 校验编号唯一
94 103 validateIpqcCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
  104 + // 1.3 校验工单存在
  105 + workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId());
  106 + // 1.4 校验工位存在
  107 + workstationService.validateWorkstationExists(updateReqVO.getWorkstationId());
  108 + // 1.5 校验检测人员存在
  109 + adminUserApi.validateUser(updateReqVO.getInspectorUserId());
  110 + // 1.6 校验物料存在
  111 + itemService.validateItemExists(updateReqVO.getItemId());
95 112  
96 113 // 2. 更新
97 114 MesQcIpqcDO updateObj = BeanUtils.toBean(updateReqVO, MesQcIpqcDO.class);
... ... @@ -102,13 +119,9 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
102 119 public void completeIpqc(Long id) {
103 120 // 1.1 校验存在 + 草稿状态
104 121 MesQcIpqcDO ipqc = validateIpqcStatusPrepare(id);
105   - // 1.2 校验合格品 + 不合格品 = 检测数量
106   - if (ipqc.getCheckQuantity() != null && ipqc.getCheckQuantity().compareTo(BigDecimal.ZERO) > 0) {
107   - BigDecimal qualified = ipqc.getQualifiedQuantity() != null ? ipqc.getQualifiedQuantity() : BigDecimal.ZERO;
108   - BigDecimal unqualified = ipqc.getUnqualifiedQuantity() != null ? ipqc.getUnqualifiedQuantity() : BigDecimal.ZERO;
109   - if (qualified.add(unqualified).compareTo(ipqc.getCheckQuantity()) != 0) {
110   - throw exception(QC_IPQC_QUANTITY_MISMATCH);
111   - }
  122 + // 1.2 校验检测结论必填
  123 + if (ipqc.getCheckResult() == null) {
  124 + throw exception(QC_IPQC_CHECK_RESULT_EMPTY);
112 125 }
113 126  
114 127 // 2. 更新状态为已完成
... ... @@ -179,11 +192,14 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
179 192  
180 193 @Override
181 194 public void recalculateDefectStats(Long ipqcId, List<MesQcDefectRecordDO> records) {
  195 + MesQcIpqcDO ipqc = validateIpqcExists(ipqcId);
182 196 // 1. 行级缺陷统计
183 197 ipqcLineService.recalculateLineDefectStats(ipqcId, records);
184 198  
185 199 // 2.1 汇总主表的缺陷数量
186   - int totalCritical = 0, totalMajor = 0, totalMinor = 0;
  200 + int totalCritical = 0;
  201 + int totalMajor = 0;
  202 + int totalMinor = 0;
187 203 for (MesQcDefectRecordDO record : records) {
188 204 int quantity = ObjUtil.defaultIfNull(record.getQuantity(), 1);
189 205 if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.CRITICAL.getType())) {
... ... @@ -196,8 +212,7 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
196 212 throw exception(QC_DEFECT_RECORD_LEVEL_UNKNOWN);
197 213 }
198 214 }
199   - // 2.2 计算缺陷率(IPQC 的 checkQuantity 是 BigDecimal)
200   - MesQcIpqcDO ipqc = validateIpqcExists(ipqcId);
  215 + // 2.2 计算缺陷率
201 216 BigDecimal criticalRate = BigDecimal.ZERO;
202 217 BigDecimal majorRate = BigDecimal.ZERO;
203 218 BigDecimal minorRate = BigDecimal.ZERO;
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java
... ... @@ -22,18 +22,16 @@ public interface MesQcIqcService {
22 22 * 创建来料检验单
23 23 *
24 24 * @param createReqVO 创建信息
25   - * @param inspectorUserId 检测人员用户 ID
26 25 * @return 编号
27 26 */
28   - Long createIqc(@Valid MesQcIqcSaveReqVO createReqVO, Long inspectorUserId);
  27 + Long createIqc(@Valid MesQcIqcSaveReqVO createReqVO);
29 28  
30 29 /**
31 30 * 更新来料检验单
32 31 *
33 32 * @param updateReqVO 更新信息
34   - * @param inspectorUserId 检测人员用户 ID
35 33 */
36   - void updateIqc(@Valid MesQcIqcSaveReqVO updateReqVO, Long inspectorUserId);
  34 + void updateIqc(@Valid MesQcIqcSaveReqVO updateReqVO);
37 35  
38 36 /**
39 37 * 完成来料检验单
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
... ... @@ -20,6 +20,7 @@ import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
20 20 import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
21 21 import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateDetailService;
22 22 import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
  23 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
23 24 import jakarta.annotation.Resource;
24 25 import org.springframework.stereotype.Service;
25 26 import org.springframework.transaction.annotation.Transactional;
... ... @@ -45,9 +46,9 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
45 46  
46 47 @Resource
47 48 private MesQcIqcMapper iqcMapper;
  49 +
48 50 @Resource
49 51 private MesQcTemplateDetailService templateDetailService;
50   -
51 52 @Resource
52 53 private MesQcIqcLineService iqcLineService;
53 54 @Resource
... ... @@ -59,17 +60,22 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
59 60 @Resource
60 61 private MesMdItemService itemService;
61 62  
  63 + @Resource
  64 + private AdminUserApi adminUserApi;
  65 +
62 66 @Override
63 67 @Transactional(rollbackFor = Exception.class)
64   - public Long createIqc(MesQcIqcSaveReqVO createReqVO, Long inspectorUserId) {
  68 + public Long createIqc(MesQcIqcSaveReqVO createReqVO) {
65 69 // 1.1 校验编号唯一
66 70 validateIqcCodeUnique(null, createReqVO.getCode());
67 71 // 1.2 校验供应商、物料存在
68 72 vendorService.validateVendorExists(createReqVO.getVendorId());
69 73 itemService.validateItemExists(createReqVO.getItemId());
70   - // 1.3 校验来源单据参数,并验证数据存在
  74 + // 1.3 校验检测人员存在
  75 + adminUserApi.validateUser(createReqVO.getInspectorUserId());
  76 + // 1.4 校验来源单据参数,并验证数据存在
71 77 validateSourceDoc(createReqVO.getSourceDocType(), createReqVO.getSourceDocId(), createReqVO.getSourceLineId());
72   - // 1.4 通过 itemId + IQC 类型自动查找模板
  78 + // 1.5 通过 itemId + IQC 类型自动查找模板
73 79 MesQcTemplateItemDO templateItem = templateDetailService.getRequiredTemplateByItemIdAndType(
74 80 createReqVO.getItemId(), MesQcTypeEnum.IQC.getType());
75 81 Long templateId = templateItem.getTemplateId();
... ... @@ -78,7 +84,6 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
78 84 MesQcIqcDO iqc = BeanUtils.toBean(createReqVO, MesQcIqcDO.class);
79 85 iqc.setTemplateId(templateId);
80 86 iqc.setCheckQuantity(createReqVO.getQualifiedQuantity().add(createReqVO.getUnqualifiedQuantity()));
81   - iqc.setInspectorUserId(inspectorUserId);
82 87 iqc.setStatus(MesOrderStatusEnum.DRAFT.getType());
83 88 iqcMapper.insert(iqc);
84 89  
... ... @@ -88,7 +93,7 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
88 93 }
89 94  
90 95 @Override
91   - public void updateIqc(MesQcIqcSaveReqVO updateReqVO, Long inspectorUserId) {
  96 + public void updateIqc(MesQcIqcSaveReqVO updateReqVO) {
92 97 // 1.1 校验存在 + 草稿状态
93 98 validateIqcStatusPrepare(updateReqVO.getId());
94 99 // 1.2 校验编号唯一
... ... @@ -96,13 +101,14 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
96 101 // 1.3 校验供应商、物料存在
97 102 vendorService.validateVendorExists(updateReqVO.getVendorId());
98 103 itemService.validateItemExists(updateReqVO.getItemId());
  104 + // 1.4 校验检测人员存在
  105 + adminUserApi.validateUser(updateReqVO.getInspectorUserId());
99 106  
100 107 // 2. 更新主表
101 108 MesQcIqcDO updateObj = BeanUtils.toBean(updateReqVO, MesQcIqcDO.class);
102 109 updateObj.setSourceDocType(null).setSourceDocId(null).setSourceLineId(null); // 不允许修改来源单据
103 110 updateObj.setTemplateId(null); // 不允许修改模板
104 111 updateObj.setCheckQuantity(updateReqVO.getQualifiedQuantity().add(updateReqVO.getUnqualifiedQuantity()));
105   - updateObj.setInspectorUserId(inspectorUserId);
106 112 iqcMapper.updateById(updateObj);
107 113 }
108 114  
... ... @@ -110,11 +116,9 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
110 116 public void completeIqc(Long id) {
111 117 // 1.1 校验存在 + 草稿状态
112 118 MesQcIqcDO iqc = validateIqcStatusPrepare(id);
113   - // DONE @AI:已修复,增加 checkResult 必填校验
114 119 if (iqc.getCheckResult() == null) {
115 120 throw exception(QC_IQC_CHECK_RESULT_EMPTY);
116 121 }
117   - // DONE @AI:已修复,数量校验已在 MesQcIqcSaveReqVO 的 @AssertTrue 中保证,此处移除
118 122  
119 123 // 2. 更新状态为已完成
120 124 MesQcIqcDO updateObj = new MesQcIqcDO()
... ... @@ -135,7 +139,6 @@ public class MesQcIqcServiceImpl implements MesQcIqcService {
135 139 if (iqc.getSourceDocType() == null) {
136 140 return;
137 141 }
138   - // DONE @AI:已修复,使用 ObjectUtil.hasNull() 简化判断
139 142 if (ObjectUtil.hasNull(iqc.getSourceLineId(), iqc.getSourceDocId())) {
140 143 throw new IllegalArgumentException(
141 144 "IQC 单[" + iqc.getId() + "] sourceDocType 非空但 sourceLineId 或 sourceDocId 为空");
... ...