package jnpf.elnService.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jnpf.elnEntity.runtime.ElnRecordEntity; import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity; import jnpf.elnEntity.runtime.ElnRecordRevisionEntity; import jnpf.elnEntity.runtime.ElnRecordRepeatRowEntity; import jnpf.elnEntity.runtime.ElnRecordRepeatValueEntity; import jnpf.elnEntity.field.ElnFieldVersionEntity; import jnpf.elnEntity.template.ElnTemplateBindingEntity; import jnpf.elnEntity.template.ElnTemplateVos; import jnpf.elnMapper.ElnRecordFieldValueMapper; import jnpf.elnMapper.ElnRecordMapper; import jnpf.elnMapper.ElnRecordRevisionMapper; import jnpf.elnMapper.ElnRecordRepeatRowMapper; import jnpf.elnMapper.ElnRecordRepeatValueMapper; import jnpf.elnMapper.ElnFieldVersionMapper; import jnpf.elnMapper.ElnTemplateBindingMapper; import jnpf.elnService.ElnRecordFillDataService; import jnpf.elnService.ElnRecordFillTicketService; import jnpf.elnService.field.ElnTestResultFields; import jnpf.elnService.template.ElnTemplateCheckboxGroupService; import jnpf.elnService.template.ElnTemplateRepeatGroupService; import jnpf.elnService.template.ElnTemplateStepService; import jnpf.database.util.TenantDataSourceUtil; import jnpf.exception.DataException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @Service public class ElnRecordFillDataServiceImpl implements ElnRecordFillDataService { private final ElnRecordMapper recordMapper; private final ElnRecordRevisionMapper revisionMapper; private final ElnRecordFieldValueMapper valueMapper; private final ElnFieldVersionMapper fieldVersionMapper; private final ElnTemplateBindingMapper bindingMapper; private final ElnRecordRepeatRowMapper repeatRowMapper; private final ElnRecordRepeatValueMapper repeatValueMapper; private final ElnTemplateRepeatGroupService repeatGroupService; private final ElnTemplateStepService stepService; private final ElnTemplateCheckboxGroupService checkboxGroupService; @Autowired public ElnRecordFillDataServiceImpl(ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper valueMapper, ElnFieldVersionMapper fieldVersionMapper, ElnTemplateBindingMapper bindingMapper, ElnRecordRepeatRowMapper repeatRowMapper, ElnRecordRepeatValueMapper repeatValueMapper, ElnTemplateRepeatGroupService repeatGroupService, ElnTemplateCheckboxGroupService checkboxGroupService, ElnTemplateStepService stepService) { this(recordMapper, revisionMapper, valueMapper, fieldVersionMapper, bindingMapper, repeatRowMapper, repeatValueMapper, repeatGroupService, checkboxGroupService, stepService, new TenantRoute() { @Override public void switchTo(String tenantId) { TenantDataSourceUtil.switchTenant(tenantId); } @Override public void clear() { TenantDataSourceUtil.clearLocalTenantInfo(); } }); } ElnRecordFillDataServiceImpl(ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper valueMapper, ElnFieldVersionMapper fieldVersionMapper, TenantRoute tenantRoute) { this(recordMapper, revisionMapper, valueMapper, fieldVersionMapper, null, null, null, null, null, null, tenantRoute); } ElnRecordFillDataServiceImpl(ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper valueMapper, ElnFieldVersionMapper fieldVersionMapper, ElnRecordRepeatRowMapper repeatRowMapper, ElnRecordRepeatValueMapper repeatValueMapper, TenantRoute tenantRoute) { this(recordMapper, revisionMapper, valueMapper, fieldVersionMapper, null, repeatRowMapper, repeatValueMapper, null, null, null, tenantRoute); } ElnRecordFillDataServiceImpl(ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper valueMapper, ElnFieldVersionMapper fieldVersionMapper, ElnRecordRepeatRowMapper repeatRowMapper, ElnRecordRepeatValueMapper repeatValueMapper, ElnTemplateRepeatGroupService repeatGroupService, TenantRoute tenantRoute) { this(recordMapper, revisionMapper, valueMapper, fieldVersionMapper, null, repeatRowMapper, repeatValueMapper, repeatGroupService, null, null, tenantRoute); } ElnRecordFillDataServiceImpl(ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper valueMapper, ElnFieldVersionMapper fieldVersionMapper, ElnRecordRepeatRowMapper repeatRowMapper, ElnRecordRepeatValueMapper repeatValueMapper, ElnTemplateRepeatGroupService repeatGroupService, ElnTemplateStepService stepService, TenantRoute tenantRoute) { this(recordMapper, revisionMapper, valueMapper, fieldVersionMapper, null, repeatRowMapper, repeatValueMapper, repeatGroupService, null, stepService, tenantRoute); } ElnRecordFillDataServiceImpl(ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper valueMapper, ElnFieldVersionMapper fieldVersionMapper, ElnTemplateBindingMapper bindingMapper, ElnRecordRepeatRowMapper repeatRowMapper, ElnRecordRepeatValueMapper repeatValueMapper, ElnTemplateRepeatGroupService repeatGroupService, ElnTemplateStepService stepService, TenantRoute tenantRoute) { this(recordMapper, revisionMapper, valueMapper, fieldVersionMapper, bindingMapper, repeatRowMapper, repeatValueMapper, repeatGroupService, null, stepService, tenantRoute); } ElnRecordFillDataServiceImpl(ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper valueMapper, ElnFieldVersionMapper fieldVersionMapper, ElnTemplateBindingMapper bindingMapper, ElnRecordRepeatRowMapper repeatRowMapper, ElnRecordRepeatValueMapper repeatValueMapper, ElnTemplateRepeatGroupService repeatGroupService, ElnTemplateCheckboxGroupService checkboxGroupService, ElnTemplateStepService stepService, TenantRoute tenantRoute) { this.recordMapper = recordMapper; this.revisionMapper = revisionMapper; this.valueMapper = valueMapper; this.fieldVersionMapper = fieldVersionMapper; this.bindingMapper = bindingMapper; this.repeatRowMapper = repeatRowMapper; this.repeatValueMapper = repeatValueMapper; this.repeatGroupService = repeatGroupService; this.stepService = stepService; this.checkboxGroupService = checkboxGroupService; this.tenantRoute = tenantRoute; } private final TenantRoute tenantRoute; @Override public Map fields(ElnRecordFillTicketService.FillTicketContext ticket) { @SuppressWarnings("unchecked") Map fields = (Map) data(ticket).get("fields"); return fields; } @Override public Map data(ElnRecordFillTicketService.FillTicketContext ticket) { if (!ElnRecordFillTicketService.BIZ_MODULE.equals(ticket.getBizModule())) { Map empty = new LinkedHashMap<>(); empty.put("fields", new LinkedHashMap<>()); empty.put("repeatGroups", new ArrayList<>()); return empty; } return inTenant(ticket.getTenantId(), () -> dataInTenant(ticket)); } private Map dataInTenant(ElnRecordFillTicketService.FillTicketContext ticket) { ElnRecordEntity record = recordMapper.selectOne(Wrappers.lambdaQuery() .eq(ElnRecordEntity::getFTenantId, ticket.getTenantId()) .eq(ElnRecordEntity::getId, ticket.getBizDataId()) .and(query -> query.isNull(ElnRecordEntity::getFDeleteMark) .or().eq(ElnRecordEntity::getFDeleteMark, 0)).last("limit 1")); if (record == null || record.getDangqianXiudingId() == null) { throw new DataException("电子记录不存在或当前修订无效"); } ElnRecordRevisionEntity revision = revisionMapper.selectOne( Wrappers.lambdaQuery() .eq(ElnRecordRevisionEntity::getFTenantId, ticket.getTenantId()) .eq(ElnRecordRevisionEntity::getId, record.getDangqianXiudingId()) .eq(ElnRecordRevisionEntity::getDianziJiluId, record.getId()) .eq(ElnRecordRevisionEntity::getJiluWendang, ticket.getFileId()) .and(query -> query.isNull(ElnRecordRevisionEntity::getFDeleteMark) .or().eq(ElnRecordRevisionEntity::getFDeleteMark, 0)).last("limit 1")); if (revision == null) { throw new DataException("电子记录文档与当前修订不匹配"); } Map bindings = activeBindings( ticket.getTenantId(), record.getMubanId()); Map checkboxGroups = checkboxGroupRules( ticket.getTenantId(), record.getMubanId()); List values = valueMapper.selectList( Wrappers.lambdaQuery() .eq(ElnRecordFieldValueEntity::getFTenantId, ticket.getTenantId()) .eq(ElnRecordFieldValueEntity::getDianziJiluId, record.getId()) .eq(ElnRecordFieldValueEntity::getXiudingId, revision.getId()) .and(query -> query.isNull(ElnRecordFieldValueEntity::getFDeleteMark) .or().eq(ElnRecordFieldValueEntity::getFDeleteMark, 0)) .orderByAsc(ElnRecordFieldValueEntity::getId)); Map fields = new LinkedHashMap<>(); for (ElnRecordFieldValueEntity value : values) { if (value == null || value.getZiduanBianma() == null) { continue; } Map field = new LinkedHashMap<>(); ElnFieldVersionEntity version = fixedVersion( ticket.getTenantId(), value.getZiduanBanbenId()); String type = typeOf(version); field.put("type", type); field.put("value", displayValue(value, type, version.getShujuLeixing())); field.put("required", Integer.valueOf(1).equals(version.getShifouBitian())); appendValueSource(field, value.getZhiLaiyuan()); if ("LIMS_TASK".equals(value.getZhiLaiyuan()) && value.getYuanShuxingBanben() != null) { field.put("readOnly", true); if (value.getZhiWenben() == null && value.getZhiShuzhi() == null && value.getZhiRiqi() == null) { field.put("value", null); } } appendCheckboxGroup(field, bindings.get(value.getMubanBangdingId()), "PRIMARY", type, checkboxGroups); appendFormulaState(field, value.getJisuanZhuangtai(), value.getJisuanXiaoxi()); for (String tag : ElnTestResultFields.docxTags(value.getZiduanBianma())) { fields.put(tag, field); } } List> repeatGroups = appendRepeatFields( fields, bindings, checkboxGroups, ticket, record, revision); Map result = new LinkedHashMap<>(); result.put("fields", fields); result.put("repeatGroups", repeatGroups); if (stepService != null) { result.put("stepStructure", stepService.fillStructure( ticket.getTenantId(), record.getMubanId())); } return result; } private List> appendRepeatFields(Map result, Map bindings, Map checkboxGroups, ElnRecordFillTicketService.FillTicketContext ticket, ElnRecordEntity record, ElnRecordRevisionEntity revision) { if (repeatRowMapper == null && repeatValueMapper == null) return new ArrayList<>(); if (repeatRowMapper == null || repeatValueMapper == null) { throw new DataException("重复行读取服务配置不完整"); } List rows = repeatRowMapper.selectList( Wrappers.lambdaQuery() .eq(ElnRecordRepeatRowEntity::getFTenantId, ticket.getTenantId()) .eq(ElnRecordRepeatRowEntity::getDianziJiluId, record.getId()) .eq(ElnRecordRepeatRowEntity::getXiudingId, revision.getId()) .in(ElnRecordRepeatRowEntity::getOperationStatus, "PREPARED", "ACTIVE", "DELETE_PREPARED") .and(q -> q.isNull(ElnRecordRepeatRowEntity::getFDeleteMark) .or().eq(ElnRecordRepeatRowEntity::getFDeleteMark, 0))); rows.sort(Comparator.comparing(ElnRecordRepeatRowEntity::getRepeatGroupCode) .thenComparing(ElnRecordRepeatRowEntity::getRowOrder)); Map rowsByIdentity = new LinkedHashMap<>(); for (ElnRecordRepeatRowEntity row : rows) { if (row == null || !record.getMubanId().equals(row.getMubanId()) || rowsByIdentity.put(repeatRowKey( row.getRepeatGroupCode(), row.getRowId()), row) != null) { throw new DataException("电子记录重复行身份无效"); } } List values = repeatValueMapper.selectList( Wrappers.lambdaQuery() .eq(ElnRecordRepeatValueEntity::getFTenantId, ticket.getTenantId()) .eq(ElnRecordRepeatValueEntity::getDianziJiluId, record.getId()) .eq(ElnRecordRepeatValueEntity::getXiudingId, revision.getId()) .and(q -> q.isNull(ElnRecordRepeatValueEntity::getFDeleteMark) .or().eq(ElnRecordRepeatValueEntity::getFDeleteMark, 0))); Map> valuesByRow = new LinkedHashMap<>(); for (ElnRecordRepeatValueEntity value : values) { String rowKey = repeatRowKey(value.getRepeatGroupCode(), value.getRepeatRowId()); ElnRecordRepeatRowEntity row = rowsByIdentity.get(rowKey); if (row == null || !record.getMubanId().equals(value.getMubanId())) { throw new DataException("电子记录重复字段值与运行时行不一致"); } valuesByRow.computeIfAbsent(rowKey, ignored -> new ArrayList<>()).add(value); } for (ElnRecordRepeatRowEntity row : rows) { String rowKey = repeatRowKey(row.getRepeatGroupCode(), row.getRowId()); List rowValues = valuesByRow.getOrDefault( rowKey, new ArrayList<>()); rowValues.sort(Comparator.comparing(ElnRecordRepeatValueEntity::getZiduanBianma)); for (ElnRecordRepeatValueEntity value : rowValues) { ElnFieldVersionEntity version = fixedVersion( ticket.getTenantId(), value.getZiduanBanbenId()); String type = typeOf(version); Map field = new LinkedHashMap<>(); field.put("type", type); field.put("value", displayValue(value.getZhiWenben(), value.getZhiShuzhi(), value.getZhiRiqi(), type, version.getShujuLeixing())); field.put("required", Integer.valueOf(1).equals(version.getShifouBitian())); appendValueSource(field, value.getZhiLaiyuan()); appendCheckboxGroup(field, bindings.get(value.getMubanBangdingId()), "REPEAT", type, checkboxGroups); appendFormulaState(field, value.getJisuanZhuangtai(), value.getJisuanXiaoxi()); String tag = "eln.repeat." + row.getRepeatGroupCode() + "." + row.getRowId() + "." + value.getZiduanBianma(); if (result.put(tag, field) != null) { throw new DataException("电子记录重复字段 Tag 冲突"); } } } return repeatGroupMetadata(ticket, record, rows, bindings); } private List> repeatGroupMetadata( ElnRecordFillTicketService.FillTicketContext ticket, ElnRecordEntity record, List rows, Map bindings) { Map configured = new LinkedHashMap<>(); if (repeatGroupService != null) { for (ElnTemplateVos.RepeatGroup group : repeatGroupService.list( ticket.getTenantId(), record.getMubanId())) { if (group == null || !StringUtils.hasText(group.getGroupCode()) || configured.put(group.getGroupCode(), group) != null) { throw new DataException("电子记录重复组配置无效"); } } } for (ElnRecordRepeatRowEntity row : rows) { if (!configured.containsKey(row.getRepeatGroupCode())) { if (repeatGroupService != null) { throw new DataException("电子记录重复行缺少模板组配置"); } ElnTemplateVos.RepeatGroup derived = new ElnTemplateVos.RepeatGroup(); derived.setGroupCode(row.getRepeatGroupCode()); configured.put(row.getRepeatGroupCode(), derived); } } List> result = new ArrayList<>(); for (ElnTemplateVos.RepeatGroup group : configured.values()) { List> rowMetadata = new ArrayList<>(); for (ElnRecordRepeatRowEntity row : rows) { if (!group.getGroupCode().equals(row.getRepeatGroupCode())) continue; Map item = new LinkedHashMap<>(); item.put("groupCode", row.getRepeatGroupCode()); item.put("rowId", row.getRowId()); item.put("rowOrder", row.getRowOrder()); item.put("status", row.getOperationStatus()); item.put("deleteOperationCode", row.getDeleteOperationCode()); item.put("deleteReason", row.getDeleteReason()); rowMetadata.add(item); } Map metadata = new LinkedHashMap<>(); metadata.put("groupCode", group.getGroupCode()); metadata.put("minRows", group.getMinRows()); metadata.put("maxRows", group.getMaxRows()); metadata.put("stepId", group.getStepId()); metadata.put("checkboxGroups", repeatCheckboxGroups( group.getGroupCode(), bindings)); metadata.put("activeRowCount", rowMetadata.size()); metadata.put("rows", rowMetadata); result.add(metadata); } return result; } private static Map repeatCheckboxGroups(String groupCode, Map bindings) { Map result = new LinkedHashMap<>(); for (ElnTemplateBindingEntity binding : bindings.values()) { if (binding == null || !"REPEAT".equals(binding.getKongjianJuese()) || !"CHECKBOX".equals(binding.getKongjianLeixing()) || !groupCode.equals(binding.getRepeatGroupCode()) || !StringUtils.hasText(binding.getZiduanBianma()) || !StringUtils.hasText(binding.getCheckboxGroupCode())) { continue; } String previous = result.put( binding.getZiduanBianma(), binding.getCheckboxGroupCode()); if (previous != null && !previous.equals(binding.getCheckboxGroupCode())) { throw new DataException("电子记录重复列选择组配置冲突"); } } return result; } private static String repeatRowKey(String groupCode, String rowId) { return groupCode + "\n" + rowId; } private Map activeBindings( String tenantId, String templateId) { Map result = new LinkedHashMap<>(); if (bindingMapper == null) return result; List bindings = bindingMapper.selectList( Wrappers.lambdaQuery() .eq(ElnTemplateBindingEntity::getFTenantId, tenantId) .eq(ElnTemplateBindingEntity::getMubanId, templateId) .eq(ElnTemplateBindingEntity::getBangdingZhuangtai, "ACTIVE") .and(query -> query.isNull(ElnTemplateBindingEntity::getFDeleteMark) .or().eq(ElnTemplateBindingEntity::getFDeleteMark, 0))); for (ElnTemplateBindingEntity binding : bindings) { if (binding == null || result.put(binding.getId(), binding) != null) { throw new DataException("电子记录活动字段绑定重复"); } } return result; } private static void appendCheckboxGroup(Map field, ElnTemplateBindingEntity binding, String expectedRole, String type, Map checkboxGroups) { if (binding != null && expectedRole.equals(binding.getKongjianJuese()) && "CHECKBOX".equals(binding.getKongjianLeixing()) && "checkbox".equals(type) && !Boolean.TRUE.equals(field.get("readOnly")) && StringUtils.hasText(binding.getCheckboxGroupCode())) { String groupCode = binding.getCheckboxGroupCode(); ElnTemplateVos.CheckboxGroup rule = checkboxGroups.get(groupCode); field.put("checkboxGroupCode", groupCode); field.put("checkboxGroupMinSelected", rule == null ? Integer.valueOf(0) : rule.getMinSelected()); field.put("checkboxGroupMaxSelected", rule == null ? Integer.valueOf(1) : rule.getMaxSelected()); } } private Map checkboxGroupRules( String tenantId, String templateId) { Map result = new LinkedHashMap<>(); if (checkboxGroupService == null) return result; for (ElnTemplateVos.CheckboxGroup group : checkboxGroupService.list(tenantId, templateId)) { if (group == null || !StringUtils.hasText(group.getGroupCode()) || !validCheckboxGroupRule(group) || result.put(group.getGroupCode(), group) != null) { throw new DataException("电子记录选择组数量规则无效"); } } return result; } private static boolean validCheckboxGroupRule(ElnTemplateVos.CheckboxGroup group) { Integer minSelected = group.getMinSelected(); Integer maxSelected = group.getMaxSelected(); return minSelected != null && minSelected >= 0 && (maxSelected == null || (maxSelected >= 1 && minSelected <= maxSelected)); } private static void appendValueSource(Map field, String valueSource) { if (StringUtils.hasText(valueSource)) field.put("valueSource", valueSource); field.put("readOnly", "CROSS_TEMPLATE".equals(valueSource)); } private static void appendFormulaState(Map field, String status, String message) { if (StringUtils.hasText(status)) field.put("formulaStatus", status); if (StringUtils.hasText(message)) field.put("formulaMessage", message); } private ElnFieldVersionEntity fixedVersion(String tenantId, String versionId) { ElnFieldVersionEntity version = fieldVersionMapper.selectOne( Wrappers.lambdaQuery() .eq(ElnFieldVersionEntity::getFTenantId, tenantId) .eq(ElnFieldVersionEntity::getId, versionId).last("limit 1")); if (version == null) throw new DataException("电子记录固定字段版本不存在"); return version; } private T inTenant(String tenantId, TenantAction action) { if (!StringUtils.hasText(tenantId)) { throw new DataException("电子记录租户不能为空"); } try { tenantRoute.switchTo(tenantId.trim()); return action.get(); } finally { tenantRoute.clear(); } } @FunctionalInterface interface TenantAction { T get(); } interface TenantRoute { void switchTo(String tenantId); void clear(); } private static String typeOf(ElnFieldVersionEntity version) { if (version.getShujuLeixing() == null) return "text"; String type = version.getShujuLeixing().toUpperCase(); if ("TEXTAREA".equals(type) || "MULTILINE".equals(type)) return "multiline"; if ("SELECT".equals(type) || "DROPDOWN".equals(type)) return "select"; if ("CHECKBOX".equals(type) || "BOOLEAN".equals(type)) return "checkbox"; if ("DATE".equals(type) || "DATETIME".equals(type)) return "date"; return "text"; } private static Object displayValue(ElnRecordFieldValueEntity value, String type, String fieldType) { return displayValue(value.getZhiWenben(), value.getZhiShuzhi(), value.getZhiRiqi(), type, fieldType); } private static Object displayValue(String text, BigDecimal number, Date date, String type, String fieldType) { if (number != null) return number.toPlainString(); if (date != null) return new SimpleDateFormat("DATETIME".equalsIgnoreCase(fieldType) ? "yyyy-MM-dd HH:mm:ss" : "yyyy-MM-dd").format(date); if ("checkbox".equals(type)) { return Boolean.valueOf(text); } return text; } }