| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.tools; |
| | | |
| | | 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.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 |
| | | 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; |
| | | private final IApproveNodeService approveNodeService; |
| | | private final ApproveProcessServiceImpl approveProcessService; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | |
| | | public ApproveTodoTools(ApproveProcessMapper approveProcessMapper, |
| | | ApproveNodeMapper approveNodeMapper, |
| | | 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 = "æ¥è¯¢å½åç»å½äººç¸å
³ç审æ¹å¾
åï¼ä¼å
è¿åèªå·±å¾
å¤çç审æ¹ï¼æ¯ææç¶æãç±»åãå
³é®ååèå´è¿æ»¤ã") |
| | | 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, |
| | | @P(value = "æ¥è¯¢èå´ï¼å¯éå¼ï¼relatedãapplicantãapproverï¼related 表示å½åç¨æ·ç¸å
³ï¼applicant 表示æåèµ·çï¼approver 表示å¾
æå¤çç", required = false) String scope) { |
| | | |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | Long userId = loginUser.getUserId(); |
| | | Integer statusCode = parseStatus(status); |
| | | String normalizedScope = normalizeScope(scope); |
| | | |
| | | LambdaQueryWrapper<ApproveProcess> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(ApproveProcess::getApproveDelete, 0); |
| | | if (statusCode == null) { |
| | | wrapper.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 ("applicant".equals(normalizedScope)) { |
| | | wrapper.eq(ApproveProcess::getApproveUser, userId); |
| | | if (statusCode != null) { |
| | | wrapper.eq(ApproveProcess::getApproveStatus, statusCode); |
| | | } |
| | | } else if ("approver".equals(normalizedScope)) { |
| | | wrapper.eq(ApproveProcess::getApproveUserCurrentId, userId); |
| | | if (statusCode != null) { |
| | | wrapper.eq(ApproveProcess::getApproveStatus, statusCode); |
| | | } |
| | | } else if (statusCode != null && (statusCode == 0 || statusCode == 1)) { |
| | | wrapper.eq(ApproveProcess::getApproveUserCurrentId, userId); |
| | | wrapper.eq(ApproveProcess::getApproveStatus, statusCode); |
| | | } else { |
| | | wrapper.and(w -> w.eq(ApproveProcess::getApproveUser, userId) |
| | | .or().eq(ApproveProcess::getApproveUserCurrentId, userId) |
| | | .or().apply("FIND_IN_SET({0}, approve_user_ids)", userId)); |
| | | if (statusCode != null) { |
| | | wrapper.eq(ApproveProcess::getApproveStatus, statusCode); |
| | | } |
| | | } |
| | | |
| | | wrapper.orderByDesc(ApproveProcess::getCreateTime) |
| | | .last("limit " + normalizeLimit(limit)); |
| | | |
| | | List<ApproveProcess> processes = defaultList(approveProcessMapper.selectList(wrapper)); |
| | | if (processes.isEmpty()) { |
| | | return jsonResponse(true, "todo_list", "æªæ¥è¯¢å°å½åç¨æ·ç¬¦åæ¡ä»¶ç审æ¹å¾
åã", |
| | | Map.of("count", 0), |
| | | Map.of("columns", todoColumns(), "items", List.of()), |
| | | Map.of()); |
| | | } |
| | | |
| | | 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", 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()); |
| | | |
| | | return jsonResponse(true, "todo_list", "å·²è¿åå½åç¨æ·ç¸å
³å®¡æ¹å表ã", |
| | | Map.of( |
| | | "count", items.size(), |
| | | "statusFilter", StringUtils.hasText(status) ? status : "all", |
| | | "approveType", approveType == null ? "" : approveType, |
| | | "keyword", keyword == null ? "" : keyword, |
| | | "scope", normalizedScope |
| | | ), |
| | | Map.of("columns", todoColumns(), "items", items), |
| | | Map.of()); |
| | | } |
| | | |
| | | @Tool(name = "æ¥è¯¢å®¡æ¹å¾
å详æ
", value = "æ ¹æ®æµç¨ç¼å·æ¥è¯¢å½åç»å½äººå¯è§ç审æ¹è¯¦æ
ã") |
| | | public String getTodoDetail(@ToolMemoryId String memoryId, |
| | | @P("æµç¨ç¼å· approveId") String approveId) { |
| | | ApproveProcess process = getAccessibleProcess(memoryId, 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())); |
| | | detail.add("ä¸å½åç¨æ·å
³ç³»: " + relationName(process, currentUserId(memoryId))); |
| | | return detail.toString(); |
| | | } |
| | | |
| | | @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", safe(approveId)), |
| | | 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", safe(node.getApproveNodeUser())); |
| | | item.put("approveNodeUserId", node.getApproveNodeUserId()); |
| | | item.put("approveNodeStatus", approveNodeStatusName(node.getApproveNodeStatus())); |
| | | item.put("approveNodeTime", formatDate(node.getApproveNodeTime())); |
| | | 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", safe(log.getApproveRemark())); |
| | | return item; |
| | | }).collect(Collectors.toList()); |
| | | |
| | | return jsonResponse(true, "todo_progress", "å·²è¿åå®¡æ¹æµè½¬è®°å½ã", |
| | | Map.of( |
| | | "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()); |
| | | } |
| | | |
| | | @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(), |
| | | "trend", List.of() |
| | | ), |
| | | Map.of()); |
| | | } |
| | | |
| | | Map<String, Long> statusStats = filteredProcesses.stream() |
| | | .collect(Collectors.groupingBy(p -> approveStatusName(p.getApproveStatus()), LinkedHashMap::new, Collectors.counting())); |
| | | Map<String, Long> typeStats = filteredProcesses.stream() |
| | | .collect(Collectors.groupingBy(p -> approveTypeName(p.getApproveType()), LinkedHashMap::new, Collectors.counting())); |
| | | |
| | | 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); |
| | | |
| | | TrendRange trendRange = buildTrendRange(dateRange.start(), dateRange.end(), filteredProcesses); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | 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, 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("trendLineOption", buildTrendLineOption(trendRange.labels(), trendRange.values(), trendRange.label())); |
| | | |
| | | return jsonResponse(true, "todo_stats", "å·²è¿åå½åç¨æ·ç¸å
³å®¡æ¹ç»è®¡ã", |
| | | summary, |
| | | Map.of( |
| | | "statusDistribution", statusStats, |
| | | "typeDistribution", typeStats, |
| | | "trend", toTrendItems(trendRange.labels(), trendRange.values()) |
| | | ), |
| | | charts); |
| | | } |
| | | |
| | | @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 = getAccessibleProcess(memoryId, approveId); |
| | | if (process == null) { |
| | | 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); |
| | | } |
| | | |
| | | List<ApproveNode> nodes = listNodes(process); |
| | | ApproveNode currentNode = findCurrentNode(nodes); |
| | | if (currentNode == null || !Objects.equals(currentNode.getApproveNodeUserId(), currentUserId(memoryId))) { |
| | | return actionResult(false, "review_action", "æªæ¾å°å½åç¨æ·å¯å¤çç审æ¹èç¹ã", approveId, null); |
| | | } |
| | | |
| | | String normalizedAction = action == null ? "" : action.trim().toLowerCase(); |
| | | currentNode.setApproveNodeRemark(remark); |
| | | currentNode.setApproveNodeReason("reject".equals(normalizedAction) ? remark : null); |
| | | currentNode.setUpdateUser(currentUserId(memoryId)); |
| | | currentNode.setUpdateTime(LocalDateTime.now()); |
| | | currentNode.setIsLast(isLastNode(nodes, currentNode)); |
| | | |
| | | try { |
| | | switch (normalizedAction) { |
| | | case "approve" -> currentNode.setApproveNodeStatus(1); |
| | | case "reject" -> currentNode.setApproveNodeStatus(2); |
| | | default -> { |
| | | return actionResult(false, "review_action", "action åªæ¯æ approve æ rejectã", approveId, null); |
| | | } |
| | | } |
| | | approveNodeService.updateApproveNode(currentNode); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException("审æ¹å¤ç失败", e); |
| | | } |
| | | |
| | | ApproveProcess refreshed = getProcessByApproveId(approveId); |
| | | writeApproveLog(memoryId, refreshed, currentNode, remark); |
| | | ApproveNode nextNode = refreshed == null ? null : findCurrentNode(listNodes(refreshed)); |
| | | |
| | | return actionResult(true, "review_action", |
| | | "approve".equals(normalizedAction) ? "审æ¹å·²éè¿ã" : "审æ¹å·²é©³åã", |
| | | approveId, |
| | | Map.of( |
| | | "action", normalizedAction, |
| | | "currentStatus", refreshed == null ? "" : approveStatusName(refreshed.getApproveStatus()), |
| | | "nextApprover", nextNode == null ? "" : safe(nextNode.getApproveNodeUser()), |
| | | "remark", safe(remark) |
| | | )); |
| | | } |
| | | |
| | | @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 = getAccessibleProcess(memoryId, 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); |
| | | } |
| | | |
| | | 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); |
| | | |
| | | ApproveLog latestLog = listLogs(process.getId()).stream() |
| | | .max(Comparator.comparing(ApproveLog::getApproveNodeOrder) |
| | | .thenComparing(ApproveLog::getApproveTime, Comparator.nullsLast(Date::compareTo))) |
| | | .orElse(null); |
| | | if (latestLog != null) { |
| | | approveLogMapper.deleteById(latestLog.getId()); |
| | | } |
| | | |
| | | process.setApproveOverTime(null); |
| | | process.setApproveRemark(reason); |
| | | 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( |
| | | "rollbackNodeOrder", lastReviewedNode.getApproveNodeOrder(), |
| | | "currentStatus", approveStatusName(process.getApproveStatus()), |
| | | "currentApprover", safe(process.getApproveUserCurrentName()), |
| | | "reason", safe(reason) |
| | | )); |
| | | } |
| | | |
| | | @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 = "æ°çç»ææ¥æ 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 = getAccessibleProcess(memoryId, approveId); |
| | | if (process == 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) |
| | | && !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 (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(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); |
| | | } |
| | | 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); |
| | | } |
| | | |
| | | approveProcessService.delByIds(Collections.singletonList(process.getId())); |
| | | return actionResult(true, "delete_action", "å®¡æ¹æµç¨å·²å é¤ã", approveId, Map.of( |
| | | "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) { |
| | | if (!StringUtils.hasText(approveId)) { |
| | | return null; |
| | | } |
| | | return approveProcessMapper.selectOne(new LambdaQueryWrapper<ApproveProcess>() |
| | | .eq(ApproveProcess::getApproveId, approveId) |
| | | .eq(ApproveProcess::getApproveDelete, 0) |
| | | .last("limit 1")); |
| | | } |
| | | |
| | | private List<ApproveNode> listNodes(ApproveProcess process) { |
| | | 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.isEmpty()) { |
| | | return nodes; |
| | | } |
| | | return defaultList(approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>() |
| | | .eq(ApproveNode::getDeleteFlag, 0) |
| | | .eq(ApproveNode::getApproveProcessId, String.valueOf(process.getId())) |
| | | .orderByAsc(ApproveNode::getApproveNodeOrder))); |
| | | } |
| | | |
| | | private List<ApproveLog> listLogs(Long processId) { |
| | | return defaultList(approveLogMapper.selectList(new LambdaQueryWrapper<ApproveLog>() |
| | | .eq(ApproveLog::getApproveId, processId) |
| | | .orderByAsc(ApproveLog::getApproveNodeOrder, ApproveLog::getApproveTime))); |
| | | } |
| | | |
| | | 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 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 (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 normalizeScope(String scope) { |
| | | if (!StringUtils.hasText(scope)) { |
| | | return "related"; |
| | | } |
| | | return switch (scope.trim().toLowerCase()) { |
| | | case "applicant", "mine", "created", "initiated" -> "applicant"; |
| | | case "approver", "handler", "todo", "pending" -> "approver"; |
| | | default -> "related"; |
| | | }; |
| | | } |
| | | |
| | | 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 -> "å±é©ä½ä¸å®¡æ¹"; |
| | | case 9 -> "åå
¬ç¨å审æ¹"; |
| | | 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%"; |
| | | } |
| | | return String.format("%.2f%%", part * 100.0 / total); |
| | | } |
| | | |
| | | 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> buildTrendLineOption(List<String> dates, List<Long> values, String label) { |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | 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")); |
| | | 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 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", safe(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); |
| | | } |
| | | |
| | | 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) { |
| | | } |
| | | } |