package cn.iocoder.yudao.module.mes.service.qc.template; import cn.hutool.crypto.digest.DigestUtil; import cn.iocoder.yudao.module.mes.controller.admin.qc.indicator.vo.MesQcIndicatorSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.MesQcTemplateSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo.indicator.MesQcTemplateIndicatorSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicator.MesQcIndicatorDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateIndicatorDO; import cn.iocoder.yudao.module.mes.dal.mysql.qc.indicator.MesQcIndicatorMapper; import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateIndicatorMapper; import cn.iocoder.yudao.module.mes.dal.mysql.qc.template.MesQcTemplateMapper; import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportCommitReqVO; import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportModuleAdapter; import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportPreflightReqVO; import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportPreflightRespVO; import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportRowReceipt; import cn.iocoder.yudao.module.system.framework.agent.AgentPrincipal; import cn.iocoder.yudao.module.system.service.agent.imports.SystemAgentGenericImportService; import jakarta.annotation.Resource; import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.util.*; /** * 质检方案导入:按检测项名称复用现有主数据;缺失项作为副作用提案,用户确认预检后才创建。 */ @Component public class MesQcTemplateImportAdapter implements AgentImportModuleAdapter { public static final String MODULE_ID = "mes.qc-template"; @Resource private MesQcTemplateMapper templateMapper; @Resource private MesQcTemplateService templateService; @Resource private MesQcIndicatorMapper indicatorMapper; @Resource private MesQcIndicatorService indicatorService; @Resource private MesQcTemplateIndicatorMapper templateIndicatorMapper; @Resource private MesQcTemplateIndicatorService templateIndicatorService; @Override public String moduleId() { return MODULE_ID; } @Override public String permission() { return "mes:qc-template:create"; } @Override public List permissions() { return List.of("mes:qc-template:create", "mes:qc-indicator:create"); } @Override public AgentImportPreflightRespVO preflight(AgentPrincipal principal, AgentImportPreflightReqVO request) { List plannedRows = new ArrayList<>(); List> resolvedPayload = new ArrayList<>(); Set fileKeys = new HashSet<>(); int create = 0; int skip = 0; int blocked = 0; for (AgentImportPreflightReqVO.Row row : request.getRows()) { List blockers = new ArrayList<>(); List warnings = new ArrayList<>(); String templateCode = value(row, "code"); String templateName = value(row, "name"); String indicatorName = value(row, "indicatorName"); if (templateCode.isEmpty()) blockers.add(issue("TEMPLATE_CODE_REQUIRED", "code", "方案编号不能为空")); if (templateName.isEmpty()) blockers.add(issue("TEMPLATE_NAME_REQUIRED", "name", "方案名称不能为空")); if (indicatorName.isEmpty()) blockers.add(issue("INDICATOR_NAME_REQUIRED", "indicatorName", "检测项不能为空")); List types = parseTypes(value(row, "types"), blockers); Integer status = parseInteger(value(row, "status"), 0, "status", blockers); Integer indicatorType = parseInteger(value(row, "indicatorType"), 5, "indicatorType", blockers); Integer resultType = parseInteger(value(row, "resultType"), 1, "resultType", blockers); BigDecimal thresholdMax = decimal(value(row, "thresholdMax"), "thresholdMax", blockers); BigDecimal thresholdMin = decimal(value(row, "thresholdMin"), "thresholdMin", blockers); MesQcIndicatorDO indicator = findIndicator(value(row, "indicatorCode"), indicatorName); String indicatorCode = indicator == null ? proposedCode(indicatorName) : indicator.getCode(); String indicatorAction = indicator == null ? "CREATE_AFTER_REVIEW" : "REUSE"; if (indicator == null && !indicatorName.isEmpty()) { warnings.add(issue("INDICATOR_WILL_CREATE", "indicatorName", "系统不存在检测项“" + indicatorName + "”;确认导入后将先新增检测项 " + indicatorCode)); } if (!value(row, "unitCode").isEmpty()) { warnings.add(issue("UNIT_REQUIRES_REVIEW", "unitCode", "计量单位“" + value(row, "unitCode") + "”暂以原文保留,请复核系统单位关联")); } MesQcTemplateDO template = templateCode.isEmpty() ? null : templateMapper.selectByCode(templateCode); MesQcTemplateIndicatorDO relation = template == null || indicator == null ? null : templateIndicatorMapper.selectListByTemplateId(template.getId()).stream() .filter(item -> Objects.equals(item.getIndicatorId(), indicator.getId())).findFirst().orElse(null); String fileKey = templateCode + "\u0000" + indicatorName; String action; Long targetId = null; if (!blockers.isEmpty()) { action = "BLOCKED"; blocked++; } else if (!fileKeys.add(fileKey)) { blockers.add(issue("DUPLICATE_IN_FILE", "indicatorName", "文件内检测项重复,请保留一条")); action = "BLOCKED"; blocked++; } else if (relation != null) { action = "SKIP"; targetId = relation.getId(); skip++; warnings.add(issue("TEMPLATE_INDICATOR_EXISTS", "indicatorName", "方案已关联该检测项,已跳过")); } else { action = "CREATE"; create++; } Map resolved = new LinkedHashMap<>(); resolved.put("code", templateCode); resolved.put("name", templateName); resolved.put("types", types); resolved.put("status", status); resolved.put("indicatorCode", indicatorCode); resolved.put("indicatorName", indicatorName); resolved.put("indicatorType", indicatorType); resolved.put("resultType", resultType); resolved.put("indicatorAction", indicatorAction); resolved.put("thresholdMax", thresholdMax); resolved.put("thresholdMin", thresholdMin); resolved.put("standardValue", value(row, "standardValue")); resolved.put("unitCode", value(row, "unitCode")); resolved.put("action", action); resolvedPayload.add(resolved); plannedRows.add(AgentImportPreflightRespVO.Row.builder() .candidateId(row.getCandidateId()).rowNo(row.getRowNo()).action(action) .resolved(resolved).blockers(blockers).warnings(warnings).targetId(targetId).build()); } return AgentImportPreflightRespVO.builder() .moduleId(MODULE_ID) .preflightHash(SystemAgentGenericImportService.preflightHash(MODULE_ID, request, resolvedPayload)) .source(AgentImportPreflightRespVO.Source.builder() .attachmentId(request.getAttachmentId()).sessionId(request.getSessionId()) .sourceSha256(request.getSourceSha256()).proposalRevision(request.getProposalRevision()).build()) .summary(AgentImportPreflightRespVO.Summary.builder() .total(plannedRows.size()).create(create).skip(skip).blocked(blocked).build()) .canCommit(blocked == 0) .sideEffects(List.of( "按方案编号创建质检方案(检测种类使用系统 mes_qc_type)", "复用同名检测项;缺失检测项经本次人工确认后新增", "创建方案与检测项关联,不覆盖现有检测项主数据")) .rows(plannedRows).blockingItems(List.of()).build(); } @Override public AgentImportRowReceipt commitRow(AgentPrincipal principal, AgentImportCommitReqVO request, AgentImportPreflightRespVO.Row planned, AgentImportPreflightReqVO.Row row) { MesQcTemplateDO template = templateMapper.selectByCode(value(row, "code")); if (template == null) { MesQcTemplateSaveReqVO create = new MesQcTemplateSaveReqVO(); create.setCode(value(row, "code")); create.setName(value(row, "name")); create.setTypes(parseTypes(value(row, "types"), new ArrayList<>())); create.setStatus(parseInteger(value(row, "status"), 0, "status", new ArrayList<>())); create.setRemark("来源:工业智能体文档补录;已由用户确认"); Long id = templateService.createTemplate(create); template = templateService.getTemplate(id); } String indicatorName = value(row, "indicatorName"); MesQcIndicatorDO indicator = findIndicator(value(row, "indicatorCode"), indicatorName); if (indicator == null) { MesQcIndicatorSaveReqVO create = new MesQcIndicatorSaveReqVO(); create.setCode(proposedCode(indicatorName)); create.setName(indicatorName); create.setType(parseInteger(value(row, "indicatorType"), 5, "indicatorType", new ArrayList<>())); create.setItemType(1); create.setParentId(0L); create.setResultType(parseInteger(value(row, "resultType"), 1, "resultType", new ArrayList<>())); create.setRemark("来源:工业智能体从已授权 SOP 提取;经用户审核新增"); Long id = indicatorService.createIndicator(create); indicator = indicatorService.getIndicator(id); } Long indicatorId = indicator.getId(); MesQcTemplateIndicatorDO existing = templateIndicatorMapper.selectListByTemplateId(template.getId()).stream() .filter(item -> Objects.equals(item.getIndicatorId(), indicatorId)).findFirst().orElse(null); if (existing != null) { return AgentImportRowReceipt.created(existing.getId(), value(row, "code"), indicatorName); } MesQcTemplateIndicatorSaveReqVO create = new MesQcTemplateIndicatorSaveReqVO(); create.setTemplateId(template.getId()); create.setIndicatorId(indicatorId); create.setThresholdMax(decimal(value(row, "thresholdMax"), "thresholdMax", new ArrayList<>())); create.setThresholdMin(decimal(value(row, "thresholdMin"), "thresholdMin", new ArrayList<>())); String standard = value(row, "standardValue"); String unit = value(row, "unitCode"); create.setRemark("原文标准:" + standard + (unit.isEmpty() ? "" : ";单位:" + unit)); Long relationId = templateIndicatorService.createTemplateIndicator(create); return AgentImportRowReceipt.created(relationId, value(row, "code"), indicatorName); } private MesQcIndicatorDO findIndicator(String code, String name) { if (!code.isEmpty()) { MesQcIndicatorDO found = indicatorMapper.selectByCode(code); if (found != null) return found; } if (name.isEmpty()) return null; return indicatorService.getIndicatorList().stream() .filter(item -> name.equalsIgnoreCase(item.getName().trim())).findFirst().orElse(null); } private static String proposedCode(String name) { return "AI-QI-" + DigestUtil.sha256Hex(name == null ? "" : name.trim()).substring(0, 12).toUpperCase(Locale.ROOT); } private static List parseTypes(String raw, List blockers) { List values = new ArrayList<>(); for (String part : raw.replace("[", "").replace("]", "").split("[,,]")) { if (part.isBlank()) continue; try { int value = Integer.parseInt(part.trim()); if (value < 1 || value > 4) throw new NumberFormatException(); values.add(value); } catch (NumberFormatException ex) { blockers.add(issue("INVALID_QC_TYPE", "types", "检测种类必须使用系统字典值 1/2/3/4")); } } if (values.isEmpty()) blockers.add(issue("QC_TYPE_REQUIRED", "types", "检测种类不能为空")); return values; } private static Integer parseInteger(String raw, int defaultValue, String field, List blockers) { if (raw.isEmpty()) return defaultValue; try { return Integer.valueOf(raw); } catch (NumberFormatException ex) { blockers.add(issue("INVALID_INTEGER", field, field + " 必须是整数")); return defaultValue; } } private static BigDecimal decimal(String raw, String field, List blockers) { if (raw.isEmpty()) return null; try { return new BigDecimal(raw); } catch (NumberFormatException ex) { blockers.add(issue("INVALID_DECIMAL", field, field + " 必须是数字")); return null; } } private static String value(AgentImportPreflightReqVO.Row row, String key) { String raw = row.getValues() == null ? null : row.getValues().get(key); return raw == null ? "" : raw.trim(); } private static AgentImportPreflightRespVO.Issue issue(String code, String field, String message) { return AgentImportPreflightRespVO.Issue.builder() .code(code).field(field).message(message).retryable(false).build(); } }