刘光辉
15 小时以前 34981c30a78e8bbd7791131059a9210f9928b62c
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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
package jnpf.limsService.print;
 
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jnpf.exception.DataException;
import jnpf.limsEntity.JianyanXiangmuGuanliEntity;
import jnpf.limsEntity.LimsJianyanXiangEntity;
import jnpf.limsEntity.LimsPrintTemplateEntity;
import jnpf.limsEntity.LimsQingyandanEntity;
import jnpf.limsMapper.JianyanXiangmuGuanliMapper;
import jnpf.limsService.LimsJianyanXiangService;
import jnpf.limsService.LimsQingyandanService;
import jnpf.permission.UserApi;
import jnpf.permission.entity.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
 
/**
 * "成品质量报告"模板渲染器(template_id = jianyan_baogao)。
 *
 * <p>输入:lims_qingyandan.f_id;报告数据与表头都在这张行上(baogao_* 列)。
 * 输出包含表头、检测项分组(按 fenxi_leixing 聚合 lims_jianyan_xiang)、
 * 签名区(报告人/复核人/QC 经理)、依据 / 结论 / 备注(来自模板 config)。
 */
@Component
public class JianyanBaogaoRenderer implements PrintTemplateRenderer {
 
    public static final String TEMPLATE_ID = "jianyan_baogao";
 
    @Autowired
    private LimsQingyandanService qingyandanService;
 
    @Autowired
    private LimsJianyanXiangService jianyanXiangService;
 
    @Autowired
    private JianyanXiangmuGuanliMapper xiangmuGuanliMapper;
 
    @Autowired
    private UserApi usersApi;
 
    @Autowired
    private ObjectMapper objectMapper;
 
    @Override
    public String getTemplateId() {
        return TEMPLATE_ID;
    }
 
    @Override
    public Map<String, Object> render(String qingyandanId, LimsPrintTemplateEntity tpl) {
        if (qingyandanId == null || qingyandanId.isEmpty()) {
            throw new DataException("缺少请验单 id");
        }
 
        // 1. 请验单(报告数据 + 表头都在这一行上,必须存在 + 未软删)
        LimsQingyandanEntity qingyandan = qingyandanService.getById(qingyandanId);
        if (qingyandan == null
                || (qingyandan.getDeleteMark() != null && qingyandan.getDeleteMark() == 1)) {
            throw new DataException("请验单不存在或已删除: " + qingyandanId);
        }
 
        // 2. 该流水号下未软删的所有 jianyan_xiang
        QueryWrapper<LimsJianyanXiangEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("jianyan_liushuihao", qingyandan.getJianyanLiushuihao());
        wrapper.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
        wrapper.orderByAsc("f_id");
        List<LimsJianyanXiangEntity> rows = jianyanXiangService.list(wrapper);
 
        // 3.1 排除 lims_jianyan_xiang.shifou_chuju_baogao = 'no' 的检验项。
        // 仅显式 "no" 才会被剔除;NULL / 其它值按"出报告"处理(保守不误伤)。
        // 该字段在 buildJianyanXiang / buildWaijianJianyanXiang 阶段就从 jiancexiang 快照过来,
        // 渲染时不再回查 lims_qingyandan_jiancexiang。
        rows = rows.stream()
                .filter(r -> !"no".equalsIgnoreCase(r.getShifouChujuBaogao()))
                .collect(Collectors.toList());
 
        // 4. 解析模板 config
        Map<String, Object> cfg = parseConfig(tpl);
        Map<String, Object> labels = asMap(cfg.get("labels"));
 
        // 5. 拼装返回结构(保持 LinkedHashMap 顺序便于阅读)
        // 顶层 companyName / docNumber / reportBookNumber 命名与前端模板
        // jianyan_baogao.html 内 templateData.* 的读取键一致,避免前端再做映射。
        //
        // docNumber 的真实业务语义是"产品质量标准文档编号 + 版次"(如
        // STP-QAD-Z003-MP-R01-06),应跟随产品/产品标准维护。当前业务尚未
        // 在主数据里建立该字段,**临时**用 jianyan_liushuihao(每次请验唯一)
        // 占位,避免页头空白。等 lims_quality_manager 等主数据补齐 doc_number
        // 后切回 cfg.doc_number 或新源即可(cfg.doc_number 字段保留不删)。
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("template_id", tpl.getTemplateId());
        data.put("template_name", tpl.getTemplateName());
        data.put("companyName", asString(cfg.get("company_name")));
        data.put("docNumber", asString(qingyandan.getJianyanLiushuihao()));
        data.put("reportBookNumber", asString(qingyandan.getBaogaoBianhao()));
        data.put("yiju", asString(cfg.get("yiju")));
        data.put("header", buildHeader(qingyandan));
        data.put("groups", buildGroups(rows));
        data.put("jielun", asString(cfg.get("jielun")));
        data.put("signatures", buildSignatures(qingyandan, labels));
        data.put("beizhu", asString(cfg.get("beizhu")));
        return data;
    }
 
