package jnpf.elnService.cross.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jnpf.elnEntity.field.ElnFieldDefEntity; import jnpf.elnEntity.field.ElnFieldVersionEntity; import jnpf.elnEntity.template.ElnTemplateBindingEntity; import jnpf.elnEntity.template.ElnTemplateCrossReferenceEntity; import jnpf.elnEntity.template.ElnTemplateVersionEntity; import jnpf.elnEntity.template.ElnTemplateVos; import jnpf.elnMapper.ElnFieldDefMapper; import jnpf.elnMapper.ElnFieldVersionMapper; import jnpf.elnMapper.ElnTemplateBindingMapper; import jnpf.elnMapper.ElnTemplateCrossReferenceMapper; import jnpf.elnMapper.ElnTemplateMapper; import jnpf.elnService.cross.ElnCrossTemplateScalarReferencePolicy; import jnpf.elnService.template.ElnTemplatePublishValidator; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** Publication is the final gate for rule lineage and unsupported template dependency chains. */ @Service public class ElnCrossTemplatePublishValidator implements ElnTemplatePublishValidator { private final ElnTemplateMapper templates; private final ElnTemplateBindingMapper bindings; private final ElnTemplateCrossReferenceMapper references; private final ElnFieldDefMapper fieldDefinitions; private final ElnFieldVersionMapper fieldVersions; public ElnCrossTemplatePublishValidator(ElnTemplateMapper templates, ElnTemplateBindingMapper bindings, ElnTemplateCrossReferenceMapper references, ElnFieldDefMapper fieldDefinitions, ElnFieldVersionMapper fieldVersions) { this.templates = templates; this.bindings = bindings; this.references = references; this.fieldDefinitions = fieldDefinitions; this.fieldVersions = fieldVersions; } @Override public List validate(String tenantId, String templateId) { List result = new ArrayList<>(); ElnTemplateVersionEntity target = template(tenantId, templateId); if (target == null || !StringUtils.hasText(target.getMubanBianhao())) return result; List all = liveRules(tenantId); Map bindingById = new LinkedHashMap<>(); for (ElnTemplateBindingEntity binding : liveBindings(tenantId)) bindingById.put(binding.getId(), binding); for (ElnTemplateCrossReferenceEntity rule : all) { ElnTemplateBindingEntity binding = bindingById.get(rule.getMubanBangdingId()); if (binding == null || !templateId.equals(binding.getMubanId())) continue; if (!"ACTIVE".equals(binding.getBangdingZhuangtai())) continue; if (!"PRIMARY".equals(binding.getKongjianJuese()) || StringUtils.hasText(binding.getRepeatGroupCode())) { result.add(error("CROSS_TEMPLATE_TARGET_INVALID", "跨模板规则必须绑定当前模板的活动普通主字段", binding.getId())); } ElnTemplateVersionEntity sourceTemplate = template(tenantId, rule.getLaiyuanMubanId()); ElnFieldDefEntity sourceField = fieldDefinition( tenantId, rule.getLaiyuanZiduanDingyiId()); ElnFieldVersionEntity sourceVersion = fieldVersion(tenantId, rule.getLaiyuanZiduanBanbenId()); ElnTemplateBindingEntity sourceBinding = sourceBinding( rule, bindingById.values()); if (sourceTemplate == null || !"PUBLISHED".equals(sourceTemplate.getBizStatus()) || !"enabled".equals(sourceTemplate.getBizEnabled()) || !rule.getLaiyuanMubanBianhao().equals(sourceTemplate.getMubanBianhao()) || sourceField == null || !"enabled".equals(sourceField.getBizEnabled()) || sourceVersion == null || !rule.getLaiyuanZiduanDingyiId().equals(sourceVersion.getZiduanDingyiId()) || !ElnCrossTemplateScalarReferencePolicy.isEligible( templateId, sourceTemplate.getId(), sourceTemplate.getBizStatus(), sourceField.getZuoyongFanwei(), sourceVersion, sourceBinding)) { result.add(error("CROSS_TEMPLATE_SOURCE_INVALID", "跨模板来源模板或精确字段版本已失效", binding == null ? null : binding.getId())); } } Map> graph = graph(all, bindingById); List path = new ArrayList<>(); path.add(target.getMubanBianhao()); inspectPath(target.getMubanBianhao(), graph, path, new LinkedHashSet<>(), result); return result; } private static void inspectPath(String node, Map> graph, List path, Set visiting, List result) { if (!visiting.add(node)) return; for (String next : graph.getOrDefault(node, Collections.emptyList())) { List candidate = new ArrayList<>(path); candidate.add(next); if (path.contains(next)) { result.add(error("CROSS_TEMPLATE_DEPENDENCY_CYCLE", "跨模板字段引用存在循环:" + join(candidate), null)); } else if (candidate.size() > 2) { result.add(error("CROSS_TEMPLATE_INDIRECT_CHAIN", "跨模板字段引用不支持间接链:" + join(candidate), null)); } else { inspectPath(next, graph, candidate, visiting, result); } } visiting.remove(node); } private static Map> graph(List rules, Map bindingById) { Map> result = new LinkedHashMap<>(); for (ElnTemplateCrossReferenceEntity rule : rules) { ElnTemplateBindingEntity binding = bindingById.get(rule.getMubanBangdingId()); if (binding != null && "ACTIVE".equals(binding.getBangdingZhuangtai()) && StringUtils.hasText(binding.getMubanBianhao()) && StringUtils.hasText(rule.getLaiyuanMubanBianhao())) { result.computeIfAbsent(binding.getMubanBianhao(), ignored -> new ArrayList<>()).add(rule.getLaiyuanMubanBianhao()); } } return result; } private List liveRules(String tenantId) { List result = references.selectList(Wrappers.lambdaQuery() .eq(ElnTemplateCrossReferenceEntity::getFTenantId, tenantId).eq(ElnTemplateCrossReferenceEntity::getFDeleteMark, 0)); return result == null ? Collections.emptyList() : result; } private List liveBindings(String tenantId) { List result = bindings.selectList(Wrappers.lambdaQuery() .eq(ElnTemplateBindingEntity::getFTenantId, tenantId).eq(ElnTemplateBindingEntity::getFDeleteMark, 0)); return result == null ? Collections.emptyList() : result; } private ElnTemplateVersionEntity template(String tenantId, String id) { return templates.selectOne(Wrappers.lambdaQuery().eq(ElnTemplateVersionEntity::getFTenantId, tenantId).eq(ElnTemplateVersionEntity::getId, id).eq(ElnTemplateVersionEntity::getFDeleteMark, 0)); } private ElnFieldDefEntity fieldDefinition(String tenantId, String id) { return fieldDefinitions.selectOne(Wrappers.lambdaQuery().eq(ElnFieldDefEntity::getFTenantId, tenantId).eq(ElnFieldDefEntity::getId, id).eq(ElnFieldDefEntity::getFDeleteMark, 0)); } private ElnFieldVersionEntity fieldVersion(String tenantId, String id) { return fieldVersions.selectOne(Wrappers.lambdaQuery().eq(ElnFieldVersionEntity::getFTenantId, tenantId).eq(ElnFieldVersionEntity::getId, id).eq(ElnFieldVersionEntity::getFDeleteMark, 0)); } private static ElnTemplateBindingEntity sourceBinding(ElnTemplateCrossReferenceEntity rule, Iterable bindings) { for (ElnTemplateBindingEntity binding : bindings) { if (rule.getLaiyuanMubanId().equals(binding.getMubanId()) && rule.getLaiyuanZiduanDingyiId().equals(binding.getZiduanDingyiId()) && rule.getLaiyuanZiduanBanbenId().equals(binding.getZiduanBanbenId()) && "ACTIVE".equals(binding.getBangdingZhuangtai()) && "PRIMARY".equals(binding.getKongjianJuese()) && !StringUtils.hasText(binding.getRepeatGroupCode())) return binding; } return null; } private static String join(List path) { return String.join(" -> ", path); } private static ElnTemplateVos.Diagnostic error(String code, String message, String bindingId) { ElnTemplateVos.Diagnostic diagnostic = new ElnTemplateVos.Diagnostic(); diagnostic.setSeverity("ERROR"); diagnostic.setCode(code); diagnostic.setMessage(message); diagnostic.setBindingId(bindingId); return diagnostic; } }