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