云
3 小时以前 95ae0c83d2aa58d86b4039e3f7f16ee93afb8f00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
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<String> permissions() {
        return List.of("mes:qc-template:create", "mes:qc-indicator:create");
    }
 
    @Override
    public AgentImportPreflightRespVO preflight(AgentPrincipal principal, AgentImportPreflightReqVO request) {
        List<AgentImportPreflightRespVO.Row> plannedRows = new ArrayList<>();
        List<Map<String, Object>> resolvedPayload = new ArrayList<>();
        Set<String> fileKeys = new HashSet<>();
        int create = 0;
        int skip = 0;
        int blocked = 0;
 
        for (AgentImportPreflightReqVO.Row row : request.getRows()) {
            List<AgentImportPreflightRespVO.Issue> blockers = new ArrayList<>();
            List<AgentImportPreflightRespVO.Issue> 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<Integer> 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<String, Object> 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<Integer> parseTypes(String raw, List<AgentImportPreflightRespVO.Issue> blockers) {
        List<Integer> 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<AgentImportPreflightRespVO.Issue> 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<AgentImportPreflightRespVO.Issue> 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();
    }
}