11 小时以前 9bad721754fe8bbe2e5f459d0706e0fefac569f3
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
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;
 
/**
 * 报告上下文与「数据快照」之间的编解码。
 * <p>
 * 报告实例落库时要冻结一份 {@code data_snapshot},保证历史报告不因业务数据变化而改变。
 * 快照就是 {@link ReportContext#toScope()} 那份结构({@code {report, inspectionItems}}),
 * 能直接交给 JSON 列的类型处理器;反过来要重新生成时,得把它读回 {@link ReportContext}。
 * <p>
 * <b>为什么手写 fromMap 而不用 Jackson 反序列化:</b>一是与引擎其余部分风格一致
 * ({@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() {
    }
 
    /**
     * 上下文 → 快照。
     * <p>
     * 传进来的通常已经是判定后的上下文({@code RenderOutcome.context()}),
     * 冻结的应当是「产出这份 HTML 的那份数据」,包含算好的 PASS/FAIL 与合格率。
     */
    public static Map<String, Object> toMap(ReportContext context) {
        return context == null ? ReportContext.empty().toScope() : context.toScope();
    }
 
    /** 快照 → 上下文。缺字段、类型不对都按「这个字段没有值」处理,不抛异常 */
    public static ReportContext fromMap(Map<String, Object> 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<InspectionItem> itemsOf(List<?> items) {
        List<InspectionItem> 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;
    }
 
}