package jnpf.elnService.impl; import cn.hutool.core.util.IdUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jnpf.base.UserInfo; import jnpf.database.util.TenantDataSourceUtil; import jnpf.elnEntity.field.ElnFieldForms; import jnpf.elnEntity.field.ElnFieldVersionEntity; import jnpf.elnEntity.ElnDocumentFileVo; import jnpf.elnEntity.runtime.ElnRecordEntity; import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity; import jnpf.elnEntity.runtime.ElnRecordForms; import jnpf.elnEntity.runtime.ElnRecordRevisionEntity; import jnpf.elnEntity.runtime.ElnRecordVos; import jnpf.elnEntity.template.ElnTemplateBindingEntity; import jnpf.elnEntity.template.ElnTemplateVersionEntity; import jnpf.elnMapper.ElnTemplateMapper; import jnpf.elnMapper.ElnFieldVersionMapper; import jnpf.elnMapper.ElnRecordFieldValueMapper; import jnpf.elnMapper.ElnRecordMapper; import jnpf.elnMapper.ElnRecordRevisionMapper; import jnpf.elnMapper.ElnTemplateBindingMapper; import jnpf.elnService.ElnTestWorkItemSnapshotProvider; import jnpf.elnService.ElnRecordCreationService; import jnpf.elnService.ElnCrossTemplateSnapshotResolver; import jnpf.elnService.ElnRecordFormulaCalculationService; import jnpf.elnService.ElnRecordRevisionService; import jnpf.elnService.template.ElnTemplateFileService; import jnpf.exception.DataException; import jnpf.lims.api.model.TestWorkItemSnapshotBatch; import jnpf.lims.api.model.TestWorkItemSnapshot; import jnpf.lims.api.model.TestWorkItemSnapshotProperty; import jnpf.lims.api.model.ElnSourcePropertyRef; import jnpf.lims.api.model.ElnSourceSnapshot; import jnpf.model.tenant.TenantVO; import jnpf.util.JsonUtil; import jnpf.util.UserProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @Service public class ElnRecordCreationServiceImpl implements ElnRecordCreationService { private static final String ACTIVE = "ACTIVE"; private static final String PRIMARY = "PRIMARY"; private static final String DRAFT = "DRAFT"; private static final String LIMS_TASK = "LIMS_TASK"; private static final String SYSTEM = "SYSTEM"; private final ElnTestWorkItemSnapshotProvider taskProvider; private final ElnTemplateMapper templateMapper; private final ElnTemplateBindingMapper bindingMapper; private final ElnFieldVersionMapper fieldVersionMapper; private final ElnRecordMapper recordMapper; private final ElnRecordRevisionMapper revisionMapper; private final ElnRecordFieldValueMapper fieldValueMapper; private final ElnTemplateFileService fileService; private final ElnRecordFormulaCalculationService formulaCalculationService; private final ElnRecordRevisionService revisionService; private final CurrentUserProvider currentUserProvider; private final ElnCrossTemplateSnapshotResolver crossTemplateSnapshotResolver; @Autowired public ElnRecordCreationServiceImpl(ElnTestWorkItemSnapshotProvider taskProvider, ElnTemplateMapper templateMapper, ElnTemplateBindingMapper bindingMapper, ElnFieldVersionMapper fieldVersionMapper, ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper fieldValueMapper, ElnTemplateFileService fileService, ElnRecordFormulaCalculationService formulaCalculationService, ElnRecordRevisionService revisionService, ElnCrossTemplateSnapshotResolver crossTemplateSnapshotResolver) { this(taskProvider, templateMapper, bindingMapper, fieldVersionMapper, recordMapper, revisionMapper, fieldValueMapper, fileService, formulaCalculationService, revisionService, ElnRecordCreationServiceImpl::currentUser, crossTemplateSnapshotResolver); } ElnRecordCreationServiceImpl(ElnTestWorkItemSnapshotProvider taskProvider, ElnTemplateMapper templateMapper, ElnTemplateBindingMapper bindingMapper, ElnFieldVersionMapper fieldVersionMapper, ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper fieldValueMapper, ElnTemplateFileService fileService, ElnRecordFormulaCalculationService formulaCalculationService, ElnRecordRevisionService revisionService, CurrentUserProvider currentUserProvider) { this(taskProvider, templateMapper, bindingMapper, fieldVersionMapper, recordMapper, revisionMapper, fieldValueMapper, fileService, formulaCalculationService, revisionService, currentUserProvider, ElnCrossTemplateSnapshotResolver.none()); } ElnRecordCreationServiceImpl(ElnTestWorkItemSnapshotProvider taskProvider, ElnTemplateMapper templateMapper, ElnTemplateBindingMapper bindingMapper, ElnFieldVersionMapper fieldVersionMapper, ElnRecordMapper recordMapper, ElnRecordRevisionMapper revisionMapper, ElnRecordFieldValueMapper fieldValueMapper, ElnTemplateFileService fileService, ElnRecordFormulaCalculationService formulaCalculationService, ElnRecordRevisionService revisionService, CurrentUserProvider currentUserProvider, ElnCrossTemplateSnapshotResolver crossTemplateSnapshotResolver) { this.taskProvider = taskProvider; this.templateMapper = templateMapper; this.bindingMapper = bindingMapper; this.fieldVersionMapper = fieldVersionMapper; this.recordMapper = recordMapper; this.revisionMapper = revisionMapper; this.fieldValueMapper = fieldValueMapper; this.fileService = fileService; this.formulaCalculationService = formulaCalculationService; this.revisionService = revisionService; this.currentUserProvider = currentUserProvider; this.crossTemplateSnapshotResolver = crossTemplateSnapshotResolver; } @Override @Transactional(rollbackFor = Exception.class) public ElnRecordVos.Created create(ElnRecordForms.Create command) { String testWorkItemId = required(command == null ? null : command.getTestWorkItemId(), "检验项不能为空"); String operationCode = required(command == null ? null : command.getOperationCode(), "操作码不能为空"); UserContext user = currentUserProvider.get(); ElnRecordEntity existing = recordMapper.selectLiveByTestWorkItem(user.tenantId, testWorkItemId); if (existing != null) { return revisionService.open(command); } // Resolve the project first; template bindings determine the task properties needed next. TestWorkItemSnapshot projectSnapshot = snapshot( user, testWorkItemId, Collections.emptyList()); ElnTemplateVersionEntity template = resolveTemplate( user.tenantId, projectSnapshot.getTestDefinitionCode()); List bindings = activePrimaryBindings(user.tenantId, template.getId()); if (bindings.isEmpty()) { throw new DataException("当前电子记录模板没有活动主字段绑定"); } List versions = new ArrayList<>(bindings.size()); Set taskProperties = new LinkedHashSet<>(); Set configuredProperties = new LinkedHashSet<>(); for (ElnTemplateBindingEntity binding : bindings) { ElnFieldVersionEntity version = fieldVersion(user.tenantId, binding); versions.add(version); ElnFieldForms.Binding config = parseBinding(version.getBangdingPeizhi()); if (config != null && config.getRead() != null && LIMS_TASK.equals(config.getRead().getSource()) && StringUtils.hasText(config.getRead().getProperty())) { if (config.getRead().getPropertyRevision() == null) { taskProperties.add(config.getRead().getProperty()); } else { configuredProperties.add(ElnConfiguredSourceValues.reference(config.getRead())); } } } TestWorkItemSnapshot snapshot = projectSnapshot; if (!taskProperties.isEmpty()) { snapshot = snapshot(user, testWorkItemId, new ArrayList<>(taskProperties)); if (!projectSnapshot.getTestDefinitionCode().equals(snapshot.getTestDefinitionCode())) { throw new DataException("LIMS 检验任务项目编码在电子记录创建期间发生变化"); } } List sourceRefs = new ArrayList<>(configuredProperties); Map configuredSnapshots = sourceRefs.isEmpty() ? Collections.emptyMap() : ElnConfiguredSourceValues.index(testWorkItemId, sourceRefs, taskProvider.resolveConfiguredAsActor(user.tenantId, user.userId, Collections.singletonList(testWorkItemId), sourceRefs)); String recordId = IdUtil.getSnowflakeNextIdStr(); String revisionId = IdUtil.getSnowflakeNextIdStr(); Date now = new Date(); ElnRecordEntity record = record(recordId, revisionId, template, testWorkItemId, operationCode, user, now); List fieldValues = new ArrayList<>(bindings.size()); for (int index = 0; index < bindings.size(); index++) { ElnTemplateBindingEntity binding = bindings.get(index); ElnFieldVersionEntity version = versions.get(index); fieldValues.add(fieldValue(recordId, revisionId, template.getId(), binding, version, snapshot, configuredSnapshots, user, now)); } Map crossTemplateSources = crossTemplateSnapshotResolver.resolve( user.tenantId, testWorkItemId, bindings); for (ElnRecordFieldValueEntity value : fieldValues) { ElnRecordFieldValueEntity source = crossTemplateSources.get(value.getMubanBangdingId()); if (source == null) continue; value.setZhiWenben(source.getZhiWenben()); value.setZhiShuzhi(source.getZhiShuzhi()); value.setZhiRiqi(source.getZhiRiqi()); value.setZhiLaiyuan("CROSS_TEMPLATE"); value.setYuanYinyongLeixing("CROSS_TEMPLATE"); value.setYuanYinyongId(source.getId()); value.setYuanHuoquShijian(now); value.setKuamoLaiyuanJiluId(source.getDianziJiluId()); value.setKuamoLaiyuanXiudingId(source.getXiudingId()); value.setKuamoLaiyuanZhiId(source.getId()); value.setKuamoLaiyuanZiduanBanbenId(source.getZiduanBanbenId()); value.setKuamoHuoquShijian(now); } if (recordMapper.insertIfAbsent(record) != 1) { ElnRecordEntity concurrent = recordMapper.selectLiveByTestWorkItem(user.tenantId, testWorkItemId); if (concurrent != null) { return revisionService.open(command); } throw new DataException("电子记录创建冲突,请重试"); } ElnDocumentFileVo document = copiedDocument(fileService.copyDocx(template.getMubanFile(), revisionId)); ElnRecordRevisionEntity revision = revision(revisionId, recordId, operationCode, document.getFileId(), user, now); if (revisionMapper.insert(revision) != 1) { throw new DataException("电子记录初始修订创建失败"); } for (ElnRecordFieldValueEntity fieldValue : fieldValues) { if (fieldValueMapper.insert(fieldValue) != 1) { throw new DataException("电子记录字段值初始化失败"); } } record.setDangqianXiudingId(revisionId); if (recordMapper.updateById(record) != 1) { throw new DataException("电子记录当前修订更新失败"); } Set initializedBindingIds = new LinkedHashSet<>(); for (ElnRecordFieldValueEntity value : fieldValues) { initializedBindingIds.add(value.getMubanBangdingId()); } List calculated = formulaCalculationService.recalculateAffected( user.tenantId, record, revision, fieldValues, initializedBindingIds, operationCode, user.userId); for (ElnRecordFieldValueEntity value : calculated) { if (fieldValueMapper.updateById(value) != 1) { throw new DataException("电子记录初始公式结果保存失败"); } } return created(recordId, revisionId, document, false); } private TestWorkItemSnapshot snapshot(UserContext user, String testWorkItemId, List properties) { TestWorkItemSnapshotBatch batch = taskProvider.resolveAsActor( user.tenantId, user.userId, Collections.singletonList(testWorkItemId), properties); if (batch == null || batch.getItems() == null || batch.getItems().size() != 1) { throw new DataException("LIMS 检验任务快照返回无效"); } TestWorkItemSnapshot item = batch.getItems().get(0); if (item == null || !testWorkItemId.equals(item.getTestWorkItemId())) { throw new DataException("LIMS 检验任务快照与请求不一致"); } if (!"SUCCESS".equals(item.getOutcomeCode())) { throw new DataException(StringUtils.hasText(item.getReason()) ? item.getReason() : "检验项不允许创建电子记录"); } if (!StringUtils.hasText(item.getTestDefinitionCode())) { throw new DataException("检验项缺少检验项目编码,无法解析电子记录模板"); } return item; } private ElnTemplateVersionEntity resolveTemplate(String tenantId, String testDefinitionCode) { List templates = templateMapper.selectCurrentPublishedByTestDefinition( tenantId, testDefinitionCode.trim()); if (templates == null || templates.isEmpty()) { throw new DataException("未配置当前已发布电子记录模板"); } if (templates.size() != 1) { throw new DataException("检验项目命中多个当前已发布电子记录模板"); } ElnTemplateVersionEntity template = templates.get(0); if (!testDefinitionCode.equals(template.getJianyanXiangmuBianma())) { throw new DataException("当前模板检验项目编码与任务快照不一致"); } if (!StringUtils.hasText(template.getMubanFile())) { throw new DataException("当前电子记录模板缺少 DOCX 文件"); } return template; } private List activePrimaryBindings(String tenantId, String templateId) { return bindingMapper.selectList(Wrappers.lambdaQuery() .eq(ElnTemplateBindingEntity::getFTenantId, tenantId) .eq(ElnTemplateBindingEntity::getMubanId, templateId) .eq(ElnTemplateBindingEntity::getBangdingZhuangtai, ACTIVE) .eq(ElnTemplateBindingEntity::getKongjianJuese, PRIMARY) .and(query -> query.isNull(ElnTemplateBindingEntity::getFDeleteMark) .or().eq(ElnTemplateBindingEntity::getFDeleteMark, 0)) .orderByAsc(ElnTemplateBindingEntity::getId)); } private ElnFieldVersionEntity fieldVersion(String tenantId, ElnTemplateBindingEntity binding) { ElnFieldVersionEntity version = fieldVersionMapper.selectOne( Wrappers.lambdaQuery() .eq(ElnFieldVersionEntity::getFTenantId, tenantId) .eq(ElnFieldVersionEntity::getId, binding.getZiduanBanbenId()) .and(query -> query.isNull(ElnFieldVersionEntity::getFDeleteMark) .or().eq(ElnFieldVersionEntity::getFDeleteMark, 0))); if (version == null || !binding.getZiduanDingyiId().equals(version.getZiduanDingyiId())) { throw new DataException("模板绑定的字段版本无效"); } return version; } private ElnRecordFieldValueEntity fieldValue(String recordId, String revisionId, String templateId, ElnTemplateBindingEntity binding, ElnFieldVersionEntity version, TestWorkItemSnapshot snapshot, Map configuredSnapshots, UserContext user, Date now) { ElnRecordFieldValueEntity value = new ElnRecordFieldValueEntity(); value.setId(IdUtil.getSnowflakeNextIdStr()); value.setDianziJiluId(recordId); value.setXiudingId(revisionId); value.setMubanId(templateId); value.setMubanBangdingId(binding.getId()); value.setZiduanDingyiId(binding.getZiduanDingyiId()); value.setZiduanBianma(binding.getZiduanBianma()); value.setZiduanBanbenId(binding.getZiduanBanbenId()); value.setBangdingJuese(PRIMARY); value.setBangdingZhuangtai(ACTIVE); value.setFTenantId(user.tenantId); value.setFVersion(0); value.setFDeleteMark(0); value.setFCreatorUserId(user.userId); value.setFLastModifyUserId(user.userId); ElnFieldForms.Binding config = parseBinding(version.getBangdingPeizhi()); String source = config == null || config.getRead() == null ? null : config.getRead().getSource(); String property = config == null || config.getRead() == null ? null : config.getRead().getProperty(); if (LIMS_TASK.equals(source)) { if (config.getRead().getPropertyRevision() != null) { ElnConfiguredSourceValues.apply(value, version, configuredSnapshots.get(ElnConfiguredSourceValues.reference(config.getRead()))); return value; } TestWorkItemSnapshotProperty propertyValue = taskProperty(snapshot, property); boolean optionalMissing = "MISSING".equals(propertyValue.getOutcomeCode()) && !Integer.valueOf(1).equals(version.getShifouBitian()); if (!"SUCCESS".equals(propertyValue.getOutcomeCode()) && !optionalMissing) { throw new DataException(StringUtils.hasText(propertyValue.getReason()) ? propertyValue.getReason() : "LIMS 任务快照字段不可用"); } value.setZhiWenben(propertyValue.getValue()); value.setZhiLaiyuan(LIMS_TASK); value.setYuanShuxing(property); value.setYuanYinyongLeixing(propertyValue.getSource()); value.setYuanYinyongId(propertyValue.getSourceRef()); value.setYuanHuoquShijian(now); } else if (SYSTEM.equals(source)) { applySystemValue(value, property, user, now); } return value; } private static TestWorkItemSnapshotProperty taskProperty(TestWorkItemSnapshot item, String property) { if (item.getProperties() != null) { for (TestWorkItemSnapshotProperty candidate : item.getProperties()) { if (candidate != null && property != null && property.equals(candidate.getProperty())) { return candidate; } } } throw new DataException("LIMS 任务快照缺少字段:" + property); } private static void applySystemValue(ElnRecordFieldValueEntity value, String property, UserContext user, Date now) { value.setZhiLaiyuan(SYSTEM); value.setYuanShuxing(property); value.setYuanYinyongLeixing(SYSTEM); value.setYuanYinyongId(user.userId); value.setYuanHuoquShijian(now); if ("current_user.id".equals(property)) { value.setZhiWenben(user.userId); } else if ("current_user.real_name".equals(property)) { value.setZhiWenben(user.userName); } else if ("current_date".equals(property)) { value.setZhiWenben(new SimpleDateFormat("yyyy-MM-dd").format(now)); value.setZhiRiqi(now); } else if ("current_datetime".equals(property)) { value.setZhiWenben(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(now)); value.setZhiRiqi(now); } else { throw new DataException("未支持的 SYSTEM property:" + property); } } private static ElnFieldForms.Binding parseBinding(String json) { try { return StringUtils.hasText(json) ? JsonUtil.getJsonToBean(json, ElnFieldForms.Binding.class) : null; } catch (RuntimeException e) { throw new DataException("字段绑定配置无效"); } } private static ElnRecordEntity record(String recordId, String revisionId, ElnTemplateVersionEntity template, String testWorkItemId, String operationCode, UserContext user, Date now) { ElnRecordEntity record = new ElnRecordEntity(); record.setId(recordId); record.setJiluBianhao("ELN-" + recordId); record.setJianyanXiangId(testWorkItemId); record.setMubanId(template.getId()); record.setMubanBianhao(template.getMubanBianhao()); record.setMubanBanben(template.getMubanBanben()); record.setChuangjianCaozuoBianma(operationCode); record.setDangqianXiudingId(null); record.setFTenantId(user.tenantId); record.setFVersion(0); record.setFDeleteMark(0); record.setFCreatorUserId(user.userId); record.setFLastModifyUserId(user.userId); record.setFCreatorTime(now); record.setFLastModifyTime(now); return record; } private static ElnRecordRevisionEntity revision(String revisionId, String recordId, String operationCode, String document, UserContext user, Date now) { ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity(); revision.setId(revisionId); revision.setDianziJiluId(recordId); revision.setXiudingHao(1); revision.setXiudingCaozuoBianma(operationCode); revision.setJiluWendang(document); revision.setBizStatus(DRAFT); revision.setFTenantId(user.tenantId); revision.setFVersion(0); revision.setFDeleteMark(0); revision.setFCreatorUserId(user.userId); revision.setFLastModifyUserId(user.userId); revision.setFCreatorTime(now); revision.setFLastModifyTime(now); return revision; } private static ElnRecordVos.Created created(String recordId, String revisionId, ElnDocumentFileVo document, boolean existing) { ElnRecordVos.Created result = new ElnRecordVos.Created(); result.setRecordId(recordId); result.setRevisionId(revisionId); result.setDocument(document); result.setExisting(existing); return result; } private static ElnDocumentFileVo copiedDocument(String attachments) { try { List files = JsonUtil.getJsonToList(attachments, ElnDocumentFileVo.class); if (files == null || files.size() != 1 || files.get(0) == null || !StringUtils.hasText(files.get(0).getFileId())) { throw new DataException("独占电子记录文档复制结果无效"); } return files.get(0); } catch (DataException e) { throw e; } catch (RuntimeException e) { throw new DataException("独占电子记录文档复制结果无效"); } } private static String required(String value, String message) { if (!StringUtils.hasText(value)) { throw new DataException(message); } return value.trim(); } private static UserContext currentUser() { UserInfo user = UserProvider.getUser(); if (user == null || !StringUtils.hasText(user.getUserId())) { throw new DataException("当前登录用户无效"); } TenantVO routed = TenantDataSourceUtil.getTenantInfo(); String tenantId = routed != null && StringUtils.hasText(routed.getEnCode()) ? routed.getEnCode().trim() : user.getTenantId(); return userContext(StringUtils.hasText(tenantId) ? tenantId.trim() : "0", user.getUserId().trim(), user.getUserName()); } static UserContext userContext(String tenantId, String userId, String userName) { return new UserContext(tenantId, userId, userName); } @FunctionalInterface interface CurrentUserProvider { UserContext get(); } static final class UserContext { private final String tenantId; private final String userId; private final String userName; private UserContext(String tenantId, String userId, String userName) { this.tenantId = tenantId; this.userId = userId; this.userName = userName; } } }