Commit dd34cfdce4c6af1c977971261104b6a86b5a2348

Authored by YunaiV
1 parent 3ed86517

feat(mes): 新增 IPQC 过程检验模块,对接缺陷记录与检验结果

- 新增 IPQC 过程检验单全套后端:DO、Mapper、VO、Service、Controller
- MesQcIpqcService 新增 validateIpqcExists、recalculateDefectStats 方法
- MesQcIpqcLineService 新增 validateIpqcLineExists、recalculateLineDefectStats 方法
- MesQcDefectRecordServiceImpl 扩展 IPQC 分支(校验 + 缺陷统计重算)
- MesQcIndicatorResultServiceImpl 扩展 IPQC 分支(获取 itemId)
- MesQcTemplateService 新增 getTemplateByItemIdAndType 方法,支持按产品+类型自动匹配模板
- IPQC 删除时级联清理缺陷记录
- 新增错误码 1_040_604_xxx 系列
Showing 19 changed files with 1648 additions and 3 deletions
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/MesQcIpqcController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  10 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO;
  11 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcRespVO;
  12 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO;
  13 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
  14 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
  15 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
  16 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
  17 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
  18 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
  19 +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
  20 +import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
  21 +import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
  22 +import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
  23 +import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
  24 +import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcService;
  25 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  26 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  27 +import io.swagger.v3.oas.annotations.Operation;
  28 +import io.swagger.v3.oas.annotations.Parameter;
  29 +import io.swagger.v3.oas.annotations.tags.Tag;
  30 +import jakarta.annotation.Resource;
  31 +import jakarta.servlet.http.HttpServletResponse;
  32 +import jakarta.validation.Valid;
  33 +import org.springframework.security.access.prepost.PreAuthorize;
  34 +import org.springframework.validation.annotation.Validated;
  35 +import org.springframework.web.bind.annotation.*;
  36 +
  37 +import java.io.IOException;
  38 +import java.util.ArrayList;
  39 +import java.util.Collections;
  40 +import java.util.List;
  41 +import java.util.Map;
  42 +
  43 +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
  44 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  45 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  46 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
  47 +
  48 +@Tag(name = "管理后台 - MES 过程检验单(IPQC)")
  49 +@RestController
  50 +@RequestMapping("/mes/qc/ipqc")
  51 +@Validated
  52 +public class MesQcIpqcController {
  53 +
  54 + @Resource
  55 + private MesQcIpqcService ipqcService;
  56 + @Resource
  57 + private MesProWorkOrderService workOrderService;
  58 + @Resource
  59 + private MesMdWorkstationService workstationService;
  60 + @Resource
  61 + private MesProProcessService processService;
  62 + @Resource
  63 + private MesMdItemService itemService;
  64 + @Resource
  65 + private MesMdUnitMeasureService unitMeasureService;
  66 +
  67 + @Resource
  68 + private AdminUserApi adminUserApi;
  69 +
  70 + @PostMapping("/create")
  71 + @Operation(summary = "创建过程检验单")
  72 + @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:create')")
  73 + public CommonResult<Long> createIpqc(@Valid @RequestBody MesQcIpqcSaveReqVO createReqVO) {
  74 + return success(ipqcService.createIpqc(createReqVO));
  75 + }
  76 +
  77 + @PutMapping("/update")
  78 + @Operation(summary = "更新过程检验单")
  79 + @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:update')")
  80 + public CommonResult<Boolean> updateIpqc(@Valid @RequestBody MesQcIpqcSaveReqVO updateReqVO) {
  81 + ipqcService.updateIpqc(updateReqVO);
  82 + return success(true);
  83 + }
  84 +
  85 + @PutMapping("/complete")
  86 + @Operation(summary = "完成过程检验单")
  87 + @Parameter(name = "id", description = "编号", required = true)
  88 + @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:update')")
  89 + public CommonResult<Boolean> completeIpqc(@RequestParam("id") Long id) {
  90 + ipqcService.completeIpqc(id);
  91 + return success(true);
  92 + }
  93 +
  94 + @DeleteMapping("/delete")
  95 + @Operation(summary = "删除过程检验单")
  96 + @Parameter(name = "id", description = "编号", required = true)
  97 + @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:delete')")
  98 + public CommonResult<Boolean> deleteIpqc(@RequestParam("id") Long id) {
  99 + ipqcService.deleteIpqc(id);
  100 + return success(true);
  101 + }
  102 +
  103 + @GetMapping("/get")
  104 + @Operation(summary = "获得过程检验单")
  105 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  106 + @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:query')")
  107 + public CommonResult<MesQcIpqcRespVO> getIpqc(@RequestParam("id") Long id) {
  108 + MesQcIpqcDO ipqc = ipqcService.getIpqc(id);
  109 + return success(buildIpqcRespVOList(Collections.singletonList(ipqc)).get(0));
  110 + }
  111 +
  112 + @GetMapping("/page")
  113 + @Operation(summary = "获得过程检验单分页")
  114 + @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:query')")
  115 + public CommonResult<PageResult<MesQcIpqcRespVO>> getIpqcPage(@Valid MesQcIpqcPageReqVO pageReqVO) {
  116 + PageResult<MesQcIpqcDO> pageResult = ipqcService.getIpqcPage(pageReqVO);
  117 + return success(new PageResult<>(buildIpqcRespVOList(pageResult.getList()), pageResult.getTotal()));
  118 + }
  119 +
  120 + @GetMapping("/export-excel")
  121 + @Operation(summary = "导出过程检验单 Excel")
  122 + @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:export')")
  123 + @ApiAccessLog(operateType = EXPORT)
  124 + public void exportIpqcExcel(@Valid MesQcIpqcPageReqVO pageReqVO,
  125 + HttpServletResponse response) throws IOException {
  126 + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
  127 + List<MesQcIpqcDO> list = ipqcService.getIpqcPage(pageReqVO).getList();
  128 + List<MesQcIpqcRespVO> voList = buildIpqcRespVOList(list);
  129 + ExcelUtils.write(response, "过程检验单.xls", "数据", MesQcIpqcRespVO.class, voList);
  130 + }
  131 +
  132 + // ==================== 拼接 VO ====================
  133 +
  134 + private List<MesQcIpqcRespVO> buildIpqcRespVOList(List<MesQcIpqcDO> list) {
  135 + if (CollUtil.isEmpty(list)) {
  136 + return Collections.emptyList();
  137 + }
  138 + // 批量查询工单
  139 + Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap(
  140 + convertSet(list, MesQcIpqcDO::getWorkOrderId));
  141 + // 批量查询工位
  142 + Map<Long, MesMdWorkstationDO> workstationMap = workstationService.getWorkstationMap(
  143 + convertSet(list, MesQcIpqcDO::getWorkstationId));
  144 + // 批量查询工序
  145 + Map<Long, MesProProcessDO> processMap = processService.getProcessMap(
  146 + convertSet(list, MesQcIpqcDO::getProcessId));
  147 + // 批量查询物料
  148 + Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
  149 + convertSet(list, MesQcIpqcDO::getItemId));
  150 + // 批量查询计量单位(
  151 + Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
  152 + convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
  153 + // 批量查询检测人员
  154 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  155 + convertSet(list, MesQcIpqcDO::getInspectorUserId));
  156 + // 拼装 VO
  157 + return BeanUtils.toBean(list, MesQcIpqcRespVO.class, vo -> {
  158 + findAndThen(workOrderMap, vo.getWorkOrderId(), workOrder -> vo
  159 + .setWorkOrderCode(workOrder.getCode()).setWorkOrderName(workOrder.getName()));
  160 + findAndThen(workstationMap, vo.getWorkstationId(),
  161 + ws -> vo.setWorkstationName(ws.getName()));
  162 + findAndThen(processMap, vo.getProcessId(),
  163 + process -> vo.setProcessName(process.getName()));
  164 + findAndThen(itemMap, vo.getItemId(), item -> {
  165 + vo.setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification());
  166 + findAndThen(unitMeasureMap, item.getUnitMeasureId(),
  167 + unit -> vo.setUnitName(unit.getName()));
  168 + });
  169 + findAndThen(userMap, vo.getInspectorUserId(),
  170 + user -> vo.setInspectorNickname(user.getNickname()));
  171 + });
  172 + }
  173 +
  174 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/MesQcIpqcLineController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.line.MesQcIpqcLinePageReqVO;
  8 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.line.MesQcIpqcLineRespVO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
  10 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO;
  11 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcLineDO;
  12 +import cn.iocoder.yudao.module.mes.dal.dataobject.tm.tool.MesTmToolDO;
  13 +import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
  14 +import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService;
  15 +import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcLineService;
  16 +import cn.iocoder.yudao.module.mes.service.tm.tool.MesTmToolService;
  17 +import io.swagger.v3.oas.annotations.Operation;
  18 +import io.swagger.v3.oas.annotations.Parameter;
  19 +import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import jakarta.annotation.Resource;
  21 +import jakarta.validation.Valid;
  22 +import org.springframework.security.access.prepost.PreAuthorize;
  23 +import org.springframework.validation.annotation.Validated;
  24 +import org.springframework.web.bind.annotation.GetMapping;
  25 +import org.springframework.web.bind.annotation.RequestMapping;
  26 +import org.springframework.web.bind.annotation.RequestParam;
  27 +import org.springframework.web.bind.annotation.RestController;
  28 +
  29 +import java.util.Collections;
  30 +import java.util.List;
  31 +import java.util.Map;
  32 +
  33 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
  36 +
  37 +@Tag(name = "管理后台 - MES 过程检验单行")
  38 +@RestController
  39 +@RequestMapping("/mes/qc/ipqc/line")
  40 +@Validated
  41 +public class MesQcIpqcLineController {
  42 +
  43 + @Resource
  44 + private MesQcIpqcLineService ipqcLineService;
  45 + @Resource
  46 + private MesQcIndicatorService indicatorService;
  47 + @Resource
  48 + private MesMdUnitMeasureService unitMeasureService;
  49 + @Resource
  50 + private MesTmToolService toolService;
  51 +
  52 + @GetMapping("/get")
  53 + @Operation(summary = "获得过程检验单行")
  54 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  55 + @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:query')")
  56 + public CommonResult<MesQcIpqcLineRespVO> getIpqcLine(@RequestParam("id") Long id) {
  57 + MesQcIpqcLineDO line = ipqcLineService.getIpqcLine(id);
  58 + return success(buildLineRespVOList(Collections.singletonList(line)).get(0));
  59 + }
  60 +
  61 + @GetMapping("/page")
  62 + @Operation(summary = "获得过程检验单行分页")
  63 + @PreAuthorize("@ss.hasPermission('mes:qc-ipqc:query')")
  64 + public CommonResult<PageResult<MesQcIpqcLineRespVO>> getIpqcLinePage(@Valid MesQcIpqcLinePageReqVO pageReqVO) {
  65 + PageResult<MesQcIpqcLineDO> pageResult = ipqcLineService.getIpqcLinePage(pageReqVO);
  66 + return success(new PageResult<>(buildLineRespVOList(pageResult.getList()), pageResult.getTotal()));
  67 + }
  68 +
  69 + // ==================== 拼接 VO ====================
  70 +
  71 + private List<MesQcIpqcLineRespVO> buildLineRespVOList(List<MesQcIpqcLineDO> list) {
  72 + if (CollUtil.isEmpty(list)) {
  73 + return Collections.emptyList();
  74 + }
  75 + // 批量查询检测指标
  76 + Map<Long, MesQcIndicatorDO> indicatorMap = indicatorService.getIndicatorMap(
  77 + convertSet(list, MesQcIpqcLineDO::getIndicatorId));
  78 + // 批量查询计量单位
  79 + Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
  80 + convertSet(list, MesQcIpqcLineDO::getUnitMeasureId));
  81 + // 拼装 VO
  82 + return BeanUtils.toBean(list, MesQcIpqcLineRespVO.class, vo -> {
  83 + findAndThen(indicatorMap, vo.getIndicatorId(), indicator ->
  84 + vo.setIndicatorCode(indicator.getCode()).setIndicatorName(indicator.getName())
  85 + .setIndicatorType(indicator.getType()));
  86 + findAndThen(unitMeasureMap, vo.getUnitMeasureId(),
  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 + });
  96 + }
  97 +
  98 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +@Schema(description = "管理后台 - MES 过程检验单分页 Request VO")
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@ToString(callSuper = true)
  18 +public class MesQcIpqcPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "检验单编号", example = "IPQC2025")
  21 + private String code;
  22 +
  23 + @Schema(description = "IPQC 检验类型", example = "IPQC")
  24 + private String type;
  25 +
  26 + @Schema(description = "生产工单 ID", example = "10")
  27 + private Long workOrderId;
  28 +
  29 + @Schema(description = "工位 ID", example = "30")
  30 + private Long workstationId;
  31 +
  32 + @Schema(description = "产品物料 ID", example = "50")
  33 + private Long itemId;
  34 +
  35 + @Schema(description = "检测结果", example = "1")
  36 + private Integer checkResult;
  37 +
  38 + @Schema(description = "状态", example = "0")
  39 + private Integer status;
  40 +
  41 + @Schema(description = "检测人员用户 ID", example = "1")
  42 + private Long inspectorUserId;
  43 +
  44 + @Schema(description = "检测日期")
  45 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  46 + private LocalDateTime[] inspectDate;
  47 +
  48 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo;
  2 +
  3 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  4 +import cn.idev.excel.annotation.ExcelProperty;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.Data;
  7 +
  8 +import java.math.BigDecimal;
  9 +import java.time.LocalDateTime;
  10 +
  11 +@Schema(description = "管理后台 - MES 过程检验单 Response VO")
  12 +@Data
  13 +@ExcelIgnoreUnannotated
  14 +public class MesQcIpqcRespVO {
  15 +
  16 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + @ExcelProperty("编号")
  18 + private Long id;
  19 +
  20 + @Schema(description = "检验单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "IPQC20250101001")
  21 + @ExcelProperty("检验单编号")
  22 + private String code;
  23 +
  24 + @Schema(description = "检验单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "XX工单过程检验")
  25 + @ExcelProperty("检验单名称")
  26 + private String name;
  27 +
  28 + @Schema(description = "IPQC 检验类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "IPQC")
  29 + @ExcelProperty("检验类型")
  30 + private String type;
  31 +
  32 + @Schema(description = "检验模板 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  33 + private Long templateId;
  34 +
  35 + // ========== 来源单据 ==========
  36 +
  37 + @Schema(description = "来源单据 ID", example = "200")
  38 + private Long sourceDocId;
  39 +
  40 + @Schema(description = "来源单据类型", example = "FEEDBACK")
  41 + private String sourceDocType;
  42 +
  43 + @Schema(description = "来源单据编号", example = "FB20250101001")
  44 + private String sourceDocCode;
  45 +
  46 + @Schema(description = "来源单据行 ID", example = "300")
  47 + private Long sourceLineId;
  48 +
  49 + // ========== 生产关联 ==========
  50 +
  51 + @Schema(description = "生产工单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  52 + private Long workOrderId;
  53 +
  54 + @Schema(description = "工单编号", example = "WO20250101001")
  55 + @ExcelProperty("工单编号")
  56 + private String workOrderCode;
  57 +
  58 + @Schema(description = "工单名称", example = "生产工单A")
  59 + private String workOrderName;
  60 +
  61 + @Schema(description = "生产任务 ID", example = "20")
  62 + private Long taskId;
  63 +
  64 + @Schema(description = "工位 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
  65 + private Long workstationId;
  66 +
  67 + @Schema(description = "工位名称", example = "工位A")
  68 + @ExcelProperty("工位名称")
  69 + private String workstationName;
  70 +
  71 + @Schema(description = "工序 ID", example = "40")
  72 + private Long processId;
  73 +
  74 + @Schema(description = "工序名称", example = "组装")
  75 + @ExcelProperty("工序名称")
  76 + private String processName;
  77 +
  78 + // ========== 物料 ==========
  79 +
  80 + @Schema(description = "产品物料 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
  81 + private Long itemId;
  82 +
  83 + @Schema(description = "产品物料编码", example = "ITEM001")
  84 + @ExcelProperty("产品物料编码")
  85 + private String itemCode;
  86 +
  87 + @Schema(description = "产品物料名称", example = "物料A")
  88 + @ExcelProperty("产品物料名称")
  89 + private String itemName;
  90 +
  91 + @Schema(description = "规格型号", example = "100mm*50mm")
  92 + private String itemSpecification;
  93 +
  94 + @Schema(description = "单位名称", example = "个")
  95 + private String unitName;
  96 +
  97 + // ========== 数量 ==========
  98 +
  99 + @Schema(description = "检测数量", example = "100")
  100 + @ExcelProperty("检测数量")
  101 + private BigDecimal checkQuantity;
  102 +
  103 + @Schema(description = "合格品数量", example = "95")
  104 + @ExcelProperty("合格品数量")
  105 + private BigDecimal qualifiedQuantity;
  106 +
  107 + @Schema(description = "不合格品数量", example = "5")
  108 + @ExcelProperty("不合格品数量")
  109 + private BigDecimal unqualifiedQuantity;
  110 +
  111 + @Schema(description = "工废数量", example = "2")
  112 + @ExcelProperty("工废数量")
  113 + private BigDecimal laborScrapQuantity;
  114 +
  115 + @Schema(description = "料废数量", example = "2")
  116 + @ExcelProperty("料废数量")
  117 + private BigDecimal materialScrapQuantity;
  118 +
  119 + @Schema(description = "其他废品数量", example = "1")
  120 + @ExcelProperty("其他废品数量")
  121 + private BigDecimal otherScrapQuantity;
  122 +
  123 + // ========== 缺陷统计 ==========
  124 +
  125 + @Schema(description = "致命缺陷率(%)", example = "0.00")
  126 + private BigDecimal criticalRate;
  127 +
  128 + @Schema(description = "严重缺陷率(%)", example = "0.00")
  129 + private BigDecimal majorRate;
  130 +
  131 + @Schema(description = "轻微缺陷率(%)", example = "10.00")
  132 + private BigDecimal minorRate;
  133 +
  134 + @Schema(description = "致命缺陷数量", example = "0")
  135 + private Integer criticalQuantity;
  136 +
  137 + @Schema(description = "严重缺陷数量", example = "0")
  138 + private Integer majorQuantity;
  139 +
  140 + @Schema(description = "轻微缺陷数量", example = "1")
  141 + private Integer minorQuantity;
  142 +
  143 + // ========== 检验 ==========
  144 +
  145 + @Schema(description = "检测结果", example = "1")
  146 + @ExcelProperty("检测结果")
  147 + private Integer checkResult;
  148 +
  149 + @Schema(description = "检测日期")
  150 + @ExcelProperty("检测日期")
  151 + private LocalDateTime inspectDate;
  152 +
  153 + @Schema(description = "检测人员用户 ID", example = "1")
  154 + private Long inspectorUserId;
  155 +
  156 + @Schema(description = "检测人员昵称", example = "张三")
  157 + @ExcelProperty("检测人员")
  158 + private String inspectorNickname;
  159 +
  160 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  161 + @ExcelProperty("状态")
  162 + private Integer status;
  163 +
  164 + @Schema(description = "备注", example = "备注")
  165 + @ExcelProperty("备注")
  166 + private String remark;
  167 +
  168 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  169 + @ExcelProperty("创建时间")
  170 + private LocalDateTime createTime;
  171 +
  172 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/MesQcIpqcSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.math.BigDecimal;
  9 +import java.time.LocalDateTime;
  10 +
  11 +@Schema(description = "管理后台 - MES 过程检验单新增/修改 Request VO")
  12 +@Data
  13 +public class MesQcIpqcSaveReqVO {
  14 +
  15 + @Schema(description = "编号", example = "1024")
  16 + private Long id;
  17 +
  18 + @Schema(description = "检验单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "IPQC20250101001")
  19 + @NotEmpty(message = "检验单编号不能为空")
  20 + private String code;
  21 +
  22 + @Schema(description = "检验单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "XX工单过程检验")
  23 + @NotEmpty(message = "检验单名称不能为空")
  24 + private String name;
  25 +
  26 + @Schema(description = "IPQC 检验类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "IPQC")
  27 + @NotEmpty(message = "检验类型不能为空")
  28 + private String type;
  29 +
  30 + @Schema(description = "检验模板 ID", example = "100")
  31 + private Long templateId;
  32 +
  33 + // ========== 来源单据 ==========
  34 +
  35 + @Schema(description = "来源单据 ID", example = "200")
  36 + private Long sourceDocId;
  37 +
  38 + @Schema(description = "来源单据类型", example = "FEEDBACK")
  39 + private String sourceDocType;
  40 +
  41 + @Schema(description = "来源单据编号", example = "FB20250101001")
  42 + private String sourceDocCode;
  43 +
  44 + @Schema(description = "来源单据行 ID", example = "300")
  45 + private Long sourceLineId;
  46 +
  47 + // ========== 生产关联 ==========
  48 +
  49 + @Schema(description = "生产工单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  50 + @NotNull(message = "生产工单不能为空")
  51 + private Long workOrderId;
  52 +
  53 + @Schema(description = "生产任务 ID", example = "20")
  54 + private Long taskId;
  55 +
  56 + @Schema(description = "工位 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
  57 + @NotNull(message = "工位不能为空")
  58 + private Long workstationId;
  59 +
  60 + @Schema(description = "工序 ID", example = "40")
  61 + private Long processId;
  62 +
  63 + // ========== 物料 ==========
  64 +
  65 + @Schema(description = "产品物料 ID", example = "50")
  66 + private Long itemId;
  67 +
  68 + // ========== 数量 ==========
  69 +
  70 + @Schema(description = "检测数量", example = "100")
  71 + private BigDecimal checkQuantity;
  72 +
  73 + @Schema(description = "合格品数量", example = "95")
  74 + private BigDecimal qualifiedQuantity;
  75 +
  76 + @Schema(description = "不合格品数量", example = "5")
  77 + private BigDecimal unqualifiedQuantity;
  78 +
  79 + @Schema(description = "工废数量", example = "2")
  80 + private BigDecimal laborScrapQuantity;
  81 +
  82 + @Schema(description = "料废数量", example = "2")
  83 + private BigDecimal materialScrapQuantity;
  84 +
  85 + @Schema(description = "其他废品数量", example = "1")
  86 + private BigDecimal otherScrapQuantity;
  87 +
  88 + // ========== 检验 ==========
  89 +
  90 + @Schema(description = "检测结果", example = "1")
  91 + private Integer checkResult;
  92 +
  93 + @Schema(description = "检测日期")
  94 + private LocalDateTime inspectDate;
  95 +
  96 + @Schema(description = "检测人员用户 ID", example = "1")
  97 + private Long inspectorUserId;
  98 +
  99 + @Schema(description = "备注", example = "备注")
  100 + private String remark;
  101 +
  102 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/line/MesQcIpqcLinePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.line;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +import lombok.ToString;
  9 +
  10 +@Schema(description = "管理后台 - MES 过程检验单行分页 Request VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class MesQcIpqcLinePageReqVO extends PageParam {
  15 +
  16 + @Schema(description = "过程检验单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  17 + @NotNull(message = "过程检验单 ID 不能为空")
  18 + private Long ipqcId;
  19 +
  20 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/ipqc/vo/line/MesQcIpqcLineRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.line;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.experimental.Accessors;
  6 +
  7 +import java.math.BigDecimal;
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - MES 过程检验单行 Response VO")
  11 +@Data
  12 +@Accessors(chain = true)
  13 +public class MesQcIpqcLineRespVO {
  14 +
  15 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long id;
  17 +
  18 + @Schema(description = "过程检验单 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  19 + private Long ipqcId;
  20 +
  21 + @Schema(description = "检测指标 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  22 + private Long indicatorId;
  23 +
  24 + @Schema(description = "检测指标编码", example = "IND001")
  25 + private String indicatorCode;
  26 +
  27 + @Schema(description = "检测指标名称", example = "外观")
  28 + private String indicatorName;
  29 +
  30 + @Schema(description = "检测指标类型", example = "QUANTITATIVE")
  31 + private String indicatorType;
  32 +
  33 + @Schema(description = "检测工具 ID", example = "5")
  34 + private Long toolId;
  35 +
  36 + @Schema(description = "检测工具名称", example = "卡尺")
  37 + private String toolName;
  38 +
  39 + @Schema(description = "检测方法", example = "目测")
  40 + private String checkMethod;
  41 +
  42 + @Schema(description = "标准值", example = "10.5000")
  43 + private BigDecimal standardValue;
  44 +
  45 + @Schema(description = "计量单位 ID", example = "3")
  46 + private Long unitMeasureId;
  47 +
  48 + @Schema(description = "计量单位名称", example = "毫米")
  49 + private String unitMeasureName;
  50 +
  51 + @Schema(description = "误差上限", example = "11.0000")
  52 + private BigDecimal maxThreshold;
  53 +
  54 + @Schema(description = "误差下限", example = "10.0000")
  55 + private BigDecimal minThreshold;
  56 +
  57 + @Schema(description = "致命缺陷数量", example = "0")
  58 + private Integer criticalQuantity;
  59 +
  60 + @Schema(description = "严重缺陷数量", example = "0")
  61 + private Integer majorQuantity;
  62 +
  63 + @Schema(description = "轻微缺陷数量", example = "1")
  64 + private Integer minorQuantity;
  65 +
  66 + @Schema(description = "备注", example = "备注")
  67 + private String remark;
  68 +
  69 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  70 + private LocalDateTime createTime;
  71 +
  72 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/ipqc/MesQcIpqcDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
  5 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
  6 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
  8 +import com.baomidou.mybatisplus.annotation.KeySequence;
  9 +import com.baomidou.mybatisplus.annotation.TableId;
  10 +import com.baomidou.mybatisplus.annotation.TableName;
  11 +import lombok.*;
  12 +
  13 +import java.math.BigDecimal;
  14 +import java.time.LocalDateTime;
  15 +
  16 +/**
  17 + * MES 过程检验单(IPQC, In-Process Quality Control) DO
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +@TableName("mes_qc_ipqc")
  22 +@KeySequence("mes_qc_ipqc_seq")
  23 +@Data
  24 +@EqualsAndHashCode(callSuper = true)
  25 +@ToString(callSuper = true)
  26 +@Builder
  27 +@NoArgsConstructor
  28 +@AllArgsConstructor
  29 +public class MesQcIpqcDO extends BaseDO {
  30 +
  31 + /**
  32 + * 编号
  33 + */
  34 + @TableId
  35 + private Long id;
  36 + /**
  37 + * 检验单编号
  38 + */
  39 + private String code;
  40 + /**
  41 + * 检验单名称
  42 + */
  43 + private String name;
  44 + // TODO @AI:搞成 int 类型;
  45 + /**
  46 + * IPQC 检验类型
  47 + *
  48 + * 字典 {@link cn.iocoder.yudao.module.mes.enums.DictTypeConstants#MES_IPQC_TYPE}
  49 + */
  50 + private String type;
  51 + /**
  52 + * 检验模板 ID
  53 + *
  54 + * 关联 {@link MesQcTemplateDO#getId()}
  55 + */
  56 + private Long templateId;
  57 +
  58 + // ========== 来源单据(TODO @芋艿:PRO/WM 模块联动后接入) ==========
  59 +
  60 + /**
  61 + * 来源单据 ID
  62 + */
  63 + private Long sourceDocId;
  64 + /**
  65 + * 来源单据类型
  66 + */
  67 + private String sourceDocType;
  68 + /**
  69 + * 来源单据编号
  70 + */
  71 + private String sourceDocCode;
  72 + /**
  73 + * 来源单据行 ID
  74 + */
  75 + private Long sourceLineId;
  76 +
  77 + // ========== 生产关联 ==========
  78 +
  79 + /**
  80 + * 生产工单 ID
  81 + *
  82 + * 关联 {@link MesProWorkOrderDO#getId()}
  83 + */
  84 + private Long workOrderId;
  85 + /**
  86 + * 生产任务 ID
  87 + *
  88 + * 关联 {@link cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO#getId()}
  89 + */
  90 + private Long taskId;
  91 + /**
  92 + * 工位 ID
  93 + *
  94 + * 关联 {@link MesMdWorkstationDO#getId()}
  95 + */
  96 + private Long workstationId;
  97 + /**
  98 + * 工序 ID
  99 + *
  100 + * 关联 {@link cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO#getId()}
  101 + */
  102 + private Long processId;
  103 +
  104 + // ========== 物料 ==========
  105 +
  106 + /**
  107 + * 产品物料 ID
  108 + *
  109 + * 关联 {@link MesMdItemDO#getId()}
  110 + */
  111 + private Long itemId;
  112 +
  113 + // ========== 数量 ==========
  114 +
  115 + /**
  116 + * 检测数量
  117 + */
  118 + private BigDecimal checkQuantity;
  119 + /**
  120 + * 合格品数量
  121 + */
  122 + private BigDecimal qualifiedQuantity;
  123 + /**
  124 + * 不合格品数量
  125 + */
  126 + private BigDecimal unqualifiedQuantity;
  127 + /**
  128 + * 工废数量
  129 + */
  130 + private BigDecimal laborScrapQuantity;
  131 + /**
  132 + * 料废数量
  133 + */
  134 + private BigDecimal materialScrapQuantity;
  135 + /**
  136 + * 其他废品数量
  137 + */
  138 + private BigDecimal otherScrapQuantity;
  139 +
  140 + // ========== 缺陷统计 ==========
  141 +
  142 + /**
  143 + * 致命缺陷率(%)
  144 + */
  145 + private BigDecimal criticalRate;
  146 + /**
  147 + * 严重缺陷率(%)
  148 + */
  149 + private BigDecimal majorRate;
  150 + /**
  151 + * 轻微缺陷率(%)
  152 + */
  153 + private BigDecimal minorRate;
  154 + /**
  155 + * 致命缺陷数量
  156 + */
  157 + private Integer criticalQuantity;
  158 + /**
  159 + * 严重缺陷数量
  160 + */
  161 + private Integer majorQuantity;
  162 + /**
  163 + * 轻微缺陷数量
  164 + */
  165 + private Integer minorQuantity;
  166 +
  167 + // ========== 检验 ==========
  168 +
  169 + /**
  170 + * 检测结果
  171 + *
  172 + * 枚举 {@link cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum}
  173 + */
  174 + private Integer checkResult;
  175 + /**
  176 + * 检测日期
  177 + */
  178 + private LocalDateTime inspectDate;
  179 + /**
  180 + * 检测人员用户 ID
  181 + *
  182 + * 关联 AdminUserDO 的 id
  183 + */
  184 + private Long inspectorUserId;
  185 + /**
  186 + * 状态
  187 + *
  188 + * 枚举 {@link cn.iocoder.yudao.module.mes.enums.qc.MesQcIqcStatusEnum}
  189 + */
  190 + private Integer status;
  191 + /**
  192 + * 备注
  193 + */
  194 + private String remark;
  195 +
  196 + // ========== 预留字段 ==========
  197 +
  198 + /**
  199 + * 预留字段 1
  200 + */
  201 + private String attribute1;
  202 + /**
  203 + * 预留字段 2
  204 + */
  205 + private String attribute2;
  206 + /**
  207 + * 预留字段 3
  208 + */
  209 + private Integer attribute3;
  210 + /**
  211 + * 预留字段 4
  212 + */
  213 + private Integer attribute4;
  214 +
  215 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/ipqc/MesQcIpqcLineDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
  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 +import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableId;
  9 +import com.baomidou.mybatisplus.annotation.TableName;
  10 +import lombok.*;
  11 +
  12 +import java.math.BigDecimal;
  13 +
  14 +/**
  15 + * MES 过程检验单行 DO
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@TableName("mes_qc_ipqc_line")
  20 +@KeySequence("mes_qc_ipqc_line_seq")
  21 +@Data
  22 +@EqualsAndHashCode(callSuper = true)
  23 +@ToString(callSuper = true)
  24 +@Builder
  25 +@NoArgsConstructor
  26 +@AllArgsConstructor
  27 +public class MesQcIpqcLineDO extends BaseDO {
  28 +
  29 + /**
  30 + * 编号
  31 + */
  32 + @TableId
  33 + private Long id;
  34 + /**
  35 + * 过程检验单 ID
  36 + *
  37 + * 关联 {@link MesQcIpqcDO#getId()}
  38 + */
  39 + private Long ipqcId;
  40 + /**
  41 + * 检测指标 ID
  42 + *
  43 + * 关联 {@link MesQcIndicatorDO#getId()}
  44 + */
  45 + private Long indicatorId;
  46 + /**
  47 + * 检测工具 ID
  48 + *
  49 + * 关联 {@link MesTmToolDO#getId()}
  50 + */
  51 + private Long toolId;
  52 + /**
  53 + * 检测方法
  54 + */
  55 + private String checkMethod;
  56 + /**
  57 + * 标准值
  58 + */
  59 + private BigDecimal standardValue;
  60 + /**
  61 + * 计量单位 ID
  62 + *
  63 + * 关联 {@link MesMdUnitMeasureDO#getId()}
  64 + */
  65 + private Long unitMeasureId;
  66 + /**
  67 + * 误差上限
  68 + */
  69 + private BigDecimal maxThreshold;
  70 + /**
  71 + * 误差下限
  72 + */
  73 + private BigDecimal minThreshold;
  74 + /**
  75 + * 致命缺陷数量
  76 + */
  77 + private Integer criticalQuantity;
  78 + /**
  79 + * 严重缺陷数量
  80 + */
  81 + private Integer majorQuantity;
  82 + /**
  83 + * 轻微缺陷数量
  84 + */
  85 + private Integer minorQuantity;
  86 + /**
  87 + * 备注
  88 + */
  89 + private String remark;
  90 +
  91 + // ========== 预留字段 ==========
  92 +
  93 + /**
  94 + * 预留字段 1
  95 + */
  96 + private String attribute1;
  97 + /**
  98 + * 预留字段 2
  99 + */
  100 + private String attribute2;
  101 + /**
  102 + * 预留字段 3
  103 + */
  104 + private Integer attribute3;
  105 + /**
  106 + * 预留字段 4
  107 + */
  108 + private Integer attribute4;
  109 +
  110 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcLineMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.line.MesQcIpqcLinePageReqVO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcLineDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * MES 过程检验单行 Mapper
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Mapper
  18 +public interface MesQcIpqcLineMapper extends BaseMapperX<MesQcIpqcLineDO> {
  19 +
  20 + default PageResult<MesQcIpqcLineDO> selectPage(MesQcIpqcLinePageReqVO reqVO) {
  21 + return selectPage(reqVO, new LambdaQueryWrapperX<MesQcIpqcLineDO>()
  22 + .eqIfPresent(MesQcIpqcLineDO::getIpqcId, reqVO.getIpqcId())
  23 + .orderByAsc(MesQcIpqcLineDO::getId));
  24 + }
  25 +
  26 + default List<MesQcIpqcLineDO> selectListByIpqcId(Long ipqcId) {
  27 + return selectList(MesQcIpqcLineDO::getIpqcId, ipqcId);
  28 + }
  29 +
  30 + default void deleteByIpqcId(Long ipqcId) {
  31 + delete(new LambdaQueryWrapperX<MesQcIpqcLineDO>()
  32 + .eq(MesQcIpqcLineDO::getIpqcId, ipqcId));
  33 + }
  34 +
  35 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +/**
  11 + * MES 过程检验单(IPQC) Mapper
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Mapper
  16 +public interface MesQcIpqcMapper extends BaseMapperX<MesQcIpqcDO> {
  17 +
  18 + default PageResult<MesQcIpqcDO> selectPage(MesQcIpqcPageReqVO reqVO) {
  19 + return selectPage(reqVO, new LambdaQueryWrapperX<MesQcIpqcDO>()
  20 + .likeIfPresent(MesQcIpqcDO::getCode, reqVO.getCode())
  21 + .eqIfPresent(MesQcIpqcDO::getType, reqVO.getType())
  22 + .eqIfPresent(MesQcIpqcDO::getWorkOrderId, reqVO.getWorkOrderId())
  23 + .eqIfPresent(MesQcIpqcDO::getWorkstationId, reqVO.getWorkstationId())
  24 + .eqIfPresent(MesQcIpqcDO::getItemId, reqVO.getItemId())
  25 + .eqIfPresent(MesQcIpqcDO::getCheckResult, reqVO.getCheckResult())
  26 + .eqIfPresent(MesQcIpqcDO::getStatus, reqVO.getStatus())
  27 + .betweenIfPresent(MesQcIpqcDO::getInspectDate, reqVO.getInspectDate())
  28 + .eqIfPresent(MesQcIpqcDO::getInspectorUserId, reqVO.getInspectorUserId())
  29 + .orderByDesc(MesQcIpqcDO::getId));
  30 + }
  31 +
  32 + default MesQcIpqcDO selectByCode(String code) {
  33 + return selectOne(MesQcIpqcDO::getCode, code);
  34 + }
  35 +
  36 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -270,6 +270,24 @@ public interface ErrorCodeConstants { @@ -270,6 +270,24 @@ public interface ErrorCodeConstants {
270 // ========== MES 质量管理-检验结果(1-040-606-000) ========== 270 // ========== MES 质量管理-检验结果(1-040-606-000) ==========
271 ErrorCode QC_RESULT_NOT_EXISTS = new ErrorCode(1_040_606_000, "检验结果不存在"); 271 ErrorCode QC_RESULT_NOT_EXISTS = new ErrorCode(1_040_606_000, "检验结果不存在");
272 272
  273 + // ========== MES 质量管理-出货检验(1-040-607-000) ==========
  274 + ErrorCode QC_OQC_NOT_EXISTS = new ErrorCode(1_040_607_000, "出货检验单不存在");
  275 + ErrorCode QC_OQC_CODE_DUPLICATE = new ErrorCode(1_040_607_001, "出货检验单编号已存在");
  276 + ErrorCode QC_OQC_NOT_PREPARE = new ErrorCode(1_040_607_002, "只有草稿状态的检验单才可操作");
  277 + ErrorCode QC_OQC_QUANTITY_MISMATCH = new ErrorCode(1_040_607_004, "合格品与不合格品数量之和须等于检测数量");
  278 + ErrorCode QC_OQC_NO_TEMPLATE = new ErrorCode(1_040_607_005, "当前产品未配置 OQC 检测模板");
  279 + // ========== MES 质量管理-出货检验行(1-040-607-100) ==========
  280 + ErrorCode QC_OQC_LINE_NOT_EXISTS = new ErrorCode(1_040_607_100, "出货检验行不存在");
  281 +
  282 + // ========== MES 质量管理-退货检验 RQC(1-040-608-000) ==========
  283 + ErrorCode QC_RQC_NOT_EXISTS = new ErrorCode(1_040_608_000, "退货检验单不存在");
  284 + ErrorCode QC_RQC_CODE_DUPLICATE = new ErrorCode(1_040_608_001, "退货检验单编号已存在");
  285 + ErrorCode QC_RQC_NOT_PREPARE = new ErrorCode(1_040_608_002, "只有草稿状态的检验单才可操作");
  286 + ErrorCode QC_RQC_QUANTITY_MISMATCH = new ErrorCode(1_040_608_004, "合格品与不合格品数量之和须等于检测数量");
  287 + ErrorCode QC_RQC_NO_TEMPLATE = new ErrorCode(1_040_608_005, "当前产品未配置 RQC 检测模板");
  288 + // ========== MES 质量管理-退货检验行(1-040-608-100) ==========
  289 + ErrorCode QC_RQC_LINE_NOT_EXISTS = new ErrorCode(1_040_608_100, "退货检验行不存在");
  290 +
273 // ========== MES 仓库管理-仓库(1-040-700-000) ========== 291 // ========== MES 仓库管理-仓库(1-040-700-000) ==========
274 ErrorCode WM_WAREHOUSE_NOT_EXISTS = new ErrorCode(1_040_700_000, "仓库不存在"); 292 ErrorCode WM_WAREHOUSE_NOT_EXISTS = new ErrorCode(1_040_700_000, "仓库不存在");
275 ErrorCode WM_WAREHOUSE_CODE_DUPLICATE = new ErrorCode(1_040_700_001, "仓库编码已存在"); 293 ErrorCode WM_WAREHOUSE_CODE_DUPLICATE = new ErrorCode(1_040_700_001, "仓库编码已存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcOqcStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.enums.qc;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * MES 出货检验单状态枚举
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Getter
  15 +@AllArgsConstructor
  16 +public enum MesQcOqcStatusEnum implements ArrayValuable<Integer> {
  17 +
  18 + PREPARE(0, "草稿"),
  19 + FINISHED(1, "已完成");
  20 +
  21 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesQcOqcStatusEnum::getType).toArray(Integer[]::new);
  22 +
  23 + /**
  24 + * 状态值
  25 + */
  26 + private final Integer type;
  27 + /**
  28 + * 状态名
  29 + */
  30 + private final String name;
  31 +
  32 + @Override
  33 + public Integer[] array() {
  34 + return ARRAYS;
  35 + }
  36 +
  37 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/process/MesProProcessService.java
@@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.process.vo.MesProProcess @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.process.vo.MesProProcess
6 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
7 7
8 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
  9 +
  10 +import java.util.Collection;
9 import java.util.List; 11 import java.util.List;
10 import java.util.Map; 12 import java.util.Map;
11 13
@@ -54,7 +56,7 @@ public interface MesProProcessService { @@ -54,7 +56,7 @@ public interface MesProProcessService {
54 * @param ids 编号列表 56 * @param ids 编号列表
55 * @return 生产工序列表 57 * @return 生产工序列表
56 */ 58 */
57 - List<MesProProcessDO> getProcessList(List<Long> ids); 59 + List<MesProProcessDO> getProcessList(Collection<Long> ids);
58 60
59 /** 61 /**
60 * 获得生产工序分页 62 * 获得生产工序分页
@@ -85,7 +87,7 @@ public interface MesProProcessService { @@ -85,7 +87,7 @@ public interface MesProProcessService {
85 * @param ids 编号列表 87 * @param ids 编号列表
86 * @return 工序 Map 88 * @return 工序 Map
87 */ 89 */
88 - default Map<Long, MesProProcessDO> getProcessMap(List<Long> ids) { 90 + default Map<Long, MesProProcessDO> getProcessMap(Collection<Long> ids) {
89 return convertMap(getProcessList(ids), MesProProcessDO::getId); 91 return convertMap(getProcessList(ids), MesProProcessDO::getId);
90 } 92 }
91 93
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/process/MesProProcessServiceImpl.java
@@ -13,6 +13,7 @@ import org.springframework.stereotype.Service; @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
13 import org.springframework.transaction.annotation.Transactional; 13 import org.springframework.transaction.annotation.Transactional;
14 import org.springframework.validation.annotation.Validated; 14 import org.springframework.validation.annotation.Validated;
15 15
  16 +import java.util.Collection;
16 import java.util.Collections; 17 import java.util.Collections;
17 import java.util.List; 18 import java.util.List;
18 19
@@ -119,7 +120,7 @@ public class MesProProcessServiceImpl implements MesProProcessService { @@ -119,7 +120,7 @@ public class MesProProcessServiceImpl implements MesProProcessService {
119 } 120 }
120 121
121 @Override 122 @Override
122 - public List<MesProProcessDO> getProcessList(List<Long> ids) { 123 + public List<MesProProcessDO> getProcessList(Collection<Long> ids) {
123 if (CollUtil.isEmpty(ids)) { 124 if (CollUtil.isEmpty(ids)) {
124 return Collections.emptyList(); 125 return Collections.emptyList();
125 } 126 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.qc.ipqc;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.line.MesQcIpqcLinePageReqVO;
  5 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
  6 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcLineDO;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * MES 过程检验单行 Service 接口
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public interface MesQcIpqcLineService {
  16 +
  17 + /**
  18 + * 获得过程检验行
  19 + *
  20 + * @param id 编号
  21 + * @return 过程检验行
  22 + */
  23 + MesQcIpqcLineDO getIpqcLine(Long id);
  24 +
  25 + /**
  26 + * 获得过程检验行分页
  27 + *
  28 + * @param pageReqVO 分页查询
  29 + * @return 过程检验行分页
  30 + */
  31 + PageResult<MesQcIpqcLineDO> getIpqcLinePage(MesQcIpqcLinePageReqVO pageReqVO);
  32 +
  33 + /**
  34 + * 根据过程检验单 ID 查询所有行
  35 + *
  36 + * @param ipqcId 过程检验单 ID
  37 + * @return 行列表
  38 + */
  39 + List<MesQcIpqcLineDO> selectListByIpqcId(Long ipqcId);
  40 +
  41 + /**
  42 + * 从模板指标自动生成检验行
  43 + *
  44 + * @param ipqcId 过程检验单 ID
  45 + * @param templateId 模板 ID
  46 + */
  47 + void createLinesFromTemplate(Long ipqcId, Long templateId);
  48 +
  49 + /**
  50 + * 根据过程检验单 ID 级联删除所有行
  51 + *
  52 + * @param ipqcId 过程检验单 ID
  53 + */
  54 + void deleteByIpqcId(Long ipqcId);
  55 +
  56 + /**
  57 + * 校验过程检验行存在
  58 + *
  59 + * @param id 编号
  60 + * @return 过程检验行
  61 + */
  62 + MesQcIpqcLineDO validateIpqcLineExists(Long id);
  63 +
  64 + /**
  65 + * 根据缺陷记录重新计算行级缺陷统计
  66 + *
  67 + * @param ipqcId 过程检验单 ID
  68 + * @param records 缺陷记录列表
  69 + */
  70 + void recalculateLineDefectStats(Long ipqcId, List<MesQcDefectRecordDO> records);
  71 +
  72 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcLineServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.qc.ipqc;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.line.MesQcIpqcLinePageReqVO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcLineDO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO;
  10 +import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcLineMapper;
  11 +import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateIndicatorMapper;
  12 +import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
  13 +import jakarta.annotation.Resource;
  14 +import org.springframework.stereotype.Service;
  15 +import org.springframework.validation.annotation.Validated;
  16 +
  17 +import java.util.ArrayList;
  18 +import java.util.List;
  19 +import java.util.Objects;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  23 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
  24 +
  25 +/**
  26 + * MES 过程检验单行 Service 实现类
  27 + *
  28 + * @author 芋道源码
  29 + */
  30 +@Service
  31 +@Validated
  32 +public class MesQcIpqcLineServiceImpl implements MesQcIpqcLineService {
  33 +
  34 + @Resource
  35 + private MesQcIpqcLineMapper ipqcLineMapper;
  36 + @Resource
  37 + private MesQcTemplateIndicatorMapper templateIndicatorMapper;
  38 +
  39 + @Override
  40 + public MesQcIpqcLineDO getIpqcLine(Long id) {
  41 + return ipqcLineMapper.selectById(id);
  42 + }
  43 +
  44 + @Override
  45 + public PageResult<MesQcIpqcLineDO> getIpqcLinePage(MesQcIpqcLinePageReqVO pageReqVO) {
  46 + return ipqcLineMapper.selectPage(pageReqVO);
  47 + }
  48 +
  49 + @Override
  50 + public List<MesQcIpqcLineDO> selectListByIpqcId(Long ipqcId) {
  51 + return ipqcLineMapper.selectListByIpqcId(ipqcId);
  52 + }
  53 +
  54 + @Override
  55 + 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));
  64 + ipqcLineMapper.insertBatch(lines);
  65 + }
  66 +
  67 + @Override
  68 + public void deleteByIpqcId(Long ipqcId) {
  69 + ipqcLineMapper.deleteByIpqcId(ipqcId);
  70 + }
  71 +
  72 + @Override
  73 + public MesQcIpqcLineDO validateIpqcLineExists(Long id) {
  74 + MesQcIpqcLineDO line = ipqcLineMapper.selectById(id);
  75 + if (line == null) {
  76 + throw exception(QC_IPQC_LINE_NOT_EXISTS);
  77 + }
  78 + return line;
  79 + }
  80 +
  81 + @Override
  82 + public void recalculateLineDefectStats(Long ipqcId, List<MesQcDefectRecordDO> records) {
  83 + List<MesQcIpqcLineDO> lines = ipqcLineMapper.selectListByIpqcId(ipqcId);
  84 + if (CollUtil.isEmpty(lines)) {
  85 + return;
  86 + }
  87 + List<MesQcIpqcLineDO> updateLines = new ArrayList<>(lines.size());
  88 + for (MesQcIpqcLineDO line : lines) {
  89 + int critical = 0, major = 0, minor = 0;
  90 + for (MesQcDefectRecordDO record : records) {
  91 + if (ObjUtil.notEqual(record.getLineId(), line.getId())) {
  92 + continue;
  93 + }
  94 + int quantity = ObjUtil.defaultIfNull(record.getQuantity(), 1);
  95 + if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.CRITICAL.getType())) {
  96 + critical += quantity;
  97 + } else if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.MAJOR.getType())) {
  98 + major += quantity;
  99 + } else if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.MINOR.getType())) {
  100 + minor += quantity;
  101 + } else {
  102 + throw exception(QC_DEFECT_RECORD_LEVEL_UNKNOWN);
  103 + }
  104 + }
  105 + updateLines.add(new MesQcIpqcLineDO().setId(line.getId())
  106 + .setCriticalQuantity(critical).setMajorQuantity(major).setMinorQuantity(minor));
  107 + }
  108 + for (MesQcIpqcLineDO updateLine : updateLines) {
  109 + ipqcLineMapper.updateById(updateLine);
  110 + }
  111 + }
  112 +
  113 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.qc.ipqc;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO;
  5 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO;
  6 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
  8 +import jakarta.validation.Valid;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * MES 过程检验单(IPQC) Service 接口
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +public interface MesQcIpqcService {
  18 +
  19 + /**
  20 + * 创建过程检验单
  21 + *
  22 + * @param createReqVO 创建信息
  23 + * @return 编号
  24 + */
  25 + Long createIpqc(@Valid MesQcIpqcSaveReqVO createReqVO);
  26 +
  27 + /**
  28 + * 更新过程检验单
  29 + *
  30 + * @param updateReqVO 更新信息
  31 + */
  32 + void updateIpqc(@Valid MesQcIpqcSaveReqVO updateReqVO);
  33 +
  34 + /**
  35 + * 完成过程检验单
  36 + *
  37 + * @param id 编号
  38 + */
  39 + void completeIpqc(Long id);
  40 +
  41 + /**
  42 + * 删除过程检验单
  43 + *
  44 + * @param id 编号
  45 + */
  46 + void deleteIpqc(Long id);
  47 +
  48 + /**
  49 + * 获得过程检验单
  50 + *
  51 + * @param id 编号
  52 + * @return 过程检验单
  53 + */
  54 + MesQcIpqcDO getIpqc(Long id);
  55 +
  56 + /**
  57 + * 获得过程检验单分页
  58 + *
  59 + * @param pageReqVO 分页查询
  60 + * @return 过程检验单分页
  61 + */
  62 + PageResult<MesQcIpqcDO> getIpqcPage(MesQcIpqcPageReqVO pageReqVO);
  63 +
  64 + /**
  65 + * 校验过程检验单存在
  66 + *
  67 + * @param id 编号
  68 + * @return 过程检验单
  69 + */
  70 + MesQcIpqcDO validateIpqcExists(Long id);
  71 +
  72 + /**
  73 + * 根据缺陷记录重新计算主表的缺陷统计(含行级下沉)
  74 + *
  75 + * @param ipqcId 过程检验单 ID
  76 + * @param records 缺陷记录列表
  77 + */
  78 + void recalculateDefectStats(Long ipqcId, List<MesQcDefectRecordDO> records);
  79 +
  80 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.qc.ipqc;
  2 +
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcPageReqVO;
  7 +import cn.iocoder.yudao.module.mes.controller.admin.qc.ipqc.vo.MesQcIpqcSaveReqVO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
  10 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
  11 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
  12 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO;
  13 +import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
  14 +import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper;
  15 +import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
  16 +import cn.iocoder.yudao.module.mes.enums.qc.MesQcIqcStatusEnum;
  17 +import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
  18 +import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
  19 +import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
  20 +import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
  21 +import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateService;
  22 +import jakarta.annotation.Resource;
  23 +import org.springframework.context.annotation.Lazy;
  24 +import org.springframework.stereotype.Service;
  25 +import org.springframework.transaction.annotation.Transactional;
  26 +import org.springframework.validation.annotation.Validated;
  27 +
  28 +import java.math.BigDecimal;
  29 +import java.math.RoundingMode;
  30 +import java.util.List;
  31 +import java.util.Objects;
  32 +
  33 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  34 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
  35 +
  36 +/**
  37 + * MES 过程检验单(IPQC) Service 实现类
  38 + *
  39 + * @author 芋道源码
  40 + */
  41 +@Service
  42 +@Validated
  43 +public class MesQcIpqcServiceImpl implements MesQcIpqcService {
  44 +
  45 + @Resource
  46 + private MesQcIpqcMapper ipqcMapper;
  47 +
  48 + @Resource
  49 + private MesQcTemplateService templateService;
  50 + @Resource
  51 + private MesQcIpqcLineService ipqcLineService;
  52 + @Resource
  53 + @Lazy
  54 + private MesProWorkOrderService workOrderService;
  55 + @Resource
  56 + @Lazy
  57 + private MesMdWorkstationService workstationService;
  58 + @Resource
  59 + @Lazy
  60 + private MesQcDefectRecordService defectRecordService;
  61 +
  62 + @Override
  63 + @Transactional(rollbackFor = Exception.class)
  64 + public Long createIpqc(MesQcIpqcSaveReqVO createReqVO) {
  65 + // 1.1 校验编号唯一
  66 + validateIpqcCodeUnique(null, createReqVO.getCode());
  67 +
  68 + // 1.2 从工单获取产品物料 ID
  69 + // TODO @AI:需要校验 workOrderService 存在;
  70 +// TODO createReqVO 里的 itemId 字段去掉;直接去下面的实体去 set;
  71 + MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(createReqVO.getWorkOrderId());
  72 + if (workOrder != null) {
  73 + createReqVO.setItemId(workOrder.getProductId());
  74 + }
  75 +
  76 + // 1.3 从工位获取工序 ID
  77 + // TODO @AI:需要校验 workstationService 存在;
  78 + // TODO @AI:processId 不存储,去读取好了;
  79 + MesMdWorkstationDO workstation = workstationService.getWorkstation(createReqVO.getWorkstationId());
  80 + if (workstation != null && workstation.getProcessId() != null) {
  81 + createReqVO.setProcessId(workstation.getProcessId());
  82 + }
  83 +
  84 + // 1.4 根据产品 + 检验类型自动匹配模板
  85 + // TODO @AI:直接调用 templateService 的校验方法;
  86 + MesQcTemplateDO template = templateService.getTemplateByItemIdAndType(
  87 + createReqVO.getItemId(), MesQcTypeEnum.IPQC.getType());
  88 + if (template == null) {
  89 + throw exception(QC_IPQC_NO_TEMPLATE);
  90 + }
  91 + createReqVO.setTemplateId(template.getId());
  92 +
  93 + // 1.5 从模板的产品关联中获取检测参数
  94 + MesQcTemplateItemDO templateItem = templateService.getTemplateItemByTemplateIdAndItemId(
  95 + template.getId(), createReqVO.getItemId());
  96 + // 模板产品关联可能为空(模板关联了其他产品但也可手动选择),此处不强制
  97 +
  98 + // 2. 插入主表
  99 + MesQcIpqcDO ipqc = BeanUtils.toBean(createReqVO, MesQcIpqcDO.class);
  100 + ipqc.setStatus(MesQcIqcStatusEnum.PREPARE.getType());
  101 + ipqcMapper.insert(ipqc);
  102 +
  103 + // 3. 从模板指标自动生成检验行
  104 + ipqcLineService.createLinesFromTemplate(ipqc.getId(), template.getId());
  105 + return ipqc.getId();
  106 + }
  107 +
  108 + @Override
  109 + public void updateIpqc(MesQcIpqcSaveReqVO updateReqVO) {
  110 + // 1.1 校验存在 + 草稿状态
  111 + validateIpqcStatusPrepare(updateReqVO.getId());
  112 + // 1.2 校验编号唯一
  113 + validateIpqcCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
  114 +
  115 + // 2. 更新
  116 + MesQcIpqcDO updateObj = BeanUtils.toBean(updateReqVO, MesQcIpqcDO.class);
  117 + ipqcMapper.updateById(updateObj);
  118 + }
  119 +
  120 + @Override
  121 + public void completeIpqc(Long id) {
  122 + // 1.1 校验存在 + 草稿状态
  123 + MesQcIpqcDO ipqc = validateIpqcStatusPrepare(id);
  124 + // 1.2 校验合格品 + 不合格品 = 检测数量
  125 + if (ipqc.getCheckQuantity() != null && ipqc.getCheckQuantity().compareTo(BigDecimal.ZERO) > 0) {
  126 + BigDecimal qualified = ipqc.getQualifiedQuantity() != null ? ipqc.getQualifiedQuantity() : BigDecimal.ZERO;
  127 + BigDecimal unqualified = ipqc.getUnqualifiedQuantity() != null ? ipqc.getUnqualifiedQuantity() : BigDecimal.ZERO;
  128 + if (qualified.add(unqualified).compareTo(ipqc.getCheckQuantity()) != 0) {
  129 + throw exception(QC_IPQC_QUANTITY_MISMATCH);
  130 + }
  131 + }
  132 +
  133 + // 2. 更新状态为已完成
  134 + MesQcIpqcDO updateObj = new MesQcIpqcDO();
  135 + updateObj.setId(id);
  136 + updateObj.setStatus(MesQcIqcStatusEnum.FINISHED.getType());
  137 + ipqcMapper.updateById(updateObj);
  138 +
  139 + // TODO @芋艿:IPQC 完成时的 PRO/WM 联动(报工反馈更新、产品产出单拆分),待 WM 模块迁移后对接
  140 + }
  141 +
  142 + @Override
  143 + @Transactional(rollbackFor = Exception.class)
  144 + public void deleteIpqc(Long id) {
  145 + // 1. 校验存在 + 草稿状态
  146 + validateIpqcStatusPrepare(id);
  147 +
  148 + // 2.1 删除主表
  149 + ipqcMapper.deleteById(id);
  150 + // 2.2 级联删除行
  151 + ipqcLineService.deleteByIpqcId(id);
  152 + // 2.3 级联删除缺陷记录
  153 + defectRecordService.deleteByQcTypeAndQcId(MesQcTypeEnum.IPQC.getType(), id);
  154 + }
  155 +
  156 + @Override
  157 + public MesQcIpqcDO validateIpqcExists(Long id) {
  158 + MesQcIpqcDO ipqc = ipqcMapper.selectById(id);
  159 + if (ipqc == null) {
  160 + throw exception(QC_IPQC_NOT_EXISTS);
  161 + }
  162 + return ipqc;
  163 + }
  164 +
  165 + /**
  166 + * 校验过程检验单存在且为草稿状态
  167 + *
  168 + * @param id 过程检验单 ID
  169 + * @return 过程检验单
  170 + */
  171 + private MesQcIpqcDO validateIpqcStatusPrepare(Long id) {
  172 + MesQcIpqcDO ipqc = validateIpqcExists(id);
  173 + if (ObjUtil.notEqual(ipqc.getStatus(), MesQcIqcStatusEnum.PREPARE.getType())) {
  174 + throw exception(QC_IPQC_NOT_PREPARE);
  175 + }
  176 + return ipqc;
  177 + }
  178 +
  179 + private void validateIpqcCodeUnique(Long id, String code) {
  180 + MesQcIpqcDO ipqc = ipqcMapper.selectByCode(code);
  181 + if (ipqc == null) {
  182 + return;
  183 + }
  184 + if (ObjUtil.notEqual(ipqc.getId(), id)) {
  185 + throw exception(QC_IPQC_CODE_DUPLICATE);
  186 + }
  187 + }
  188 +
  189 + @Override
  190 + public MesQcIpqcDO getIpqc(Long id) {
  191 + return ipqcMapper.selectById(id);
  192 + }
  193 +
  194 + @Override
  195 + public PageResult<MesQcIpqcDO> getIpqcPage(MesQcIpqcPageReqVO pageReqVO) {
  196 + return ipqcMapper.selectPage(pageReqVO);
  197 + }
  198 +
  199 + @Override
  200 + public void recalculateDefectStats(Long ipqcId, List<MesQcDefectRecordDO> records) {
  201 + // 1. 行级缺陷统计
  202 + ipqcLineService.recalculateLineDefectStats(ipqcId, records);
  203 +
  204 + // 2.1 汇总主表的缺陷数量
  205 + int totalCritical = 0, totalMajor = 0, totalMinor = 0;
  206 + for (MesQcDefectRecordDO record : records) {
  207 + int quantity = ObjUtil.defaultIfNull(record.getQuantity(), 1);
  208 + if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.CRITICAL.getType())) {
  209 + totalCritical += quantity;
  210 + } else if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.MAJOR.getType())) {
  211 + totalMajor += quantity;
  212 + } else if (Objects.equals(record.getLevel(), MesQcDefectLevelEnum.MINOR.getType())) {
  213 + totalMinor += quantity;
  214 + } else {
  215 + throw exception(QC_DEFECT_RECORD_LEVEL_UNKNOWN);
  216 + }
  217 + }
  218 + // 2.2 计算缺陷率(IPQC 的 checkQuantity 是 BigDecimal)
  219 + MesQcIpqcDO ipqc = validateIpqcExists(ipqcId);
  220 + BigDecimal criticalRate = BigDecimal.ZERO;
  221 + BigDecimal majorRate = BigDecimal.ZERO;
  222 + BigDecimal minorRate = BigDecimal.ZERO;
  223 + if (ipqc.getCheckQuantity() != null && ipqc.getCheckQuantity().compareTo(BigDecimal.ZERO) > 0) {
  224 + BigDecimal checkQty = ipqc.getCheckQuantity();
  225 + criticalRate = BigDecimal.valueOf(totalCritical).multiply(BigDecimal.valueOf(100))
  226 + .divide(checkQty, 2, RoundingMode.HALF_UP);
  227 + majorRate = BigDecimal.valueOf(totalMajor).multiply(BigDecimal.valueOf(100))
  228 + .divide(checkQty, 2, RoundingMode.HALF_UP);
  229 + minorRate = BigDecimal.valueOf(totalMinor).multiply(BigDecimal.valueOf(100))
  230 + .divide(checkQty, 2, RoundingMode.HALF_UP);
  231 + }
  232 +
  233 + // 3. 更新主表
  234 + MesQcIpqcDO updateIpqc = new MesQcIpqcDO().setId(ipqcId)
  235 + .setCriticalQuantity(totalCritical).setMajorQuantity(totalMajor).setMinorQuantity(totalMinor)
  236 + .setCriticalRate(criticalRate).setMajorRate(majorRate).setMinorRate(minorRate);
  237 + ipqcMapper.updateById(updateIpqc);
  238 + }
  239 +
  240 +}