package jnpf.elnService.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jnpf.elnEntity.runtime.ElnRecordEntity; import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity; import jnpf.elnEntity.template.ElnTemplateBindingEntity; import jnpf.elnEntity.template.ElnTemplateCrossReferenceEntity; import jnpf.elnMapper.ElnRecordFieldValueMapper; import jnpf.elnMapper.ElnTemplateCrossReferenceMapper; import jnpf.elnService.ElnCrossTemplateSnapshotResolver; import jnpf.elnService.ElnTestRequestIdentityLookup; import jnpf.elnService.ElnRecordSourceResolver; import jnpf.exception.DataException; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @Component public class ElnCrossTemplateSnapshotResolverImpl implements ElnCrossTemplateSnapshotResolver { private final ElnTemplateCrossReferenceMapper referenceMapper; private final ElnRecordFieldValueMapper valueMapper; private final ElnTestRequestIdentityLookup identityLookup; private final ElnRecordSourceResolver sourceResolver; public ElnCrossTemplateSnapshotResolverImpl(ElnTemplateCrossReferenceMapper referenceMapper, ElnRecordFieldValueMapper valueMapper, ElnTestRequestIdentityLookup identityLookup, ElnRecordSourceResolver sourceResolver) { this.referenceMapper = referenceMapper; this.valueMapper = valueMapper; this.identityLookup = identityLookup; this.sourceResolver = sourceResolver; } @Override public Map resolve(String tenantId, String targetTestWorkItemId, List targetBindings) { Map rules = new LinkedHashMap<>(); if (targetBindings == null || targetBindings.isEmpty()) return Collections.emptyMap(); for (ElnTemplateBindingEntity binding : targetBindings) { if (binding == null || !StringUtils.hasText(binding.getId())) continue; ElnTemplateCrossReferenceEntity rule = rule(tenantId, binding.getId()); if (rule != null) rules.put(binding.getId(), rule); } return resolveRules(tenantId, targetTestWorkItemId, rules); } @Override public Map resolveByBindingIds(String tenantId, String targetTestWorkItemId, Set targetBindingIds) { if (targetBindingIds == null || targetBindingIds.isEmpty()) return Collections.emptyMap(); Map rules = new LinkedHashMap<>(); for (String bindingId : targetBindingIds) { if (!StringUtils.hasText(bindingId)) continue; ElnTemplateCrossReferenceEntity rule = rule(tenantId, bindingId); if (rule != null) rules.put(bindingId, rule); } return resolveRules(tenantId, targetTestWorkItemId, rules); } @Override public List validatePersistedSnapshots(String tenantId, String targetTestWorkItemId, List targetValues) { List snapshots = crossTemplateValues(targetValues); if (snapshots.isEmpty()) return Collections.emptyList(); Map identities; try { identities = identityLookup.resolve(tenantId, Collections.singletonList(targetTestWorkItemId)); } catch (RuntimeException exception) { return issuesForAll(tenantId, snapshots, "SOURCE_RECORD_IDENTITY_INVALID", message(exception, "目标检验项请验单身份解析失败")); } ElnTestRequestIdentityLookup.Identity target = identities == null ? null : identities.get(targetTestWorkItemId); if (target == null || !StringUtils.hasText(target.getTestRequestId())) { return issuesForAll(tenantId, snapshots, "SOURCE_RECORD_IDENTITY_INVALID", "目标检验项缺少请验单身份"); } List issues = new ArrayList<>(); for (ElnRecordFieldValueEntity snapshot : snapshots) { validateSnapshot(tenantId, target.getTestRequestId(), snapshot, issues); } return issues; } private ElnTemplateCrossReferenceEntity rule(String tenantId, String bindingId) { return referenceMapper.selectOne(Wrappers.lambdaQuery() .eq(ElnTemplateCrossReferenceEntity::getFTenantId, tenantId) .eq(ElnTemplateCrossReferenceEntity::getMubanBangdingId, bindingId) .eq(ElnTemplateCrossReferenceEntity::getFDeleteMark, 0)); } private void validateSnapshot(String tenantId, String testRequestId, ElnRecordFieldValueEntity snapshot, List issues) { ElnTemplateCrossReferenceEntity rule = rule(tenantId, snapshot.getMubanBangdingId()); if (rule == null) { issues.add(issue("CROSS_TEMPLATE_RULE_MISSING", "跨模板来源规则不存在", snapshot, null)); return; } try { ElnRecordEntity source = sourceResolver.resolve( tenantId, testRequestId, rule.getLaiyuanMubanBianhao()); if (!Objects.equals(snapshot.getKuamoLaiyuanJiluId(), source.getId())) { issues.add(issue("SOURCE_RECORD_DRIFT", "跨模板源记录已变化", snapshot, rule)); return; } if (!Objects.equals(snapshot.getKuamoLaiyuanXiudingId(), source.getDangqianXiudingId())) { issues.add(issue("SOURCE_REVISION_DRIFT", "跨模板源记录当前修订已变化", snapshot, rule)); return; } ElnRecordFieldValueEntity current = currentSourceValue(tenantId, source, rule); if (current == null || !hasValue(current)) { issues.add(issue("SOURCE_FIELD_VALUE_INVALID", "跨模板源字段值为空或不存在", snapshot, rule)); return; } if ("CROSS_TEMPLATE".equals(current.getZhiLaiyuan())) { issues.add(issue("SOURCE_FIELD_CHAIN_NOT_SUPPORTED", "跨模板源字段形成了间接引用链", snapshot, rule)); return; } if (!Objects.equals(snapshot.getKuamoLaiyuanZhiId(), current.getId())) { issues.add(issue("SOURCE_VALUE_ID_DRIFT", "跨模板源字段值身份已变化", snapshot, rule)); return; } if (!Objects.equals(rule.getLaiyuanZiduanBanbenId(), current.getZiduanBanbenId()) || !Objects.equals(snapshot.getKuamoLaiyuanZiduanBanbenId(), current.getZiduanBanbenId())) { issues.add(issue("SOURCE_FIELD_VERSION_DRIFT", "跨模板源字段固定版本已变化", snapshot, rule)); return; } if (!sameValue(snapshot, current)) { issues.add(issue("SOURCE_VALUE_DRIFT", "跨模板源字段值已变化", snapshot, rule)); } } catch (RuntimeException exception) { issues.add(issue(code(exception, "SOURCE_RECORD_INVALID"), message(exception, "跨模板源记录当前不可用"), snapshot, rule)); } } private ElnRecordFieldValueEntity currentSourceValue(String tenantId, ElnRecordEntity source, ElnTemplateCrossReferenceEntity rule) { return valueMapper.selectOne(Wrappers.lambdaQuery() .eq(ElnRecordFieldValueEntity::getFTenantId, tenantId) .eq(ElnRecordFieldValueEntity::getDianziJiluId, source.getId()) .eq(ElnRecordFieldValueEntity::getXiudingId, source.getDangqianXiudingId()) .eq(ElnRecordFieldValueEntity::getZiduanDingyiId, rule.getLaiyuanZiduanDingyiId()) .eq(ElnRecordFieldValueEntity::getBangdingJuese, "PRIMARY") .eq(ElnRecordFieldValueEntity::getBangdingZhuangtai, "ACTIVE") .and(q -> q.isNull(ElnRecordFieldValueEntity::getFDeleteMark) .or().eq(ElnRecordFieldValueEntity::getFDeleteMark, 0))); } private List issuesForAll(String tenantId, List snapshots, String code, String message) { List issues = new ArrayList<>(); for (ElnRecordFieldValueEntity snapshot : snapshots) { ElnTemplateCrossReferenceEntity rule = rule(tenantId, snapshot.getMubanBangdingId()); issues.add(issue(code, message, snapshot, rule)); } return issues; } private static List crossTemplateValues( List values) { if (values == null || values.isEmpty()) return Collections.emptyList(); List snapshots = new ArrayList<>(); for (ElnRecordFieldValueEntity value : values) { if (value != null && "CROSS_TEMPLATE".equals(value.getZhiLaiyuan())) snapshots.add(value); } return snapshots; } private static SnapshotIssue issue(String code, String message, ElnRecordFieldValueEntity snapshot, ElnTemplateCrossReferenceEntity rule) { return new SnapshotIssue(code, message, snapshot.getMubanBangdingId(), snapshot.getZiduanBianma(), rule == null ? null : rule.getLaiyuanMubanBianhao()); } private static boolean sameValue(ElnRecordFieldValueEntity snapshot, ElnRecordFieldValueEntity source) { return Objects.equals(snapshot.getZhiWenben(), source.getZhiWenben()) && decimalEquals(snapshot.getZhiShuzhi(), source.getZhiShuzhi()) && dateEquals(snapshot.getZhiRiqi(), source.getZhiRiqi()); } private static boolean decimalEquals(BigDecimal left, BigDecimal right) { return left == null ? right == null : right != null && left.compareTo(right) == 0; } private static boolean dateEquals(Date left, Date right) { return left == null ? right == null : right != null && left.getTime() == right.getTime(); } private static String code(RuntimeException exception, String fallback) { String value = exception == null ? null : exception.getMessage(); if (!StringUtils.hasText(value)) return fallback; int separator = value.indexOf(':'); String candidate = separator < 0 ? value : value.substring(0, separator); return candidate.matches("[A-Z][A-Z0-9_]*") ? candidate : fallback; } private static String message(RuntimeException exception, String fallback) { return exception != null && StringUtils.hasText(exception.getMessage()) ? exception.getMessage() : fallback; } private Map resolveRules(String tenantId, String targetTestWorkItemId, Map rules) { Map snapshots = new LinkedHashMap<>(); if (rules.isEmpty()) return snapshots; Map identities = identityLookup.resolve( tenantId, Collections.singletonList(targetTestWorkItemId)); if (identities == null) identities = Collections.emptyMap(); ElnTestRequestIdentityLookup.Identity target = identities.get(targetTestWorkItemId); if (target == null || !StringUtils.hasText(target.getTestRequestId())) { throw new DataException("SOURCE_RECORD_IDENTITY_INVALID: 目标检验项缺少请验单身份"); } for (Map.Entry entry : rules.entrySet()) { String bindingId = entry.getKey(); ElnTemplateCrossReferenceEntity rule = entry.getValue(); ElnRecordEntity source = sourceResolver.resolve(tenantId, target.getTestRequestId(), rule.getLaiyuanMubanBianhao()); ElnRecordFieldValueEntity sourceValue = valueMapper.selectOne( Wrappers.lambdaQuery().eq(ElnRecordFieldValueEntity::getFTenantId, tenantId) .eq(ElnRecordFieldValueEntity::getDianziJiluId, source.getId()) .eq(ElnRecordFieldValueEntity::getXiudingId, source.getDangqianXiudingId()) .eq(ElnRecordFieldValueEntity::getZiduanDingyiId, rule.getLaiyuanZiduanDingyiId()) .eq(ElnRecordFieldValueEntity::getZiduanBanbenId, rule.getLaiyuanZiduanBanbenId()) .eq(ElnRecordFieldValueEntity::getBangdingJuese, "PRIMARY") .eq(ElnRecordFieldValueEntity::getBangdingZhuangtai, "ACTIVE") .and(q -> q.isNull(ElnRecordFieldValueEntity::getFDeleteMark) .or().eq(ElnRecordFieldValueEntity::getFDeleteMark, 0))); if (sourceValue == null || !hasValue(sourceValue)) { throw new DataException("SOURCE_FIELD_VALUE_INVALID: 源字段值为空或版本不符"); } if ("CROSS_TEMPLATE".equals(sourceValue.getZhiLaiyuan())) { throw new DataException("SOURCE_FIELD_CHAIN_NOT_SUPPORTED: 源字段不能来自跨模板引用"); } snapshots.put(bindingId, sourceValue); } return snapshots; } private static boolean hasValue(ElnRecordFieldValueEntity value) { return StringUtils.hasText(value.getZhiWenben()) || value.getZhiShuzhi() != null || value.getZhiRiqi() != null; } }