From 2b382a92207dfabf0eb30e743265df5c7c50e7bc Mon Sep 17 00:00:00 2001
From: liyong <18434998025@163.com>
Date: 星期四, 07 五月 2026 14:27:45 +0800
Subject: [PATCH] feat(approve): 新增办公用品审批功能
---
src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java | 821 ++++++++++++++++++++++++++++++++++++++--------------------
1 files changed, 533 insertions(+), 288 deletions(-)
diff --git a/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java b/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
index 431b848..cd3e933 100644
--- a/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
+++ b/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
@@ -2,25 +2,43 @@
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
@@ -34,30 +52,43 @@
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) {
+ 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 = "鏌ヨ瀹℃壒寰呭姙锛屾敮鎸佹寜鐘舵�併�佺被鍨嬨�佸叧閿瓧杩囨护锛岃繑鍥� Markdown 琛ㄦ牸銆�")
- public String listTodos(
- @P(value = "瀹℃壒鐘舵�侊紝鍙�夊�硷細all銆乸ending銆乸rocessing銆乤pproved銆乺ejected銆乺esubmitted", required = false) String status,
- @P(value = "瀹℃壒绫诲瀷缂栧彿锛屽彲涓嶄紶", required = false) Integer approveType,
- @P(value = "鍏抽敭瀛楋紝鍙尮閰嶆祦绋嬬紪鍙枫�佹爣棰樸�佺敵璇蜂汉銆佸綋鍓嶅鎵逛汉", required = false) String keyword,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�20", required = false) Integer limit) {
+ @Tool(name = "鏌ヨ瀹℃壒寰呭姙鍒楄〃", value = "鏌ヨ褰撳墠鐧诲綍浜虹浉鍏崇殑瀹℃壒寰呭姙锛屼紭鍏堣繑鍥炶嚜宸卞緟澶勭悊鐨勫鎵癸紝鏀寔鎸夌姸鎬併�佺被鍨嬨�佸叧閿瓧鍜岃寖鍥磋繃婊ゃ��")
+ public String listTodos(@ToolMemoryId String memoryId,
+ @P(value = "瀹℃壒鐘舵�侊紝鍙�夊�硷細all銆乸ending銆乸rocessing銆乤pproved銆乺ejected銆乺esubmitted", 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銆乤pplicant銆乤pprover锛況elated 琛ㄧず褰撳墠鐢ㄦ埛鐩稿叧锛宎pplicant 琛ㄧず鎴戝彂璧风殑锛宎pprover 琛ㄧず寰呮垜澶勭悊鐨�", 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);
-
- Integer statusCode = parseStatus(status);
- if (statusCode != null) {
- wrapper.eq(ApproveProcess::getApproveStatus, statusCode);
+ if (statusCode == null) {
+ wrapper.ne(ApproveProcess::getApproveStatus, 2);
}
+
if (approveType != null) {
wrapper.eq(ApproveProcess::getApproveType, approveType);
}
@@ -67,60 +98,76 @@
.or().like(ApproveProcess::getApproveUserName, keyword)
.or().like(ApproveProcess::getApproveUserCurrentName, keyword));
}
-
- wrapper.orderByDesc(ApproveProcess::getCreateTime);
- wrapper.last("limit " + normalizeLimit(limit));
-
- List<ApproveProcess> processes = approveProcessMapper.selectList(wrapper);
- if (processes == null || processes.isEmpty()) {
- return jsonResponse(
- true,
- "todo_list",
- "鏈煡璇㈠埌绗﹀悎鏉′欢鐨勫鎵瑰緟鍔炪��",
- Map.of("count", 0),
- Map.of(
- "columns", List.of("approveId", "approveType", "approveUserName", "approveUserCurrentName", "approveReason", "approveStatus", "createTime"),
- "items", List.of()
- ),
- Map.of()
- );
+ 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);
+ }
}
- List<Map<String, Object>> items = processes.stream().map(process -> {
- Map<String, Object> item = new LinkedHashMap<>();
- item.put("approveId", process.getApproveId());
- item.put("approveType", approveTypeName(process.getApproveType()));
- item.put("approveUserName", process.getApproveUserName());
- item.put("approveUserCurrentName", process.getApproveUserCurrentName());
- item.put("approveReason", process.getApproveReason());
- item.put("approveStatus", approveStatusName(process.getApproveStatus()));
- item.put("createTime", formatDateTime(process.getCreateTime()));
- return item;
- }).collect(Collectors.toList());
+ wrapper.orderByDesc(ApproveProcess::getCreateTime)
+ .last("limit " + normalizeLimit(limit));
- 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", 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", status == null ? "all" : status,
+ "statusFilter", StringUtils.hasText(status) ? status : "all",
"approveType", approveType == null ? "" : approveType,
- "keyword", keyword == null ? "" : keyword
+ "keyword", keyword == null ? "" : keyword,
+ "scope", normalizedScope
),
- 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 "鏈壘鍒板搴旂殑瀹℃壒娴佺▼锛岃纭娴佺▼缂栧彿鏄惁姝g‘銆�";
+ return "鏈壘鍒板搴斿鎵癸紝鎴栧綋鍓嶇敤鎴锋棤鏉冩煡鐪嬭娴佺▼銆�";
}
StringJoiner detail = new StringJoiner("\n");
@@ -139,255 +186,231 @@
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",
- "鏈壘鍒板搴旂殑瀹℃壒娴佺▼锛岃纭娴佺▼缂栧彿鏄惁姝g‘銆�",
- 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 = "鎵ц瀹℃壒鍔ㄤ綔銆俛ction 鍙敮鎸� approve 鎴� reject銆俛pprove 琛ㄧず閫氳繃锛宺eject 琛ㄧず椹冲洖銆�")
- public String reviewTodo(
- @P("娴佺▼缂栧彿 approveId") String approveId,
- @P("鍔ㄤ綔锛宎pprove=閫氳繃锛宺eject=椹冲洖") String action,
- @P(value = "瀹℃壒澶囨敞锛屽彲涓嶄紶", required = false) String remark) {
- ApproveProcess process = getProcessByApproveId(approveId);
+ @Transactional(rollbackFor = Exception.class)
+ @Tool(name = "瀹℃壒寰呭姙", value = "鎵ц瀹℃壒鍔ㄤ綔锛宎ction 浠呮敮鎸� approve 鎴� reject锛屼笖鍙兘澶勭悊褰撳墠鐧诲綍浜鸿嚜宸辩殑寰呭鑺傜偣銆�")
+ public String reviewTodo(@ToolMemoryId String memoryId,
+ @P("娴佺▼缂栧彿 approveId") String approveId,
+ @P("鍔ㄤ綔锛宎pprove=閫氳繃锛宺eject=椹冲洖") String action,
+ @P(value = "瀹℃壒澶囨敞锛屽彲涓嶄紶", required = false) String remark) {
+
+ 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);
-
- log.setApproveStatus(nextNode == null ? 2 : 1);
- approveLogMapper.insert(log);
- return actionResult(true, "review_action",
- nextNode == null ? "瀹℃壒宸查�氳繃锛屼笖璇ユ祦绋嬪凡鍏ㄩ儴瀹屾垚銆�" : "瀹℃壒宸查�氳繃锛屾祦绋嬪凡娴佽浆鍒颁笅涓�瀹℃壒鑺傜偣銆�",
- approveId,
- Map.of(
- "action", "approve",
- "currentStatus", approveStatusName(process.getApproveStatus()),
- "nextApprover", nextNode == null ? "" : safe(nextNode.getApproveNodeUser()),
- "remark", safe(remark)
- ));
+ approveNodeService.updateApproveNode(currentNode);
+ } catch (IOException e) {
+ throw new RuntimeException("瀹℃壒澶勭悊澶辫触", e);
}
- if ("reject".equals(normalizedAction)) {
- currentNode.setApproveNodeStatus(2);
- currentNode.setApproveNodeReason(remark);
- currentNode.setApproveNodeRemark(remark);
- approveNodeMapper.updateById(currentNode);
+ ApproveProcess refreshed = getProcessByApproveId(approveId);
+ writeApproveLog(memoryId, refreshed, currentNode, remark);
+ ApproveNode nextNode = refreshed == null ? null : findCurrentNode(listNodes(refreshed));
- 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);
+ 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
- @Tool(name = "鍙栨秷瀹℃壒寰呭姙瀹℃牳", value = "鎾ら攢鏈�杩戜竴娆″鏍哥粨鏋滐紝灏嗘渶杩戝鏍歌妭鐐规仮澶嶄负鏈鏍革紝骞跺洖婊氭祦绋嬬姸鎬併��")
- public String cancelReviewTodo(
- @P("娴佺▼缂栧彿 approveId") String approveId,
- @P(value = "鍙栨秷鍘熷洜锛屽彲涓嶄紶", required = false) String reason) {
- ApproveProcess process = getProcessByApproveId(approveId);
+ @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);
+ 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);
@@ -395,20 +418,14 @@
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()),
@@ -416,27 +433,36 @@
));
}
- @Transactional
- @Tool(name = "淇敼瀹℃壒寰呭姙", value = "淇敼瀹℃壒鍗曞熀纭�淇℃伅銆傛敮鎸佷慨鏀规爣棰樸�佸紑濮嬫棩鏈熴�佺粨鏉熸棩鏈熴�侀噾棰濄�佸湴鐐广�佺被鍨嬪拰澶囨敞銆傛棩鏈熸牸寮忓繀椤绘槸 yyyy-MM-dd銆�")
- public String updateTodo(
- @P("娴佺▼缂栧彿 approveId") String approveId,
- @P(value = "鏂扮殑鏍囬锛屽彲涓嶄紶", required = false) String approveReason,
- @P(value = "鏂扮殑寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
- @P(value = "鏂扮殑缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
- @P(value = "鏂扮殑閲戦锛屽彲涓嶄紶", required = false) BigDecimal price,
- @P(value = "鏂扮殑鍦扮偣锛屽彲涓嶄紶", required = false) String location,
- @P(value = "鏂扮殑瀹℃壒绫诲瀷锛屽彲涓嶄紶", required = false) Integer approveType,
- @P(value = "鏂扮殑澶囨敞锛屽彲涓嶄紶", required = false) String approveRemark) {
- ApproveProcess process = getProcessByApproveId(approveId);
+ @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);
+ 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);
}
@@ -456,9 +482,6 @@
if (StringUtils.hasText(location)) {
process.setLocation(location);
}
- if (approveType != null) {
- process.setApproveType(approveType);
- }
if (StringUtils.hasText(approveRemark)) {
process.setApproveRemark(approveRemark);
}
@@ -475,30 +498,34 @@
));
}
- @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) {
@@ -507,29 +534,31 @@
}
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) {
@@ -539,12 +568,72 @@
.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) {
@@ -565,6 +654,17 @@
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";
};
}
@@ -607,6 +707,7 @@
case 6 -> "鎶ヤ环瀹℃壒";
case 7 -> "鍙戣揣瀹℃壒";
case 8 -> "鍗遍櫓浣滀笟瀹℃壒";
+ case 9 -> "鍔炲叕鐢ㄥ搧瀹℃壒";
default -> "绫诲瀷" + type;
};
}
@@ -640,17 +741,7 @@
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) {
@@ -702,9 +793,9 @@
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"));
@@ -726,19 +817,147 @@
}
}
+ 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,
- String type,
- String description,
- Map<String, Object> summary,
- Map<String, Object> data,
- Map<String, Object> charts) {
+ 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);
@@ -748,4 +967,30 @@
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) {
+ }
}
--
Gitblit v1.9.3