package com.ruoyi.ai.tools;
|
|
import com.alibaba.fastjson2.JSON;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.ruoyi.approve.mapper.ApproveLogMapper;
|
import com.ruoyi.approve.mapper.ApproveNodeMapper;
|
import com.ruoyi.approve.mapper.ApproveProcessMapper;
|
import com.ruoyi.approve.pojo.ApproveLog;
|
import com.ruoyi.approve.pojo.ApproveNode;
|
import com.ruoyi.approve.pojo.ApproveProcess;
|
import dev.langchain4j.agent.tool.P;
|
import dev.langchain4j.agent.tool.Tool;
|
import org.springframework.stereotype.Component;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
|
import java.math.BigDecimal;
|
import java.text.ParseException;
|
import java.text.SimpleDateFormat;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.*;
|
import java.util.stream.Collectors;
|
|
@Component
|
public class ApproveTodoTools {
|
|
private static final int DEFAULT_LIMIT = 10;
|
private static final int MAX_LIMIT = 20;
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
private final ApproveProcessMapper approveProcessMapper;
|
private final ApproveNodeMapper approveNodeMapper;
|
private final ApproveLogMapper approveLogMapper;
|
|
public ApproveTodoTools(
|
ApproveProcessMapper approveProcessMapper,
|
ApproveNodeMapper approveNodeMapper,
|
ApproveLogMapper approveLogMapper) {
|
this.approveProcessMapper = approveProcessMapper;
|
this.approveNodeMapper = approveNodeMapper;
|
this.approveLogMapper = approveLogMapper;
|
}
|
|
@Tool(name = "查询审批待办列表", value = "查询审批待办,支持按状态、类型、关键字过滤,返回 Markdown 表格。")
|
public String listTodos(
|
@P(value = "审批状态,可选值:all、pending、processing、approved、rejected、resubmitted", required = false) String status,
|
@P(value = "审批类型编号,可不传", required = false) Integer approveType,
|
@P(value = "关键字,可匹配流程编号、标题、申请人、当前审批人", required = false) String keyword,
|
@P(value = "返回条数,默认10,最大20", required = false) Integer limit) {
|
|
LambdaQueryWrapper<ApproveProcess> wrapper = new LambdaQueryWrapper<>();
|
wrapper.eq(ApproveProcess::getApproveDelete, 0);
|
|
Integer statusCode = parseStatus(status);
|
if (statusCode != null) {
|
wrapper.eq(ApproveProcess::getApproveStatus, statusCode);
|
}
|
if (approveType != null) {
|
wrapper.eq(ApproveProcess::getApproveType, approveType);
|
}
|
if (StringUtils.hasText(keyword)) {
|
wrapper.and(w -> w.like(ApproveProcess::getApproveId, keyword)
|
.or().like(ApproveProcess::getApproveReason, keyword)
|
.or().like(ApproveProcess::getApproveUserName, keyword)
|
.or().like(ApproveProcess::getApproveUserCurrentName, keyword));
|
}
|
|
wrapper.orderByDesc(ApproveProcess::getCreateTime);
|
wrapper.last("limit " + normalizeLimit(limit));
|
|
List<ApproveProcess> processes = approveProcessMapper.selectList(wrapper);
|
if (processes == null || processes.isEmpty()) {
|
return jsonResponse(
|
true,
|
"todo_list",
|
"未查询到符合条件的审批待办。",
|
Map.of("count", 0),
|
Map.of(
|
"columns", List.of("approveId", "approveType", "approveUserName", "approveUserCurrentName", "approveReason", "approveStatus", "createTime"),
|
"items", List.of()
|
),
|
Map.of()
|
);
|
}
|
|
List<Map<String, Object>> items = processes.stream().map(process -> {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("approveId", process.getApproveId());
|
item.put("approveType", approveTypeName(process.getApproveType()));
|
item.put("approveUserName", process.getApproveUserName());
|
item.put("approveUserCurrentName", process.getApproveUserCurrentName());
|
item.put("approveReason", process.getApproveReason());
|
item.put("approveStatus", approveStatusName(process.getApproveStatus()));
|
item.put("createTime", formatDateTime(process.getCreateTime()));
|
return item;
|
}).collect(Collectors.toList());
|
|
return jsonResponse(
|
true,
|
"todo_list",
|
"已返回审批待办列表,可直接渲染表格或卡片。",
|
Map.of(
|
"count", items.size(),
|
"statusFilter", status == null ? "all" : status,
|
"approveType", approveType == null ? "" : approveType,
|
"keyword", keyword == null ? "" : keyword
|
),
|
Map.of(
|
"columns", List.of("approveId", "approveType", "approveUserName", "approveUserCurrentName", "approveReason", "approveStatus", "createTime"),
|
"items", items
|
),
|
Map.of()
|
);
|
}
|
|
@Tool(name = "查询审批待办详情", value = "根据流程编号查询单条审批待办详情,返回结构化文本。")
|
public String getTodoDetail(@P("流程编号 approveId") String approveId) {
|
ApproveProcess process = getProcessByApproveId(approveId);
|
if (process == null) {
|
return "未找到对应的审批流程,请确认流程编号是否正确。";
|
}
|
|
StringJoiner detail = new StringJoiner("\n");
|
detail.add("审批详情");
|
detail.add("流程编号: " + safe(process.getApproveId()));
|
detail.add("审批类型: " + approveTypeName(process.getApproveType()));
|
detail.add("申请人: " + safe(process.getApproveUserName()));
|
detail.add("申请部门: " + safe(process.getApproveDeptName()));
|
detail.add("当前审批人: " + safe(process.getApproveUserCurrentName()));
|
detail.add("标题: " + safe(process.getApproveReason()));
|
detail.add("状态: " + approveStatusName(process.getApproveStatus()));
|
detail.add("申请日期: " + formatDate(process.getApproveTime()));
|
detail.add("开始日期: " + formatDate(process.getStartDate()));
|
detail.add("结束日期: " + formatDate(process.getEndDate()));
|
detail.add("地点: " + safe(process.getLocation()));
|
detail.add("金额: " + (process.getPrice() == null ? "" : process.getPrice().toPlainString()));
|
detail.add("备注: " + safe(process.getApproveRemark()));
|
detail.add("创建时间: " + formatDateTime(process.getCreateTime()));
|
return detail.toString();
|
}
|
|
@Tool(name = "查询审批流转记录", value = "根据流程编号查询审批节点和审批日志,适合回答进度、卡在哪个节点、谁处理过。")
|
public String getTodoProgress(@P("流程编号 approveId") String approveId) {
|
ApproveProcess process = getProcessByApproveId(approveId);
|
if (process == null) {
|
return jsonResponse(
|
false,
|
"todo_progress",
|
"未找到对应的审批流程,请确认流程编号是否正确。",
|
Map.of("approveId", approveId == null ? "" : approveId),
|
Map.of(),
|
Map.of()
|
);
|
}
|
|
List<ApproveNode> nodes = listNodes(process);
|
List<ApproveLog> logs = listLogs(process.getId());
|
List<Map<String, Object>> nodeItems = nodes.stream().map(node -> {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("approveNodeOrder", node.getApproveNodeOrder());
|
item.put("approveNodeUser", node.getApproveNodeUser());
|
item.put("approveNodeStatus", approveNodeStatusName(node.getApproveNodeStatus()));
|
item.put("approveNodeTime", formatDate(node.getApproveNodeTime()));
|
item.put("approveNodeReason", node.getApproveNodeReason());
|
item.put("approveNodeRemark", node.getApproveNodeRemark());
|
return item;
|
}).collect(Collectors.toList());
|
List<Map<String, Object>> logItems = logs.stream().map(log -> {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("approveNodeOrder", log.getApproveNodeOrder());
|
item.put("approveUser", log.getApproveUser());
|
item.put("approveStatus", approveStatusName(log.getApproveStatus()));
|
item.put("approveTime", formatDate(log.getApproveTime()));
|
item.put("approveRemark", log.getApproveRemark());
|
return item;
|
}).collect(Collectors.toList());
|
return jsonResponse(
|
true,
|
"todo_progress",
|
"已返回审批流转记录,可渲染时间线、步骤条和日志表格。",
|
Map.of(
|
"approveId", process.getApproveId(),
|
"currentStatus", approveStatusName(process.getApproveStatus()),
|
"currentApprover", safe(process.getApproveUserCurrentName()),
|
"nodeCount", nodeItems.size(),
|
"logCount", logItems.size()
|
),
|
Map.of(
|
"nodes", nodeItems,
|
"logs", logItems
|
),
|
Map.of()
|
);
|
}
|
|
@Tool(name = "统计审批待办数据", value = "返回专业化统计结果,包含摘要指标和可直接用于 ECharts 的图表 option。")
|
public String getTodoStats() {
|
List<ApproveProcess> processes = approveProcessMapper.selectList(new LambdaQueryWrapper<ApproveProcess>()
|
.eq(ApproveProcess::getApproveDelete, 0));
|
if (processes == null || processes.isEmpty()) {
|
return jsonResponse(
|
true,
|
"todo_stats",
|
"当前没有审批待办数据。",
|
Map.of("total", 0),
|
Map.of(
|
"statusDistribution", Map.of(),
|
"typeDistribution", Map.of(),
|
"recent7DayTrend", List.of(),
|
"tips", List.of()
|
),
|
Map.of()
|
);
|
}
|
|
Map<String, Long> statusStats = processes.stream()
|
.collect(Collectors.groupingBy(p -> approveStatusName(p.getApproveStatus()), LinkedHashMap::new, Collectors.counting()));
|
Map<String, Long> typeStats = processes.stream()
|
.collect(Collectors.groupingBy(p -> approveTypeName(p.getApproveType()), LinkedHashMap::new, Collectors.counting()));
|
|
long pendingCount = countByStatus(processes, 0);
|
long processingCount = countByStatus(processes, 1);
|
long approvedCount = countByStatus(processes, 2);
|
long rejectedCount = countByStatus(processes, 3);
|
long resubmittedCount = countByStatus(processes, 4);
|
|
List<String> recentDates = buildRecentDates(7);
|
List<Long> trendValues = recentDates.stream()
|
.map(date -> processes.stream()
|
.filter(process -> process.getCreateTime() != null)
|
.filter(process -> process.getCreateTime().toLocalDate().equals(LocalDate.parse(date)))
|
.count())
|
.collect(Collectors.toList());
|
|
Map<String, Object> summary = new LinkedHashMap<>();
|
summary.put("total", processes.size());
|
summary.put("pending", pendingCount);
|
summary.put("processing", processingCount);
|
summary.put("approved", approvedCount);
|
summary.put("rejected", rejectedCount);
|
summary.put("resubmitted", resubmittedCount);
|
summary.put("approvalCompletionRate", calculateRate(approvedCount, processes.size()));
|
summary.put("rejectionRate", calculateRate(rejectedCount, processes.size()));
|
|
Map<String, Object> charts = new LinkedHashMap<>();
|
charts.put("statusBarOption", buildStatusBarOption(statusStats));
|
charts.put("typePieOption", buildTypePieOption(typeStats));
|
charts.put("recentTrendLineOption", buildRecentTrendLineOption(recentDates, trendValues));
|
|
return jsonResponse(
|
true,
|
"todo_stats",
|
"已返回审批统计概览与图表配置,前端可直接使用 charts 中的 ECharts option 渲染。",
|
summary,
|
Map.of(
|
"statusDistribution", statusStats,
|
"typeDistribution", typeStats,
|
"recent7DayTrend", toTrendItems(recentDates, trendValues),
|
"tips", List.of(
|
"statusBarOption 适合展示各审批状态数量对比",
|
"typePieOption 适合展示各审批类型占比",
|
"recentTrendLineOption 适合展示最近7天新增审批趋势"
|
)
|
),
|
charts
|
);
|
}
|
|
@Transactional
|
@Tool(name = "审批待办", value = "执行审批动作。action 只支持 approve 或 reject。approve 表示通过,reject 表示驳回。")
|
public String reviewTodo(
|
@P("流程编号 approveId") String approveId,
|
@P("动作,approve=通过,reject=驳回") String action,
|
@P(value = "审批备注,可不传", required = false) String remark) {
|
ApproveProcess process = getProcessByApproveId(approveId);
|
if (process == null) {
|
return actionResult(false, "review_action", "未找到对应审批流程。", approveId, null);
|
}
|
if (process.getApproveDelete() != null && process.getApproveDelete() == 1) {
|
return actionResult(false, "review_action", "该审批流程已删除,不能再审核。", approveId, null);
|
}
|
if (process.getApproveStatus() != null && (process.getApproveStatus() == 2 || process.getApproveStatus() == 3)) {
|
return actionResult(false, "review_action", "该审批流程已结束,不能重复审核。", approveId, null);
|
}
|
|
List<ApproveNode> nodes = listNodes(process);
|
ApproveNode currentNode = findCurrentNode(nodes);
|
if (currentNode == null) {
|
return actionResult(false, "review_action", "未找到可审核的当前节点。", approveId, null);
|
}
|
|
String normalizedAction = action == null ? "" : action.trim().toLowerCase();
|
Date now = new Date();
|
currentNode.setApproveNodeTime(now);
|
currentNode.setUpdateTime(LocalDateTime.now());
|
|
ApproveLog log = new ApproveLog();
|
log.setApproveId(process.getId());
|
log.setApproveNodeOrder(currentNode.getApproveNodeOrder());
|
log.setApproveUser(currentNode.getApproveNodeUserId());
|
log.setApproveTime(now);
|
log.setApproveRemark(remark);
|
|
if ("approve".equals(normalizedAction)) {
|
currentNode.setApproveNodeStatus(1);
|
currentNode.setApproveNodeReason(null);
|
approveNodeMapper.updateById(currentNode);
|
|
ApproveNode nextNode = findNextNode(nodes, currentNode.getApproveNodeOrder());
|
if (nextNode == null) {
|
process.setApproveStatus(2);
|
process.setApproveOverTime(now);
|
process.setApproveUserCurrentId(null);
|
process.setApproveUserCurrentName(null);
|
} else {
|
process.setApproveStatus(1);
|
process.setApproveUserCurrentId(nextNode.getApproveNodeUserId());
|
process.setApproveUserCurrentName(nextNode.getApproveNodeUser());
|
}
|
process.setApproveRemark(remark);
|
approveProcessMapper.updateById(process);
|
|
log.setApproveStatus(nextNode == null ? 2 : 1);
|
approveLogMapper.insert(log);
|
return actionResult(true, "review_action",
|
nextNode == null ? "审批已通过,且该流程已全部完成。" : "审批已通过,流程已流转到下一审批节点。",
|
approveId,
|
Map.of(
|
"action", "approve",
|
"currentStatus", approveStatusName(process.getApproveStatus()),
|
"nextApprover", nextNode == null ? "" : safe(nextNode.getApproveNodeUser()),
|
"remark", safe(remark)
|
));
|
}
|
|
if ("reject".equals(normalizedAction)) {
|
currentNode.setApproveNodeStatus(2);
|
currentNode.setApproveNodeReason(remark);
|
currentNode.setApproveNodeRemark(remark);
|
approveNodeMapper.updateById(currentNode);
|
|
process.setApproveStatus(3);
|
process.setApproveOverTime(now);
|
process.setApproveUserCurrentId(null);
|
process.setApproveUserCurrentName(null);
|
process.setApproveRemark(remark);
|
approveProcessMapper.updateById(process);
|
|
log.setApproveStatus(3);
|
approveLogMapper.insert(log);
|
return actionResult(true, "review_action", "审批已驳回。", approveId, Map.of(
|
"action", "reject",
|
"currentStatus", approveStatusName(process.getApproveStatus()),
|
"remark", safe(remark)
|
));
|
}
|
|
return actionResult(false, "review_action", "action 只支持 approve 或 reject。", approveId, null);
|
}
|
|
@Transactional
|
@Tool(name = "取消审批待办审核", value = "撤销最近一次审核结果,将最近审核节点恢复为未审核,并回滚流程状态。")
|
public String cancelReviewTodo(
|
@P("流程编号 approveId") String approveId,
|
@P(value = "取消原因,可不传", required = false) String reason) {
|
ApproveProcess process = getProcessByApproveId(approveId);
|
if (process == null) {
|
return actionResult(false, "cancel_review_action", "未找到对应审批流程。", approveId, null);
|
}
|
List<ApproveNode> nodes = listNodes(process);
|
ApproveNode lastReviewedNode = nodes.stream()
|
.filter(node -> node.getApproveNodeStatus() != null && node.getApproveNodeStatus() != 0)
|
.max(Comparator.comparing(ApproveNode::getApproveNodeOrder))
|
.orElse(null);
|
if (lastReviewedNode == null) {
|
return actionResult(false, "cancel_review_action", "当前流程没有可取消的审核记录。", approveId, null);
|
}
|
|
lastReviewedNode.setApproveNodeStatus(0);
|
lastReviewedNode.setApproveNodeTime(null);
|
lastReviewedNode.setApproveNodeReason(null);
|
lastReviewedNode.setApproveNodeRemark(reason);
|
lastReviewedNode.setUpdateTime(LocalDateTime.now());
|
approveNodeMapper.updateById(lastReviewedNode);
|
|
List<ApproveLog> logs = listLogs(process.getId());
|
ApproveLog latestLog = logs.stream()
|
.max(Comparator.comparing(ApproveLog::getApproveNodeOrder)
|
.thenComparing(ApproveLog::getApproveTime, Comparator.nullsLast(Date::compareTo)))
|
.orElse(null);
|
if (latestLog != null) {
|
approveLogMapper.deleteById(latestLog.getId());
|
}
|
|
ApproveNode previousReviewedNode = nodes.stream()
|
.filter(node -> !node.getId().equals(lastReviewedNode.getId()))
|
.filter(node -> node.getApproveNodeStatus() != null && node.getApproveNodeStatus() == 1)
|
.max(Comparator.comparing(ApproveNode::getApproveNodeOrder))
|
.orElse(null);
|
|
process.setApproveOverTime(null);
|
process.setApproveRemark(reason);
|
process.setApproveStatus(previousReviewedNode == null ? 0 : 1);
|
process.setApproveUserCurrentId(lastReviewedNode.getApproveNodeUserId());
|
process.setApproveUserCurrentName(lastReviewedNode.getApproveNodeUser());
|
approveProcessMapper.updateById(process);
|
|
return actionResult(true, "cancel_review_action", "最近一次审核已取消,流程已回退到对应审批节点。", approveId, Map.of(
|
"rollbackNodeOrder", lastReviewedNode.getApproveNodeOrder(),
|
"currentStatus", approveStatusName(process.getApproveStatus()),
|
"currentApprover", safe(process.getApproveUserCurrentName()),
|
"reason", safe(reason)
|
));
|
}
|
|
@Transactional
|
@Tool(name = "修改审批待办", value = "修改审批单基础信息。支持修改标题、开始日期、结束日期、金额、地点、类型和备注。日期格式必须是 yyyy-MM-dd。")
|
public String updateTodo(
|
@P("流程编号 approveId") String approveId,
|
@P(value = "新的标题,可不传", required = false) String approveReason,
|
@P(value = "新的开始日期 yyyy-MM-dd,可不传", required = false) String startDate,
|
@P(value = "新的结束日期 yyyy-MM-dd,可不传", required = false) String endDate,
|
@P(value = "新的金额,可不传", required = false) BigDecimal price,
|
@P(value = "新的地点,可不传", required = false) String location,
|
@P(value = "新的审批类型,可不传", required = false) Integer approveType,
|
@P(value = "新的备注,可不传", required = false) String approveRemark) {
|
ApproveProcess process = getProcessByApproveId(approveId);
|
if (process == null) {
|
return actionResult(false, "update_action", "未找到对应审批流程。", approveId, null);
|
}
|
if (!StringUtils.hasText(approveReason)
|
&& !StringUtils.hasText(startDate)
|
&& !StringUtils.hasText(endDate)
|
&& price == null
|
&& !StringUtils.hasText(location)
|
&& approveType == null
|
&& !StringUtils.hasText(approveRemark)) {
|
return actionResult(false, "update_action", "没有检测到可更新的字段。", approveId, null);
|
}
|
|
if (StringUtils.hasText(approveReason)) {
|
process.setApproveReason(approveReason);
|
}
|
if (StringUtils.hasText(startDate)) {
|
process.setStartDate(parseDate(startDate));
|
}
|
if (StringUtils.hasText(endDate)) {
|
process.setEndDate(parseDate(endDate));
|
}
|
if (price != null) {
|
process.setPrice(price);
|
}
|
if (StringUtils.hasText(location)) {
|
process.setLocation(location);
|
}
|
if (approveType != null) {
|
process.setApproveType(approveType);
|
}
|
if (StringUtils.hasText(approveRemark)) {
|
process.setApproveRemark(approveRemark);
|
}
|
|
approveProcessMapper.updateById(process);
|
return actionResult(true, "update_action", "审批单已更新。", approveId, Map.of(
|
"approveReason", safe(process.getApproveReason()),
|
"startDate", formatDate(process.getStartDate()),
|
"endDate", formatDate(process.getEndDate()),
|
"price", process.getPrice() == null ? "" : process.getPrice(),
|
"location", safe(process.getLocation()),
|
"approveType", approveTypeName(process.getApproveType()),
|
"approveRemark", safe(process.getApproveRemark())
|
));
|
}
|
|
@Transactional
|
@Tool(name = "删除审批待办", value = "删除审批流程。逻辑删除流程记录,并同步逻辑删除审批节点。")
|
public String deleteTodo(@P("流程编号 approveId") String approveId) {
|
ApproveProcess process = getProcessByApproveId(approveId);
|
if (process == null) {
|
return actionResult(false, "delete_action", "未找到对应审批流程。", approveId, null);
|
}
|
if (process.getApproveDelete() != null && process.getApproveDelete() == 1) {
|
return actionResult(false, "delete_action", "该审批流程已经是删除状态。", approveId, null);
|
}
|
|
process.setApproveDelete(1);
|
approveProcessMapper.updateById(process);
|
|
List<ApproveNode> nodes = listNodes(process);
|
for (ApproveNode node : nodes) {
|
node.setDeleteFlag(1);
|
node.setUpdateTime(LocalDateTime.now());
|
approveNodeMapper.updateById(node);
|
}
|
return actionResult(true, "delete_action", "审批流程已删除。", approveId, Map.of(
|
"deletedNodeCount", nodes.size(),
|
"approveStatus", approveStatusName(process.getApproveStatus())
|
));
|
}
|
|
private ApproveProcess getProcessByApproveId(String approveId) {
|
if (!StringUtils.hasText(approveId)) {
|
return null;
|
}
|
return approveProcessMapper.selectOne(new LambdaQueryWrapper<ApproveProcess>()
|
.eq(ApproveProcess::getApproveId, approveId)
|
.last("limit 1"));
|
}
|
|
private List<ApproveNode> listNodes(ApproveProcess process) {
|
List<ApproveNode> nodes = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>()
|
.eq(ApproveNode::getDeleteFlag, 0)
|
.eq(ApproveNode::getApproveProcessId, process.getApproveId())
|
.orderByAsc(ApproveNode::getApproveNodeOrder));
|
if (nodes != null && !nodes.isEmpty()) {
|
return nodes;
|
}
|
List<ApproveNode> fallbackNodes = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>()
|
.eq(ApproveNode::getDeleteFlag, 0)
|
.eq(ApproveNode::getApproveProcessId, String.valueOf(process.getId()))
|
.orderByAsc(ApproveNode::getApproveNodeOrder));
|
return fallbackNodes == null ? List.of() : fallbackNodes;
|
}
|
|
private List<ApproveLog> listLogs(Long processId) {
|
List<ApproveLog> logs = approveLogMapper.selectList(new LambdaQueryWrapper<ApproveLog>()
|
.eq(ApproveLog::getApproveId, processId)
|
.orderByAsc(ApproveLog::getApproveNodeOrder, ApproveLog::getApproveTime));
|
return logs == null ? List.of() : logs;
|
}
|
|
private ApproveNode findCurrentNode(List<ApproveNode> nodes) {
|
return nodes.stream()
|
.filter(node -> node.getApproveNodeStatus() != null && node.getApproveNodeStatus() == 0)
|
.min(Comparator.comparing(ApproveNode::getApproveNodeOrder))
|
.orElse(null);
|
}
|
|
private ApproveNode findNextNode(List<ApproveNode> nodes, Integer currentOrder) {
|
return nodes.stream()
|
.filter(node -> node.getApproveNodeOrder() != null && currentOrder != null)
|
.filter(node -> node.getApproveNodeOrder() > currentOrder)
|
.min(Comparator.comparing(ApproveNode::getApproveNodeOrder))
|
.orElse(null);
|
}
|
|
private int normalizeLimit(Integer limit) {
|
if (limit == null || limit <= 0) {
|
return DEFAULT_LIMIT;
|
}
|
return Math.min(limit, MAX_LIMIT);
|
}
|
|
private Integer parseStatus(String status) {
|
if (!StringUtils.hasText(status) || "all".equalsIgnoreCase(status)) {
|
return null;
|
}
|
return switch (status.trim().toLowerCase()) {
|
case "pending" -> 0;
|
case "processing" -> 1;
|
case "approved" -> 2;
|
case "rejected" -> 3;
|
case "resubmitted" -> 4;
|
default -> null;
|
};
|
}
|
|
private String approveStatusName(Integer status) {
|
if (status == null) {
|
return "未知";
|
}
|
return switch (status) {
|
case 0 -> "待审核";
|
case 1 -> "审核中";
|
case 2 -> "审核完成";
|
case 3 -> "审核未通过";
|
case 4 -> "已重新提交";
|
default -> "未知";
|
};
|
}
|
|
private String approveNodeStatusName(Integer status) {
|
if (status == null) {
|
return "未知";
|
}
|
return switch (status) {
|
case 0 -> "未审核";
|
case 1 -> "同意";
|
case 2 -> "拒绝";
|
default -> "未知";
|
};
|
}
|
|
private String approveTypeName(Integer type) {
|
if (type == null) {
|
return "未知";
|
}
|
return switch (type) {
|
case 1 -> "公出管理";
|
case 2 -> "请假管理";
|
case 3 -> "出差管理";
|
case 4 -> "报销管理";
|
case 5 -> "采购审批";
|
case 6 -> "报价审批";
|
case 7 -> "发货审批";
|
case 8 -> "危险作业审批";
|
default -> "类型" + type;
|
};
|
}
|
|
private String safe(Object value) {
|
return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ');
|
}
|
|
private String formatDateTime(Object value) {
|
if (value == null) {
|
return "";
|
}
|
if (value instanceof LocalDateTime localDateTime) {
|
return localDateTime.format(DATE_TIME_FORMATTER);
|
}
|
return safe(value);
|
}
|
|
private String formatDate(Date value) {
|
return value == null ? "" : DATE_FORMAT.format(value);
|
}
|
|
private long countByStatus(List<ApproveProcess> processes, int status) {
|
return processes.stream()
|
.filter(process -> process.getApproveStatus() != null)
|
.filter(process -> process.getApproveStatus() == status)
|
.count();
|
}
|
|
private String calculateRate(long part, int total) {
|
if (total <= 0) {
|
return "0.00%";
|
}
|
double rate = part * 100.0 / total;
|
return String.format("%.2f%%", rate);
|
}
|
|
private List<String> buildRecentDates(int days) {
|
List<String> dates = new ArrayList<>();
|
LocalDate today = LocalDate.now();
|
for (int i = days - 1; i >= 0; i--) {
|
dates.add(today.minusDays(i).toString());
|
}
|
return dates;
|
}
|
|
private List<Map<String, Object>> toTrendItems(List<String> dates, List<Long> values) {
|
List<Map<String, Object>> items = new ArrayList<>();
|
for (int i = 0; i < dates.size(); i++) {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("date", dates.get(i));
|
item.put("count", values.get(i));
|
items.add(item);
|
}
|
return items;
|
}
|
|
private Map<String, Object> buildStatusBarOption(Map<String, Long> statusStats) {
|
Map<String, Object> option = new LinkedHashMap<>();
|
option.put("title", Map.of("text", "审批状态分布", "left", "center"));
|
option.put("tooltip", Map.of("trigger", "axis"));
|
option.put("xAxis", Map.of("type", "category", "data", new ArrayList<>(statusStats.keySet())));
|
option.put("yAxis", Map.of("type", "value"));
|
option.put("series", List.of(Map.of(
|
"name", "数量",
|
"type", "bar",
|
"data", new ArrayList<>(statusStats.values()),
|
"barWidth", "40%"
|
)));
|
return option;
|
}
|
|
private Map<String, Object> buildTypePieOption(Map<String, Long> typeStats) {
|
List<Map<String, Object>> data = typeStats.entrySet().stream()
|
.map(entry -> {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("name", entry.getKey());
|
item.put("value", entry.getValue());
|
return item;
|
})
|
.collect(Collectors.toList());
|
|
Map<String, Object> option = new LinkedHashMap<>();
|
option.put("title", Map.of("text", "审批类型占比", "left", "center"));
|
option.put("tooltip", Map.of("trigger", "item"));
|
option.put("legend", Map.of("orient", "vertical", "left", "left"));
|
option.put("series", List.of(Map.of(
|
"name", "审批类型",
|
"type", "pie",
|
"radius", List.of("35%", "65%"),
|
"data", data
|
)));
|
return option;
|
}
|
|
private Map<String, Object> buildRecentTrendLineOption(List<String> dates, List<Long> values) {
|
Map<String, Object> option = new LinkedHashMap<>();
|
option.put("title", Map.of("text", "最近7天审批新增趋势", "left", "center"));
|
option.put("tooltip", Map.of("trigger", "axis"));
|
option.put("xAxis", Map.of("type", "category", "data", dates));
|
option.put("yAxis", Map.of("type", "value"));
|
option.put("series", List.of(Map.of(
|
"name", "新增审批",
|
"type", "line",
|
"smooth", true,
|
"data", values,
|
"areaStyle", Map.of()
|
)));
|
return option;
|
}
|
|
private Date parseDate(String dateText) {
|
try {
|
return DATE_FORMAT.parse(dateText);
|
} catch (ParseException e) {
|
throw new IllegalArgumentException("日期格式必须是 yyyy-MM-dd");
|
}
|
}
|
|
private String actionResult(boolean success, String type, String description, String approveId, Map<String, Object> data) {
|
Map<String, Object> summary = new LinkedHashMap<>();
|
summary.put("approveId", approveId == null ? "" : approveId);
|
return jsonResponse(success, type, description, summary, data == null ? Map.of() : data, Map.of());
|
}
|
|
private String jsonResponse(
|
boolean success,
|
String type,
|
String description,
|
Map<String, Object> summary,
|
Map<String, Object> data,
|
Map<String, Object> charts) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("success", success);
|
result.put("type", type);
|
result.put("description", description);
|
result.put("summary", summary == null ? Map.of() : summary);
|
result.put("data", data == null ? Map.of() : data);
|
result.put("charts", charts == null ? Map.of() : charts);
|
return JSON.toJSONString(result);
|
}
|
}
|