package cn.iocoder.yudao.module.qcreport.engine.context; import cn.iocoder.yudao.module.qcreport.engine.JsValues; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * 报告上下文与「数据快照」之间的编解码。 *

* 报告实例落库时要冻结一份 {@code data_snapshot},保证历史报告不因业务数据变化而改变。 * 快照就是 {@link ReportContext#toScope()} 那份结构({@code {report, inspectionItems}}), * 能直接交给 JSON 列的类型处理器;反过来要重新生成时,得把它读回 {@link ReportContext}。 *

* 为什么手写 fromMap 而不用 Jackson 反序列化:一是与引擎其余部分风格一致 * ({@link ReportContext#reportMap()}、{@link ReportContext#itemMap} 都是显式字段清单, * 加字段时改一处就能看出来);二是这个模块里 Jackson 3(JSON 列类型处理器)与 Jackson 2 * (Spring 请求体)并存,靠反射映射等于把「取哪套配置」变成一件碰运气的事。 * 这里是历史报告能否复现的唯一路径,值得写死。 */ public final class ReportContextCodec { private static final String REPORT = "report"; private static final String INSPECTION_ITEMS = "inspectionItems"; private ReportContextCodec() { } /** * 上下文 → 快照。 *

* 传进来的通常已经是判定后的上下文({@code RenderOutcome.context()}), * 冻结的应当是「产出这份 HTML 的那份数据」,包含算好的 PASS/FAIL 与合格率。 */ public static Map toMap(ReportContext context) { return context == null ? ReportContext.empty().toScope() : context.toScope(); } /** 快照 → 上下文。缺字段、类型不对都按「这个字段没有值」处理,不抛异常 */ public static ReportContext fromMap(Map snapshot) { ReportContext context = ReportContext.empty(); if (snapshot == null) { return context; } if (snapshot.get(REPORT) instanceof Map report) { context.setReport(reportOf(report)); } if (snapshot.get(INSPECTION_ITEMS) instanceof List items) { context.setInspectionItems(itemsOf(items)); } return context; } private static ReportFields reportOf(Map report) { return new ReportFields() .setReportNo(text(report.get("reportNo"))) .setReportName(text(report.get("reportName"))) .setSampleNo(text(report.get("sampleNo"))) .setProductCode(text(report.get("productCode"))) .setProductName(text(report.get("productName"))) .setSpec(text(report.get("spec"))) .setBatchNo(text(report.get("batchNo"))) .setWorkOrderNo(text(report.get("workOrderNo"))) .setInspectType(text(report.get("inspectType"))) .setInspector(text(report.get("inspector"))) .setInspectDate(text(report.get("inspectDate"))) .setDepartment(text(report.get("department"))) .setCustomerName(text(report.get("customerName"))) .setSupplierName(text(report.get("supplierName"))) .setResult(text(report.get("result"))) .setResultText(text(report.get("resultText"))) .setConclusion(text(report.get("conclusion"))) .setPassRate(text(report.get("passRate"))) .setQcResult(text(report.get("qcResult"))) .setQcResultText(text(report.get("qcResultText"))) .setTotal(integer(report.get("total"))) .setPassCount(integer(report.get("passCount"))) .setFailCount(integer(report.get("failCount"))); } private static List itemsOf(List items) { List result = new ArrayList<>(items.size()); for (Object element : items) { if (element instanceof Map item) { result.add(itemOf(item)); } } return result; } private static InspectionItem itemOf(Map item) { return new InspectionItem() .setIndex(integer(item.get("index"))) .setItemCode(text(item.get("itemCode"))) .setItemName(text(item.get("itemName"))) .setStandardValue(text(item.get("standardValue"))) .setActualValue(text(item.get("actualValue"))) .setUnit(text(item.get("unit"))) .setRequirement(text(item.get("requirement"))) .setCheckMethod(text(item.get("checkMethod"))) .setGroup(item.get("group") instanceof Map group ? groupOf(group) : null) .setUpperLimit(decimal(item.get("upperLimit"))) .setLowerLimit(decimal(item.get("lowerLimit"))) .setResult(text(item.get("result"))) .setResultText(text(item.get("resultText"))) .setRemark(text(item.get("remark"))); } /** 分组信息回读:丢了它,历史报告重出时组名就跨不住整组了 */ private static InspectionItem.Group groupOf(Map group) { return new InspectionItem.Group() .setChildName(text(group.get("childName"))) .setSpan(span(group.get("span"))) .setHidden(text(group.get("hidden"))); } /** 文本字段:null 得空串。与 {@link ReportFields} 的默认值语义一致——空串是「没值」,不是「缺字段」 */ private static String text(Object value) { return JsValues.asText(value); } private static int integer(Object value) { return value instanceof Number number ? number.intValue() : 0; } /** 合并行数取不到值时按 1 算:0 会渲染成 rowspan="0",把整行的列对齐搞坏 */ private static int span(Object value) { return value instanceof Number number ? number.intValue() : 1; } /** 上下限可以为空(没有规格区间),空值要原样保留成 null,不能变成 0 */ private static Double decimal(Object value) { return value instanceof Number number ? number.doubleValue() : null; } }