package com.gxwebsoft.gxmu.service; import com.gxwebsoft.common.core.exception.BusinessException; import com.gxwebsoft.gxmu.entity.ReviewFlow; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 审批链层级连续性校验(spec #58 行为约定:层级从 1 连续升序)。 * *

一条审批链的层级必须恰好是 1..N,缺级会让申报卡在半路,重复级会让两个人拿到同一条待办。 * 按 title 分组校验,纯函数,不依赖数据库。 * * @author Codex * @since 2026-11-03 */ public final class ReviewFlowLevelValidator { private ReviewFlowLevelValidator() { } /** * 校验一组审批流节点的层级;不合法时抛出 {@link BusinessException}。 * * @param flows 同一轮保存涉及的节点,可包含多条审批链(按 title 分组) */ public static void validate(List flows) { if (flows == null || flows.isEmpty()) { return; } Map> levelsByTitle = new LinkedHashMap<>(); for (ReviewFlow flow : flows) { if (flow == null) { continue; } levelsByTitle.computeIfAbsent(titleOf(flow), key -> new ArrayList<>()).add(flow.getLevel()); } for (Map.Entry> entry : levelsByTitle.entrySet()) { validateOne(entry.getKey(), entry.getValue()); } } private static void validateOne(String title, List levels) { for (Integer level : levels) { if (level == null) { throw new BusinessException("审批链「" + displayTitle(title) + "」存在未填写层级的节点,层级必须从 1 连续升序"); } } List sorted = new ArrayList<>(levels); Collections.sort(sorted); for (int index = 0; index < sorted.size(); index++) { if (sorted.get(index) != index + 1) { throw new BusinessException("审批链「" + displayTitle(title) + "」的层级必须从 1 连续升序,当前层级为 " + join(sorted) + ",请调整后再保存"); } } } private static String titleOf(ReviewFlow flow) { return flow.getTitle() == null ? "" : flow.getTitle().trim(); } private static String displayTitle(String title) { return title.isEmpty() ? "未命名" : title; } private static String join(List levels) { StringBuilder builder = new StringBuilder(); for (int index = 0; index < levels.size(); index++) { if (index > 0) { builder.append(", "); } builder.append(levels.get(index)); } return builder.toString(); } }