Files
tuanwei-java/src/main/java/com/gxwebsoft/gxmu/service/ReviewFlowLevelValidator.java
T
weicw 17499cb526 fix(review): 驳回重走时清理旧节点,审批链层级连续性保存校验
- restartReviewWhenLastRejected 先删同一申报记录下旧节点再建第一级,同一事务
- 新增 ReviewFlowLevelValidator,按 title 校验层级从 1 连续升序
- ReviewFlowController 的 save/update/saveBatch/updateBatch 接入校验
- 单测覆盖驳回清理、层级缺级/重复/多链独立校验
2026-11-03 22:24:56 +08:00

82 lines
2.8 KiB
Java

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 连续升序)。
*
* <p>一条审批链的层级必须恰好是 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<ReviewFlow> flows) {
if (flows == null || flows.isEmpty()) {
return;
}
Map<String, List<Integer>> levelsByTitle = new LinkedHashMap<>();
for (ReviewFlow flow : flows) {
if (flow == null) {
continue;
}
levelsByTitle.computeIfAbsent(titleOf(flow), key -> new ArrayList<>()).add(flow.getLevel());
}
for (Map.Entry<String, List<Integer>> entry : levelsByTitle.entrySet()) {
validateOne(entry.getKey(), entry.getValue());
}
}
private static void validateOne(String title, List<Integer> levels) {
for (Integer level : levels) {
if (level == null) {
throw new BusinessException("审批链「" + displayTitle(title) + "」存在未填写层级的节点,层级必须从 1 连续升序");
}
}
List<Integer> 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<Integer> 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();
}
}