    private Map<String, Object> buildHeader(LimsQingyandanEntity q) {
        Map<String, Object> h = new LinkedHashMap<>();
        h.put("chanpin_mingcheng", q.getChanpinMingcheng());
        h.put("pihao", q.getPihao());
        h.put("laiyuan_bianma", q.getLaiyuanBianma());
        h.put("chanpin_bianma", q.getChanpinBianma());
        h.put("guige", q.getGuige());
        h.put("shengchan_riqi", q.getShengchanRiqi());
        h.put("piliang", q.getPiliang());
        h.put("piliang_danwei", q.getPiliangDanwei());
        h.put("youxiaoqi", q.getYouxiaoqi());
        return h;
    }
 
    /**
     * 按"质量属性中文名"分组,组内按 jianyan_xiang 自然顺序(f_id 升序)。
     *
     * <p>"质量属性"取自检验项目主数据 jianyan_xiangmu_guanli:
     * <ol>
     *   <li>用 lims_jianyan_xiang.jianyan_xiangmu_bianma 反查主数据,得到 self;</li>
     *   <li>self.fenxi_leixing 为空 → 质量属性 = self.mingcheng(自身既是质量属性又是检验项目);</li>
     *   <li>self.fenxi_leixing 不空 → 用其值再次反查主数据 bianma 得到 parent,
     *       质量属性 = parent.mingcheng。</li>
     * </ol>
     *
     * <p>主数据缺失的容错:self 缺 → 用 lims_jianyan_xiang.jianyan_xiangmu_mingcheng 兜底;
     * parent 缺 → 用 self.mingcheng 兜底;都没有就空字符串。
     *
     * <p>下发字段 key 仍叫 "fenxi_leixing",与历史前端模板兼容;其值的语义已变成
     * "质量属性中文名"(不再是分类编码)。
     */
    private List<Map<String, Object>> buildGroups(List<LimsJianyanXiangEntity> rows) {
        if (rows.isEmpty()) {
            return new ArrayList<>();
        }
 
        // 1. 一次性把这批 jianyan_xiang 涉及到的"自身 bianma"的主数据捞出来
        Set<String> selfBianmas = rows.stream()
                .map(LimsJianyanXiangEntity::getJianyanXiangmuBianma)
                .filter(s -> s != null && !s.isEmpty())
                .collect(Collectors.toSet());
        Map<String, JianyanXiangmuGuanliEntity> selfMap =
                fetchXiangmuByBianmas(selfBianmas);
 
        // 2. 在 self 集合里找需要进一步反查的 parent bianma
        Set<String> parentBianmas = selfMap.values().stream()
                .map(JianyanXiangmuGuanliEntity::getFenxiLeixing)
                .filter(s -> s != null && !s.isEmpty())
                .collect(Collectors.toSet());
        Map<String, JianyanXiangmuGuanliEntity> parentMap =
                fetchXiangmuByBianmas(parentBianmas);
 
        // 3. 按"质量属性中文名"分组,保留出现顺序
        Map<String, List<LimsJianyanXiangEntity>> grouped = new LinkedHashMap<>();
        for (LimsJianyanXiangEntity r : rows) {
            String key = resolveZhiliangShuxing(r, selfMap, parentMap);
            grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(r);
        }
 
        // 4. 拼装 groups
        List<Map<String, Object>> groups = new ArrayList<>();
        for (Map.Entry<String, List<LimsJianyanXiangEntity>> e : grouped.entrySet()) {
            Map<String, Object> g = new LinkedHashMap<>();
            g.put("fenxi_leixing", e.getKey());
            List<Map<String, Object>> items = new ArrayList<>();
            for (LimsJianyanXiangEntity r : e.getValue()) {
                Map<String, Object> item = new LinkedHashMap<>();
                item.put("jianyan_xiangmu_mingcheng", r.getJianyanXiangmuMingcheng());
                item.put("biaozhun_guiding", r.getBiaozhunGuiding());
                item.put("jianyan_jieguo", r.getJianyanJieguo());
                items.add(item);
            }
            g.put("items", items);
            groups.add(g);
        }
        return groups;
    }
 
