package jnpf.dmsService; import jnpf.base.entity.VisualdevEntity; import jnpf.flowable.WorkFlowApi; import jnpf.flowable.entity.TemplateJsonEntity; import jnpf.flowable.entity.TemplateNodeEntity; import jnpf.flowable.enums.NodeEnum; import jnpf.flowable.model.templatenode.nodejson.NodeModel; import jnpf.onlinedev.VisualdevOnlineApi; import jnpf.util.JsonUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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; /** Discovers fields from configured published workflows using the shared display schema. */ @org.springframework.stereotype.Service @RequiredArgsConstructor @Slf4j public class DmsQianpiyeCatalogService { private final jnpf.dmsService.DmsQianpiyeProperties properties; private final WorkFlowApi workFlowApi; private final VisualdevOnlineApi visualdevOnlineApi; private final DmsQianpiyeDocxPropertiesReader docxPropertiesReader; public Map load() { return load(Collections.emptyMap()); } public Map loadForDocument(String fileType, String fileId, boolean refresh) { Map saved = refresh ? Collections.emptyMap() : docxPropertiesReader.read(fileType, fileId); return load(DmsQianpiyeDocxPropertiesReader.hasCompleteFlowVersions(saved) ? saved : Collections.emptyMap()); } private Map load(Map savedVersions) { List catalogs = new ArrayList<>(); List> fieldCatalog = new ArrayList<>(); Map publishedVersions = new LinkedHashMap<>(); for (jnpf.dmsService.DmsQianpiyeProperties.Flow configured : properties.getFlows()) { if (configured.getTemplateId() == null || configured.getTemplateId().isBlank()) throw new jnpf.exception.DataException("签批页适用流程缺少模板 ID"); FlowDefinition flow = new FlowDefinition(configured.getKey(), configured.getTemplateId()); List versions = workFlowApi.getFlowIdsByTemplate(flow.templateId); String savedVersionId = "new".equals(flow.key) ? DmsQianpiyeDocxPropertiesReader.newFlowVersionId(savedVersions) : "revision".equals(flow.key) ? DmsQianpiyeDocxPropertiesReader.revisionFlowVersionId(savedVersions) : null; TemplateJsonEntity version = resolveVersion(versions, savedVersionId, flow.key); if (version == null) { continue; } publishedVersions.put(configured.getKey(), version.getId()); List steps = new ArrayList<>(); Set formIds = new LinkedHashSet<>(); List nodes = DmsQianpiyeNodeOrder.order(version.getFlowXml(), workFlowApi.getNodesByFlowId(version.getId())); for (TemplateNodeEntity node : nodes) { if (org.springframework.util.StringUtils.hasText(node.getFormId())) formIds.add(node.getFormId()); String role = NodeEnum.START.getType().equals(node.getNodeType()) ? "START" : NodeEnum.APPROVER.getType().equals(node.getNodeType()) ? "APPROVE" : null; if (role == null) { continue; } NodeModel model = JsonUtil.getJsonToBean(node.getNodeJson(), NodeModel.class); String nodeName = model == null || !org.springframework.util.StringUtils.hasText(model.getNodeName()) ? node.getNodeCode() : model.getNodeName(); if (!org.springframework.util.StringUtils.hasText(nodeName)) { continue; } steps.add(new StepDefinition(role, nodeName)); } catalogs.add(new FlowCatalog(flow, steps, Collections.emptyList())); if (!"new".equals(flow.key) && !"revision".equals(flow.key)) { continue; } for (String formId : formIds) { VisualdevEntity form = visualdevOnlineApi.getFormConfig(formId); if (form == null) throw new jnpf.exception.DataException("表单配置不存在: " + formId); for (jnpf.onlinedev.model.FormDisplaySchema.Field field : jnpf.onlinedev.model.FormDisplaySchema.parse(form.getFormData())) { if (!field.isSupported()) { continue; } Map item = new LinkedHashMap<>(); item.put("flowKey", flow.key); item.put("key", field.getPath()); item.put("label", field.getLabel()); item.put("path", field.getPath()); item.put("table", field.getTable()); item.put("column", field.getColumn()); item.put("signature", field.displaySignature()); fieldCatalog.add(item); } } } Map result = new LinkedHashMap<>(); result.put("workflowSteps", mergeSteps(catalogs)); result.put("formFields", mergeCompatibleFields(fieldCatalog)); result.put("newFlowVersionId", publishedVersions.get("new")); result.put("revisionFlowVersionId", publishedVersions.get("revision")); return result; } private TemplateJsonEntity resolveVersion(List versions, String savedVersionId, String flowKey) { if (versions == null) versions = Collections.emptyList(); if (org.springframework.util.StringUtils.hasText(savedVersionId)) { for (TemplateJsonEntity version : versions) { if (savedVersionId.equals(version.getId())) return version; } throw new jnpf.exception.DataException("签批页保存的" + flowKey + "流程版本不存在,请点击刷新后重新保存"); } return versions.stream().filter(item -> Integer.valueOf(1).equals(item.getState())) .max((left, right) -> compareVersion(left.getVersion(), right.getVersion())).orElse(null); } private static List> mergeCompatibleFields(List> fields) { Map>> grouped = new LinkedHashMap<>(); for (Map field : fields) grouped.computeIfAbsent(String.valueOf(field.get("path")), key -> new ArrayList<>()).add(field); List> result = new ArrayList<>(); for (List> group : grouped.values()) { Map first = group.get(0); Set flowKeys = new LinkedHashSet<>(); for (Map field : group) { flowKeys.add(field.get("flowKey")); } if (!flowKeys.equals(Set.of("new", "revision"))) { continue; } boolean compatible = group.stream().allMatch(field -> java.util.Objects.equals(first.get("signature"), field.get("signature"))); if (!compatible) { continue; } Map common = new LinkedHashMap<>(); common.put("key", first.get("path")); common.put("tag", "dms.qianpiye.form." + first.get("path")); common.put("label", first.get("label")); common.put("formId", "common"); common.put("table", first.get("table")); common.put("column", first.get("column")); result.add(common); } return result; } /** * 节点编码只在单个流程版本中有效。签批页模板需要跨新增/修订复用,因此按角色内顺序生成稳定步骤键。 */ private static List> mergeSteps(List catalogs) { Map> result = new LinkedHashMap<>(); for (FlowCatalog catalog : catalogs) { List> steps = new ArrayList<>(); appendMergedSteps(steps, "START", "start", stepsFor(catalog, "START"), Collections.emptyList()); appendMergedSteps(steps, "APPROVE", "approve", stepsFor(catalog, "APPROVE"), Collections.emptyList()); for (Map step : steps) { Map existing = result.putIfAbsent(step.get("key"), step); if (existing != null && !existing.get("label").equals(step.get("label"))) existing.put("label", existing.get("label") + " / " + step.get("label")); } } return new ArrayList<>(result.values()); } private static void appendMergedSteps(List> result, String role, String keyPrefix, List newSteps, List revisionSteps) { int count = Math.max(newSteps.size(), revisionSteps.size()); for (int index = 0; index < count; index++) { StepDefinition newStep = index < newSteps.size() ? newSteps.get(index) : null; StepDefinition revisionStep = index < revisionSteps.size() ? revisionSteps.get(index) : null; String key = "START".equals(role) && index == 0 ? keyPrefix : keyPrefix + "-" + (index + 1); Map item = new LinkedHashMap<>(); item.put("key", key); item.put("label", stepLabel(newStep, revisionStep)); item.put("role", role); result.add(item); } } private static String stepLabel(StepDefinition newStep, StepDefinition revisionStep) { if (newStep == null) return revisionStep.name; if (revisionStep == null || newStep.name.equals(revisionStep.name)) return newStep.name; return newStep.name + " / " + revisionStep.name; } private static List stepsFor(FlowCatalog catalog, String role) { if (catalog == null) return Collections.emptyList(); List result = new ArrayList<>(); for (StepDefinition step : catalog.steps) if (role.equals(step.role)) result.add(step); return result; } private static int compareVersion(String left, String right) { if (left == null) { return right == null ? 0 : -1; } if (right == null) { return 1; } String[] leftParts = left.split("\\."); String[] rightParts = right.split("\\."); int size = Math.max(leftParts.length, rightParts.length); for (int index = 0; index < size; index++) { int comparison = Integer.compare(versionPart(leftParts, index), versionPart(rightParts, index)); if (comparison != 0) { return comparison; } } return left.compareTo(right); } private static int versionPart(String[] parts, int index) { if (index >= parts.length) { return 0; } try { return Integer.parseInt(parts[index]); } catch (NumberFormatException e) { return 0; } } private static final class FlowDefinition { private final String key; private final String templateId; private FlowDefinition(String key, String templateId) { this.key = key; this.templateId = templateId; } } private static final class FlowCatalog { private final FlowDefinition definition; private final List steps; private final List formFields; private FlowCatalog(FlowDefinition definition, List steps, List formFields) { this.definition = definition; this.steps = steps; this.formFields = formFields; } } private static final class StepDefinition { private final String role; private final String name; private StepDefinition(String role, String name) { this.role = role; this.name = name; } } private static final class FormField { private final String path; private final String label; private FormField(String path, String label) { this.path = path; this.label = label; } } }