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;
}
}