| | |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | 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 com.ruoyi.approve.service.IApproveNodeService; |
| | | import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import dev.langchain4j.agent.tool.P; |
| | | import dev.langchain4j.agent.tool.Tool; |
| | | import dev.langchain4j.agent.tool.ToolMemoryId; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | | import java.text.ParseException; |
| | | import java.text.SimpleDateFormat; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.StringJoiner; |
| | | import java.util.regex.Matcher; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Component |
| | |
| | | private final ApproveProcessMapper approveProcessMapper; |
| | | private final ApproveNodeMapper approveNodeMapper; |
| | | private final ApproveLogMapper approveLogMapper; |
| | | private final IApproveNodeService approveNodeService; |
| | | private final ApproveProcessServiceImpl approveProcessService; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | |
| | | public ApproveTodoTools( |
| | | ApproveProcessMapper approveProcessMapper, |
| | | public ApproveTodoTools(ApproveProcessMapper approveProcessMapper, |
| | | ApproveNodeMapper approveNodeMapper, |
| | | ApproveLogMapper approveLogMapper) { |
| | | ApproveLogMapper approveLogMapper, |
| | | IApproveNodeService approveNodeService, |
| | | ApproveProcessServiceImpl approveProcessService, |
| | | AiSessionUserContext aiSessionUserContext) { |
| | | this.approveProcessMapper = approveProcessMapper; |
| | | this.approveNodeMapper = approveNodeMapper; |
| | | this.approveLogMapper = approveLogMapper; |
| | | this.approveNodeService = approveNodeService; |
| | | this.approveProcessService = approveProcessService; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | } |
| | | |
| | | @Tool(name = "查询审批待办列表", value = "查询审批待办,支持按状态、类型、关键字过滤,返回 Markdown 表格。") |
| | | public String listTodos( |
| | | @Tool(name = "查询审批待办列表", value = "查询当前登录人相关的审批待办,优先返回自己待处理的审批,支持按状态、类型、关键字过滤。") |
| | | public String listTodos(@ToolMemoryId String memoryId, |
| | | @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); |
| | | |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | Long userId = loginUser.getUserId(); |
| | | Integer statusCode = parseStatus(status); |
| | | if (statusCode != null) { |
| | | wrapper.eq(ApproveProcess::getApproveStatus, statusCode); |
| | | } |
| | | |
| | | LambdaQueryWrapper<ApproveProcess> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(ApproveProcess::getApproveDelete, 0) |
| | | .ne(ApproveProcess::getApproveStatus, 2); |
| | | |
| | | 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)); |
| | | // 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)); |
| | | // } |
| | | if (statusCode != null && (statusCode == 0 || statusCode == 1)) { |
| | | wrapper.eq(ApproveProcess::getApproveUserCurrentId, userId); |
| | | } else { |
| | | wrapper.and(w -> w.eq(ApproveProcess::getApproveUser, userId) |
| | | .or().eq(ApproveProcess::getApproveUserCurrentId, userId) |
| | | .or().apply("FIND_IN_SET({0}, approve_user_ids)", userId)); |
| | | } |
| | | |
| | | wrapper.orderByDesc(ApproveProcess::getCreateTime); |
| | | wrapper.last("limit " + normalizeLimit(limit)); |
| | | wrapper.orderByDesc(ApproveProcess::getCreateTime) |
| | | .last("limit " + normalizeLimit(limit)); |
| | | |
| | | List<ApproveProcess> processes = approveProcessMapper.selectList(wrapper); |
| | | if (processes == null || processes.isEmpty()) { |
| | | return jsonResponse( |
| | | true, |
| | | "todo_list", |
| | | "未查询到符合条件的审批待办。", |
| | | List<ApproveProcess> processes = defaultList(approveProcessMapper.selectList(wrapper)); |
| | | if (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() |
| | | ); |
| | | Map.of("columns", todoColumns(), "items", List.of()), |
| | | Map.of()); |
| | | } |
| | | |
| | | List<Map<String, Object>> items = processes.stream().map(process -> { |
| | | List<Map<String, Object>> items = processes.stream() |
| | | .filter(process -> canView(process, userId)) |
| | | .sorted(Comparator |
| | | .comparing((ApproveProcess process) -> !Objects.equals(process.getApproveUserCurrentId(), userId)) |
| | | .thenComparing(ApproveProcess::getCreateTime, Comparator.nullsLast(Comparator.reverseOrder()))) |
| | | .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("approveUserName", safe(process.getApproveUserName())); |
| | | item.put("approveUserCurrentName", safe(process.getApproveUserCurrentName())); |
| | | item.put("approveReason", safe(process.getApproveReason())); |
| | | item.put("approveStatus", approveStatusName(process.getApproveStatus())); |
| | | item.put("createTime", formatDateTime(process.getCreateTime())); |
| | | item.put("relation", relationName(process, userId)); |
| | | return item; |
| | | }).collect(Collectors.toList()); |
| | | }) |
| | | .collect(Collectors.toList()); |
| | | |
| | | return jsonResponse( |
| | | true, |
| | | "todo_list", |
| | | "已返回审批待办列表,可直接渲染表格或卡片。", |
| | | return jsonResponse(true, "todo_list", "已返回当前用户相关审批列表。", |
| | | Map.of( |
| | | "count", items.size(), |
| | | "statusFilter", status == null ? "all" : status, |
| | | "statusFilter", StringUtils.hasText(status) ? status : "all", |
| | | "approveType", approveType == null ? "" : approveType, |
| | | "keyword", keyword == null ? "" : keyword |
| | | ), |
| | | Map.of( |
| | | "columns", List.of("approveId", "approveType", "approveUserName", "approveUserCurrentName", "approveReason", "approveStatus", "createTime"), |
| | | "items", items |
| | | ), |
| | | Map.of() |
| | | ); |
| | | Map.of("columns", todoColumns(), "items", items), |
| | | Map.of()); |
| | | } |
| | | |
| | | @Tool(name = "查询审批待办详情", value = "根据流程编号查询单条审批待办详情,返回结构化文本。") |
| | | public String getTodoDetail(@P("流程编号 approveId") String approveId) { |
| | | ApproveProcess process = getProcessByApproveId(approveId); |
| | | @Tool(name = "查询审批待办详情", value = "根据流程编号查询当前登录人可见的审批详情。") |
| | | public String getTodoDetail(@ToolMemoryId String memoryId, |
| | | @P("流程编号 approveId") String approveId) { |
| | | ApproveProcess process = getAccessibleProcess(memoryId, approveId); |
| | | if (process == null) { |
| | | return "未找到对应的审批流程,请确认流程编号是否正确。"; |
| | | return "未找到对应审批,或当前用户无权查看该流程。"; |
| | | } |
| | | |
| | | StringJoiner detail = new StringJoiner("\n"); |
| | |
| | | detail.add("金额: " + (process.getPrice() == null ? "" : process.getPrice().toPlainString())); |
| | | detail.add("备注: " + safe(process.getApproveRemark())); |
| | | detail.add("创建时间: " + formatDateTime(process.getCreateTime())); |
| | | detail.add("与当前用户关系: " + relationName(process, currentUserId(memoryId))); |
| | | return detail.toString(); |
| | | } |
| | | |
| | | @Tool(name = "查询审批流转记录", value = "根据流程编号查询审批节点和审批日志,适合回答进度、卡在哪个节点、谁处理过。") |
| | | public String getTodoProgress(@P("流程编号 approveId") String approveId) { |
| | | ApproveProcess process = getProcessByApproveId(approveId); |
| | | @Tool(name = "查询审批流转记录", value = "根据流程编号查询审批节点和审批日志,用于回答进度、当前卡点和历史处理记录。") |
| | | public String getTodoProgress(@ToolMemoryId String memoryId, |
| | | @P("流程编号 approveId") String approveId) { |
| | | ApproveProcess process = getAccessibleProcess(memoryId, approveId); |
| | | if (process == null) { |
| | | return jsonResponse( |
| | | false, |
| | | "todo_progress", |
| | | "未找到对应的审批流程,请确认流程编号是否正确。", |
| | | Map.of("approveId", approveId == null ? "" : approveId), |
| | | return jsonResponse(false, "todo_progress", "未找到对应审批,或当前用户无权查看该流程。", |
| | | Map.of("approveId", safe(approveId)), |
| | | Map.of(), |
| | | Map.of() |
| | | ); |
| | | Map.of()); |
| | | } |
| | | |
| | | List<ApproveNode> nodes = listNodes(process); |
| | | List<ApproveLog> logs = listLogs(process.getId()); |
| | | ApproveNode currentNode = findCurrentNode(nodes); |
| | | |
| | | 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("approveNodeUser", safe(node.getApproveNodeUser())); |
| | | item.put("approveNodeUserId", node.getApproveNodeUserId()); |
| | | item.put("approveNodeStatus", approveNodeStatusName(node.getApproveNodeStatus())); |
| | | item.put("approveNodeTime", formatDate(node.getApproveNodeTime())); |
| | | item.put("approveNodeReason", node.getApproveNodeReason()); |
| | | item.put("approveNodeRemark", node.getApproveNodeRemark()); |
| | | item.put("approveNodeReason", safe(node.getApproveNodeReason())); |
| | | item.put("approveNodeRemark", safe(node.getApproveNodeRemark())); |
| | | item.put("isCurrent", currentNode != null && Objects.equals(currentNode.getId(), node.getId())); |
| | | 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()); |
| | | item.put("approveRemark", safe(log.getApproveRemark())); |
| | | return item; |
| | | }).collect(Collectors.toList()); |
| | | return jsonResponse( |
| | | true, |
| | | "todo_progress", |
| | | "已返回审批流转记录,可渲染时间线、步骤条和日志表格。", |
| | | |
| | | return jsonResponse(true, "todo_progress", "已返回审批流转记录。", |
| | | Map.of( |
| | | "approveId", process.getApproveId(), |
| | | "approveId", safe(process.getApproveId()), |
| | | "currentStatus", approveStatusName(process.getApproveStatus()), |
| | | "currentApprover", safe(process.getApproveUserCurrentName()), |
| | | "currentNodeOrder", currentNode == null ? "" : currentNode.getApproveNodeOrder(), |
| | | "nodeCount", nodeItems.size(), |
| | | "logCount", logItems.size() |
| | | ), |
| | | Map.of( |
| | | "nodes", nodeItems, |
| | | "logs", logItems |
| | | ), |
| | | Map.of() |
| | | ); |
| | | 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), |
| | | @Tool(name = "统计审批待办数据", value = "按用户指定的时间范围统计当前登录人相关审批的状态分布、类型分布和趋势;未指定时默认近7天。") |
| | | public String getTodoStats(@ToolMemoryId String memoryId, |
| | | @P(value = "开始日期 yyyy-MM-dd,可不传", required = false) String startDate, |
| | | @P(value = "结束日期 yyyy-MM-dd,可不传", required = false) String endDate, |
| | | @P(value = "时间范围描述,例如 今天、本月、近30天、2026-04-01到2026-04-27", required = false) String timeRange) { |
| | | Long userId = currentUserId(memoryId); |
| | | List<ApproveProcess> processes = defaultList(approveProcessMapper.selectList(new LambdaQueryWrapper<ApproveProcess>() |
| | | .eq(ApproveProcess::getApproveDelete, 0) |
| | | .and(w -> w.eq(ApproveProcess::getApproveUser, userId) |
| | | .or().eq(ApproveProcess::getApproveUserCurrentId, userId) |
| | | .or().apply("FIND_IN_SET({0}, approve_user_ids)", userId)))); |
| | | |
| | | DateRange dateRange = resolveDateRange(startDate, endDate, timeRange); |
| | | List<ApproveProcess> filteredProcesses = processes.stream() |
| | | .filter(process -> withinDateRange(process.getCreateTime(), dateRange)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (filteredProcesses.isEmpty()) { |
| | | return jsonResponse(true, "todo_stats", "当前用户没有相关审批数据。", |
| | | Map.of( |
| | | "total", 0, |
| | | "startDate", dateRange.start().toString(), |
| | | "endDate", dateRange.end().toString(), |
| | | "timeRange", dateRange.label() |
| | | ), |
| | | Map.of( |
| | | "statusDistribution", Map.of(), |
| | | "typeDistribution", Map.of(), |
| | | "recent7DayTrend", List.of(), |
| | | "tips", List.of() |
| | | "trend", List.of() |
| | | ), |
| | | Map.of() |
| | | ); |
| | | Map.of()); |
| | | } |
| | | |
| | | Map<String, Long> statusStats = processes.stream() |
| | | Map<String, Long> statusStats = filteredProcesses.stream() |
| | | .collect(Collectors.groupingBy(p -> approveStatusName(p.getApproveStatus()), LinkedHashMap::new, Collectors.counting())); |
| | | Map<String, Long> typeStats = processes.stream() |
| | | Map<String, Long> typeStats = filteredProcesses.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); |
| | | long pendingCount = countByStatus(filteredProcesses, 0); |
| | | long processingCount = countByStatus(filteredProcesses, 1); |
| | | long approvedCount = countByStatus(filteredProcesses, 2); |
| | | long rejectedCount = countByStatus(filteredProcesses, 3); |
| | | long resubmittedCount = countByStatus(filteredProcesses, 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()); |
| | | TrendRange trendRange = buildTrendRange(dateRange.start(), dateRange.end(), filteredProcesses); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("total", processes.size()); |
| | | summary.put("total", filteredProcesses.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())); |
| | | summary.put("approvalCompletionRate", calculateRate(approvedCount, filteredProcesses.size())); |
| | | summary.put("rejectionRate", calculateRate(rejectedCount, filteredProcesses.size())); |
| | | summary.put("startDate", dateRange.start().toString()); |
| | | summary.put("endDate", dateRange.end().toString()); |
| | | summary.put("timeRange", dateRange.label()); |
| | | summary.put("trendGranularity", trendRange.granularity()); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("statusBarOption", buildStatusBarOption(statusStats)); |
| | | charts.put("typePieOption", buildTypePieOption(typeStats)); |
| | | charts.put("recentTrendLineOption", buildRecentTrendLineOption(recentDates, trendValues)); |
| | | charts.put("trendLineOption", buildTrendLineOption(trendRange.labels(), trendRange.values(), trendRange.label())); |
| | | |
| | | return jsonResponse( |
| | | true, |
| | | "todo_stats", |
| | | "已返回审批统计概览与图表配置,前端可直接使用 charts 中的 ECharts option 渲染。", |
| | | return jsonResponse(true, "todo_stats", "已返回当前用户相关审批统计。", |
| | | summary, |
| | | Map.of( |
| | | "statusDistribution", statusStats, |
| | | "typeDistribution", typeStats, |
| | | "recent7DayTrend", toTrendItems(recentDates, trendValues), |
| | | "tips", List.of( |
| | | "statusBarOption 适合展示各审批状态数量对比", |
| | | "typePieOption 适合展示各审批类型占比", |
| | | "recentTrendLineOption 适合展示最近7天新增审批趋势" |
| | | ) |
| | | "trend", toTrendItems(trendRange.labels(), trendRange.values()) |
| | | ), |
| | | charts |
| | | ); |
| | | charts); |
| | | } |
| | | |
| | | @Transactional |
| | | @Tool(name = "审批待办", value = "执行审批动作。action 只支持 approve 或 reject。approve 表示通过,reject 表示驳回。") |
| | | public String reviewTodo( |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @Tool(name = "审批待办", value = "执行审批动作,action 仅支持 approve 或 reject,且只能处理当前登录人自己的待审节点。") |
| | | public String reviewTodo(@ToolMemoryId String memoryId, |
| | | @P("流程编号 approveId") String approveId, |
| | | @P("动作,approve=通过,reject=驳回") String action, |
| | | @P(value = "审批备注,可不传", required = false) String remark) { |
| | | ApproveProcess process = getProcessByApproveId(approveId); |
| | | |
| | | ApproveProcess process = getAccessibleProcess(memoryId, approveId); |
| | | if (process == null) { |
| | | return actionResult(false, "review_action", "未找到对应审批流程。", approveId, null); |
| | | return actionResult(false, "review_action", "未找到对应审批,或当前用户无权访问该流程。", approveId, null); |
| | | } |
| | | if (process.getApproveDelete() != null && process.getApproveDelete() == 1) { |
| | | return actionResult(false, "review_action", "该审批流程已删除,不能再审核。", approveId, null); |
| | | if (!canOperate(process, currentUserId(memoryId))) { |
| | | return actionResult(false, "review_action", "当前登录人不是该审批的当前处理人。", approveId, null); |
| | | } |
| | | if (process.getApproveStatus() != null && (process.getApproveStatus() == 2 || process.getApproveStatus() == 3)) { |
| | | return actionResult(false, "review_action", "该审批流程已结束,不能重复审核。", approveId, null); |
| | | 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); |
| | | if (currentNode == null || !Objects.equals(currentNode.getApproveNodeUserId(), currentUserId(memoryId))) { |
| | | return actionResult(false, "review_action", "未找到当前用户可处理的审批节点。", approveId, null); |
| | | } |
| | | |
| | | String normalizedAction = action == null ? "" : action.trim().toLowerCase(); |
| | | Date now = new Date(); |
| | | currentNode.setApproveNodeTime(now); |
| | | currentNode.setApproveNodeRemark(remark); |
| | | currentNode.setApproveNodeReason("reject".equals(normalizedAction) ? remark : null); |
| | | currentNode.setUpdateUser(currentUserId(memoryId)); |
| | | currentNode.setUpdateTime(LocalDateTime.now()); |
| | | currentNode.setIsLast(isLastNode(nodes, currentNode)); |
| | | |
| | | 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()); |
| | | try { |
| | | switch (normalizedAction) { |
| | | case "approve" -> currentNode.setApproveNodeStatus(1); |
| | | case "reject" -> currentNode.setApproveNodeStatus(2); |
| | | default -> { |
| | | return actionResult(false, "review_action", "action 只支持 approve 或 reject。", approveId, null); |
| | | } |
| | | process.setApproveRemark(remark); |
| | | approveProcessMapper.updateById(process); |
| | | } |
| | | approveNodeService.updateApproveNode(currentNode); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException("审批处理失败", e); |
| | | } |
| | | |
| | | log.setApproveStatus(nextNode == null ? 2 : 1); |
| | | approveLogMapper.insert(log); |
| | | ApproveProcess refreshed = getProcessByApproveId(approveId); |
| | | writeApproveLog(memoryId, refreshed, currentNode, remark); |
| | | ApproveNode nextNode = refreshed == null ? null : findCurrentNode(listNodes(refreshed)); |
| | | |
| | | return actionResult(true, "review_action", |
| | | nextNode == null ? "审批已通过,且该流程已全部完成。" : "审批已通过,流程已流转到下一审批节点。", |
| | | "approve".equals(normalizedAction) ? "审批已通过。" : "审批已驳回。", |
| | | approveId, |
| | | Map.of( |
| | | "action", "approve", |
| | | "currentStatus", approveStatusName(process.getApproveStatus()), |
| | | "action", normalizedAction, |
| | | "currentStatus", refreshed == null ? "" : approveStatusName(refreshed.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( |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @Tool(name = "取消审批待办审核", value = "撤销最近一次审核结果,仅允许最近一次审核人或申请人操作。") |
| | | public String cancelReviewTodo(@ToolMemoryId String memoryId, |
| | | @P("流程编号 approveId") String approveId, |
| | | @P(value = "取消原因,可不传", required = false) String reason) { |
| | | ApproveProcess process = getProcessByApproveId(approveId); |
| | | |
| | | ApproveProcess process = getAccessibleProcess(memoryId, approveId); |
| | | if (process == null) { |
| | | return actionResult(false, "cancel_review_action", "未找到对应审批流程。", approveId, 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); |
| | | return actionResult(false, "cancel_review_action", "当前流程没有可撤销的审核记录。", approveId, null); |
| | | } |
| | | |
| | | Long userId = currentUserId(memoryId); |
| | | if (!isAdmin(userId) |
| | | && !Objects.equals(process.getApproveUser(), userId) |
| | | && !Objects.equals(lastReviewedNode.getApproveNodeUserId(), userId)) { |
| | | return actionResult(false, "cancel_review_action", "只有申请人、最近一次审核人或管理员可以撤销。", approveId, null); |
| | | } |
| | | |
| | | lastReviewedNode.setApproveNodeStatus(0); |
| | | lastReviewedNode.setApproveNodeTime(null); |
| | | lastReviewedNode.setApproveNodeReason(null); |
| | | lastReviewedNode.setApproveNodeRemark(reason); |
| | | lastReviewedNode.setUpdateUser(userId); |
| | | lastReviewedNode.setUpdateTime(LocalDateTime.now()); |
| | | approveNodeMapper.updateById(lastReviewedNode); |
| | | |
| | | List<ApproveLog> logs = listLogs(process.getId()); |
| | | ApproveLog latestLog = logs.stream() |
| | | ApproveLog latestLog = listLogs(process.getId()).stream() |
| | | .max(Comparator.comparing(ApproveLog::getApproveNodeOrder) |
| | | .thenComparing(ApproveLog::getApproveTime, Comparator.nullsLast(Date::compareTo))) |
| | | .orElse(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.setApproveStatus(lastReviewedNode.getApproveNodeOrder() == null || lastReviewedNode.getApproveNodeOrder() <= 1 ? 0 : 1); |
| | | process.setApproveUserCurrentId(lastReviewedNode.getApproveNodeUserId()); |
| | | process.setApproveUserCurrentName(lastReviewedNode.getApproveNodeUser()); |
| | | approveProcessMapper.updateById(process); |
| | | |
| | | return actionResult(true, "cancel_review_action", "最近一次审核已取消,流程已回退到对应审批节点。", approveId, Map.of( |
| | | return actionResult(true, "cancel_review_action", "最近一次审核已撤销。", approveId, Map.of( |
| | | "rollbackNodeOrder", lastReviewedNode.getApproveNodeOrder(), |
| | | "currentStatus", approveStatusName(process.getApproveStatus()), |
| | | "currentApprover", safe(process.getApproveUserCurrentName()), |
| | |
| | | )); |
| | | } |
| | | |
| | | @Transactional |
| | | @Tool(name = "修改审批待办", value = "修改审批单基础信息。支持修改标题、开始日期、结束日期、金额、地点、类型和备注。日期格式必须是 yyyy-MM-dd。") |
| | | public String updateTodo( |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @Tool(name = "修改审批待办", value = "修改审批单基础信息,仅允许申请人修改;不支持通过 AI 变更审批类型。") |
| | | public String updateTodo(@ToolMemoryId String memoryId, |
| | | @P("流程编号 approveId") String approveId, |
| | | @P(value = "新的标题,可不传", required = false) String approveReason, |
| | | @P(value = "新的开始日期 yyyy-MM-dd,可不传", required = false) String startDate, |
| | |
| | | @P(value = "新的地点,可不传", required = false) String location, |
| | | @P(value = "新的审批类型,可不传", required = false) Integer approveType, |
| | | @P(value = "新的备注,可不传", required = false) String approveRemark) { |
| | | ApproveProcess process = getProcessByApproveId(approveId); |
| | | |
| | | ApproveProcess process = getAccessibleProcess(memoryId, approveId); |
| | | if (process == null) { |
| | | return actionResult(false, "update_action", "未找到对应审批流程。", approveId, null); |
| | | return actionResult(false, "update_action", "未找到对应审批,或当前用户无权访问该流程。", approveId, null); |
| | | } |
| | | if (!Objects.equals(process.getApproveUser(), currentUserId(memoryId)) && !isAdmin(currentUserId(memoryId))) { |
| | | return actionResult(false, "update_action", "只有申请人或管理员可以修改审批单。", approveId, null); |
| | | } |
| | | if (process.getApproveStatus() != null && (process.getApproveStatus() == 1 || process.getApproveStatus() == 2)) { |
| | | return actionResult(false, "update_action", "审批处理中或已完成时,不允许通过 AI 修改。", approveId, null); |
| | | } |
| | | if (approveType != null && !Objects.equals(approveType, process.getApproveType())) { |
| | | return actionResult(false, "update_action", "AI 助手暂不支持直接变更审批类型,避免节点配置失真。", 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(location)) { |
| | | process.setLocation(location); |
| | | } |
| | | if (approveType != null) { |
| | | process.setApproveType(approveType); |
| | | } |
| | | if (StringUtils.hasText(approveRemark)) { |
| | | process.setApproveRemark(approveRemark); |
| | | } |
| | |
| | | )); |
| | | } |
| | | |
| | | @Transactional |
| | | @Tool(name = "删除审批待办", value = "删除审批流程。逻辑删除流程记录,并同步逻辑删除审批节点。") |
| | | public String deleteTodo(@P("流程编号 approveId") String approveId) { |
| | | ApproveProcess process = getProcessByApproveId(approveId); |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @Tool(name = "删除审批待办", value = "删除审批流程,仅允许申请人删除未完成的流程。") |
| | | public String deleteTodo(@ToolMemoryId String memoryId, |
| | | @P("流程编号 approveId") String approveId) { |
| | | ApproveProcess process = getAccessibleProcess(memoryId, approveId); |
| | | if (process == null) { |
| | | return actionResult(false, "delete_action", "未找到对应审批流程。", approveId, null); |
| | | return actionResult(false, "delete_action", "未找到对应审批,或当前用户无权访问该流程。", approveId, null); |
| | | } |
| | | if (process.getApproveDelete() != null && process.getApproveDelete() == 1) { |
| | | return actionResult(false, "delete_action", "该审批流程已经是删除状态。", approveId, null); |
| | | if (!Objects.equals(process.getApproveUser(), currentUserId(memoryId)) && !isAdmin(currentUserId(memoryId))) { |
| | | return actionResult(false, "delete_action", "只有申请人或管理员可以删除审批单。", approveId, null); |
| | | } |
| | | if (process.getApproveStatus() != null && (process.getApproveStatus() == 1 || process.getApproveStatus() == 2)) { |
| | | return actionResult(false, "delete_action", "审批处理中或已完成的流程不允许通过 AI 删除。", 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); |
| | | } |
| | | approveProcessService.delByIds(Collections.singletonList(process.getId())); |
| | | return actionResult(true, "delete_action", "审批流程已删除。", approveId, Map.of( |
| | | "deletedNodeCount", nodes.size(), |
| | | "deletedProcessId", process.getId(), |
| | | "approveStatus", approveStatusName(process.getApproveStatus()) |
| | | )); |
| | | } |
| | | |
| | | private ApproveProcess getAccessibleProcess(String memoryId, String approveId) { |
| | | ApproveProcess process = getProcessByApproveId(approveId); |
| | | if (process == null) { |
| | | return null; |
| | | } |
| | | return canView(process, currentUserId(memoryId)) ? process : null; |
| | | } |
| | | |
| | | private ApproveProcess getProcessByApproveId(String approveId) { |
| | |
| | | } |
| | | return approveProcessMapper.selectOne(new LambdaQueryWrapper<ApproveProcess>() |
| | | .eq(ApproveProcess::getApproveId, approveId) |
| | | .eq(ApproveProcess::getApproveDelete, 0) |
| | | .last("limit 1")); |
| | | } |
| | | |
| | | private List<ApproveNode> listNodes(ApproveProcess process) { |
| | | List<ApproveNode> nodes = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>() |
| | | if (process == null) { |
| | | return List.of(); |
| | | } |
| | | List<ApproveNode> nodes = defaultList(approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>() |
| | | .eq(ApproveNode::getDeleteFlag, 0) |
| | | .eq(ApproveNode::getApproveProcessId, process.getApproveId()) |
| | | .orderByAsc(ApproveNode::getApproveNodeOrder)); |
| | | if (nodes != null && !nodes.isEmpty()) { |
| | | .orderByAsc(ApproveNode::getApproveNodeOrder))); |
| | | if (!nodes.isEmpty()) { |
| | | return nodes; |
| | | } |
| | | List<ApproveNode> fallbackNodes = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>() |
| | | return defaultList(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; |
| | | .orderByAsc(ApproveNode::getApproveNodeOrder))); |
| | | } |
| | | |
| | | private List<ApproveLog> listLogs(Long processId) { |
| | | List<ApproveLog> logs = approveLogMapper.selectList(new LambdaQueryWrapper<ApproveLog>() |
| | | return defaultList(approveLogMapper.selectList(new LambdaQueryWrapper<ApproveLog>() |
| | | .eq(ApproveLog::getApproveId, processId) |
| | | .orderByAsc(ApproveLog::getApproveNodeOrder, ApproveLog::getApproveTime)); |
| | | return logs == null ? List.of() : logs; |
| | | .orderByAsc(ApproveLog::getApproveNodeOrder, ApproveLog::getApproveTime))); |
| | | } |
| | | |
| | | private ApproveNode findCurrentNode(List<ApproveNode> nodes) { |
| | |
| | | .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)) |
| | | private boolean isLastNode(List<ApproveNode> nodes, ApproveNode currentNode) { |
| | | Integer maxOrder = nodes.stream() |
| | | .map(ApproveNode::getApproveNodeOrder) |
| | | .filter(Objects::nonNull) |
| | | .max(Integer::compareTo) |
| | | .orElse(null); |
| | | return maxOrder != null && Objects.equals(maxOrder, currentNode.getApproveNodeOrder()); |
| | | } |
| | | |
| | | private void writeApproveLog(String memoryId, ApproveProcess process, ApproveNode currentNode, String remark) { |
| | | if (process == null || currentNode == null) { |
| | | return; |
| | | } |
| | | ApproveLog log = new ApproveLog(); |
| | | log.setApproveId(process.getId()); |
| | | log.setApproveNodeOrder(currentNode.getApproveNodeOrder()); |
| | | log.setApproveUser(currentUserId(memoryId)); |
| | | log.setApproveTime(new Date()); |
| | | log.setApproveStatus(process.getApproveStatus()); |
| | | log.setApproveRemark(remark); |
| | | approveLogMapper.insert(log); |
| | | } |
| | | |
| | | private boolean canView(ApproveProcess process, Long userId) { |
| | | if (process == null || userId == null) { |
| | | return false; |
| | | } |
| | | return isAdmin(userId) |
| | | || Objects.equals(process.getApproveUser(), userId) |
| | | || Objects.equals(process.getApproveUserCurrentId(), userId) |
| | | || containsUserId(process.getApproveUserIds(), userId); |
| | | } |
| | | |
| | | private boolean canOperate(ApproveProcess process, Long userId) { |
| | | return process != null && userId != null && Objects.equals(process.getApproveUserCurrentId(), userId); |
| | | } |
| | | |
| | | private boolean containsUserId(String csv, Long userId) { |
| | | if (!StringUtils.hasText(csv) || userId == null) { |
| | | return false; |
| | | } |
| | | String target = String.valueOf(userId); |
| | | for (String item : csv.split(",")) { |
| | | if (target.equals(item.trim())) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String relationName(ApproveProcess process, Long userId) { |
| | | if (Objects.equals(process.getApproveUserCurrentId(), userId)) { |
| | | return "当前审批人"; |
| | | } |
| | | if (Objects.equals(process.getApproveUser(), userId)) { |
| | | return "申请人"; |
| | | } |
| | | if (containsUserId(process.getApproveUserIds(), userId)) { |
| | | return "审批链成员"; |
| | | } |
| | | return "可见"; |
| | | } |
| | | |
| | | private List<String> todoColumns() { |
| | | return List.of("approveId", "approveType", "approveUserName", "approveUserCurrentName", |
| | | "approveReason", "approveStatus", "createTime", "relation"); |
| | | } |
| | | |
| | | private int normalizeLimit(Integer limit) { |
| | |
| | | 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; |
| | | return String.format("%.2f%%", part * 100.0 / total); |
| | | } |
| | | |
| | | private List<Map<String, Object>> toTrendItems(List<String> dates, List<Long> values) { |
| | |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildRecentTrendLineOption(List<String> dates, List<Long> values) { |
| | | private Map<String, Object> buildTrendLineOption(List<String> dates, List<Long> values, String label) { |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "最近7天审批新增趋势", "left", "center")); |
| | | option.put("title", Map.of("text", label + "审批新增趋势", "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")); |
| | |
| | | } |
| | | } |
| | | |
| | | private DateRange resolveDateRange(String startDateText, String endDateText, String timeRange) { |
| | | LocalDate today = LocalDate.now(); |
| | | LocalDate explicitStart = parseLocalDate(startDateText); |
| | | LocalDate explicitEnd = parseLocalDate(endDateText); |
| | | if (explicitStart != null || explicitEnd != null) { |
| | | LocalDate start = explicitStart != null ? explicitStart : explicitEnd; |
| | | LocalDate end = explicitEnd != null ? explicitEnd : explicitStart; |
| | | if (start.isAfter(end)) { |
| | | LocalDate temp = start; |
| | | start = end; |
| | | end = temp; |
| | | } |
| | | return new DateRange(start, end, start + "至" + end); |
| | | } |
| | | if (!StringUtils.hasText(timeRange)) { |
| | | return new DateRange(today.minusDays(6), today, "近7天"); |
| | | } |
| | | |
| | | String text = timeRange.trim(); |
| | | if (text.contains("今天")) { |
| | | return new DateRange(today, today, "今天"); |
| | | } |
| | | if (text.contains("昨天") || text.contains("昨日")) { |
| | | LocalDate day = today.minusDays(1); |
| | | return new DateRange(day, day, "昨天"); |
| | | } |
| | | if (text.contains("本周")) { |
| | | LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | return new DateRange(start, today, "本周"); |
| | | } |
| | | if (text.contains("上周")) { |
| | | LocalDate thisWeekStart = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | LocalDate start = thisWeekStart.minusWeeks(1); |
| | | LocalDate end = thisWeekStart.minusDays(1); |
| | | return new DateRange(start, end, "上周"); |
| | | } |
| | | if (text.contains("本月")) { |
| | | LocalDate start = today.withDayOfMonth(1); |
| | | return new DateRange(start, today, "本月"); |
| | | } |
| | | if (text.contains("上月")) { |
| | | YearMonth lastMonth = YearMonth.from(today).minusMonths(1); |
| | | return new DateRange(lastMonth.atDay(1), lastMonth.atEndOfMonth(), "上月"); |
| | | } |
| | | if (text.contains("本年") || text.contains("今年")) { |
| | | LocalDate start = today.withDayOfYear(1); |
| | | return new DateRange(start, today, "本年"); |
| | | } |
| | | if (text.contains("去年")) { |
| | | LocalDate start = today.minusYears(1).withDayOfYear(1); |
| | | LocalDate end = today.minusYears(1).withMonth(12).withDayOfMonth(31); |
| | | return new DateRange(start, end, "去年"); |
| | | } |
| | | |
| | | Matcher relativeMatcher = java.util.regex.Pattern.compile("(近|最近)(\\d+)(天|周|个月|月|年)").matcher(text); |
| | | if (relativeMatcher.find()) { |
| | | int amount = Integer.parseInt(relativeMatcher.group(2)); |
| | | String unit = relativeMatcher.group(3); |
| | | LocalDate start = switch (unit) { |
| | | case "天" -> today.minusDays(Math.max(amount - 1L, 0)); |
| | | case "周" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1); |
| | | case "个月", "月" -> today.minusMonths(Math.max(amount, 1)).plusDays(1); |
| | | case "年" -> today.minusYears(Math.max(amount, 1)).plusDays(1); |
| | | default -> today.minusDays(6); |
| | | }; |
| | | return new DateRange(start, today, "近" + amount + unit); |
| | | } |
| | | |
| | | Matcher dateMatcher = java.util.regex.Pattern.compile("(\\d{4}-\\d{2}-\\d{2})").matcher(text); |
| | | if (dateMatcher.find()) { |
| | | LocalDate start = LocalDate.parse(dateMatcher.group(1)); |
| | | LocalDate end = dateMatcher.find() ? LocalDate.parse(dateMatcher.group(1)) : start; |
| | | if (start.isAfter(end)) { |
| | | LocalDate temp = start; |
| | | start = end; |
| | | end = temp; |
| | | } |
| | | return new DateRange(start, end, start + "至" + end); |
| | | } |
| | | |
| | | return new DateRange(today.minusDays(6), today, "近7天"); |
| | | } |
| | | |
| | | private boolean withinDateRange(LocalDateTime createTime, DateRange dateRange) { |
| | | if (createTime == null) { |
| | | return false; |
| | | } |
| | | LocalDate date = createTime.toLocalDate(); |
| | | return !date.isBefore(dateRange.start()) && !date.isAfter(dateRange.end()); |
| | | } |
| | | |
| | | private TrendRange buildTrendRange(LocalDate start, LocalDate end, List<ApproveProcess> processes) { |
| | | long days = ChronoUnit.DAYS.between(start, end) + 1; |
| | | if (days <= 31) { |
| | | List<String> labels = new ArrayList<>(); |
| | | List<Long> values = new ArrayList<>(); |
| | | for (LocalDate cursor = start; !cursor.isAfter(end); cursor = cursor.plusDays(1)) { |
| | | LocalDate current = cursor; |
| | | labels.add(current.toString()); |
| | | values.add(processes.stream() |
| | | .filter(process -> process.getCreateTime() != null) |
| | | .filter(process -> process.getCreateTime().toLocalDate().equals(current)) |
| | | .count()); |
| | | } |
| | | return new TrendRange(labels, values, "day", start + "至" + end); |
| | | } |
| | | |
| | | List<String> labels = new ArrayList<>(); |
| | | List<Long> values = new ArrayList<>(); |
| | | YearMonth startMonth = YearMonth.from(start); |
| | | YearMonth endMonth = YearMonth.from(end); |
| | | for (YearMonth cursor = startMonth; !cursor.isAfter(endMonth); cursor = cursor.plusMonths(1)) { |
| | | YearMonth current = cursor; |
| | | labels.add(current.toString()); |
| | | values.add(processes.stream() |
| | | .filter(process -> process.getCreateTime() != null) |
| | | .filter(process -> YearMonth.from(process.getCreateTime()).equals(current)) |
| | | .count()); |
| | | } |
| | | return new TrendRange(labels, values, "month", start + "至" + end); |
| | | } |
| | | |
| | | private LocalDate parseLocalDate(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | return LocalDate.parse(text.trim()); |
| | | } |
| | | |
| | | 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); |
| | | summary.put("approveId", safe(approveId)); |
| | | return jsonResponse(success, type, description, summary, data == null ? Map.of() : data, Map.of()); |
| | | } |
| | | |
| | | private String jsonResponse( |
| | | boolean success, |
| | | private String jsonResponse(boolean success, |
| | | String type, |
| | | String description, |
| | | Map<String, Object> summary, |
| | |
| | | result.put("charts", charts == null ? Map.of() : charts); |
| | | return JSON.toJSONString(result); |
| | | } |
| | | |
| | | private LoginUser currentLoginUser(String memoryId) { |
| | | LoginUser loginUser = aiSessionUserContext.get(memoryId); |
| | | if (loginUser != null) { |
| | | return loginUser; |
| | | } |
| | | return SecurityUtils.getLoginUser(); |
| | | } |
| | | |
| | | private Long currentUserId(String memoryId) { |
| | | return currentLoginUser(memoryId).getUserId(); |
| | | } |
| | | |
| | | private boolean isAdmin(Long userId) { |
| | | return SecurityUtils.isAdmin(userId); |
| | | } |
| | | |
| | | private <T> List<T> defaultList(List<T> list) { |
| | | return list == null ? List.of() : list; |
| | | } |
| | | |
| | | private record DateRange(LocalDate start, LocalDate end, String label) { |
| | | } |
| | | |
| | | private record TrendRange(List<String> labels, List<Long> values, String granularity, String label) { |
| | | } |
| | | } |