    /** 批量按 bianma 反查主数据(自动过滤软删 + 多租户拦截器自动加 tenant 条件)。 */
    private Map<String, JianyanXiangmuGuanliEntity> fetchXiangmuByBianmas(Set<String> bianmas) {
        if (bianmas == null || bianmas.isEmpty()) {
            return new HashMap<>();
        }
        QueryWrapper<JianyanXiangmuGuanliEntity> qw = new QueryWrapper<>();
        qw.in("bianma", bianmas);
        qw.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
        Map<String, JianyanXiangmuGuanliEntity> map = new HashMap<>();
        for (JianyanXiangmuGuanliEntity e : xiangmuGuanliMapper.selectList(qw)) {
            if (e != null && e.getBianma() != null) {
                map.put(e.getBianma(), e);
            }
        }
        return map;
    }
 
    private String resolveZhiliangShuxing(LimsJianyanXiangEntity r,
                                          Map<String, JianyanXiangmuGuanliEntity> selfMap,
                                          Map<String, JianyanXiangmuGuanliEntity> parentMap) {
        JianyanXiangmuGuanliEntity self = selfMap.get(r.getJianyanXiangmuBianma());
        if (self == null) {
            return r.getJianyanXiangmuMingcheng() != null ? r.getJianyanXiangmuMingcheng() : "";
        }
        if (self.getFenxiLeixing() == null || self.getFenxiLeixing().isEmpty()) {
            return self.getMingcheng() != null ? self.getMingcheng() : "";
        }
        JianyanXiangmuGuanliEntity parent = parentMap.get(self.getFenxiLeixing());
        if (parent == null || parent.getMingcheng() == null) {
            return self.getMingcheng() != null ? self.getMingcheng() : "";
        }
        return parent.getMingcheng();
    }
 
    private Map<String, Object> buildSignatures(LimsQingyandanEntity r, Map<String, Object> labels) {
        // 三个签名人 id 一次批量查回真实姓名
        List<String> userIds = Arrays.asList(r.getBaogaoRen(), r.getBaogaoShenheRen(), r.getBaogaoPizhunRen())
                .stream()
                .filter(s -> s != null && !s.isEmpty())
                .distinct()
                .collect(Collectors.toList());
        Map<String, String> idToRealName = new HashMap<>();
        if (!userIds.isEmpty()) {
            List<UserEntity> users = usersApi.getUserName(userIds);
            if (users != null) {
                for (UserEntity u : users) {
                    if (u != null && u.getId() != null) {
                        idToRealName.put(u.getId(), u.getRealName());
                    }
                }
            }
        }
 
        Map<String, Object> sig = new LinkedHashMap<>();
        sig.put("baogaoren", makeSignatureBlock(asString(labels.get("baogaoren"), "报告人/日期"),
                r.getBaogaoRen(), r.getBaogaoRiqi(), idToRealName));
        sig.put("fuheren", makeSignatureBlock(asString(labels.get("fuheren"), "复核人/日期"),
                r.getBaogaoShenheRen(), r.getBaogaoShenheRiqi(), idToRealName));
        sig.put("pizhunren", makeSignatureBlock(asString(labels.get("pizhunren"), "QC经理/日期"),
                r.getBaogaoPizhunRen(), r.getBaogaoPizhunRiqi(), idToRealName));
        return sig;
    }
 
    private Map<String, Object> makeSignatureBlock(String label, String userId, Date date,
                                                   Map<String, String> idToRealName) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("label", label);
        m.put("user_id", userId);
        m.put("name", userId != null ? idToRealName.get(userId) : null);
        m.put("date", date);
        return m;
    }
 
    private Map<String, Object> parseConfig(LimsPrintTemplateEntity tpl) {
        String json = tpl.getConfig();
        if (json == null || json.isEmpty()) {
            return Collections.emptyMap();
        }
        try {
            return objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
        } catch (Exception e) {
            throw new DataException("打印模板 config JSON 解析失败: " + e.getMessage());
        }
    }
 
    private static String asString(Object v) {
        return v == null ? "" : v.toString();
    }
 
    private static String asString(Object v, String fallback) {
        return v == null ? fallback : v.toString();
    }
 
    @SuppressWarnings("unchecked")
    private static Map<String, Object> asMap(Object v) {
        if (v instanceof Map) {
            return (Map<String, Object>) v;
        }
        return Collections.emptyMap();
    }
}