From 756f5606dd50d0bffe39a0de7a460ff2efb8f7fa Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 28 五月 2026 10:54:51 +0800
Subject: [PATCH] refactor(ai): 移除AI聊天相关实体类和服务实现
---
/dev/null | 1475 -----------------------------------------------------------
src/main/resources/application-jhy.yml | 4
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java | 4
3 files changed, 4 insertions(+), 1,479 deletions(-)
diff --git a/src/main/java/com/ruoyi/ai/assistant/ApproveTodoAgent.java b/src/main/java/com/ruoyi/ai/assistant/ApproveTodoAgent.java
deleted file mode 100644
index 3b37909..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/ApproveTodoAgent.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import dev.langchain4j.service.MemoryId;
-import dev.langchain4j.service.SystemMessage;
-import dev.langchain4j.service.UserMessage;
-import dev.langchain4j.service.spring.AiService;
-import reactor.core.publisher.Flux;
-
-import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
-
-@AiService(
- wiringMode = EXPLICIT,
- streamingChatModel = "qwenStreamingChatModel",
- chatMemoryProvider = "chatMemoryProviderApproveTodo",
- tools = "approveTodoTools")
-public interface ApproveTodoAgent {
-
- @SystemMessage(fromResource = "approve-todo-agent-prompt.txt")
- Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
-}
diff --git a/src/main/java/com/ruoyi/ai/assistant/ApproveTodoIntentExecutor.java b/src/main/java/com/ruoyi/ai/assistant/ApproveTodoIntentExecutor.java
deleted file mode 100644
index 8d8ad45..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/ApproveTodoIntentExecutor.java
+++ /dev/null
@@ -1,451 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import com.alibaba.fastjson2.JSON;
-import com.ruoyi.ai.tools.ApproveTodoTools;
-import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
-
-import java.math.BigDecimal;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-@Component
-public class ApproveTodoIntentExecutor {
-
- private static final Pattern APPROVE_ID_BY_LABEL_PATTERN = Pattern.compile("(娴佺▼缂栧彿|娴佺▼鍙穦娴佺▼ID|瀹℃壒缂栧彿|缂栧彿)\\s*[:锛歖?\\s*([A-Za-z0-9_-]{2,64})");
- private static final Pattern APPROVE_ID_PATTERN = Pattern.compile("\\b[A-Za-z]*\\d{6,}[A-Za-z0-9_-]*\\b");
- private static final Pattern LIMIT_PATTERN = Pattern.compile("(鍓峾鏈�杩�)?\\s*(\\d{1,2})\\s*鏉�");
- private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
- private static final Pattern NUMBER_PATTERN = Pattern.compile("(\\d+(?:\\.\\d+)?)");
- private static final Pattern RECENT_RANGE_PATTERN = Pattern.compile("杩慭\d+(澶﹟鍛▅涓湀|鏈坾骞�)");
- private static final Pattern HALF_RANGE_PATTERN = Pattern.compile("(鏈�杩憒杩�)?鍗�(涓�)?(鏈坾骞�)");
- private static final Pattern EXPLICIT_RANGE_PATTERN = Pattern.compile(".*(鍒皘鑷�).*");
-
- private final ApproveTodoTools approveTodoTools;
-
- public ApproveTodoIntentExecutor(ApproveTodoTools approveTodoTools) {
- this.approveTodoTools = approveTodoTools;
- }
-
- public String tryExecute(String memoryId, String message) {
- if (!StringUtils.hasText(message)) {
- return null;
- }
-
- String text = message.trim();
- String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text);
- if (StringUtils.hasText(quickPromptResponse)) {
- return quickPromptResponse;
- }
-
- String approveId = extractApproveId(text);
- boolean hasApproveId = StringUtils.hasText(approveId) && !isPlaceholderApproveId(approveId);
- String startDate = extractStartDate(text);
- String endDate = extractEndDate(text);
- String timeRange = extractTimeRange(text);
-
- if (isStatsIntent(text)) {
- return approveTodoTools.getTodoStats(
- memoryId,
- startDate,
- endDate,
- timeRange
- );
- }
- if (containsAny(text, "娴佽浆", "杩涘害", "鑺傜偣", "鏃ュ織", "鍗″湪", "鍗″埌", "褰撳墠瀹℃壒浜�", "澶勭悊璁板綍")) {
- return hasApproveId
- ? approveTodoTools.getTodoProgress(memoryId, approveId)
- : missingApproveId("todo_progress", "鏌ヨ瀹℃壒杩涘害闇�瑕佹彁渚涙祦绋嬬紪鍙枫��");
- }
- if (containsAny(text, "璇︽儏", "鏄庣粏") && !containsAny(text, "鍒楄〃")) {
- return hasApproveId
- ? approveTodoTools.getTodoDetail(memoryId, approveId)
- : missingApproveId("todo_detail", "鏌ヨ瀹℃壒璇︽儏闇�瑕佹彁渚涙祦绋嬬紪鍙枫��");
- }
- if (containsAny(text, "鍙栨秷瀹℃牳", "鎾ら攢瀹℃牳", "鍥為��瀹℃牳", "鎾ら攢瀹℃壒", "鎾ゅ洖瀹℃壒")
- || (containsAny(text, "鎾ら攢", "鎾ゅ洖") && containsAny(text, "瀹℃壒鎿嶄綔", "瀹℃牳鎿嶄綔"))) {
- return hasApproveId
- ? approveTodoTools.cancelReviewTodo(memoryId, approveId, extractRemark(text))
- : missingApproveId("cancel_review_action", "鍙栨秷瀹℃牳闇�瑕佹彁渚涙祦绋嬬紪鍙枫��");
- }
- if (containsAny(text, "鍒犻櫎", "绉婚櫎")) {
- return hasApproveId
- ? approveTodoTools.deleteTodo(memoryId, approveId)
- : missingApproveId("delete_action", "鍒犻櫎瀹℃壒鍗曢渶瑕佹彁渚涙祦绋嬬紪鍙枫��");
- }
- if (containsAny(text, "椹冲洖", "鎷掔粷")) {
- return hasApproveId
- ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", extractRemark(text))
- : missingApproveId("review_action", "椹冲洖瀹℃壒闇�瑕佹彁渚涙祦绋嬬紪鍙枫��");
- }
- if (containsAny(text, "瀹℃牳閫氳繃", "瀹℃壒閫氳繃", "閫氳繃瀹℃壒", "鍚屾剰瀹℃壒", "瀹℃壒鍚屾剰")) {
- return hasApproveId
- ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text))
- : missingApproveId("review_action", "瀹℃壒閫氳繃闇�瑕佹彁渚涙祦绋嬬紪鍙枫��");
- }
- if (hasApproveId
- && containsAny(text, "閫氳繃", "鍚屾剰")
- && !containsAny(text, "鏈�氳繃", "閫氳繃鐜�", "瀹℃壒閫氳繃鐜�", "瀹℃牳閫氳繃鐜�")) {
- return approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text));
- }
- if (containsAny(text, "淇敼", "鏇存柊", "鍙樻洿")) {
- return hasApproveId
- ? approveTodoTools.updateTodo(
- memoryId,
- approveId,
- extractValue(text, "鏍囬"),
- extractDateValue(text, "寮�濮嬫棩鏈�"),
- extractDateValue(text, "缁撴潫鏃ユ湡"),
- extractBigDecimalValue(text, "閲戦"),
- extractValue(text, "鍦扮偣"),
- extractIntegerValue(text, "绫诲瀷"),
- extractValue(text, "澶囨敞"))
- : missingApproveId("update_action", "淇敼瀹℃壒鍗曢渶瑕佹彁渚涙祦绋嬬紪鍙枫��");
- }
- if (containsAny(text, "鍒楄〃", "寰呭姙", "鏌ヨ瀹℃壒", "鍗曟嵁", "娴佺▼", "瀹℃壒鎵�")) {
- return approveTodoTools.listTodos(
- memoryId,
- extractStatus(text),
- extractApproveType(text),
- extractKeyword(text),
- extractLimit(text),
- extractScope(text),
- startDate,
- endDate,
- timeRange);
- }
- return null;
- }
-
- private String tryExecuteQuickPrompt(String memoryId, String text) {
- String normalized = normalizeForMatch(text);
- String approveId = extractApproveId(text);
- boolean hasApproveId = StringUtils.hasText(approveId) && !isPlaceholderApproveId(approveId);
-
- if ("鎴戝綋鍓嶆湁鍝簺瀹℃壒寰呭姙闇�瑕佸鐞�".equals(normalized)) {
- return approveTodoTools.listTodos(memoryId, "pending", null, null, 10, "approver", null, null, null);
- }
- if ("甯垜鍒楀嚭浠婂ぉ鏂板鐨勫鎵瑰緟鍔�".equals(normalized)) {
- return approveTodoTools.listTodos(memoryId, "all", null, null, 10, "related", null, null, "浠婂ぉ");
- }
- if ("褰撳墠寰呮垜瀹℃壒鐨勫崟鎹寜鏃堕棿鍊掑簭鍒楀嚭鏉�".equals(normalized)) {
- return approveTodoTools.listTodos(memoryId, "pending", null, null, 10, "approver", null, null, null);
- }
- if ("鎴戝彂璧风殑瀹℃壒閲屽摢浜涜繕鍦ㄥ鐞嗕腑".equals(normalized)) {
- return approveTodoTools.listTodos(memoryId, "processing", null, null, 10, "applicant", null, null, null);
- }
- if ("杩�7澶╂垜鐨勫鎵瑰緟鍔炵粺璁℃儏鍐垫�庝箞鏍�".equals(normalized)) {
- return approveTodoTools.getTodoStats(memoryId, null, null, "杩�7澶�");
- }
- if ("鏈湀鎴戠殑瀹℃壒涓�氳繃椹冲洖澶勭悊涓悇鏈夊灏�".equals(normalized)) {
- return approveTodoTools.getTodoStats(memoryId, null, null, "鏈湀");
- }
- if ("杩�30澶╁悇绫诲瀷瀹℃壒鏁伴噺鍒嗗竷鏄粈涔�".equals(normalized)) {
- return approveTodoTools.getTodoStats(memoryId, null, null, "杩�30澶�");
- }
-
- if (normalized.startsWith("鏌ヨ娴佺▼缂栧彿") && normalized.contains("瀹℃壒璇︽儏")) {
- return hasApproveId
- ? approveTodoTools.getTodoDetail(memoryId, approveId)
- : missingApproveId("todo_detail", "鏌ヨ瀹℃壒璇︽儏闇�瑕佹彁渚涚湡瀹炴祦绋嬬紪鍙枫��");
- }
- if (normalized.startsWith("娴佺▼缂栧彿")
- && normalized.contains("鍗″湪鍝釜瀹℃壒鑺傜偣")
- && normalized.contains("褰撳墠瀹℃壒浜烘槸璋�")) {
- return hasApproveId
- ? approveTodoTools.getTodoProgress(memoryId, approveId)
- : missingApproveId("todo_progress", "鏌ヨ瀹℃壒杩涘害闇�瑕佹彁渚涚湡瀹炴祦绋嬬紪鍙枫��");
- }
- if (normalized.startsWith("甯垜鏌ョ湅娴佺▼缂栧彿") && normalized.contains("瀹℃壒娴佽浆璁板綍")) {
- return hasApproveId
- ? approveTodoTools.getTodoProgress(memoryId, approveId)
- : missingApproveId("todo_progress", "鏌ヨ瀹℃壒娴佽浆璁板綍闇�瑕佹彁渚涚湡瀹炴祦绋嬬紪鍙枫��");
- }
- if (normalized.startsWith("甯垜瀹℃壒閫氳繃娴佺▼缂栧彿")) {
- return hasApproveId
- ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text))
- : missingApproveId("review_action", "瀹℃壒閫氳繃闇�瑕佹彁渚涚湡瀹炴祦绋嬬紪鍙枫��");
- }
- if (normalized.startsWith("甯垜椹冲洖娴佺▼缂栧彿")) {
- return hasApproveId
- ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", extractRemark(text))
- : missingApproveId("review_action", "椹冲洖瀹℃壒闇�瑕佹彁渚涚湡瀹炴祦绋嬬紪鍙枫��");
- }
- if (normalized.startsWith("鎾ら攢鎴戝垰鍒氬娴佺▼缂栧彿") && normalized.contains("瀹℃壒鎿嶄綔")) {
- return hasApproveId
- ? approveTodoTools.cancelReviewTodo(memoryId, approveId, extractRemark(text))
- : missingApproveId("cancel_review_action", "鎾ら攢瀹℃壒鎿嶄綔闇�瑕佹彁渚涚湡瀹炴祦绋嬬紪鍙枫��");
- }
- if (normalized.startsWith("甯垜淇敼娴佺▼缂栧彿") && normalized.contains("澶囨敞涓�")) {
- return hasApproveId
- ? approveTodoTools.updateTodo(memoryId, approveId, null, null, null, null, null, null, extractRemark(text))
- : missingApproveId("update_action", "淇敼瀹℃壒鍗曢渶瑕佹彁渚涚湡瀹炴祦绋嬬紪鍙枫��");
- }
- if (normalized.startsWith("鍒犻櫎鎴戝彂璧风殑娴佺▼缂栧彿")) {
- return hasApproveId
- ? approveTodoTools.deleteTodo(memoryId, approveId)
- : missingApproveId("delete_action", "鍒犻櫎瀹℃壒鍗曢渶瑕佹彁渚涚湡瀹炴祦绋嬬紪鍙枫��");
- }
- return null;
- }
-
- private boolean isStatsIntent(String text) {
- if (containsAny(text, "缁熻", "鍒嗘瀽", "鍥捐〃", "瓒嬪娍", "鍗犳瘮", "姹囨��", "鎬婚噺", "鍒嗗竷", "鍚勬湁澶氬皯", "鏈夊灏�")) {
- return true;
- }
- boolean hasQueryWord = containsAny(text, "鏌ヨ", "鏌ョ湅", "鐪嬩笅", "鐪嬬湅", "鑾峰彇");
- boolean hasDataWord = containsAny(text, "鏁版嵁", "鎶ヨ〃", "鎯呭喌", "鏁伴噺", "閲戦");
- boolean hasTimeWord = containsAny(text, "浠婂ぉ", "鏄ㄦ棩", "鏄ㄥぉ", "鏈懆", "涓婂懆", "鏈湀", "涓婃湀", "鏈勾", "浠婂勾", "鍘诲勾")
- || DATE_PATTERN.matcher(text).find()
- || RECENT_RANGE_PATTERN.matcher(text).find()
- || HALF_RANGE_PATTERN.matcher(text).find()
- || EXPLICIT_RANGE_PATTERN.matcher(text).matches();
- return hasQueryWord && hasDataWord && hasTimeWord;
- }
-
- private boolean containsAny(String text, String... keywords) {
- for (String keyword : keywords) {
- if (text.contains(keyword)) {
- return true;
- }
- }
- return false;
- }
-
- private String extractApproveId(String text) {
- Matcher keywordMatcher = APPROVE_ID_BY_LABEL_PATTERN.matcher(text);
- if (keywordMatcher.find()) {
- return keywordMatcher.group(2);
- }
- Matcher matcher = APPROVE_ID_PATTERN.matcher(text);
- return matcher.find() ? matcher.group() : null;
- }
-
- private Integer extractLimit(String text) {
- Matcher matcher = LIMIT_PATTERN.matcher(text);
- return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10;
- }
-
- private String extractStatus(String text) {
- if (containsAny(text, "寰呭鏍�", "寰呭鎵�")) {
- return "pending";
- }
- if (containsAny(text, "瀹℃牳涓�", "澶勭悊涓�", "澶勭悊涓殑", "鍔炵悊涓�")) {
- return "processing";
- }
- if (containsAny(text, "宸查�氳繃", "閫氳繃", "瀹℃牳瀹屾垚", "瀹℃壒瀹屾垚")) {
- return "approved";
- }
- if (containsAny(text, "鏈�氳繃", "椹冲洖", "宸查┏鍥�", "鎷掔粷")) {
- return "rejected";
- }
- if (containsAny(text, "閲嶆柊鎻愪氦")) {
- return "resubmitted";
- }
- return "all";
- }
-
- private Integer extractApproveType(String text) {
- if (text.contains("鍏嚭")) {
- return 1;
- }
- if (text.contains("璇峰亣")) {
- return 2;
- }
- if (text.contains("鍑哄樊")) {
- return 3;
- }
- if (text.contains("鎶ラ攢")) {
- return 4;
- }
- if (text.contains("閲囪喘")) {
- return 5;
- }
- if (text.contains("鎶ヤ环")) {
- return 6;
- }
- if (text.contains("鍙戣揣")) {
- return 7;
- }
- if (text.contains("鍗遍櫓浣滀笟")) {
- return 8;
- }
- return null;
- }
-
- private String extractKeyword(String text) {
- String cleaned = text
- .replace("鏌ヨ", "")
- .replace("鏌ョ湅", "")
- .replace("鍒楀嚭", "")
- .replace("甯垜", "")
- .replace("瀹℃壒", "")
- .replace("鍗曟嵁", "")
- .replace("寰呭姙", "")
- .replace("鍒楄〃", "")
- .replace("娴佺▼缂栧彿", "")
- .replace("娴佺▼鍙�", "")
- .replace("鍓�10鏉�", "")
- .replace("鏈�杩�10鏉�", "")
- .trim();
- return cleaned.length() >= 2 ? cleaned : null;
- }
-
- private String extractValue(String text, String fieldName) {
- Pattern pattern = Pattern.compile(fieldName + "(鏀逛负|淇敼涓簗涓簗鏄�)[:锛歖?[\\s]*([^,锛屻�傦紱;\\s]+)");
- Matcher matcher = pattern.matcher(text);
- return matcher.find() ? matcher.group(2) : null;
- }
-
- private String extractDateValue(String text, String fieldName) {
- int index = text.indexOf(fieldName);
- if (index < 0) {
- return null;
- }
- Matcher matcher = DATE_PATTERN.matcher(text.substring(index));
- return matcher.find() ? matcher.group(1) : null;
- }
-
- private String extractStartDate(String text) {
- Matcher matcher = DATE_PATTERN.matcher(text);
- return matcher.find() ? matcher.group(1) : null;
- }
-
- private String extractEndDate(String text) {
- Matcher matcher = DATE_PATTERN.matcher(text);
- if (!matcher.find()) {
- return null;
- }
- return matcher.find() ? matcher.group(1) : null;
- }
-
- private String extractTimeRange(String text) {
- if (containsAny(text, "浠婂ぉ", "鏄ㄦ棩", "鏄ㄥぉ", "鏈懆", "涓婂懆", "鏈湀", "涓婃湀", "鏈勾", "浠婂勾", "鍘诲勾")) {
- return text;
- }
- if (RECENT_RANGE_PATTERN.matcher(text).find()) {
- return text;
- }
- if (HALF_RANGE_PATTERN.matcher(text).find()) {
- return text;
- }
- if (EXPLICIT_RANGE_PATTERN.matcher(text).matches()) {
- return text;
- }
- return null;
- }
-
- private Integer extractIntegerValue(String text, String fieldName) {
- if (!text.contains(fieldName)) {
- return null;
- }
- Matcher matcher = Pattern.compile(fieldName + "(鏀逛负|淇敼涓簗涓簗鏄�)[:锛歖?[\\s]*(\\d{1,2})").matcher(text);
- return matcher.find() ? Integer.parseInt(matcher.group(2)) : null;
- }
-
- private BigDecimal extractBigDecimalValue(String text, String fieldName) {
- int index = text.indexOf(fieldName);
- if (index < 0) {
- return null;
- }
- Matcher matcher = NUMBER_PATTERN.matcher(text.substring(index));
- return matcher.find() ? new BigDecimal(matcher.group(1)) : null;
- }
-
- private String extractTail(String text, String key) {
- Pattern quotedPattern = Pattern.compile(key + "(鏄瘄涓�)?[:锛歖?[\\s]*[鈥淺"]([^鈥漒"]+)[鈥漒"]");
- Matcher quotedMatcher = quotedPattern.matcher(text);
- if (quotedMatcher.find()) {
- return cleanContent(quotedMatcher.group(2));
- }
- Pattern pattern = Pattern.compile(key + "(鏄瘄涓�)?[:锛歖?[\\s]*(.+)");
- Matcher matcher = pattern.matcher(text);
- return matcher.find() ? cleanContent(matcher.group(2)) : null;
- }
-
- private String extractScope(String text) {
- if (containsAny(text, "鎴戝彂璧�", "鎴戞彁浜�", "鎴戠敵璇�", "鐢宠浜烘槸鎴�")) {
- return "applicant";
- }
- if (containsAny(text, "寰呮垜瀹℃壒", "寰呮垜瀹℃牳", "鎴戝鐞�", "鎴戝鎵�", "褰撳墠寰呮垜", "闇�瑕佹垜澶勭悊", "闇�瑕佸鐞�")) {
- return "approver";
- }
- return "related";
- }
-
- private String extractRemark(String text) {
- return firstNonBlank(firstNonBlank(extractTail(text, "澶囨敞"), extractTail(text, "鍘熷洜")), extractQuotedContent(text));
- }
-
- private String extractQuotedContent(String text) {
- Matcher matcher = Pattern.compile("[鈥淺"]([^鈥漒"]+)[鈥漒"]").matcher(text);
- return matcher.find() ? cleanContent(matcher.group(1)) : null;
- }
-
- private String normalizeForMatch(String text) {
- if (!StringUtils.hasText(text)) {
- return "";
- }
- return text.replace("锛�", "")
- .replace(",", "")
- .replace("銆�", "")
- .replace(".", "")
- .replace("锛�", "")
- .replace("!", "")
- .replace("锛�", "")
- .replace("?", "")
- .replace("锛�", "")
- .replace(":", "")
- .replace("锛�", "")
- .replace(";", "")
- .replace("鈥�", "")
- .replace("鈥�", "")
- .replace("\"", "")
- .replace(" ", "")
- .trim();
- }
-
- private boolean isPlaceholderApproveId(String approveId) {
- if (!StringUtils.hasText(approveId)) {
- return true;
- }
- String value = approveId.trim();
- return "xxx".equalsIgnoreCase(value)
- || value.matches("[xX]{2,}")
- || "娴佺▼缂栧彿".equals(value)
- || "缂栧彿".equals(value)
- || value.contains("绀轰緥")
- || value.contains("璇疯緭鍏�");
- }
-
- private String cleanContent(String text) {
- if (!StringUtils.hasText(text)) {
- return null;
- }
- return text.trim()
- .replace("鈥�", "")
- .replace("鈥�", "")
- .replace("\"", "")
- .replace("銆�", "")
- .replace("锛�", "")
- .replace(";", "")
- .trim();
- }
-
- private String firstNonBlank(String first, String second) {
- return StringUtils.hasText(first) ? first : second;
- }
-
- private String missingApproveId(String type, String description) {
- Map<String, Object> result = new LinkedHashMap<>();
- result.put("success", false);
- result.put("type", type);
- result.put("description", description);
- result.put("summary", Map.of());
- result.put("data", Map.of());
- result.put("charts", Map.of());
- return JSON.toJSONString(result);
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/assistant/Assistant.java b/src/main/java/com/ruoyi/ai/assistant/Assistant.java
deleted file mode 100644
index f0718c0..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/Assistant.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import dev.langchain4j.service.spring.AiService;
-
-import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
-
-/**
- * @author :yys
- * @date : 2025/5/2 18:26
- */
-//鍥犱负鎴戜滑鍦ㄩ厤缃枃浠朵腑鍚屾椂閰嶇疆浜嗗涓ぇ璇█妯″瀷锛屾墍浠ラ渶瑕佸湪杩欓噷鏄庣‘鎸囧畾锛圗XPLICIT锛夋ā鍨嬬殑beanName
-
-@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel")
-public interface Assistant {
- String chat(String userMessage);
-}
diff --git a/src/main/java/com/ruoyi/ai/assistant/FileAnalyzeAgent.java b/src/main/java/com/ruoyi/ai/assistant/FileAnalyzeAgent.java
deleted file mode 100644
index 131a9b7..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/FileAnalyzeAgent.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import dev.langchain4j.service.MemoryId;
-import dev.langchain4j.service.SystemMessage;
-import dev.langchain4j.service.UserMessage;
-import dev.langchain4j.service.spring.AiService;
-import reactor.core.publisher.Flux;
-
-import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
-
-@AiService(
- wiringMode = EXPLICIT,
- streamingChatModel = "qwenStreamingChatModel",
- chatMemoryProvider = "chatMemoryProvider"
-)
-public interface FileAnalyzeAgent {
-
- @SystemMessage("""
- 浣犳槸浼佷笟鏂囨。鍒嗘瀽鍔╂墜銆�
- 璇蜂弗鏍煎熀浜庣敤鎴锋彁渚涚殑鏂囦欢鍐呭杩涜鍒嗘瀽锛岃緭鍑鸿缁撴瀯鍖栥�佸噯纭�佺畝娲併��
- 鑻ユ枃浠跺唴瀹逛笉瓒充互鏀寔缁撹锛屾槑纭寚鍑衡�滀笉瓒充俊鎭�濆苟缁欏嚭闇�瑕佽ˉ鍏呯殑鏁版嵁椤广��
- """)
- Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
-}
diff --git a/src/main/java/com/ruoyi/ai/assistant/ManufacturingAgent.java b/src/main/java/com/ruoyi/ai/assistant/ManufacturingAgent.java
deleted file mode 100644
index f0e8cf7..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/ManufacturingAgent.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import dev.langchain4j.service.MemoryId;
-import dev.langchain4j.service.SystemMessage;
-import dev.langchain4j.service.UserMessage;
-import dev.langchain4j.service.spring.AiService;
-import reactor.core.publisher.Flux;
-
-import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
-
-@AiService(
- wiringMode = EXPLICIT,
- streamingChatModel = "qwenStreamingChatModel",
- chatMemoryProvider = "chatMemoryProviderManufacturing",
- tools = "manufacturingAgentTools"
-)
-public interface ManufacturingAgent {
-
- @SystemMessage(fromResource = "manufacturing-agent-prompt.txt")
- Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
-}
diff --git a/src/main/java/com/ruoyi/ai/assistant/ManufacturingIntentExecutor.java b/src/main/java/com/ruoyi/ai/assistant/ManufacturingIntentExecutor.java
deleted file mode 100644
index e9d9396..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/ManufacturingIntentExecutor.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import com.ruoyi.ai.tools.ManufacturingAgentTools;
-import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-@Component
-public class ManufacturingIntentExecutor {
-
- private static final Pattern LIMIT_PATTERN = Pattern.compile("(鍓峾鏈�杩�)?(\\d{1,2})鏉�");
- private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
-
- private final ManufacturingAgentTools manufacturingAgentTools;
-
- public ManufacturingIntentExecutor(ManufacturingAgentTools manufacturingAgentTools) {
- this.manufacturingAgentTools = manufacturingAgentTools;
- }
-
- public String tryExecute(String memoryId, String message) {
- if (!StringUtils.hasText(message)) {
- return null;
- }
- String text = message.trim();
- String keyword = extractKeyword(text);
- Integer limit = extractLimit(text);
- String startDate = extractStartDate(text);
- String endDate = extractEndDate(text);
-
- if (containsAny(text, "棰勮", "鍛婅", "椋庨櫓", "鎻愰啋")) {
- return manufacturingAgentTools.getWarningBoard(memoryId, startDate, endDate, text);
- }
- if (containsAny(text, "鍒嗘瀽", "缁熻", "瓒嬪娍", "鐪嬫澘", "鎶ヨ〃", "鎬昏")) {
- return manufacturingAgentTools.analyzeFactory(memoryId, startDate, endDate, text);
- }
- if (containsAny(text, "鍔�", "澶勭悊", "娲惧伐", "瀹夋帓", "闂幆", "璺熻繘", "澶勭疆")) {
- return manufacturingAgentTools.planActions(memoryId, text);
- }
-
- if (containsAny(text, "鐢熶骇鐜板満", "鐜板満", "杞﹂棿")) {
- return manufacturingAgentTools.queryDomain(memoryId, "site", keyword, limit, startDate, endDate, text);
- }
- if (containsAny(text, "璁″垝", "鎺掍骇", "mps")) {
- return manufacturingAgentTools.queryDomain(memoryId, "plan", keyword, limit, startDate, endDate, text);
- }
- if (containsAny(text, "宸ュ崟", "浣滀笟鍗�", "浠诲姟鍗�", "浠诲姟")) {
- return manufacturingAgentTools.queryDomain(memoryId, "workorder", keyword, limit, startDate, endDate, text);
- }
- if (containsAny(text, "璁惧", "缁翠慨", "淇濆吇", "鏁呴殰")) {
- return manufacturingAgentTools.queryDomain(memoryId, "device", keyword, limit, startDate, endDate, text);
- }
- if (containsAny(text, "璐ㄩ噺", "璐ㄦ", "涓嶅悎鏍�", "妫�楠�")) {
- return manufacturingAgentTools.queryDomain(memoryId, "quality", keyword, limit, startDate, endDate, text);
- }
- if (containsAny(text, "鐗╂枡", "搴撳瓨", "搴撲綅", "鍏ュ簱", "鍑哄簱")) {
- return manufacturingAgentTools.queryDomain(memoryId, "material", keyword, limit, startDate, endDate, text);
- }
- if (containsAny(text, "寮傚父", "渚嬪", "鍋忓樊")) {
- return manufacturingAgentTools.queryDomain(memoryId, "exception", keyword, limit, startDate, endDate, text);
- }
- return null;
- }
-
- private boolean containsAny(String text, String... keywords) {
- for (String keyword : keywords) {
- if (text.toLowerCase().contains(keyword.toLowerCase())) {
- return true;
- }
- }
- return false;
- }
-
- private Integer extractLimit(String text) {
- Matcher matcher = LIMIT_PATTERN.matcher(text);
- return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10;
- }
-
- private String extractStartDate(String text) {
- Matcher matcher = DATE_PATTERN.matcher(text);
- return matcher.find() ? matcher.group(1) : null;
- }
-
- private String extractEndDate(String text) {
- Matcher matcher = DATE_PATTERN.matcher(text);
- if (!matcher.find()) {
- return null;
- }
- return matcher.find() ? matcher.group(1) : null;
- }
-
- private String extractKeyword(String text) {
- String cleaned = text
- .replace("鏌ヨ", "")
- .replace("鏌ョ湅", "")
- .replace("甯垜", "")
- .replace("璇�", "")
- .replace("涓�涓�", "")
- .replace("鎵�鏈�", "")
- .replace("鍏ㄩ儴", "")
- .replace("浠婂勾", "")
- .replace("鏈勾", "")
- .replace("鍘诲勾", "")
- .replace("鏈湀", "")
- .replace("涓婃湀", "")
- .replace("鏈懆", "")
- .replace("涓婂懆", "")
- .replace("浠婂ぉ", "")
- .replace("鏄ㄥぉ", "")
- .replace("杩�30澶�", "")
- .replace("杩�7澶�", "")
- .replace("杩�15澶�", "")
- .replace("杩�60澶�", "")
- .replace("鏈�杩�30澶�", "")
- .replace("鏈�杩�7澶�", "")
- .replace("鏈�杩�15澶�", "")
- .replace("鏈�杩�60澶�", "")
- .replace("鐢熶骇鐜板満", "")
- .replace("鐜板満", "")
- .replace("鐢熶骇宸ュ崟", "")
- .replace("鐢熶骇", "")
- .replace("璁″垝", "")
- .replace("鎺掍骇", "")
- .replace("宸ュ崟", "")
- .replace("璁惧", "")
- .replace("璐ㄩ噺", "")
- .replace("鐗╂枡", "")
- .replace("搴撳瓨", "")
- .replace("寮傚父", "")
- .replace("鍓�10鏉�", "")
- .replace("鏈�杩�10鏉�", "")
- .trim();
- return cleaned.length() >= 2 ? cleaned : null;
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/assistant/PurchaseAgent.java b/src/main/java/com/ruoyi/ai/assistant/PurchaseAgent.java
deleted file mode 100644
index 6a4ff4e..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/PurchaseAgent.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import dev.langchain4j.service.MemoryId;
-import dev.langchain4j.service.SystemMessage;
-import dev.langchain4j.service.UserMessage;
-import dev.langchain4j.service.spring.AiService;
-import reactor.core.publisher.Flux;
-
-import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
-
-@AiService(
- wiringMode = EXPLICIT,
- streamingChatModel = "qwenStreamingChatModel",
- chatMemoryProvider = "chatMemoryProviderPurchase",
- tools = "purchaseAgentTools"
-)
-public interface PurchaseAgent {
-
- @SystemMessage(fromResource = "purchase-agent-prompt.txt")
- Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
-}
diff --git a/src/main/java/com/ruoyi/ai/assistant/PurchaseIntentExecutor.java b/src/main/java/com/ruoyi/ai/assistant/PurchaseIntentExecutor.java
deleted file mode 100644
index 5991fc3..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/PurchaseIntentExecutor.java
+++ /dev/null
@@ -1,161 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import com.ruoyi.ai.tools.PurchaseAgentTools;
-import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-@Component
-public class PurchaseIntentExecutor {
-
- private static final Pattern ID_PATTERN = Pattern.compile("\\b\\d{1,12}\\b");
- private static final Pattern LIMIT_PATTERN = Pattern.compile("(鍓峾鏈�杩�)?(\\d{1,2})鏉�");
- private static final Pattern DATE_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
-
- private final PurchaseAgentTools purchaseAgentTools;
-
- public PurchaseIntentExecutor(PurchaseAgentTools purchaseAgentTools) {
- this.purchaseAgentTools = purchaseAgentTools;
- }
-
- public String tryExecute(String memoryId, String message) {
- if (!StringUtils.hasText(message)) {
- return null;
- }
- String text = message.trim();
-
- if (containsAny(text, "鎺掕", "鎺掑悕", "鍓嶅嚑", "鍓嶄簲", "鍓嶅崄") && containsAny(text, "鐗╂枡", "浜у搧", "鍘熸潗鏂�", "閲囪喘閲戦", "閲戦")) {
- return purchaseAgentTools.rankPurchaseMaterials(
- memoryId,
- extractStartDate(text),
- extractEndDate(text),
- text,
- extractLimit(text)
- );
- }
- if (containsAny(text, "鏈叆搴�", "寰呭叆搴�", "娌℃湁鍏ュ簱", "杩樻湭鍏ュ簱")) {
- return purchaseAgentTools.listUnstockedPurchaseOrders(
- memoryId,
- extractStartDate(text),
- extractEndDate(text),
- extractKeyword(text),
- extractLimit(text)
- );
- }
- if (containsAny(text, "鍒拌揣寮傚父", "鍒拌揣鏈夊紓甯�", "寮傚父鍒拌揣", "鍒拌揣闂", "渚涘簲鍟嗗埌璐у紓甯�")) {
- return purchaseAgentTools.listArrivalExceptions(
- memoryId,
- extractStartDate(text),
- extractEndDate(text),
- text,
- extractLimit(text)
- );
- }
- if (containsAny(text, "寰呬粯娆�", "鏈粯娆�", "鏈粯娓�", "寰呮敮浠�", "搴斾粯")) {
- return purchaseAgentTools.listPendingPaymentOrders(
- memoryId,
- extractStartDate(text),
- extractEndDate(text),
- extractKeyword(text),
- extractLimit(text)
- );
- }
- if (containsAny(text, "閫�璐�", "閫�鏂�", "鎷掓敹")) {
- return purchaseAgentTools.listPurchaseReturns(
- memoryId,
- extractStartDate(text),
- extractEndDate(text),
- extractKeyword(text),
- extractLimit(text)
- );
- }
- if (isStatsIntent(text)) {
- return purchaseAgentTools.getPurchaseStats(
- memoryId,
- extractStartDate(text),
- extractEndDate(text),
- text
- );
- }
- if (containsAny(text, "璇︽儏", "鏄庣粏") && extractId(text) != null) {
- return purchaseAgentTools.getPurchaseLedgerDetail(memoryId, extractId(text));
- }
- if (containsAny(text, "鍙拌处", "閲囪喘鍗�", "閲囪喘璁㈠崟", "璁㈠崟", "鍚堝悓", "鍒楄〃", "鏌ヨ")) {
- return purchaseAgentTools.listPurchaseLedgers(
- memoryId,
- extractKeyword(text),
- extractStartDate(text),
- extractEndDate(text),
- extractLimit(text)
- );
- }
- return null;
- }
-
- private boolean isStatsIntent(String text) {
- if (containsAny(text, "缁熻", "鍒嗘瀽", "鎶ヨ〃", "姹囨��", "瓒嬪娍", "鏁版嵁鐪嬫澘", "鎯呭喌", "鏈夊灏�")) {
- return true;
- }
- boolean queryWord = containsAny(text, "鏌ヨ", "鏌ョ湅", "鐪嬩笅", "鐪嬬湅", "鑾峰彇");
- boolean dataWord = containsAny(text, "鏁版嵁", "閲戦", "鏁伴噺", "鍚堝悓棰�", "浠樻棰�", "鍙戠エ棰�");
- boolean timeWord = containsAny(text, "浠婂ぉ", "鏈懆", "鏈湀", "涓婃湀", "浠婂勾", "鍘诲勾", "杩戝崐骞�", "鏈�杩戝崐涓湀", "鍗婁釜鏈�")
- || DATE_PATTERN.matcher(text).find();
- return queryWord && dataWord && timeWord;
- }
-
- private boolean containsAny(String text, String... keywords) {
- for (String keyword : keywords) {
- if (text.contains(keyword)) {
- return true;
- }
- }
- return false;
- }
-
- private Long extractId(String text) {
- Matcher matcher = ID_PATTERN.matcher(text);
- if (!matcher.find()) {
- return null;
- }
- return Long.parseLong(matcher.group());
- }
-
- private Integer extractLimit(String text) {
- Matcher matcher = LIMIT_PATTERN.matcher(text);
- return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10;
- }
-
- private String extractStartDate(String text) {
- Matcher matcher = DATE_PATTERN.matcher(text);
- return matcher.find() ? matcher.group() : null;
- }
-
- private String extractEndDate(String text) {
- Matcher matcher = DATE_PATTERN.matcher(text);
- if (!matcher.find()) {
- return null;
- }
- return matcher.find() ? matcher.group() : null;
- }
-
- private String extractKeyword(String text) {
- String cleaned = text
- .replace("鏌ヨ", "")
- .replace("鏌ョ湅", "")
- .replace("閲囪喘", "")
- .replace("閲囪喘鍗�", "")
- .replace("閲囪喘璁㈠崟", "")
- .replace("璁㈠崟", "")
- .replace("鍙拌处", "")
- .replace("鍒楄〃", "")
- .replace("鍝簺", "")
- .replace("鍒楀嚭", "")
- .replace("甯垜", "")
- .replace("鏈�杩�10鏉�", "")
- .replace("鍓�10鏉�", "")
- .trim();
- return cleaned.length() >= 2 ? cleaned : null;
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/assistant/SalesAgent.java b/src/main/java/com/ruoyi/ai/assistant/SalesAgent.java
deleted file mode 100644
index 1636239..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/SalesAgent.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import dev.langchain4j.service.MemoryId;
-import dev.langchain4j.service.SystemMessage;
-import dev.langchain4j.service.UserMessage;
-import dev.langchain4j.service.spring.AiService;
-import reactor.core.publisher.Flux;
-
-import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
-
-@AiService(
- wiringMode = EXPLICIT,
- streamingChatModel = "qwenStreamingChatModel",
- chatMemoryProvider = "chatMemoryProviderSales",
- tools = "salesAgentTools"
-)
-public interface SalesAgent {
-
- @SystemMessage(fromResource = "sales-agent-prompt.txt")
- Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
-}
-
diff --git a/src/main/java/com/ruoyi/ai/assistant/SalesIntentExecutor.java b/src/main/java/com/ruoyi/ai/assistant/SalesIntentExecutor.java
deleted file mode 100644
index 4388a3c..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/SalesIntentExecutor.java
+++ /dev/null
@@ -1,270 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import com.ruoyi.ai.tools.SalesAgentTools;
-import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
-
-import java.time.LocalDate;
-import java.time.YearMonth;
-import java.time.format.DateTimeFormatter;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-@Component
-public class SalesIntentExecutor {
-
- private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
- private static final Pattern LIMIT_PATTERN = Pattern.compile("(鍓峾鏈�杩�)?\\s*(\\d{1,2})\\s*鏉�");
- private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
- private static final Pattern RELATIVE_DAY_PATTERN = Pattern.compile("(杩憒鏈�杩�)?\\s*(\\d{1,3})\\s*澶�");
-
- private final SalesAgentTools salesAgentTools;
-
- public SalesIntentExecutor(SalesAgentTools salesAgentTools) {
- this.salesAgentTools = salesAgentTools;
- }
-
- public String tryExecute(String memoryId, String message) {
- if (!StringUtils.hasText(message)) {
- return null;
- }
- String text = message.trim();
-
- String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text);
- if (StringUtils.hasText(quickPromptResponse)) {
- return quickPromptResponse;
- }
-
- String keyword = extractKeyword(text);
- Integer limit = extractLimit(text);
- DateRange dateRange = extractDateRange(text);
- String startDate = dateRange.startDate();
- String endDate = dateRange.endDate();
-
- if (containsAny(text, "娴佸け", "娴佸け椋庨櫓", "瀹㈡埛娴佸け", "椋庨櫓鍒嗘瀽")) {
- return salesAgentTools.analyzeCustomerChurnRisk(memoryId, startDate, endDate, text, keyword, limit);
- }
- if (containsAny(text, "鍥炴", "鏀舵", "鎶ヤ环")
- && containsAny(text, "寤鸿", "绛栫暐", "浼樺寲", "鏂规")) {
- return salesAgentTools.suggestCollectionAndQuotationStrategy(
- memoryId, startDate, endDate, text, keyword, limit, shouldPrioritizeHighRisk(text));
- }
- if (containsAny(text, "鎸囨爣", "缁熻", "鐪嬫澘", "鎬昏", "缁忚惀鍒嗘瀽")) {
- return salesAgentTools.getSalesDashboard(memoryId, startDate, endDate, text);
- }
- if (containsAny(text, "瀹㈡埛妗f", "绉佹捣", "鍏捣", "瀹㈡埛姹�")) {
- return salesAgentTools.listCustomerProfiles(memoryId, extractSeaType(text), keyword, limit);
- }
- if (containsAny(text, "閿�鍞姤浠�", "鎶ヤ环鍗�", "鎶ヤ环", "璇环")) {
- return salesAgentTools.listSalesQuotations(memoryId, keyword, startDate, endDate, limit);
- }
- if (containsAny(text, "閿�鍞��璐�", "閫�璐�", "閫�娆�")) {
- return salesAgentTools.listSalesReturns(memoryId, startDate, endDate, keyword, limit);
- }
- if (containsAny(text, "瀹㈡埛寰�鏉�", "寰�鏉�", "鍥炴", "搴旀敹", "鏉ユ", "鏀舵鏄庣粏")) {
- return salesAgentTools.listCustomerInteractions(memoryId, keyword, startDate, endDate, limit);
- }
- if (containsAny(text, "鍙戣揣鍙拌处", "鍙戣揣", "鐗╂祦", "蹇��", "杩愯緭")) {
- return salesAgentTools.listShippingLedgers(memoryId, keyword, startDate, endDate, limit);
- }
- if (containsAny(text, "閿�鍞彴璐�", "閿�鍞悎鍚�", "閿�鍞鍗�", "鍚堝悓鍙拌处", "璁㈠崟鍙拌处")) {
- return salesAgentTools.listSalesLedgers(memoryId, keyword, startDate, endDate, limit);
- }
- return null;
- }
-
- private String tryExecuteQuickPrompt(String memoryId, String text) {
- String normalized = normalizeForMatch(text);
- if ("鏌ヨ绉佹捣瀹㈡埛妗f鍓�10鏉�".equals(normalized)) {
- return salesAgentTools.listCustomerProfiles(memoryId, "private", null, 10);
- }
- if ("鏌ヨ鍏捣瀹㈡埛妗f".equals(normalized)) {
- return salesAgentTools.listCustomerProfiles(memoryId, "public", null, 10);
- }
- if ("鏌ヨ鏈湀閿�鍞姤浠�".equals(normalized)) {
- DateRange range = monthRange();
- return salesAgentTools.listSalesQuotations(memoryId, null, range.startDate(), range.endDate(), 10);
- }
- if ("鏌ヨ鏈湀閿�鍞彴璐�".equals(normalized)) {
- DateRange range = monthRange();
- return salesAgentTools.listSalesLedgers(memoryId, null, range.startDate(), range.endDate(), 10);
- }
- if ("鏌ヨ杩�30澶╅攢鍞��璐�".equals(normalized)) {
- DateRange range = recentDaysRange(30);
- return salesAgentTools.listSalesReturns(memoryId, range.startDate(), range.endDate(), null, 10);
- }
- if ("鏌ヨ杩�30澶╁鎴峰洖娆惧線鏉�".equals(normalized)) {
- DateRange range = recentDaysRange(30);
- return salesAgentTools.listCustomerInteractions(memoryId, null, range.startDate(), range.endDate(), 10);
- }
- if ("鏌ヨ鏈湀鍙戣揣鍙拌处".equals(normalized)) {
- DateRange range = monthRange();
- return salesAgentTools.listShippingLedgers(memoryId, null, range.startDate(), range.endDate(), 10);
- }
- if ("鏌ョ湅閿�鍞寚鏍囩粺璁�".equals(normalized)) {
- return salesAgentTools.getSalesDashboard(memoryId, null, null, "鏈湀");
- }
- if ("甯垜鍋氬鎴锋祦澶遍闄╁垎鏋愯繎30澶╁墠20鏉�".equals(normalized)) {
- DateRange range = recentDaysRange(30);
- return salesAgentTools.analyzeCustomerChurnRisk(memoryId, range.startDate(), range.endDate(), "杩�30澶�", null, 20);
- }
- if ("鐢熸垚鍥炴涓庢姤浠风瓥鐣ュ缓璁紭鍏堥珮椋庨櫓瀹㈡埛".equals(normalized)) {
- DateRange range = recentDaysRange(30);
- return salesAgentTools.suggestCollectionAndQuotationStrategy(memoryId, range.startDate(), range.endDate(), "杩�30澶�", null, 10, true);
- }
- return null;
- }
-
- private boolean containsAny(String text, String... keywords) {
- for (String keyword : keywords) {
- if (text.contains(keyword)) {
- return true;
- }
- }
- return false;
- }
-
- private String extractSeaType(String text) {
- if (text.contains("鍏捣")) {
- return "public";
- }
- if (text.contains("绉佹捣")) {
- return "private";
- }
- return null;
- }
-
- private Integer extractLimit(String text) {
- Matcher matcher = LIMIT_PATTERN.matcher(text);
- return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10;
- }
-
- private DateRange extractDateRange(String text) {
- Matcher matcher = DATE_PATTERN.matcher(text);
- if (matcher.find()) {
- String first = matcher.group(1);
- String second = matcher.find() ? matcher.group(1) : first;
- return buildDateRange(first, second);
- }
- if (text.contains("鏈湀")) {
- return monthRange();
- }
- if (text.contains("涓婃湀")) {
- return lastMonthRange();
- }
- if (text.contains("鏈勾") || text.contains("浠婂勾")) {
- return yearRange();
- }
- Matcher relativeDayMatcher = RELATIVE_DAY_PATTERN.matcher(text);
- if (relativeDayMatcher.find()) {
- int days = Integer.parseInt(relativeDayMatcher.group(2));
- return recentDaysRange(days);
- }
- return new DateRange(null, null);
- }
-
- private DateRange buildDateRange(String start, String end) {
- LocalDate startDate = parseDate(start);
- LocalDate endDate = parseDate(end);
- if (startDate == null || endDate == null) {
- return new DateRange(null, null);
- }
- if (startDate.isAfter(endDate)) {
- LocalDate temp = startDate;
- startDate = endDate;
- endDate = temp;
- }
- return new DateRange(formatDate(startDate), formatDate(endDate));
- }
-
- private DateRange recentDaysRange(int days) {
- LocalDate end = LocalDate.now();
- int safeDays = Math.max(days, 1);
- LocalDate start = end.minusDays(safeDays - 1L);
- return new DateRange(formatDate(start), formatDate(end));
- }
-
- private DateRange monthRange() {
- LocalDate today = LocalDate.now();
- return new DateRange(formatDate(today.withDayOfMonth(1)), formatDate(today));
- }
-
- private DateRange lastMonthRange() {
- YearMonth lastMonth = YearMonth.now().minusMonths(1);
- return new DateRange(formatDate(lastMonth.atDay(1)), formatDate(lastMonth.atEndOfMonth()));
- }
-
- private DateRange yearRange() {
- LocalDate today = LocalDate.now();
- return new DateRange(formatDate(today.withDayOfYear(1)), formatDate(today));
- }
-
- private LocalDate parseDate(String text) {
- try {
- return LocalDate.parse(text, DATE_FMT);
- } catch (Exception ignored) {
- return null;
- }
- }
-
- private String formatDate(LocalDate date) {
- return date == null ? null : date.format(DATE_FMT);
- }
-
- private String normalizeForMatch(String text) {
- if (!StringUtils.hasText(text)) {
- return "";
- }
- return text.replace("锛�", "")
- .replace(",", "")
- .replace("銆�", "")
- .replace(".", "")
- .replace("锛�", "")
- .replace("!", "")
- .replace("锛�", "")
- .replace("?", "")
- .replace("锛�", "")
- .replace(":", "")
- .replace("锛�", "")
- .replace(";", "")
- .replace(" ", "")
- .trim();
- }
-
- private Boolean shouldPrioritizeHighRisk(String text) {
- return containsAny(text, "浼樺厛楂橀闄�", "楂橀闄╁鎴�", "楂橀闄�");
- }
-
- private String extractKeyword(String text) {
- String cleaned = text
- .replace("鏌ヨ", "")
- .replace("鏌ョ湅", "")
- .replace("鐪嬩笅", "")
- .replace("鐪嬬湅", "")
- .replace("甯垜", "")
- .replace("璇�", "")
- .replace("涓�涓�", "")
- .replace("閿�鍞�", "")
- .replace("瀹㈡埛妗f", "")
- .replace("鎶ヤ环鍗�", "")
- .replace("閿�鍞姤浠�", "")
- .replace("閿�鍞彴璐�", "")
- .replace("鍙戣揣鍙拌处", "")
- .replace("瀹㈡埛寰�鏉�", "")
- .replace("閿�鍞��璐�", "")
- .replace("鍓�10鏉�", "")
- .replace("鏈�杩�10鏉�", "")
- .replace("鍓�20鏉�", "")
- .replace("鏈�杩�20鏉�", "")
- .replace("杩�30澶�", "")
- .replace("鏈湀", "")
- .replace("鏈勾", "")
- .replace("浠婂勾", "")
- .replace("鏉�", "")
- .trim();
- return cleaned.length() >= 2 ? cleaned : null;
- }
-
- private record DateRange(String startDate, String endDate) {
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/assistant/SeparateChatAssistant.java b/src/main/java/com/ruoyi/ai/assistant/SeparateChatAssistant.java
deleted file mode 100644
index fa36ad5..0000000
--- a/src/main/java/com/ruoyi/ai/assistant/SeparateChatAssistant.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.ruoyi.ai.assistant;
-
-import dev.langchain4j.service.MemoryId;
-import dev.langchain4j.service.SystemMessage;
-import dev.langchain4j.service.UserMessage;
-import dev.langchain4j.service.V;
-import dev.langchain4j.service.spring.AiService;
-import dev.langchain4j.service.spring.AiServiceWiringMode;
-
-/**
- * @author :yys
- * @date : 2025/5/2 19:35
- */
-@AiService(
- wiringMode = AiServiceWiringMode.EXPLICIT,
- chatModel = "qwenChatModel",
- chatMemoryProvider = "chatMemoryProvider"
-)
-public interface SeparateChatAssistant {
-
- @SystemMessage(fromResource = "my-prompt-template.txt")//绯荤粺娑堟伅鎻愮ず璇�
- String chat(@MemoryId String memoryId, @UserMessage String userMessage);
-
- @UserMessage("浣犳槸鎴戠殑濂芥湅鍙嬶紝璇风敤绮よ鍥炵瓟闂銆倇{message}}")
- String chat2(@MemoryId String memoryId, @V("message") String userMessage);
-
- @SystemMessage(fromResource = "my-prompt-template3.txt")
- String chat3(
- @MemoryId String memoryId,
- @UserMessage String userMessage,
- @V("username") String username,
- @V("age") int age
- );
-
-}
diff --git a/src/main/java/com/ruoyi/ai/bean/ChatForm.java b/src/main/java/com/ruoyi/ai/bean/ChatForm.java
deleted file mode 100644
index 60d1a6e..0000000
--- a/src/main/java/com/ruoyi/ai/bean/ChatForm.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.ruoyi.ai.bean;
-
-import lombok.Data;
-
-/**
- * @author :yys
- * @date : 2025/5/2 20:03
- */
-@Data
-public class ChatForm {
-
- private String memoryId;//瀵硅瘽id
- private String message;//鐢ㄦ埛闂
-
-}
diff --git a/src/main/java/com/ruoyi/ai/bean/PurchaseAiConfirmRequest.java b/src/main/java/com/ruoyi/ai/bean/PurchaseAiConfirmRequest.java
deleted file mode 100644
index f727981..0000000
--- a/src/main/java/com/ruoyi/ai/bean/PurchaseAiConfirmRequest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.ruoyi.ai.bean;
-
-import java.util.Map;
-
-public class PurchaseAiConfirmRequest {
-
- private String businessType;
-
- private Map<String, Object> payload;
-
- public String getBusinessType() {
- return businessType;
- }
-
- public void setBusinessType(String businessType) {
- this.businessType = businessType;
- }
-
- public Map<String, Object> getPayload() {
- return payload;
- }
-
- public void setPayload(Map<String, Object> payload) {
- this.payload = payload;
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/config/ApproveTodoAgentConfig.java b/src/main/java/com/ruoyi/ai/config/ApproveTodoAgentConfig.java
deleted file mode 100644
index 61cc0dd..0000000
--- a/src/main/java/com/ruoyi/ai/config/ApproveTodoAgentConfig.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.ruoyi.ai.config;
-
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import dev.langchain4j.memory.chat.ChatMemoryProvider;
-import dev.langchain4j.memory.chat.MessageWindowChatMemory;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class ApproveTodoAgentConfig {
-
- @Bean
- ChatMemoryProvider chatMemoryProviderApproveTodo(MongoChatMemoryStore mongoChatMemoryStore) {
- return memoryId -> MessageWindowChatMemory.builder()
- .id(memoryId)
- .maxMessages(30)
- .chatMemoryStore(mongoChatMemoryStore)
- .build();
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java b/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
deleted file mode 100644
index ca5156d..0000000
--- a/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.ruoyi.ai.config;
-
-import dev.langchain4j.data.segment.TextSegment;
-import dev.langchain4j.model.embedding.EmbeddingModel;
-import dev.langchain4j.store.embedding.EmbeddingStore;
-import dev.langchain4j.store.embedding.pinecone.PineconeEmbeddingStore;
-import dev.langchain4j.store.embedding.pinecone.PineconeServerlessIndexConfig;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * @author :yys
- * @date : 2025/5/2 21:07
- */
-@Configuration
-public class EmbeddingStoreConfig {
-
- @Autowired
- private EmbeddingModel embeddingModel;
-
- @Bean
- public EmbeddingStore<TextSegment> embeddingStore() {
- //鍒涘缓鍚戦噺瀛樺偍
- return PineconeEmbeddingStore.builder()
- .apiKey("pcsk_4SJLnh_tNB3wSLJU8tc4E5P28PcXX8eCLdURqZpVhg1FMV8CRYxjneWdzqRdB5Ftqooi9")
- .index("xiaozhi-index")//濡傛灉鎸囧畾鐨勭储寮曚笉瀛樺湪锛屽皢鍒涘缓涓�涓柊鐨勭储寮�
- .nameSpace("xiaozhi-namespace") //濡傛灉鎸囧畾鐨勫悕绉扮┖闂翠笉瀛樺湪锛屽皢鍒涘缓涓�涓柊鐨勫悕绉� 绌洪棿
- .createIndex(PineconeServerlessIndexConfig.builder()
- .cloud("AWS") //鎸囧畾绱㈠紩閮ㄧ讲鍦� AWS 浜戞湇鍔′笂銆�
- .region("us-east-1") //鎸囧畾绱㈠紩鎵�鍦ㄧ殑 AWS 鍖哄煙涓� us-east-1銆�
- .dimension(embeddingModel.dimension()) //鎸囧畾绱㈠紩鐨勫悜閲忕淮搴︼紝璇ョ淮搴︿笌 embeddedModel 鐢熸垚鐨勫悜閲忕淮搴︾浉鍚屻��
- .build())
- .build();
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/config/ManufacturingAgentConfig.java b/src/main/java/com/ruoyi/ai/config/ManufacturingAgentConfig.java
deleted file mode 100644
index 79aa222..0000000
--- a/src/main/java/com/ruoyi/ai/config/ManufacturingAgentConfig.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.ruoyi.ai.config;
-
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import dev.langchain4j.memory.chat.ChatMemoryProvider;
-import dev.langchain4j.memory.chat.MessageWindowChatMemory;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class ManufacturingAgentConfig {
-
- @Bean
- ChatMemoryProvider chatMemoryProviderManufacturing(MongoChatMemoryStore mongoChatMemoryStore) {
- return memoryId -> MessageWindowChatMemory.builder()
- .id(memoryId)
- .maxMessages(30)
- .chatMemoryStore(mongoChatMemoryStore)
- .build();
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/config/PurchaseAgentConfig.java b/src/main/java/com/ruoyi/ai/config/PurchaseAgentConfig.java
deleted file mode 100644
index 62061ee..0000000
--- a/src/main/java/com/ruoyi/ai/config/PurchaseAgentConfig.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.ruoyi.ai.config;
-
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
-import dev.langchain4j.memory.chat.ChatMemoryProvider;
-import dev.langchain4j.memory.chat.MessageWindowChatMemory;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class PurchaseAgentConfig {
-
- @Bean
- ChatMemoryProvider chatMemoryProviderPurchase(MongoChatMemoryStore mongoChatMemoryStore) {
- return memoryId -> MessageWindowChatMemory.builder()
- .id(memoryId)
- .maxMessages(30)
- .chatMemoryStore(mongoChatMemoryStore)
- .build();
- }
-
- @Bean("purchaseVisionStreamingChatModel")
- QwenStreamingChatModel purchaseVisionStreamingChatModel(
- @Value("${langchain4j.community.dashscope.streaming-chat-model.api-key}") String apiKey) {
- return QwenStreamingChatModel.builder()
- .apiKey(apiKey)
- .modelName("qwen-vl-max")
- .isMultimodalModel(true)
- .build();
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/config/SalesAgentConfig.java b/src/main/java/com/ruoyi/ai/config/SalesAgentConfig.java
deleted file mode 100644
index aed3104..0000000
--- a/src/main/java/com/ruoyi/ai/config/SalesAgentConfig.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.ruoyi.ai.config;
-
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import dev.langchain4j.memory.chat.ChatMemoryProvider;
-import dev.langchain4j.memory.chat.MessageWindowChatMemory;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class SalesAgentConfig {
-
- @Bean
- ChatMemoryProvider chatMemoryProviderSales(MongoChatMemoryStore mongoChatMemoryStore) {
- return memoryId -> MessageWindowChatMemory.builder()
- .id(memoryId)
- .maxMessages(30)
- .chatMemoryStore(mongoChatMemoryStore)
- .build();
- }
-}
-
diff --git a/src/main/java/com/ruoyi/ai/config/SeparateChatAssistantConfig.java b/src/main/java/com/ruoyi/ai/config/SeparateChatAssistantConfig.java
deleted file mode 100644
index 1927843..0000000
--- a/src/main/java/com/ruoyi/ai/config/SeparateChatAssistantConfig.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.ruoyi.ai.config;
-
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import dev.langchain4j.memory.chat.ChatMemoryProvider;
-import dev.langchain4j.memory.chat.MessageWindowChatMemory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * @author :yys
- * @date : 2025/5/2 19:20
- */
-@Configuration
-public class SeparateChatAssistantConfig {
- //娉ㄥ叆鎸佷箙鍖栧璞�
- @Autowired
- private MongoChatMemoryStore mongoChatMemoryStore;
-
- @Bean
- ChatMemoryProvider chatMemoryProvider() {
- return memoryId -> MessageWindowChatMemory.builder()
- .id(memoryId)
- .maxMessages(10)
- .chatMemoryStore(mongoChatMemoryStore)//閰嶇疆鎸佷箙鍖栧璞�
- .build();
- }
-
-}
diff --git a/src/main/java/com/ruoyi/ai/config/XiaozhiAgentConfig.java b/src/main/java/com/ruoyi/ai/config/XiaozhiAgentConfig.java
deleted file mode 100644
index 27e5a6b..0000000
--- a/src/main/java/com/ruoyi/ai/config/XiaozhiAgentConfig.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package com.ruoyi.ai.config;
-
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import dev.langchain4j.memory.chat.ChatMemoryProvider;
-import dev.langchain4j.memory.chat.MessageWindowChatMemory;
-import dev.langchain4j.model.embedding.EmbeddingModel;
-import dev.langchain4j.rag.content.retriever.ContentRetriever;
-import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
-import dev.langchain4j.store.embedding.EmbeddingStore;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * @author :yys
- * @date : 2025/5/2 20:01
- */
-@Configuration
-public class XiaozhiAgentConfig {
-
- @Autowired
- private MongoChatMemoryStore mongoChatMemoryStore;
-
- @Autowired
- private EmbeddingStore embeddingStore;
- @Autowired
- private EmbeddingModel embeddingModel;
-
-// @Value("${knowledge.one}")
-// private String one;
-//
-// @Value("${knowledge.two}")
-// private String two;
-//
-// @Value("${knowledge.three}")
-// private String three;
-
- @Bean
- ChatMemoryProvider chatMemoryProviderXiaozhi() {
- return memoryId -> MessageWindowChatMemory.builder()
- .id(memoryId)
- .maxMessages(20)
- .chatMemoryStore(mongoChatMemoryStore)
- .build();
- }
-
-// @Bean
-// ContentRetriever contentRetrieverXiaozhi() {
-// //浣跨敤FileSystemDocumentLoader璇诲彇鎸囧畾鐩綍涓嬬殑鐭ヨ瘑搴撴枃妗�
-// //骞朵娇鐢ㄩ粯璁ょ殑鏂囨。瑙f瀽鍣ㄥ鏂囨。杩涜瑙f瀽
-// Document document1 = FileSystemDocumentLoader.loadDocument(one);
-//// Document document2 = FileSystemDocumentLoader.loadDocument(two);
-//// Document document3 = FileSystemDocumentLoader.loadDocument(three);
-//// List<Document> documents = Arrays.asList(document1, document2, document3);
-//
-// List<Document> documents = Collections.singletonList(document1);
-//// 2. 灏嗘暟鎹簱鏁版嵁杞负LangChain4j鐨凞ocument瀵硅薄
-//// List<Document> documents = new ArrayList<>();
-//
-// //浣跨敤鍐呭瓨鍚戦噺瀛樺偍
-// InMemoryEmbeddingStore<TextSegment> inMemoryEmbeddingStore = new InMemoryEmbeddingStore<>();
-// //浣跨敤榛樿鐨勬枃妗e垎鍓插櫒
-// EmbeddingStoreIngestor.builder()
-// .embeddingModel(embeddingModel)
-// .embeddingStore(inMemoryEmbeddingStore)
-// .build()
-// .ingest(documents);
-// //浠庡祵鍏ュ瓨鍌紙EmbeddingStore锛夐噷妫�绱㈠拰鏌ヨ鍐呭鐩稿叧鐨勪俊鎭�
-// return EmbeddingStoreContentRetriever.builder()
-// .embeddingModel(embeddingModel)
-// .embeddingStore(inMemoryEmbeddingStore)
-// .build();
-// }
-
- @Bean
- ContentRetriever contentRetrieverXiaozhiPincone() {
- // 鍒涘缓涓�涓� EmbeddingStoreContentRetriever 瀵硅薄锛岀敤浜庝粠宓屽叆瀛樺偍涓绱㈠唴瀹�
- return EmbeddingStoreContentRetriever
- .builder()
- // 璁剧疆鐢ㄤ簬鐢熸垚宓屽叆鍚戦噺鐨勫祵鍏ユā鍨�
- .embeddingModel(embeddingModel)
- // 鎸囧畾瑕佷娇鐢ㄧ殑宓屽叆瀛樺偍
- .embeddingStore(embeddingStore)
- // 璁剧疆鏈�澶ф绱㈢粨鏋滄暟閲忥紝杩欓噷琛ㄧず鏈�澶氳繑鍥� 1 鏉″尮閰嶇粨鏋�
- .maxResults(1)
- // 璁剧疆鏈�灏忓緱鍒嗛槇鍊硷紝鍙湁寰楀垎澶т簬绛変簬 0.8 鐨勭粨鏋滄墠浼氳杩斿洖
- .minScore(0.8)
- // 鏋勫缓鏈�缁堢殑 EmbeddingStoreContentRetriever 瀹炰緥
- .build();
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java b/src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java
deleted file mode 100644
index 3a5877b..0000000
--- a/src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.ruoyi.ai.context;
-
-import com.ruoyi.framework.security.LoginUser;
-import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-@Component
-public class AiSessionUserContext {
-
- private final Map<String, LoginUser> loginUserByMemoryId = new ConcurrentHashMap<>();
-
- public void bind(String memoryId, LoginUser loginUser) {
- if (!StringUtils.hasText(memoryId) || loginUser == null) {
- return;
- }
- loginUserByMemoryId.put(memoryId, loginUser);
- }
-
- public LoginUser get(String memoryId) {
- if (!StringUtils.hasText(memoryId)) {
- return null;
- }
- return loginUserByMemoryId.get(memoryId);
- }
-
- public void remove(String memoryId) {
- if (!StringUtils.hasText(memoryId)) {
- return;
- }
- loginUserByMemoryId.remove(memoryId);
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java b/src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java
deleted file mode 100644
index cb7c0ba..0000000
--- a/src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.ruoyi.ai.controller;
-
-import com.ruoyi.ai.assistant.ManufacturingAgent;
-import com.ruoyi.ai.assistant.ManufacturingIntentExecutor;
-import com.ruoyi.ai.bean.ChatForm;
-import com.ruoyi.ai.context.AiSessionUserContext;
-import com.ruoyi.ai.service.AiChatSessionService;
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.security.LoginUser;
-import com.ruoyi.framework.web.controller.BaseController;
-import com.ruoyi.framework.web.domain.AjaxResult;
-import dev.langchain4j.data.message.AiMessage;
-import dev.langchain4j.data.message.UserMessage;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import reactor.core.publisher.Flux;
-
-import java.util.List;
-
-@Tag(name = "鍒堕�犳櫤鑳藉姪鎵�")
-@RestController
-@RequestMapping("/manufacturing-ai")
-public class ManufacturingAiController extends BaseController {
-
- private final ManufacturingAgent manufacturingAgent;
- private final ManufacturingIntentExecutor manufacturingIntentExecutor;
- private final AiSessionUserContext aiSessionUserContext;
- private final MongoChatMemoryStore mongoChatMemoryStore;
- private final AiChatSessionService aiChatSessionService;
-
- public ManufacturingAiController(ManufacturingAgent manufacturingAgent,
- ManufacturingIntentExecutor manufacturingIntentExecutor,
- AiSessionUserContext aiSessionUserContext,
- MongoChatMemoryStore mongoChatMemoryStore,
- AiChatSessionService aiChatSessionService) {
- this.manufacturingAgent = manufacturingAgent;
- this.manufacturingIntentExecutor = manufacturingIntentExecutor;
- this.aiSessionUserContext = aiSessionUserContext;
- this.mongoChatMemoryStore = mongoChatMemoryStore;
- this.aiChatSessionService = aiChatSessionService;
- }
-
- @Operation(summary = "鍒堕�犲璇�")
- @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
- public Flux<String> chat(@RequestBody ChatForm chatForm) {
- if (!StringUtils.hasText(chatForm.getMemoryId())) {
- return Flux.just("memoryId涓嶈兘涓虹┖");
- }
- if (!StringUtils.hasText(chatForm.getMessage())) {
- return Flux.just("message涓嶈兘涓虹┖");
- }
-
- LoginUser loginUser = SecurityUtils.getLoginUser();
- String memoryId = chatForm.getMemoryId();
- String userMessage = chatForm.getMessage();
-
- aiSessionUserContext.bind(memoryId, loginUser);
- aiChatSessionService.touchSession(memoryId, loginUser, userMessage);
-
- String directResponse = manufacturingIntentExecutor.tryExecute(memoryId, userMessage);
- if (StringUtils.isNotEmpty(directResponse)) {
- mongoChatMemoryStore.appendMessages(
- memoryId,
- List.of(UserMessage.from(userMessage), AiMessage.from(directResponse))
- );
- aiChatSessionService.refreshSessionStats(memoryId, loginUser);
- return Flux.just(directResponse);
- }
-
- return manufacturingAgent.chat(memoryId, userMessage)
- .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
- .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
- }
-
- @Operation(summary = "鍒堕�犱細璇濆垪琛�")
- @GetMapping("/history/sessions")
- public AjaxResult listSessions() {
- return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
- }
-
- @Operation(summary = "鍒堕�犱細璇濇秷鎭�")
- @GetMapping("/history/messages/{memoryId}")
- public AjaxResult listMessages(@PathVariable String memoryId) {
- return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
- }
-
- @Operation(summary = "鍒犻櫎鍒堕�犱細璇�")
- @DeleteMapping("/history/{memoryId}")
- public AjaxResult deleteSession(@PathVariable String memoryId) {
- aiSessionUserContext.remove(memoryId);
- return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java b/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
deleted file mode 100644
index 158ea61..0000000
--- a/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.ruoyi.ai.controller;
-
-import com.ruoyi.ai.bean.ChatForm;
-import com.ruoyi.ai.bean.PurchaseAiConfirmRequest;
-import com.ruoyi.ai.service.PurchaseAiService;
-import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.framework.security.LoginUser;
-import com.ruoyi.framework.web.controller.BaseController;
-import com.ruoyi.framework.web.domain.AjaxResult;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
-import reactor.core.publisher.Flux;
-
-@Tag(name = "閲囪喘鏅鸿兘浣�")
-@RestController
-@RequestMapping("/purchase-ai")
-public class PurchaseAiController extends BaseController {
-
- private final PurchaseAiService purchaseAiService;
-
- public PurchaseAiController(PurchaseAiService purchaseAiService) {
- this.purchaseAiService = purchaseAiService;
- }
-
- @Operation(summary = "閲囪喘瀵硅瘽")
- @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
- public Flux<String> chat(@RequestBody ChatForm chatForm) {
- LoginUser loginUser = SecurityUtils.getLoginUser();
- return purchaseAiService.chat(chatForm, loginUser);
- }
-
- @Operation(summary = "閲囪喘澶氭枃浠跺垎鏋�")
- @PostMapping(value = "/analyze-files", consumes = "multipart/form-data", produces = "text/stream;charset=utf-8")
- public Flux<String> analyzeFiles(@RequestParam("files") MultipartFile[] files,
- @RequestParam(value = "message", required = false) String message,
- @RequestParam(value = "memoryId", required = false) String memoryId) {
- LoginUser loginUser = SecurityUtils.getLoginUser();
- return purchaseAiService.analyzeFiles(files, message, memoryId, loginUser);
- }
-
- @Operation(summary = "閲囪喘澶氭枃浠跺垎鏋愮‘璁ゅ鐞�")
- @PostMapping("/analyze-files/confirm")
- public AjaxResult confirmAnalyzeResult(@RequestBody PurchaseAiConfirmRequest request) {
- return purchaseAiService.confirmAnalyzeResult(request);
- }
-
- @Operation(summary = "閲囪喘浼氳瘽鍒楄〃")
- @GetMapping("/history/sessions")
- public AjaxResult listSessions() {
- LoginUser loginUser = SecurityUtils.getLoginUser();
- return success(purchaseAiService.listSessions(loginUser));
- }
-
- @Operation(summary = "閲囪喘浼氳瘽娑堟伅")
- @GetMapping("/history/messages/{memoryId}")
- public AjaxResult listMessages(@PathVariable String memoryId) {
- LoginUser loginUser = SecurityUtils.getLoginUser();
- return success(purchaseAiService.listMessages(memoryId, loginUser));
- }
-
- @Operation(summary = "鍒犻櫎閲囪喘浼氳瘽")
- @DeleteMapping("/history/{memoryId}")
- public AjaxResult deleteSession(@PathVariable String memoryId) {
- LoginUser loginUser = SecurityUtils.getLoginUser();
- return toAjax(purchaseAiService.deleteSession(memoryId, loginUser));
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/controller/SalesAiController.java b/src/main/java/com/ruoyi/ai/controller/SalesAiController.java
deleted file mode 100644
index c3a569b..0000000
--- a/src/main/java/com/ruoyi/ai/controller/SalesAiController.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package com.ruoyi.ai.controller;
-
-import com.ruoyi.ai.assistant.SalesAgent;
-import com.ruoyi.ai.assistant.SalesIntentExecutor;
-import com.ruoyi.ai.bean.ChatForm;
-import com.ruoyi.ai.context.AiSessionUserContext;
-import com.ruoyi.ai.service.AiChatSessionService;
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.security.LoginUser;
-import com.ruoyi.framework.web.controller.BaseController;
-import com.ruoyi.framework.web.domain.AjaxResult;
-import dev.langchain4j.data.message.AiMessage;
-import dev.langchain4j.data.message.UserMessage;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import reactor.core.publisher.Flux;
-
-import java.util.List;
-
-@Tag(name = "閿�鍞姪鎵嬫櫤鑳戒綋")
-@RestController
-@RequestMapping("/sales-ai")
-public class SalesAiController extends BaseController {
-
- private final SalesAgent salesAgent;
- private final SalesIntentExecutor salesIntentExecutor;
- private final AiSessionUserContext aiSessionUserContext;
- private final MongoChatMemoryStore mongoChatMemoryStore;
- private final AiChatSessionService aiChatSessionService;
-
- public SalesAiController(SalesAgent salesAgent,
- SalesIntentExecutor salesIntentExecutor,
- AiSessionUserContext aiSessionUserContext,
- MongoChatMemoryStore mongoChatMemoryStore,
- AiChatSessionService aiChatSessionService) {
- this.salesAgent = salesAgent;
- this.salesIntentExecutor = salesIntentExecutor;
- this.aiSessionUserContext = aiSessionUserContext;
- this.mongoChatMemoryStore = mongoChatMemoryStore;
- this.aiChatSessionService = aiChatSessionService;
- }
-
- @Operation(summary = "閿�鍞姪鎵嬪璇�")
- @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
- public Flux<String> chat(@RequestBody ChatForm chatForm) {
- if (!StringUtils.hasText(chatForm.getMemoryId())) {
- return Flux.just("memoryId涓嶈兘涓虹┖");
- }
- if (!StringUtils.hasText(chatForm.getMessage())) {
- return Flux.just("message涓嶈兘涓虹┖");
- }
-
- LoginUser loginUser = SecurityUtils.getLoginUser();
- String memoryId = chatForm.getMemoryId();
- String userMessage = chatForm.getMessage();
-
- aiSessionUserContext.bind(memoryId, loginUser);
- aiChatSessionService.touchSession(memoryId, loginUser, userMessage);
-
- String directResponse = salesIntentExecutor.tryExecute(memoryId, userMessage);
- if (StringUtils.isNotEmpty(directResponse)) {
- mongoChatMemoryStore.appendMessages(
- memoryId,
- List.of(UserMessage.from(userMessage), AiMessage.from(directResponse))
- );
- aiChatSessionService.refreshSessionStats(memoryId, loginUser);
- return Flux.just(directResponse);
- }
-
- if (isBusinessDataIntent(userMessage)) {
- String noGuessResponse = "鏈瘑鍒埌鍙墽琛岀殑鏁版嵁鏌ヨ鏉′欢銆備负淇濊瘉缁撴灉鍑嗙‘锛屽綋鍓嶄笉浼氭帹娴嬫垨缂栭�犳暟鎹紝璇疯ˉ鍏呮槑纭椂闂磋寖鍥淬�佸鎴锋垨鍗曞彿鍚庡啀鏌ヨ銆�";
- mongoChatMemoryStore.appendMessages(
- memoryId,
- List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse))
- );
- aiChatSessionService.refreshSessionStats(memoryId, loginUser);
- return Flux.just(noGuessResponse);
- }
-
- return salesAgent.chat(memoryId, userMessage)
- .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
- .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
- }
-
- @Operation(summary = "閿�鍞姪鎵嬩細璇濆垪琛�")
- @GetMapping("/history/sessions")
- public AjaxResult listSessions() {
- return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
- }
-
- @Operation(summary = "閿�鍞姪鎵嬩細璇濇秷鎭�")
- @GetMapping("/history/messages/{memoryId}")
- public AjaxResult listMessages(@PathVariable String memoryId) {
- return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
- }
-
- @Operation(summary = "鍒犻櫎閿�鍞姪鎵嬩細璇�")
- @DeleteMapping("/history/{memoryId}")
- public AjaxResult deleteSession(@PathVariable String memoryId) {
- aiSessionUserContext.remove(memoryId);
- return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
- }
-
- private boolean isBusinessDataIntent(String message) {
- if (!StringUtils.hasText(message)) {
- return false;
- }
- String text = message.trim();
- return containsAny(text,
- "鏌ヨ", "鏌ョ湅", "缁熻", "鍒嗘瀽", "寤鸿", "瀹㈡埛妗f", "绉佹捣", "鍏捣",
- "閿�鍞姤浠�", "閿�鍞彴璐�", "閿�鍞��璐�", "瀹㈡埛寰�鏉�", "鍙戣揣鍙拌处", "鍥炴", "鎶ヤ环", "椋庨櫓");
- }
-
- private boolean containsAny(String text, String... keywords) {
- for (String keyword : keywords) {
- if (text.contains(keyword)) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/controller/XiaozhiController.java b/src/main/java/com/ruoyi/ai/controller/XiaozhiController.java
deleted file mode 100644
index affa347..0000000
--- a/src/main/java/com/ruoyi/ai/controller/XiaozhiController.java
+++ /dev/null
@@ -1,193 +0,0 @@
-package com.ruoyi.ai.controller;
-
-import com.ruoyi.ai.assistant.ApproveTodoAgent;
-import com.ruoyi.ai.assistant.ApproveTodoIntentExecutor;
-import com.ruoyi.ai.assistant.FileAnalyzeAgent;
-import com.ruoyi.ai.bean.ChatForm;
-import com.ruoyi.ai.context.AiSessionUserContext;
-import com.ruoyi.ai.service.AiChatSessionService;
-import com.ruoyi.ai.service.AiFileTextExtractor;
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.security.LoginUser;
-import com.ruoyi.framework.web.controller.BaseController;
-import com.ruoyi.framework.web.domain.AjaxResult;
-import dev.langchain4j.data.message.AiMessage;
-import dev.langchain4j.data.message.UserMessage;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
-import reactor.core.publisher.Flux;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.UUID;
-
-@Tag(name = "鍗忓悓鍔炲叕鍔╂墜")
-@RestController
-@RequestMapping("/xiaozhi")
-public class XiaozhiController extends BaseController {
-
- private static final String FILE_ANALYZE_MEMORY_PREFIX = "file-analyze::";
-
- private final ApproveTodoAgent approveTodoAgent;
- private final ApproveTodoIntentExecutor approveTodoIntentExecutor;
- private final FileAnalyzeAgent fileAnalyzeAgent;
- private final AiSessionUserContext aiSessionUserContext;
- private final MongoChatMemoryStore mongoChatMemoryStore;
- private final AiFileTextExtractor aiFileTextExtractor;
- private final AiChatSessionService aiChatSessionService;
-
- public XiaozhiController(ApproveTodoAgent approveTodoAgent,
- ApproveTodoIntentExecutor approveTodoIntentExecutor,
- FileAnalyzeAgent fileAnalyzeAgent,
- AiSessionUserContext aiSessionUserContext,
- MongoChatMemoryStore mongoChatMemoryStore,
- AiFileTextExtractor aiFileTextExtractor,
- AiChatSessionService aiChatSessionService) {
- this.approveTodoAgent = approveTodoAgent;
- this.approveTodoIntentExecutor = approveTodoIntentExecutor;
- this.fileAnalyzeAgent = fileAnalyzeAgent;
- this.aiSessionUserContext = aiSessionUserContext;
- this.mongoChatMemoryStore = mongoChatMemoryStore;
- this.aiFileTextExtractor = aiFileTextExtractor;
- this.aiChatSessionService = aiChatSessionService;
- }
-
- @Operation(summary = "瀵硅瘽")
- @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
- public Flux<String> chat(@RequestBody ChatForm chatForm) {
- if (!StringUtils.hasText(chatForm.getMemoryId())) {
- return Flux.just("memoryId涓嶈兘涓虹┖");
- }
- if (!StringUtils.hasText(chatForm.getMessage())) {
- return Flux.just("message涓嶈兘涓虹┖");
- }
- LoginUser loginUser = SecurityUtils.getLoginUser();
- String memoryId = chatForm.getMemoryId();
- String userMessage = chatForm.getMessage();
-
- aiSessionUserContext.bind(memoryId, loginUser);
- aiChatSessionService.touchSession(memoryId, loginUser, userMessage);
-
- String directResponse = approveTodoIntentExecutor.tryExecute(memoryId, userMessage);
- if (StringUtils.isNotEmpty(directResponse)) {
- mongoChatMemoryStore.appendMessages(
- memoryId,
- List.of(UserMessage.from(userMessage), AiMessage.from(directResponse))
- );
- aiChatSessionService.refreshSessionStats(memoryId, loginUser);
- return Flux.just(directResponse);
- }
-
- if (isApproveTodoBusinessIntent(userMessage)) {
- String noGuessResponse = "鏈瘑鍒埌鍙墽琛岀殑瀹℃壒寰呭姙鎿嶄綔鏉′欢銆備负淇濊瘉缁撴灉鍑嗙‘锛屽綋鍓嶄笉浼氭帹娴嬫垨缂栭�犲鎵规暟鎹紝璇疯ˉ鍏呮祦绋嬬紪鍙枫�佹椂闂磋寖鍥存垨鏄庣‘鎿嶄綔鎸囦护鍚庡啀璇曘��";
- mongoChatMemoryStore.appendMessages(
- memoryId,
- List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse))
- );
- aiChatSessionService.refreshSessionStats(memoryId, loginUser);
- return Flux.just(noGuessResponse);
- }
-
- return approveTodoAgent.chat(memoryId, userMessage)
- .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
- .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
- }
-
- @Operation(summary = "涓婁紶鏂囦欢鍒嗘瀽")
- @PostMapping(value = "/analyze-file", consumes = "multipart/form-data", produces = "text/stream;charset=utf-8")
- public Flux<String> analyzeFile(@RequestParam("file") MultipartFile file,
- @RequestParam(value = "message", required = false) String message,
- @RequestParam(value = "memoryId", required = false) String memoryId) {
- String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString();
- String finalMemoryId = rawMemoryId.startsWith(FILE_ANALYZE_MEMORY_PREFIX)
- ? rawMemoryId
- : FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId;
-
- LoginUser loginUser = SecurityUtils.getLoginUser();
- aiSessionUserContext.bind(finalMemoryId, loginUser);
-
- String finalMessage = StringUtils.hasText(message) ? message : "璇峰垎鏋愯繖涓枃浠剁殑鏍稿績鍐呭骞剁粰鍑烘�荤粨";
- String fileText;
- try {
- fileText = aiFileTextExtractor.extractText(file);
- } catch (IllegalArgumentException ex) {
- return Flux.just(ex.getMessage());
- } catch (IOException ex) {
- return Flux.just("鏂囦欢璇诲彇澶辫触");
- }
-
- if (!StringUtils.hasText(fileText)) {
- return Flux.just("鏈彁鍙栧埌鏈夋晥鏂囦欢鍐呭");
- }
-
- String limitedContent = fileText.length() > 12000 ? fileText.substring(0, 12000) : fileText;
- String userPrompt = "浣犲皢鍒嗘瀽鐢ㄦ埛涓婁紶鐨勬枃浠跺唴瀹广�俓n"
- + "鏂囦欢鍚�: " + file.getOriginalFilename() + "\n"
- + "鐢ㄦ埛闂: " + finalMessage + "\n"
- + "鏂囦欢鍐呭濡備笅:\n"
- + limitedContent;
-
- aiChatSessionService.touchSession(finalMemoryId, loginUser, "鏂囦欢鍒嗘瀽: " + finalMessage);
-
- return Flux.defer(() -> fileAnalyzeAgent.chat(finalMemoryId, userPrompt))
- .onErrorResume(NoSuchElementException.class, ex -> {
- // DashScope 鍦ㄥ巻鍙叉秷鎭吋瀹瑰満鏅笅鍙兘鎶� NoSuchElementException锛屾竻绌哄悗閲嶈瘯涓�娆�
- mongoChatMemoryStore.deleteMessages(finalMemoryId);
- return fileAnalyzeAgent.chat(finalMemoryId, userPrompt);
- })
- .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
- .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
- }
-
- @Operation(summary = "浼氳瘽鍒楄〃")
- @GetMapping("/history/sessions")
- public AjaxResult listSessions() {
- return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
- }
-
- @Operation(summary = "浼氳瘽娑堟伅")
- @GetMapping("/history/messages/{memoryId}")
- public AjaxResult listMessages(@PathVariable String memoryId) {
- return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
- }
-
- @Operation(summary = "鍒犻櫎浼氳瘽")
- @DeleteMapping("/history/{memoryId}")
- public AjaxResult deleteSession(@PathVariable String memoryId) {
- aiSessionUserContext.remove(memoryId);
- return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
- }
-
- private boolean isApproveTodoBusinessIntent(String message) {
- if (!StringUtils.hasText(message)) {
- return false;
- }
- String text = message.trim();
- boolean hasDomainWord = containsAny(text,
- "瀹℃壒", "寰呭姙", "娴佺▼缂栧彿", "娴佺▼鍙�", "瀹℃壒娴佽浆", "瀹℃壒鑺傜偣", "褰撳墠瀹℃壒浜�", "椹冲洖", "閫氳繃", "鎾ら攢", "鍒犻櫎");
- boolean hasIntentWord = containsAny(text,
- "鏌ヨ", "鏌ョ湅", "鍒楀嚭", "缁熻", "鍒嗘瀽", "鍒嗗竷", "閫氳繃", "椹冲洖", "鎾ら攢", "鍒犻櫎", "淇敼", "鏈夊摢浜�", "鍗″湪");
- return hasDomainWord && hasIntentWord;
- }
-
- private boolean containsAny(String text, String... keywords) {
- for (String keyword : keywords) {
- if (text.contains(keyword)) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java b/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
deleted file mode 100644
index 081bf12..0000000
--- a/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.ruoyi.ai.dto;
-
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.util.List;
-
-@Data
-@NoArgsConstructor
-public class AiChatMessageDto {
-
- private String role;
-
- private String content;
-
- private List<String> filePaths;
-
- public AiChatMessageDto(String role, String content) {
- this.role = role;
- this.content = content;
- }
-
- public AiChatMessageDto(String role, String content, List<String> filePaths) {
- this.role = role;
- this.content = content;
- this.filePaths = filePaths;
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/dto/AiChatSessionDto.java b/src/main/java/com/ruoyi/ai/dto/AiChatSessionDto.java
deleted file mode 100644
index f62f3b5..0000000
--- a/src/main/java/com/ruoyi/ai/dto/AiChatSessionDto.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.ruoyi.ai.dto;
-
-import lombok.Data;
-
-import java.util.Date;
-
-@Data
-public class AiChatSessionDto {
-
- private String memoryId;
-
- private String title;
-
- private String lastMessage;
-
- private Integer messageCount;
-
- private Date lastChatTime;
-}
diff --git a/src/main/java/com/ruoyi/ai/mapper/AiChatSessionMapper.java b/src/main/java/com/ruoyi/ai/mapper/AiChatSessionMapper.java
deleted file mode 100644
index 8983396..0000000
--- a/src/main/java/com/ruoyi/ai/mapper/AiChatSessionMapper.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.ruoyi.ai.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.ruoyi.ai.pojo.AiChatSession;
-
-public interface AiChatSessionMapper extends BaseMapper<AiChatSession> {
-}
diff --git a/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java b/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
deleted file mode 100644
index e2944cb..0000000
--- a/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.ruoyi.ai.mongodbBean;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.bson.types.ObjectId;
-import org.springframework.data.annotation.Id;
-import org.springframework.data.mongodb.core.index.Indexed;
-import org.springframework.data.mongodb.core.mapping.Document;
-
-import java.util.Date;
-import java.util.List;
-
-@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@Document("chat_messages")
-public class ChatMessages {
-
- @Id
- private ObjectId id;
-
- @Indexed(unique = true)
- private String memoryId;
-
- private String content;
-
- /**
- * 澶氭枃浠跺垎鏋愮敤鎴锋彁闂俊鎭紙涓庢枃浠惰矾寰勫垎寮�瀛樺偍锛�
- */
- private List<String> analyzeUserQuestions;
-
- /**
- * 澶氭枃浠跺垎鏋愪笂浼犳枃浠惰矾寰勶紙鍥剧墖鍜� pdf 浣跨敤棰勮鍦板潃锛屽叾浠栦娇鐢ㄤ笅杞藉湴鍧�锛�
- */
- private List<String> analyzeFilePaths;
-
- /**
- * 澶氭枃浠跺垎鏋愭瘡娆℃彁闂搴旂殑鏂囦欢璺緞鍒嗙粍
- */
- private List<List<String>> analyzeFilePathGroups;
-
- private Date createTime;
-
- private Date updateTime;
-}
diff --git a/src/main/java/com/ruoyi/ai/pojo/AiChatSession.java b/src/main/java/com/ruoyi/ai/pojo/AiChatSession.java
deleted file mode 100644
index cc289f8..0000000
--- a/src/main/java/com/ruoyi/ai/pojo/AiChatSession.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.ruoyi.ai.pojo;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.util.Date;
-
-@Data
-@TableName("ai_chat_session")
-public class AiChatSession {
-
- @TableId(type = IdType.AUTO)
- private Long id;
-
- private String memoryId;
-
- private Long userId;
-
- private Long tenantId;
-
- private String title;
-
- private String lastMessage;
-
- private Integer messageCount;
-
- private Date lastChatTime;
-
- private Date createTime;
-
- private Date updateTime;
-}
diff --git a/src/main/java/com/ruoyi/ai/service/AiChatSessionService.java b/src/main/java/com/ruoyi/ai/service/AiChatSessionService.java
deleted file mode 100644
index ef47e03..0000000
--- a/src/main/java/com/ruoyi/ai/service/AiChatSessionService.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.ruoyi.ai.service;
-
-import com.baomidou.mybatisplus.extension.service.IService;
-import com.ruoyi.ai.dto.AiChatMessageDto;
-import com.ruoyi.ai.dto.AiChatSessionDto;
-import com.ruoyi.ai.pojo.AiChatSession;
-import com.ruoyi.framework.security.LoginUser;
-
-import java.util.List;
-
-public interface AiChatSessionService extends IService<AiChatSession> {
-
- void touchSession(String memoryId, LoginUser loginUser, String userMessage);
-
- void refreshSessionStats(String memoryId, LoginUser loginUser);
-
- List<AiChatSessionDto> listCurrentUserSessions(LoginUser loginUser);
-
- List<AiChatMessageDto> listCurrentUserMessages(String memoryId, LoginUser loginUser);
-
- boolean deleteCurrentUserSession(String memoryId, LoginUser loginUser);
-}
diff --git a/src/main/java/com/ruoyi/ai/service/AiFileTextExtractor.java b/src/main/java/com/ruoyi/ai/service/AiFileTextExtractor.java
deleted file mode 100644
index 6e37451..0000000
--- a/src/main/java/com/ruoyi/ai/service/AiFileTextExtractor.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package com.ruoyi.ai.service;
-
-import com.ruoyi.common.utils.StringUtils;
-import org.apache.poi.hssf.usermodel.HSSFWorkbook;
-import org.apache.poi.ss.usermodel.DataFormatter;
-import org.apache.poi.ss.usermodel.Row;
-import org.apache.poi.ss.usermodel.Sheet;
-import org.apache.poi.xssf.usermodel.XSSFWorkbook;
-import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
-import org.apache.poi.xwpf.usermodel.XWPFDocument;
-import org.springframework.stereotype.Component;
-import org.springframework.web.multipart.MultipartFile;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
-@Component
-public class AiFileTextExtractor {
-
- private static final long MAX_FILE_SIZE = 10L * 1024 * 1024;
-
- public String extractText(MultipartFile file) throws IOException {
- if (file == null || file.isEmpty()) {
- throw new IllegalArgumentException("鏂囦欢涓嶈兘涓虹┖");
- }
- if (file.getSize() > MAX_FILE_SIZE) {
- throw new IllegalArgumentException("鏂囦欢杩囧ぇ锛岃鎺у埗鍦�10MB浠ュ唴");
- }
-
- String filename = file.getOriginalFilename();
- String ext = getExtension(filename);
- byte[] bytes = file.getBytes();
-
- if (isPlainText(ext)) {
- return decodeText(bytes);
- }
- if ("docx".equals(ext)) {
- return extractDocx(bytes);
- }
- if ("xlsx".equals(ext)) {
- return extractXlsx(bytes);
- }
- if ("xls".equals(ext)) {
- return extractXls(bytes);
- }
- if (isImage(ext)) {
- return "鍥剧墖鏂囦欢锛�" + filename + "锛屽凡涓婁紶锛岃缁撳悎鍥剧墖鍐呭璇嗗埆閲囪喘鍗曟嵁銆佽〃鏍煎拰浜у搧鏄庣粏銆�";
- }
- throw new IllegalArgumentException("鏆備笉鏀寔璇ユ枃浠剁被鍨�: " + ext);
- }
-
- public boolean isImageFile(MultipartFile file) {
- if (file == null) {
- return false;
- }
- return isImage(getExtension(file.getOriginalFilename()));
- }
-
- private String extractDocx(byte[] bytes) throws IOException {
- try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
- XWPFDocument document = new XWPFDocument(inputStream);
- XWPFWordExtractor extractor = new XWPFWordExtractor(document)) {
- return extractor.getText();
- }
- }
-
- private String extractXlsx(byte[] bytes) throws IOException {
- try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
- XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
- return extractWorkbook(workbook);
- }
- }
-
- private String extractXls(byte[] bytes) throws IOException {
- try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
- HSSFWorkbook workbook = new HSSFWorkbook(inputStream)) {
- return extractWorkbook(workbook);
- }
- }
-
- private String extractWorkbook(org.apache.poi.ss.usermodel.Workbook workbook) {
- StringBuilder text = new StringBuilder();
- DataFormatter formatter = new DataFormatter();
- for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
- Sheet sheet = workbook.getSheetAt(i);
- text.append("Sheet: ").append(sheet.getSheetName()).append("\n");
- for (Row row : sheet) {
- short lastCellNum = row.getLastCellNum();
- if (lastCellNum <= 0) {
- text.append("\n");
- continue;
- }
- for (int c = 0; c < lastCellNum; c++) {
- String cellText = formatter.formatCellValue(row.getCell(c));
- text.append(cellText);
- if (c < lastCellNum - 1) {
- text.append('\t');
- }
- }
- text.append('\n');
- }
- }
- return text.toString();
- }
-
- private String decodeText(byte[] bytes) {
- String utf8 = new String(bytes, StandardCharsets.UTF_8);
- if (utf8.contains("锟�")) {
- return new String(bytes, java.nio.charset.Charset.forName("GBK"));
- }
- return utf8;
- }
-
- private String getExtension(String filename) {
- if (!StringUtils.hasText(filename) || !filename.contains(".")) {
- return "";
- }
- return filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
- }
-
- private boolean isPlainText(String ext) {
- return StringUtils.inStringIgnoreCase(ext,
- "txt", "md", "markdown", "json", "xml", "yaml", "yml", "csv", "log", "properties",
- "java", "js", "ts", "vue", "html", "css", "sql", "py", "go", "sh", "bat");
- }
-
- private boolean isImage(String ext) {
- return StringUtils.inStringIgnoreCase(ext, "png", "jpg", "jpeg", "webp", "bmp");
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/service/PurchaseAiService.java b/src/main/java/com/ruoyi/ai/service/PurchaseAiService.java
deleted file mode 100644
index 0f24d64..0000000
--- a/src/main/java/com/ruoyi/ai/service/PurchaseAiService.java
+++ /dev/null
@@ -1,1031 +0,0 @@
-package com.ruoyi.ai.service;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.ruoyi.ai.assistant.PurchaseAgent;
-import com.ruoyi.ai.assistant.PurchaseIntentExecutor;
-import com.ruoyi.ai.bean.ChatForm;
-import com.ruoyi.ai.bean.PurchaseAiConfirmRequest;
-import com.ruoyi.ai.context.AiSessionUserContext;
-import com.ruoyi.ai.dto.AiChatMessageDto;
-import com.ruoyi.ai.dto.AiChatSessionDto;
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import com.ruoyi.basic.dto.StorageBlobVO;
-import com.ruoyi.basic.mapper.SupplierManageMapper;
-import com.ruoyi.basic.pojo.SupplierManage;
-import com.ruoyi.basic.service.StorageBlobService;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.security.LoginUser;
-import com.ruoyi.framework.web.domain.AjaxResult;
-import com.ruoyi.purchase.dto.PurchaseLedgerDto;
-import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
-import com.ruoyi.purchase.pojo.PaymentRegistration;
-import com.ruoyi.purchase.service.IPaymentRegistrationService;
-import com.ruoyi.purchase.service.IPurchaseLedgerService;
-import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
-import com.ruoyi.sales.pojo.SalesLedgerProduct;
-import dev.langchain4j.data.image.Image;
-import dev.langchain4j.data.message.AiMessage;
-import dev.langchain4j.data.message.ChatMessage;
-import dev.langchain4j.data.message.Content;
-import dev.langchain4j.data.message.ImageContent;
-import dev.langchain4j.data.message.SystemMessage;
-import dev.langchain4j.data.message.TextContent;
-import dev.langchain4j.data.message.UserMessage;
-import dev.langchain4j.model.chat.StreamingChatLanguageModel;
-import dev.langchain4j.model.chat.response.ChatResponse;
-import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.stereotype.Service;
-import org.springframework.web.multipart.MultipartFile;
-import reactor.core.publisher.Flux;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Base64;
-import java.util.Arrays;
-import java.time.LocalDate;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.UUID;
-import java.nio.file.Files;
-
-@Service
-public class PurchaseAiService {
-
- private static final String PURCHASE_FILE_ANALYZE_MEMORY_PREFIX = "purchase-file-analyze::";
- private static final int MAX_FILE_COUNT = 10;
- private static final int MAX_SINGLE_FILE_TEXT_LENGTH = 8000;
- private static final int MAX_TOTAL_FILE_TEXT_LENGTH = 30000;
-
- private final PurchaseAgent purchaseAgent;
- private final PurchaseIntentExecutor purchaseIntentExecutor;
- private final AiSessionUserContext aiSessionUserContext;
- private final MongoChatMemoryStore mongoChatMemoryStore;
- private final AiChatSessionService aiChatSessionService;
- private final AiFileTextExtractor aiFileTextExtractor;
- private final ObjectMapper objectMapper;
- private final IPurchaseLedgerService purchaseLedgerService;
- private final IPaymentRegistrationService paymentRegistrationService;
- private final PurchaseReturnOrdersService purchaseReturnOrdersService;
- private final StorageBlobService storageBlobService;
- private final SupplierManageMapper supplierManageMapper;
- private final StreamingChatLanguageModel purchaseVisionStreamingChatModel;
-
- public PurchaseAiService(PurchaseAgent purchaseAgent,
- PurchaseIntentExecutor purchaseIntentExecutor,
- AiSessionUserContext aiSessionUserContext,
- MongoChatMemoryStore mongoChatMemoryStore,
- AiChatSessionService aiChatSessionService,
- AiFileTextExtractor aiFileTextExtractor,
- ObjectMapper objectMapper,
- IPurchaseLedgerService purchaseLedgerService,
- IPaymentRegistrationService paymentRegistrationService,
- PurchaseReturnOrdersService purchaseReturnOrdersService,
- StorageBlobService storageBlobService,
- SupplierManageMapper supplierManageMapper,
- @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) {
- this.purchaseAgent = purchaseAgent;
- this.purchaseIntentExecutor = purchaseIntentExecutor;
- this.aiSessionUserContext = aiSessionUserContext;
- this.mongoChatMemoryStore = mongoChatMemoryStore;
- this.aiChatSessionService = aiChatSessionService;
- this.aiFileTextExtractor = aiFileTextExtractor;
- this.objectMapper = objectMapper;
- this.purchaseLedgerService = purchaseLedgerService;
- this.paymentRegistrationService = paymentRegistrationService;
- this.purchaseReturnOrdersService = purchaseReturnOrdersService;
- this.storageBlobService = storageBlobService;
- this.supplierManageMapper = supplierManageMapper;
- this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel;
- }
-
- public Flux<String> chat(ChatForm chatForm, LoginUser loginUser) {
- if (!StringUtils.hasText(chatForm.getMemoryId())) {
- return Flux.just("memoryId涓嶈兘涓虹┖");
- }
- if (!StringUtils.hasText(chatForm.getMessage())) {
- return Flux.just("message涓嶈兘涓虹┖");
- }
-
- String memoryId = chatForm.getMemoryId();
- String userMessage = chatForm.getMessage();
-
- aiSessionUserContext.bind(memoryId, loginUser);
- aiChatSessionService.touchSession(memoryId, loginUser, userMessage);
-
- String directResponse = purchaseIntentExecutor.tryExecute(memoryId, userMessage);
- if (StringUtils.isNotEmpty(directResponse)) {
- mongoChatMemoryStore.appendMessages(
- memoryId,
- List.of(UserMessage.from(userMessage), AiMessage.from(directResponse))
- );
- aiChatSessionService.refreshSessionStats(memoryId, loginUser);
- return Flux.just(directResponse);
- }
-
- return purchaseAgent.chat(memoryId, userMessage)
- .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
- .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
- }
-
- public Flux<String> analyzeFiles(MultipartFile[] files,
- String message,
- String memoryId,
- LoginUser loginUser) {
- if (files == null || files.length == 0) {
- return Flux.just("files涓嶈兘涓虹┖");
- }
- if (files.length > MAX_FILE_COUNT) {
- return Flux.just("涓�娆℃渶澶氬垎鏋�" + MAX_FILE_COUNT + "涓枃浠�");
- }
-
- String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString();
- String finalMemoryId = rawMemoryId.startsWith(PURCHASE_FILE_ANALYZE_MEMORY_PREFIX)
- ? rawMemoryId
- : PURCHASE_FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId;
-
- aiSessionUserContext.bind(finalMemoryId, loginUser);
-
- String finalMessage = StringUtils.hasText(message)
- ? message
- : "璇峰垎鏋愯繖浜涢噰璐枃浠讹紝鎻愬彇鍙敤浜庝笟鍔″鐞嗙殑鏁版嵁锛屽苟鏁寸悊鎴愬緟瀹㈡埛纭鐨勬牸寮�";
-
- List<String> filePaths;
- try {
- List<StorageBlobVO> uploadedFiles = storageBlobService.upload(copyFilesForUpload(files), true);
- filePaths = resolveFileAccessPaths(uploadedFiles);
- } catch (Exception ex) {
- return Flux.just("鏂囦欢涓婁紶澶辫触");
- }
- try {
- mongoChatMemoryStore.appendAnalyzeFileContext(finalMemoryId, finalMessage, filePaths);
- } catch (Exception ex) {
- return Flux.just("浼氳瘽鏂囦欢淇℃伅淇濆瓨澶辫触");
- }
-
- String fileContent;
- try {
- fileContent = buildMultiFileContent(files);
- } catch (IllegalArgumentException ex) {
- return Flux.just(ex.getMessage());
- } catch (IOException ex) {
- return Flux.just("鏂囦欢璇诲彇澶辫触");
- }
-
- if (!StringUtils.hasText(fileContent)) {
- return Flux.just("鏈彁鍙栧埌鏈夋晥鏂囦欢鍐呭");
- }
-
- String userPrompt = buildPurchaseFileAnalyzePrompt(finalMessage, fileContent);
- aiChatSessionService.touchSession(finalMemoryId, loginUser, "閲囪喘澶氭枃浠跺垎鏋�: " + finalMessage);
-
- if (containsImageFile(files)) {
- return chatWithPurchaseVisionModel(finalMemoryId, finalMessage, userPrompt, files)
- .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
- .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
- }
-
- return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt))
- .onErrorResume(NoSuchElementException.class, ex -> {
- mongoChatMemoryStore.deleteMessages(finalMemoryId);
- return purchaseAgent.chat(finalMemoryId, userPrompt);
- })
- .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
- .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
- }
-
- public AjaxResult confirmAnalyzeResult(PurchaseAiConfirmRequest request) {
- if (request == null || !StringUtils.hasText(request.getBusinessType())) {
- return AjaxResult.error("businessType涓嶈兘涓虹┖");
- }
- if (request.getPayload() == null || request.getPayload().isEmpty()) {
- return AjaxResult.error("payload涓嶈兘涓虹┖");
- }
-
- try {
- String businessType = request.getBusinessType().trim();
- return switch (businessType) {
- case "purchase_ledger" -> processPurchaseLedger(request.getPayload());
- case "payment_registration" -> processPaymentRegistration(request.getPayload());
- case "purchase_return_order" -> processPurchaseReturnOrder(request.getPayload());
- default -> AjaxResult.error("鏆備笉鏀寔璇ヤ笟鍔$被鍨�: " + businessType);
- };
- } catch (Exception ex) {
- return AjaxResult.error(toCustomerMessage(ex));
- }
- }
-
- public List<AiChatSessionDto> listSessions(LoginUser loginUser) {
- return aiChatSessionService.listCurrentUserSessions(loginUser);
- }
-
- public List<AiChatMessageDto> listMessages(String memoryId, LoginUser loginUser) {
- return aiChatSessionService.listCurrentUserMessages(memoryId, loginUser);
- }
-
- public boolean deleteSession(String memoryId, LoginUser loginUser) {
- aiSessionUserContext.remove(memoryId);
- return aiChatSessionService.deleteCurrentUserSession(memoryId, loginUser);
- }
-
- private String buildMultiFileContent(MultipartFile[] files) throws IOException {
- StringBuilder builder = new StringBuilder();
- int totalLength = 0;
- for (MultipartFile file : files) {
- String text = aiFileTextExtractor.extractText(file);
- if (!StringUtils.hasText(text)) {
- continue;
- }
- String limitedText = text.length() > MAX_SINGLE_FILE_TEXT_LENGTH
- ? text.substring(0, MAX_SINGLE_FILE_TEXT_LENGTH)
- : text;
- if (totalLength + limitedText.length() > MAX_TOTAL_FILE_TEXT_LENGTH) {
- int remain = MAX_TOTAL_FILE_TEXT_LENGTH - totalLength;
- if (remain <= 0) {
- break;
- }
- limitedText = limitedText.substring(0, remain);
- }
- builder.append("\n--- 鏂囦欢: ")
- .append(file.getOriginalFilename())
- .append(" ---\n")
- .append(limitedText)
- .append('\n');
- totalLength += limitedText.length();
- }
- return builder.toString();
- }
-
- private boolean containsImageFile(MultipartFile[] files) {
- for (MultipartFile file : files) {
- if (aiFileTextExtractor.isImageFile(file)) {
- return true;
- }
- }
- return false;
- }
-
- private List<String> resolveFileAccessPaths(List<StorageBlobVO> uploadedFiles) {
- if (StringUtils.isEmpty(uploadedFiles)) {
- return Collections.emptyList();
- }
- List<String> filePaths = new ArrayList<>();
- for (StorageBlobVO uploadedFile : uploadedFiles) {
- if (uploadedFile == null) {
- continue;
- }
- String selectedPath;
- if (shouldUsePreviewPath(uploadedFile)) {
- selectedPath = StringUtils.hasText(uploadedFile.getPreviewURL())
- ? uploadedFile.getPreviewURL()
- : uploadedFile.getDownloadURL();
- } else {
- selectedPath = StringUtils.hasText(uploadedFile.getDownloadURL())
- ? uploadedFile.getDownloadURL()
- : uploadedFile.getPreviewURL();
- }
- if (StringUtils.hasText(selectedPath)) {
- filePaths.add(selectedPath);
- }
- }
- return filePaths;
- }
-
- private boolean shouldUsePreviewPath(StorageBlobVO uploadedFile) {
- String contentType = uploadedFile.getContentType();
- if (StringUtils.hasText(contentType)) {
- String normalized = contentType.toLowerCase(Locale.ROOT);
- if (normalized.startsWith("image/") || "application/pdf".equals(normalized)) {
- return true;
- }
- }
- String filename = uploadedFile.getOriginalFilename();
- if (!StringUtils.hasText(filename) || !filename.contains(".")) {
- return false;
- }
- String ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT);
- return StringUtils.inStringIgnoreCase(ext, "png", "jpg", "jpeg", "webp", "bmp", "pdf");
- }
-
- private List<MultipartFile> copyFilesForUpload(MultipartFile[] files) throws IOException {
- List<MultipartFile> copies = new ArrayList<>();
- for (MultipartFile file : files) {
- copies.add(new InMemoryMultipartFile(
- file.getName(),
- file.getOriginalFilename(),
- file.getContentType(),
- file.getBytes()
- ));
- }
- return copies;
- }
-
- private static final class InMemoryMultipartFile implements MultipartFile {
- private final String name;
- private final String originalFilename;
- private final String contentType;
- private final byte[] bytes;
-
- private InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) {
- this.name = name;
- this.originalFilename = originalFilename;
- this.contentType = contentType;
- this.bytes = bytes == null ? new byte[0] : bytes;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public String getOriginalFilename() {
- return originalFilename;
- }
-
- @Override
- public String getContentType() {
- return contentType;
- }
-
- @Override
- public boolean isEmpty() {
- return bytes.length == 0;
- }
-
- @Override
- public long getSize() {
- return bytes.length;
- }
-
- @Override
- public byte[] getBytes() {
- return bytes.clone();
- }
-
- @Override
- public InputStream getInputStream() {
- return new ByteArrayInputStream(bytes);
- }
-
- @Override
- public void transferTo(File dest) throws IOException, IllegalStateException {
- Files.write(dest.toPath(), bytes);
- }
- }
-
- private Flux<String> chatWithPurchaseVisionModel(String memoryId,
- String userMessage,
- String userPrompt,
- MultipartFile[] files) {
- return Flux.create(sink -> {
- StringBuilder assistantReply = new StringBuilder();
- try {
- List<Content> contents = new ArrayList<>();
- contents.add(TextContent.from(userPrompt));
- for (MultipartFile file : files) {
- if (!aiFileTextExtractor.isImageFile(file)) {
- continue;
- }
- contents.add(TextContent.from("涓嬮潰杩欏紶鍥剧墖鏂囦欢鍚嶏細" + file.getOriginalFilename()));
- contents.add(ImageContent.from(Image.builder()
- .base64Data(Base64.getEncoder().encodeToString(file.getBytes()))
- .mimeType(resolveImageMimeType(file))
- .build()));
- }
-
- List<ChatMessage> messages = List.of(
- SystemMessage.from("浣犳槸閲囪喘涓氬姟鏂囦欢鍒嗘瀽鍔╂墜銆傝浠庢枃鏈拰鍥剧墖涓瘑鍒噰璐彴璐︺�侀噰璐骇鍝佹槑缁嗐�佷粯娆炬垨閫�璐т俊鎭紝鍙緭鍑哄悎娉� JSON銆�"),
- UserMessage.from(contents)
- );
- safeAppendMessages(memoryId, List.of(UserMessage.from("閲囪喘澶氭枃浠跺垎鏋�: " + userMessage)));
- purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() {
- @Override
- public void onPartialResponse(String partialResponse) {
- if (partialResponse != null) {
- assistantReply.append(partialResponse);
- sink.next(partialResponse);
- }
- }
-
- @Override
- public void onCompleteResponse(ChatResponse completeResponse) {
- if (StringUtils.hasText(assistantReply.toString())) {
- safeAppendMessages(memoryId, List.of(AiMessage.from(assistantReply.toString())));
- }
- sink.complete();
- }
-
- @Override
- public void onError(Throwable error) {
- sink.error(error);
- }
- });
- } catch (Exception ex) {
- sink.next("鍥剧墖鏂囦欢璇诲彇澶辫触锛岃纭鍥剧墖鏍煎紡涓� png銆乯pg銆乯peg銆亀ebp 鎴� bmp锛屼笖澶у皬涓嶈秴杩�10MB");
- sink.complete();
- }
- });
- }
-
- private void safeAppendMessages(String memoryId, List<ChatMessage> messages) {
- if (!StringUtils.hasText(memoryId) || StringUtils.isEmpty(messages)) {
- return;
- }
- try {
- mongoChatMemoryStore.appendMessages(memoryId, messages);
- } catch (Exception ignored) {
- }
- }
-
- private String resolveImageMimeType(MultipartFile file) {
- String contentType = file.getContentType();
- if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) {
- return contentType;
- }
- String filename = file.getOriginalFilename();
- String ext = "";
- if (StringUtils.hasText(filename) && filename.contains(".")) {
- ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
- }
- return switch (ext) {
- case "jpg", "jpeg" -> "image/jpeg";
- case "webp" -> "image/webp";
- case "bmp" -> "image/bmp";
- default -> "image/png";
- };
- }
-
- private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) {
- return """
- 浣犳槸閲囪喘涓氬姟鏂囦欢鍒嗘瀽鍔╂墜銆傝涓ユ牸鏍规嵁鐢ㄦ埛涓婁紶鐨勫涓枃浠跺拰鐢ㄦ埛瑕佹眰鎻愬彇閲囪喘涓氬姟鏁版嵁銆�
-
- 鐢ㄦ埛瑕佹眰:
- %s
-
- 杈撳嚭瑕佹眰:
- 1. 鍙緭鍑哄悎娉� JSON锛屼笉瑕� Markdown锛屼笉瑕侀澶栬В閲娿��
- 2. JSON 椤跺眰瀛楁鍥哄畾涓�:
- - success: boolean
- - businessType: purchase_ledger | payment_registration | purchase_return_order | unknown
- - action: confirm_required
- - description: 涓枃璇存槑
- - confidence: 0鍒�1鐨勫皬鏁�
- - missingFields: 缂哄け瀛楁涓枃鍚嶇О鏁扮粍锛岄潰鍚戝鎴峰睍绀猴紝涓嶈杈撳嚭鑻辨枃瀛楁鍚�
- - warnings: 椋庨櫓鎻愮ず鏁扮粍
- - payload: 寰呭鎴风‘璁ょ殑鏁版嵁锛屽瓧娈靛悕蹇呴』浣跨敤鍚庣 DTO 瀛楁鍚�
- - preview: 缁欏鎴风‘璁ょ敤鐨勪腑鏂囨憳瑕佹暟缁�
- 3. 濡傛灉鍙垽鏂负閲囪喘鍙拌处锛宐usinessType 浣跨敤 purchase_ledger锛宲ayload.purchaseLedgers 涓洪噰璐鍗�/閲囪喘鍙拌处鏁扮粍:
- - purchaseLedgers: 閲囪喘璁㈠崟/閲囪喘鍙拌处鏁扮粍锛屾瘡鏉¤褰曞瓧娈靛悕蹇呴』涓� PurchaseLedgerDto 淇濇寔涓�鑷�
- - 浜у搧鏄庣粏蹇呴』鏀惧湪姣忔潯閲囪喘鍙拌处璁板綍鐨� productData 瀛楁涓紝productData 绫诲瀷涓� List<SalesLedgerProduct>
- - 涓嶈浼樺厛浣跨敤 payload 椤跺眰 productData锛涢《灞� productData 浠呬綔涓烘棫鏍煎紡鍏煎
- - 鏂囦欢閲岀殑鈥滈噰璐崟鍙封�濆氨鏄�滈噰璐悎鍚屽彿鈥濓紝缁熶竴鏄犲皠涓� purchaseContractNumber
- - 鏂囦欢閲岀殑鈥滈攢鍞崟鍙封�濆氨鏄�滈攢鍞悎鍚屽彿鈥濓紝缁熶竴鏄犲皠涓� salesContractNo
- - 鎵�鏈夋棩鏈熷瓧娈靛繀椤讳娇鐢� yyyy-MM-dd锛屼緥濡� 2026-04-30锛涗笉瑕佽緭鍑� 4/30/26銆�2026/4/30銆�2026骞�4鏈�30鏃� 鎴栧甫鏃跺垎绉掔殑鏍煎紡
- - 閲囪喘鍙拌处涓嶉渶瑕佸湪 payload 涓紶瀹℃壒浜猴紝涓嶈杈撳嚭 approveUserIds銆乤pproverId
- - missingFields 鍙~鍐欎笟鍔″繀濉絾鏃犳硶璇嗗埆鐨勫瓧娈碉紝涓嶈鎶� PurchaseLedgerDto 鐨勬墍鏈夌┖瀛楁閮藉垪涓虹己澶憋紱缂哄け椤瑰繀椤诲啓涓枃锛屼緥濡傗�滀緵搴斿晢鍚嶇О鈥濃�滃惈绋庡崟浠封�濓紝涓嶈鍐� supplierId銆乼axInclusiveUnitPrice
- - 閲囪喘鍙拌处涓昏〃蹇呭~瀛楁浠呮寜杩欎簺鍒ゆ柇: purchaseContractNumber銆乻upplierName 鎴� supplierId
- - productData 姣忔潯浜у搧蹇呭~瀛楁: productCategory銆乻pecificationModel銆乽nit銆乹uantity銆乼axInclusiveUnitPrice 鎴� taxInclusiveTotalPrice锛涘鏋滃彧鏈夊惈绋庢�讳环鍜屾暟閲忥紝蹇呴』璁$畻 taxInclusiveUnitPrice锛涘鏋滃彧鏈夊惈绋庡崟浠峰拰鏁伴噺锛屽繀椤昏绠� taxInclusiveTotalPrice
- - 浜у搧瀛楁鎸夐噰璐鍏ユ帴鍙� PurchaseLedgerProductImportDto 瀵归綈: 閲囪喘鍗曞彿銆佷骇鍝佸ぇ绫汇�佽鏍煎瀷鍙枫�佸崟浣嶃�佹暟閲忋�佺◣鐜囥�佸惈绋庡崟浠枫�佸惈绋庢�讳环銆佸彂绁ㄧ被鍨嬨�佹槸鍚﹁川妫�
- - 閲囪喘浜у搧 type 鍥哄畾涓� 2
- - purchaseLedgers 姣忔潯璁板綍鍙娇鐢ㄨ繖浜� PurchaseLedgerDto 瀛楁鍚�:
- entryDateStart, entryDateEnd, id, purchaseContractNumber, supplierId, supplierName, isWhite, recorderId, recorderName, salesContractNo, salesContractNoId, projectName, entryDate, executionDate, remarks, attachmentMaterials, createdAt, updatedAt, salesLedgerId, hasChildren, Type, productData, tempFileIds, SalesLedgerFiles, phoneNumber, businessPersonId, productId, productModelId, invoiceNumber, invoiceAmount, ticketRegistrationId, contractAmount, receiptPaymentAmount, unReceiptPaymentAmount, type, paymentMethod, approvalStatus, templateName
- - productData 姣忔潯浜у搧鍙娇鐢ㄨ繖浜� SalesLedgerProduct 瀛楁鍚�:
- productCategory, specificationModel, unit, quantity, taxRate, taxInclusiveUnitPrice, taxInclusiveTotalPrice, taxExclusiveTotalPrice, invoiceType, productId, productModelId, isChecked, type
- 4. 濡傛灉鍙垽鏂负浠樻鐧昏锛宐usinessType 浣跨敤 payment_registration锛宲ayload.records 涓轰粯娆剧櫥璁版暟缁勶紝瀛楁灏介噺鍖呭惈 purchaseLedgerId銆乻alesLedgerProductId銆乧urrentPaymentAmount銆乸aymentMethod銆乸aymentDate銆�
- 5. 濡傛灉鍙垽鏂负閲囪喘閫�璐э紝businessType 浣跨敤 purchase_return_order锛宲ayload 鎸� PurchaseReturnOrderDto 缁勭粐锛屾槑缁嗘斁 purchaseReturnOrderProductsDtos銆�
- 6. 缂哄皯涓氬姟澶勭悊蹇呴』瀛楁鏃讹紝涓嶈缂栭�� ID锛屾妸瀛楁鏀惧叆 missingFields锛屽苟浠嶈繑鍥炲彲纭鐨勮崏绋挎暟鎹��
- 7. 鎵�鏈変腑鏂囧唴瀹圭洿鎺ヤ繚鐣欙紝涓嶈杞箟鎴� Unicode銆�
-
- 鏂囦欢鍐呭:
- %s
- """.formatted(message, fileContent);
- }
-
- private AjaxResult processPurchaseLedger(Map<String, Object> payload) throws Exception {
- if (payload.containsKey("purchaseLedgers")) {
- return processPurchaseLedgerBatch(payload);
- }
-
- Map<String, Object> normalizedPayload = normalizePurchaseLedgerMap(payload);
- PurchaseLedgerDto dto = objectMapper.convertValue(normalizedPayload, PurchaseLedgerDto.class);
- AjaxResult ledgerResult = validatePurchaseLedger(dto, 0);
- if (ledgerResult != null) {
- return ledgerResult;
- }
- AjaxResult supplierResult = fillSupplierIdByName(dto);
- if (supplierResult != null) {
- return supplierResult;
- }
- AjaxResult productResult = validatePurchaseProducts(dto.getProductData(), 0);
- if (productResult != null) {
- return productResult;
- }
- int result = purchaseLedgerService.addOrEditPurchase(dto);
- return AjaxResult.success("閲囪喘鍙拌处宸插鐞�", result);
- }
-
- private AjaxResult processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception {
- List<Map<String, Object>> purchaseLedgers = toMapList(payload.get("purchaseLedgers"));
- if (purchaseLedgers.isEmpty()) {
- return AjaxResult.error("purchaseLedgers涓嶈兘涓虹┖");
- }
-
- List<Map<String, Object>> topLevelProductData = toMapList(payload.get("productData"));
- List<Map<String, Object>> results = new ArrayList<>();
- for (int i = 0; i < purchaseLedgers.size(); i++) {
- Map<String, Object> ledgerMap = normalizePurchaseLedgerMap(purchaseLedgers.get(i));
- PurchaseLedgerDto dto = objectMapper.convertValue(ledgerMap, PurchaseLedgerDto.class);
- AjaxResult ledgerResult = validatePurchaseLedger(dto, i);
- if (ledgerResult != null) {
- return ledgerResult;
- }
- AjaxResult supplierResult = fillSupplierIdByName(dto);
- if (supplierResult != null) {
- return supplierResult;
- }
-
- List<SalesLedgerProduct> products = dto.getProductData();
- if (products == null || products.isEmpty()) {
- products = matchProductsForLedger(ledgerMap, dto, topLevelProductData, purchaseLedgers.size() == 1);
- dto.setProductData(products);
- }
- AjaxResult productResult = validatePurchaseProducts(products, i);
- if (productResult != null) {
- return productResult;
- }
- int result = purchaseLedgerService.addOrEditPurchase(dto);
-
- Map<String, Object> item = new LinkedHashMap<>();
- item.put("index", i);
- item.put("purchaseContractNumber", dto.getPurchaseContractNumber());
- item.put("supplierId", dto.getSupplierId());
- item.put("supplierName", dto.getSupplierName());
- item.put("productCount", products.size());
- item.put("result", result);
- results.add(item);
- }
- return AjaxResult.success("閲囪喘鍙拌处宸叉壒閲忓鐞�", results);
- }
-
- private List<SalesLedgerProduct> matchProductsForLedger(Map<String, Object> ledgerMap,
- PurchaseLedgerDto dto,
- List<Map<String, Object>> productData,
- boolean onlyOneLedger) {
- List<SalesLedgerProduct> products = new ArrayList<>();
- for (Map<String, Object> productMap : productData) {
- if (onlyOneLedger || productBelongsToLedger(productMap, ledgerMap, dto)) {
- products.add(objectMapper.convertValue(normalizeSalesLedgerProductMap(productMap), SalesLedgerProduct.class));
- }
- }
- return products;
- }
-
- private boolean productBelongsToLedger(Map<String, Object> productMap, Map<String, Object> ledgerMap, PurchaseLedgerDto dto) {
- Long productPurchaseLedgerId = longValue(productMap, "purchaseLedgerId", "purchaseId", "閲囪喘璁㈠崟id", "閲囪喘鍙拌处id");
- if (productPurchaseLedgerId != null && dto.getId() != null && productPurchaseLedgerId.equals(dto.getId())) {
- return true;
- }
-
- Long productSalesLedgerId = longValue(productMap, "salesLedgerId");
- if (productSalesLedgerId != null && dto.getId() != null && productSalesLedgerId.equals(dto.getId())) {
- return true;
- }
-
- String productContractNo = stringValue(productMap, "purchaseContractNumber", "purchaseContractNo", "閲囪喘鍚堝悓鍙�", "閲囪喘鍗曞彿", "閲囪喘璁㈠崟鍙�");
- if (StringUtils.hasText(productContractNo)
- && StringUtils.hasText(dto.getPurchaseContractNumber())
- && productContractNo.trim().equals(dto.getPurchaseContractNumber().trim())) {
- return true;
- }
-
- String ledgerContractNo = stringValue(ledgerMap, "purchaseContractNumber", "purchaseContractNo", "閲囪喘鍚堝悓鍙�", "閲囪喘鍗曞彿", "閲囪喘璁㈠崟鍙�");
- if (StringUtils.hasText(productContractNo)
- && StringUtils.hasText(ledgerContractNo)
- && productContractNo.trim().equals(ledgerContractNo.trim())) {
- return true;
- }
-
- String productSalesContractNo = stringValue(productMap, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
- if (StringUtils.hasText(productSalesContractNo)
- && StringUtils.hasText(dto.getSalesContractNo())
- && productSalesContractNo.trim().equals(dto.getSalesContractNo().trim())) {
- return true;
- }
-
- String ledgerSalesContractNo = stringValue(ledgerMap, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
- if (StringUtils.hasText(productSalesContractNo)
- && StringUtils.hasText(ledgerSalesContractNo)
- && productSalesContractNo.trim().equals(ledgerSalesContractNo.trim())) {
- return true;
- }
-
- String productSupplierName = stringValue(productMap, "supplierName", "渚涘簲鍟嗗悕绉�");
- return StringUtils.hasText(productSupplierName)
- && StringUtils.hasText(dto.getSupplierName())
- && productSupplierName.trim().equals(dto.getSupplierName().trim());
- }
-
- private Map<String, Object> normalizePurchaseLedgerMap(Map<String, Object> source) {
- Map<String, Object> target = new LinkedHashMap<>();
- copyPurchaseLedgerDtoFields(source, target);
- putDtoFieldIfPresent(source, target, "entryDateStart", "褰曞叆寮�濮嬫棩鏈�", "褰曞叆鏃ユ湡寮�濮�");
- putDtoFieldIfPresent(source, target, "entryDateEnd", "褰曞叆缁撴潫鏃ユ湡", "褰曞叆鏃ユ湡缁撴潫");
- putDtoFieldIfPresent(source, target, "id", "閲囪喘鍙拌处id", "閲囪喘璁㈠崟id", "涓婚敭");
- putDtoFieldIfPresent(source, target, "purchaseContractNumber", "purchaseContractNo", "閲囪喘鍚堝悓鍙�", "閲囪喘鍗曞彿", "閲囪喘璁㈠崟鍙�");
- putDtoFieldIfPresent(source, target, "supplierId", "渚涘簲鍟唅d", "渚涘簲鍟咺D", "渚涘簲鍟嗗悕绉癷d", "渚涘簲鍟嗗悕绉癐D");
- putDtoFieldIfPresent(source, target, "supplierName", "渚涘簲鍟�", "渚涘簲鍟嗗悕绉�");
- putDtoFieldIfPresent(source, target, "isWhite", "鏄惁鐧藉悕鍗�");
- putDtoFieldIfPresent(source, target, "recorderId", "褰曞叆浜篿d", "褰曞叆浜篒D", "褰曞叆浜哄鍚峣d", "褰曞叆浜哄鍚岻D");
- putDtoFieldIfPresent(source, target, "recorderName", "褰曞叆浜�", "褰曞叆浜哄鍚�");
- putDtoFieldIfPresent(source, target, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
- putDtoFieldIfPresent(source, target, "salesContractNoId", "閿�鍞悎鍚屽彿id", "閿�鍞悎鍚屽彿ID", "閿�鍞崟鍙穒d", "閿�鍞崟鍙稩D");
- putDtoFieldIfPresent(source, target, "projectName", "椤圭洰", "椤圭洰鍚嶇О");
- putDtoFieldIfPresent(source, target, "entryDate", "褰曞叆鏃ユ湡");
- putDtoFieldIfPresent(source, target, "executionDate", "绛捐鏃ユ湡", "鍚堝悓绛捐鏃ユ湡");
- putDtoFieldIfPresent(source, target, "remarks", "澶囨敞", "璇存槑");
- putDtoFieldIfPresent(source, target, "attachmentMaterials", "闄勪欢鏉愭枡", "闄勪欢鏉愭枡璺緞鎴栧悕绉�");
- putDtoFieldIfPresent(source, target, "createdAt", "鍒涘缓鏃堕棿", "璁板綍鍒涘缓鏃堕棿");
- putDtoFieldIfPresent(source, target, "updatedAt", "鏇存柊鏃堕棿", "璁板綍鏈�鍚庢洿鏂版椂闂�");
- putDtoFieldIfPresent(source, target, "salesLedgerId", "閿�鍞彴璐d", "閿�鍞彴璐D", "鍏宠仈閿�鍞彴璐︿富琛ㄤ富閿�");
- putDtoFieldIfPresent(source, target, "hasChildren", "鏄惁鏈夊瓙绾�", "鏄惁鏈夋槑缁�");
- putDtoFieldIfPresent(source, target, "Type", "鍙拌处绫诲瀷", "涓氬姟绫诲瀷");
- putDtoFieldIfPresent(source, target, "productData", "products", "浜у搧鏄庣粏", "閲囪喘浜у搧鏄庣粏");
- putDtoFieldIfPresent(source, target, "tempFileIds", "涓存椂鏂囦欢id", "涓存椂鏂囦欢ID", "涓存椂鏂囦欢ids");
- putDtoFieldIfPresent(source, target, "SalesLedgerFiles", "闄勪欢鍒楄〃", "閿�鍞彴璐﹂檮浠�");
- putDtoFieldIfPresent(source, target, "phoneNumber", "涓氬姟鍛樻墜鏈哄彿", "鎵嬫満鍙�");
- putDtoFieldIfPresent(source, target, "businessPersonId", "涓氬姟鍛榠d", "涓氬姟鍛業D");
- putDtoFieldIfPresent(source, target, "productId", "浜у搧id", "浜у搧ID");
- putDtoFieldIfPresent(source, target, "productModelId", "浜у搧瑙勬牸id", "浜у搧瑙勬牸ID");
- putDtoFieldIfPresent(source, target, "invoiceNumber", "鍙戠エ鍙�", "鍙戠エ鍙风爜");
- putDtoFieldIfPresent(source, target, "invoiceAmount", "鍙戠エ閲戦", "鍙戠エ閲戦锛堝厓锛�");
- putDtoFieldIfPresent(source, target, "ticketRegistrationId", "鏉ョエ鐧昏id", "鏉ョエ鐧昏ID");
- putDtoFieldIfPresent(source, target, "contractAmount", "鍚堝悓閲戦", "鍚堝悓閲戦锛堜骇鍝佸惈绋庢�讳环锛�");
- putDtoFieldIfPresent(source, target, "receiptPaymentAmount", "鏉ョエ閲戦", "宸叉潵绁ㄩ噾棰�", "宸叉潵绁ㄩ噾棰�(鍏�)");
- putDtoFieldIfPresent(source, target, "unReceiptPaymentAmount", "鏈潵绁ㄩ噾棰�", "鏈潵绁ㄩ噾棰�(鍏�)");
- putDtoFieldIfPresent(source, target, "type", "鏂囦欢绫诲瀷");
- putDtoFieldIfPresent(source, target, "paymentMethod", "浠樻鏂瑰紡");
- putDtoFieldIfPresent(source, target, "approvalStatus", "瀹℃壒鐘舵��");
- putDtoFieldIfPresent(source, target, "templateName", "妯℃澘鍚嶇О");
- target.remove("approveUserIds");
- target.remove("approverId");
- normalizeNestedProductData(target);
- attachImportStyleProductData(source, target);
- if (target.get("type") == null) {
- target.put("type", 2);
- }
- target.putIfAbsent("entryDate", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
- normalizePurchaseLedgerDateFields(target);
- return target;
- }
-
- private void attachImportStyleProductData(Map<String, Object> source, Map<String, Object> target) {
- if (target.get("productData") != null) {
- return;
- }
- Map<String, Object> productMap = normalizeSalesLedgerProductMap(source);
- if (hasImportStyleProductData(productMap)) {
- target.put("productData", List.of(productMap));
- }
- }
-
- private boolean hasImportStyleProductData(Map<String, Object> productMap) {
- return hasMapText(productMap, "productCategory")
- || hasMapText(productMap, "specificationModel")
- || productMap.get("quantity") != null
- || productMap.get("taxInclusiveUnitPrice") != null
- || productMap.get("taxInclusiveTotalPrice") != null;
- }
-
- private boolean hasMapText(Map<String, Object> map, String key) {
- Object value = map.get(key);
- return value != null && StringUtils.hasText(String.valueOf(value));
- }
-
- private void normalizeNestedProductData(Map<String, Object> target) {
- Object productDataValue = target.get("productData");
- if (productDataValue == null) {
- return;
- }
- List<Map<String, Object>> productMaps = toMapList(productDataValue);
- List<Map<String, Object>> normalizedProducts = new ArrayList<>();
- for (Map<String, Object> productMap : productMaps) {
- normalizedProducts.add(normalizeSalesLedgerProductMap(productMap));
- }
- target.put("productData", normalizedProducts);
- }
-
- private Map<String, Object> normalizeSalesLedgerProductMap(Map<String, Object> source) {
- Map<String, Object> target = new LinkedHashMap<>();
- copySalesLedgerProductFields(source, target);
- putDtoFieldIfPresent(source, target, "productCategory", "浜у搧澶х被", "浜у搧鍚嶇О", "浜у搧", "鍝佸悕", "鐗╂枡鍚嶇О");
- putDtoFieldIfPresent(source, target, "specificationModel", "瑙勬牸鍨嬪彿", "鍨嬪彿", "瑙勬牸", "浜у搧瑙勬牸");
- putDtoFieldIfPresent(source, target, "unit", "鍗曚綅");
- putDtoFieldIfPresent(source, target, "quantity", "鏁伴噺", "閲囪喘鏁伴噺");
- putDtoFieldIfPresent(source, target, "taxRate", "绋庣巼");
- putDtoFieldIfPresent(source, target, "taxInclusiveUnitPrice", "鍚◣鍗曚环", "鍗曚环", "閲囪喘鍗曚环", "鍚◣浠锋牸");
- putDtoFieldIfPresent(source, target, "taxInclusiveTotalPrice", "鍚◣鎬讳环", "鎬讳环", "閲囪喘閲戦", "閲戦", "鍚堝悓閲戦");
- putDtoFieldIfPresent(source, target, "taxExclusiveTotalPrice", "涓嶅惈绋庢�讳环");
- putDtoFieldIfPresent(source, target, "invoiceType", "鍙戠エ绫诲瀷", "鍙戠エ绫诲埆");
- putDtoFieldIfPresent(source, target, "productId", "浜у搧id", "浜у搧ID");
- putDtoFieldIfPresent(source, target, "productModelId", "浜у搧瑙勬牸id", "浜у搧瑙勬牸ID", "鍨嬪彿id", "鍨嬪彿ID");
- putDtoFieldIfPresent(source, target, "isChecked", "鏄惁璐ㄦ", "鏄惁璐ㄦ楠�", "璐ㄦ");
- putDtoFieldIfPresent(source, target, "type", "鍙拌处绫诲瀷");
- normalizeProductAmounts(target);
- target.putIfAbsent("type", 2);
- return target;
- }
-
- private void copySalesLedgerProductFields(Map<String, Object> source, Map<String, Object> target) {
- String[] productFields = {
- "id", "salesLedgerId", "warnNum", "productCategory", "specificationModel", "unit",
- "speculativeTradingName", "quantity", "minStock", "taxRate", "taxInclusiveUnitPrice",
- "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", "invoiceType", "type", "ticketsNum",
- "ticketsAmount", "futureTickets", "futureTicketsAmount", "invoiceNum", "noInvoiceNum",
- "invoiceAmount", "noInvoiceAmount", "productId", "productModelId", "register", "registerDate",
- "approveStatus", "pendingInvoiceTotal", "invoiceTotal", "pendingTicketsTotal", "ticketsTotal",
- "isChecked", "isProduction"
- };
- for (String field : productFields) {
- if (source.containsKey(field)) {
- target.put(field, source.get(field));
- }
- }
- }
-
- private void normalizeProductAmounts(Map<String, Object> target) {
- BigDecimal quantity = decimalValue(target.get("quantity"));
- BigDecimal unitPrice = decimalValue(target.get("taxInclusiveUnitPrice"));
- BigDecimal totalPrice = decimalValue(target.get("taxInclusiveTotalPrice"));
- if (unitPrice == null && totalPrice != null && quantity != null && quantity.compareTo(BigDecimal.ZERO) != 0) {
- target.put("taxInclusiveUnitPrice", totalPrice.divide(quantity, 6, RoundingMode.HALF_UP));
- }
- if (totalPrice == null && unitPrice != null && quantity != null) {
- target.put("taxInclusiveTotalPrice", unitPrice.multiply(quantity));
- }
- BigDecimal taxRate = decimalValue(target.get("taxRate"));
- totalPrice = decimalValue(target.get("taxInclusiveTotalPrice"));
- if (target.get("taxExclusiveTotalPrice") == null && totalPrice != null && taxRate != null) {
- BigDecimal divisor = BigDecimal.ONE.add(taxRate.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP));
- target.put("taxExclusiveTotalPrice", totalPrice.divide(divisor, 2, RoundingMode.HALF_UP));
- }
- }
-
- private AjaxResult validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) {
- if (products == null || products.isEmpty()) {
- return null;
- }
- for (int i = 0; i < products.size(); i++) {
- SalesLedgerProduct product = products.get(i);
- String prefix = "绗�" + (ledgerIndex + 1) + "涓噰璐彴璐︾殑绗�" + (i + 1) + "鏉′骇鍝�";
- if (!StringUtils.hasText(product.getProductCategory())) {
- return AjaxResult.error(prefix + "缂哄皯浜у搧鍚嶇О锛岃琛ュ厖鍚庡啀纭");
- }
- if (!StringUtils.hasText(product.getSpecificationModel())) {
- return AjaxResult.error(prefix + "缂哄皯瑙勬牸鍨嬪彿锛岃琛ュ厖鍚庡啀纭");
- }
- if (!StringUtils.hasText(product.getUnit())) {
- return AjaxResult.error(prefix + "缂哄皯鍗曚綅锛岃琛ュ厖鍚庡啀纭");
- }
- if (product.getQuantity() == null) {
- return AjaxResult.error(prefix + "缂哄皯鏁伴噺");
- }
- if (product.getTaxInclusiveUnitPrice() == null) {
- return AjaxResult.error(prefix + "缂哄皯鍚◣鍗曚环锛岃琛ュ厖鍚庡啀纭");
- }
- if (product.getTaxInclusiveTotalPrice() == null) {
- return AjaxResult.error(prefix + "缂哄皯鍚◣鎬讳环锛岃琛ュ厖鍚庡啀纭");
- }
- }
- return null;
- }
-
- private AjaxResult validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) {
- String prefix = "绗�" + (ledgerIndex + 1) + "涓噰璐彴璐�";
- if (!StringUtils.hasText(dto.getPurchaseContractNumber())) {
- return AjaxResult.error(prefix + "缂哄皯閲囪喘鍚堝悓鍙凤紝璇疯ˉ鍏呭悗鍐嶇‘璁�");
- }
- if (dto.getSupplierId() == null && !StringUtils.hasText(dto.getSupplierName())) {
- return AjaxResult.error(prefix + "缂哄皯渚涘簲鍟嗗悕绉帮紝璇疯ˉ鍏呭悗鍐嶇‘璁�");
- }
- return null;
- }
-
- private void normalizePurchaseLedgerDateFields(Map<String, Object> target) {
- normalizeDateField(target, "entryDate");
- normalizeDateField(target, "executionDate");
- normalizeDateField(target, "createdAt");
- normalizeDateField(target, "updatedAt");
- }
-
- private void normalizeDateField(Map<String, Object> target, String fieldName) {
- Object value = target.get(fieldName);
- if (value == null) {
- return;
- }
- String normalizedDate = normalizeDateValue(value);
- if (StringUtils.hasText(normalizedDate)) {
- target.put(fieldName, normalizedDate);
- }
- }
-
- private String normalizeDateValue(Object value) {
- if (value instanceof Date date) {
- return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
- }
- if (value instanceof Number number) {
- return LocalDate.of(1899, 12, 30)
- .plusDays(number.longValue())
- .format(DateTimeFormatter.ISO_LOCAL_DATE);
- }
-
- String text = String.valueOf(value).trim();
- if (!StringUtils.hasText(text)) {
- return null;
- }
- if (text.length() >= 10 && text.charAt(4) == '-' && text.charAt(7) == '-') {
- return text.substring(0, 10);
- }
-
- String normalizedText = text.replace("骞�", "-")
- .replace("鏈�", "-")
- .replace("鏃�", "")
- .replace(".", "-")
- .replace("/", "-")
- .trim();
- DateTimeFormatter[] formatters = {
- DateTimeFormatter.ofPattern("yyyy-M-d"),
- DateTimeFormatter.ofPattern("M-d-yyyy"),
- DateTimeFormatter.ofPattern("M-d-yy")
- };
- for (DateTimeFormatter formatter : formatters) {
- try {
- return LocalDate.parse(normalizedText, formatter).format(DateTimeFormatter.ISO_LOCAL_DATE);
- } catch (DateTimeParseException ignored) {
- // Try the next supported input pattern.
- }
- }
- return text;
- }
-
- private void copyPurchaseLedgerDtoFields(Map<String, Object> source, Map<String, Object> target) {
- String[] dtoFields = {
- "entryDateStart", "entryDateEnd", "id", "purchaseContractNumber",
- "supplierId", "supplierName", "isWhite", "recorderId", "recorderName", "salesContractNo",
- "salesContractNoId", "projectName", "entryDate", "executionDate", "remarks", "attachmentMaterials",
- "createdAt", "updatedAt", "salesLedgerId", "hasChildren", "Type", "productData", "tempFileIds",
- "SalesLedgerFiles", "phoneNumber", "businessPersonId", "productId", "productModelId", "invoiceNumber",
- "invoiceAmount", "ticketRegistrationId", "contractAmount", "receiptPaymentAmount",
- "unReceiptPaymentAmount", "type", "paymentMethod", "approvalStatus", "templateName"
- };
- for (String field : dtoFields) {
- if (source.containsKey(field)) {
- target.put(field, source.get(field));
- }
- }
- }
-
- private void putDtoFieldIfPresent(Map<String, Object> source, Map<String, Object> target, String dtoField, String... aliases) {
- if (target.containsKey(dtoField) && target.get(dtoField) != null) {
- return;
- }
- for (String alias : aliases) {
- Object value = source.get(alias);
- if (value != null && StringUtils.hasText(String.valueOf(value))) {
- target.put(dtoField, value);
- return;
- }
- }
- }
-
- private List<Map<String, Object>> toMapList(Object value) {
- if (value == null) {
- return List.of();
- }
- return objectMapper.convertValue(value, new TypeReference<List<Map<String, Object>>>() {
- });
- }
-
- private String stringValue(Map<String, Object> map, String... keys) {
- for (String key : keys) {
- Object value = map.get(key);
- if (value != null && StringUtils.hasText(String.valueOf(value))) {
- return String.valueOf(value);
- }
- }
- return null;
- }
-
- private Long longValue(Map<String, Object> map, String... keys) {
- String value = stringValue(map, keys);
- if (!StringUtils.hasText(value)) {
- return null;
- }
- try {
- return Long.parseLong(value.trim());
- } catch (NumberFormatException ignored) {
- return null;
- }
- }
-
- private BigDecimal decimalValue(Object value) {
- if (value == null) {
- return null;
- }
- if (value instanceof BigDecimal decimal) {
- return decimal;
- }
- if (value instanceof Number number) {
- return new BigDecimal(String.valueOf(number));
- }
- String text = String.valueOf(value)
- .replace(",", "")
- .replace("锛�", "")
- .replace("鍏�", "")
- .replace("锟�", "")
- .trim();
- if (!StringUtils.hasText(text)) {
- return null;
- }
- try {
- return new BigDecimal(text);
- } catch (NumberFormatException ignored) {
- return null;
- }
- }
-
- private String toCustomerMessage(Exception ex) {
- String message = ex.getMessage();
- if (!StringUtils.hasText(message)) {
- return "澶勭悊澶辫触锛岃妫�鏌ョ‘璁ゆ暟鎹悗閲嶈瘯";
- }
- if (message.contains("tax_inclusive_unit_price")) {
- return "澶勭悊澶辫触锛氫骇鍝佹槑缁嗙己灏戝惈绋庡崟浠凤紝璇疯ˉ鍏呭悗鍐嶇‘璁�";
- }
- if (message.contains("tax_inclusive_total_price")) {
- return "澶勭悊澶辫触锛氫骇鍝佹槑缁嗙己灏戝惈绋庢�讳环锛岃琛ュ厖鍚庡啀纭";
- }
- if (message.contains("entryDate")) {
- return "澶勭悊澶辫触锛氬綍鍏ユ棩鏈熸牸寮忎笉姝g‘锛岃浣跨敤 yyyy-MM-dd锛屼緥濡� 2026-04-30";
- }
- if (message.contains("supplier")) {
- return "澶勭悊澶辫触锛氫緵搴斿晢淇℃伅涓嶅畬鏁达紝璇风‘璁や緵搴斿晢鍚嶇О鎴栦緵搴斿晢ID";
- }
- if (message.contains("SQL") || message.contains("java.") || message.contains("Exception")) {
- return "澶勭悊澶辫触锛氱‘璁ゆ暟鎹笉瀹屾暣鎴栨牸寮忎笉姝g‘锛岃妫�鏌ュ繀濉瓧娈靛悗閲嶈瘯";
- }
- return "澶勭悊澶辫触锛�" + message;
- }
-
- private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) {
- if (dto.getSupplierId() != null) {
- return null;
- }
- if (!StringUtils.hasText(dto.getSupplierName())) {
- return AjaxResult.error("渚涘簲鍟咺D涓嶈兘涓虹┖锛涙湭璇嗗埆鍒颁緵搴斿晢鍚嶇О锛屾棤娉曡嚜鍔ㄥ尮閰嶄緵搴斿晢ID");
- }
-
- SupplierManage supplier = supplierManageMapper.selectOne(new LambdaQueryWrapper<SupplierManage>()
- .eq(SupplierManage::getSupplierName, dto.getSupplierName().trim())
- .last("limit 1"));
- if (supplier == null) {
- return AjaxResult.error("鏈壘鍒颁緵搴斿晢锛�" + dto.getSupplierName() + "锛岃鍏堢淮鎶や緵搴斿晢鎴栨墜鍔ㄩ�夋嫨渚涘簲鍟咺D");
- }
- dto.setSupplierId(supplier.getId());
- return null;
- }
-
- private AjaxResult processPaymentRegistration(Map<String, Object> payload) {
- Object recordsValue = payload.get("records");
- List<PaymentRegistration> records;
- if (recordsValue == null) {
- records = Collections.singletonList(objectMapper.convertValue(payload, PaymentRegistration.class));
- } else {
- records = objectMapper.convertValue(recordsValue, new TypeReference<List<PaymentRegistration>>() {
- });
- }
- int result = paymentRegistrationService.insertPaymentRegistration(records);
- return AjaxResult.success("浠樻鐧昏宸插鐞�", result);
- }
-
- private AjaxResult processPurchaseReturnOrder(Map<String, Object> payload) {
- PurchaseReturnOrderDto dto = objectMapper.convertValue(payload, PurchaseReturnOrderDto.class);
- Boolean result = purchaseReturnOrdersService.add(dto);
- return AjaxResult.success("閲囪喘閫�璐у崟宸插鐞�", result);
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java b/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
deleted file mode 100644
index 0ea436d..0000000
--- a/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
+++ /dev/null
@@ -1,212 +0,0 @@
-package com.ruoyi.ai.service.impl;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.ruoyi.ai.dto.AiChatMessageDto;
-import com.ruoyi.ai.dto.AiChatSessionDto;
-import com.ruoyi.ai.mapper.AiChatSessionMapper;
-import com.ruoyi.ai.pojo.AiChatSession;
-import com.ruoyi.ai.service.AiChatSessionService;
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.security.LoginUser;
-import dev.langchain4j.data.message.AiMessage;
-import dev.langchain4j.data.message.ChatMessage;
-import dev.langchain4j.data.message.SystemMessage;
-import dev.langchain4j.data.message.ToolExecutionResultMessage;
-import dev.langchain4j.data.message.UserMessage;
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Service;
-
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-@Service
-@RequiredArgsConstructor
-public class AiChatSessionServiceImpl extends ServiceImpl<AiChatSessionMapper, AiChatSession> implements AiChatSessionService {
-
- private static final int TITLE_MAX_LENGTH = 40;
- private static final int LAST_MESSAGE_MAX_LENGTH = 300;
-
- private final MongoChatMemoryStore mongoChatMemoryStore;
-
- @Override
- public void touchSession(String memoryId, LoginUser loginUser, String userMessage) {
- if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) {
- return;
- }
- Date now = new Date();
- AiChatSession session = getSession(memoryId, loginUser.getUserId(), loginUser.getTenantId());
- if (session == null) {
- AiChatSession add = new AiChatSession();
- add.setMemoryId(memoryId);
- add.setUserId(loginUser.getUserId());
- add.setTenantId(loginUser.getTenantId());
- add.setTitle(buildTitle(userMessage));
- add.setLastMessage(trimText(userMessage, LAST_MESSAGE_MAX_LENGTH));
- add.setMessageCount(0);
- add.setLastChatTime(now);
- add.setCreateTime(now);
- add.setUpdateTime(now);
- save(add);
- return;
- }
-
- AiChatSession update = new AiChatSession();
- update.setId(session.getId());
- if (!StringUtils.hasText(session.getTitle())) {
- update.setTitle(buildTitle(userMessage));
- }
- update.setLastMessage(trimText(userMessage, LAST_MESSAGE_MAX_LENGTH));
- update.setLastChatTime(now);
- update.setUpdateTime(now);
- updateById(update);
- }
-
- @Override
- public void refreshSessionStats(String memoryId, LoginUser loginUser) {
- if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) {
- return;
- }
- AiChatSession session = getSession(memoryId, loginUser.getUserId(), loginUser.getTenantId());
- if (session == null) {
- return;
- }
- List<ChatMessage> messages = mongoChatMemoryStore.getMessages(memoryId);
- AiChatSession update = new AiChatSession();
- update.setId(session.getId());
- update.setMessageCount(messages.size());
- update.setLastMessage(trimText(lastMessageText(messages), LAST_MESSAGE_MAX_LENGTH));
- update.setLastChatTime(new Date());
- update.setUpdateTime(new Date());
- updateById(update);
- }
-
- @Override
- public List<AiChatSessionDto> listCurrentUserSessions(LoginUser loginUser) {
- if (loginUser == null || loginUser.getUserId() == null) {
- return new LinkedList<>();
- }
- LambdaQueryWrapper<AiChatSession> queryWrapper = new LambdaQueryWrapper<AiChatSession>()
- .eq(AiChatSession::getUserId, loginUser.getUserId())
- .orderByDesc(AiChatSession::getLastChatTime);
- applyTenantCondition(queryWrapper, loginUser.getTenantId());
- return list(queryWrapper).stream().map(item -> {
- AiChatSessionDto dto = new AiChatSessionDto();
- dto.setMemoryId(item.getMemoryId());
- dto.setTitle(item.getTitle());
- dto.setLastMessage(item.getLastMessage());
- dto.setMessageCount(item.getMessageCount());
- dto.setLastChatTime(item.getLastChatTime());
- return dto;
- }).collect(Collectors.toList());
- }
-
- @Override
- public List<AiChatMessageDto> listCurrentUserMessages(String memoryId, LoginUser loginUser) {
- if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) {
- return new LinkedList<>();
- }
- AiChatSession session = getSession(memoryId, loginUser.getUserId(), loginUser.getTenantId());
- if (session == null) {
- return new LinkedList<>();
- }
- List<ChatMessage> messages = mongoChatMemoryStore.getMessages(memoryId);
- List<AiChatMessageDto> messageDtos = messages.stream().map(this::convertMessage).collect(Collectors.toList());
- List<List<String>> analyzeFilePathGroups = mongoChatMemoryStore.getAnalyzeFilePathGroups(memoryId);
- attachAnalyzeFilePaths(messageDtos, analyzeFilePathGroups);
- return messageDtos;
- }
-
- @Override
- public boolean deleteCurrentUserSession(String memoryId, LoginUser loginUser) {
- if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) {
- return false;
- }
- LambdaQueryWrapper<AiChatSession> queryWrapper = new LambdaQueryWrapper<AiChatSession>()
- .eq(AiChatSession::getMemoryId, memoryId)
- .eq(AiChatSession::getUserId, loginUser.getUserId());
- applyTenantCondition(queryWrapper, loginUser.getTenantId());
- boolean removed = remove(queryWrapper);
- mongoChatMemoryStore.deleteMessages(memoryId);
- return removed;
- }
-
- private AiChatSession getSession(String memoryId, Long userId, Long tenantId) {
- LambdaQueryWrapper<AiChatSession> queryWrapper = new LambdaQueryWrapper<AiChatSession>()
- .eq(AiChatSession::getMemoryId, memoryId)
- .eq(AiChatSession::getUserId, userId);
- applyTenantCondition(queryWrapper, tenantId);
- return getOne(queryWrapper, false);
- }
-
- private void applyTenantCondition(LambdaQueryWrapper<AiChatSession> queryWrapper, Long tenantId) {
- if (tenantId == null) {
- queryWrapper.isNull(AiChatSession::getTenantId);
- return;
- }
- queryWrapper.eq(AiChatSession::getTenantId, tenantId);
- }
-
- private String buildTitle(String userMessage) {
- if (!StringUtils.hasText(userMessage)) {
- return "鏂颁細璇�";
- }
- return trimText(userMessage, TITLE_MAX_LENGTH);
- }
-
- private String trimText(String text, int maxLength) {
- if (!StringUtils.hasText(text)) {
- return "";
- }
- String source = text.trim();
- if (source.length() <= maxLength) {
- return source;
- }
- return source.substring(0, maxLength) + "...";
- }
-
- private String lastMessageText(List<ChatMessage> messages) {
- if (messages == null || messages.isEmpty()) {
- return "";
- }
- ChatMessage lastMessage = messages.get(messages.size() - 1);
- return convertMessage(lastMessage).getContent();
- }
-
- private AiChatMessageDto convertMessage(ChatMessage message) {
- if (message instanceof UserMessage userMessage) {
- return new AiChatMessageDto("user", userMessage.singleText());
- }
- if (message instanceof AiMessage aiMessage) {
- return new AiChatMessageDto("assistant", aiMessage.text());
- }
- if (message instanceof SystemMessage systemMessage) {
- return new AiChatMessageDto("system", systemMessage.text());
- }
- if (message instanceof ToolExecutionResultMessage toolExecutionResultMessage) {
- return new AiChatMessageDto("tool", toolExecutionResultMessage.text());
- }
- return new AiChatMessageDto("unknown", String.valueOf(message));
- }
-
- private void attachAnalyzeFilePaths(List<AiChatMessageDto> messages,
- List<List<String>> analyzeFilePathGroups) {
- if (StringUtils.isEmpty(messages) || StringUtils.isEmpty(analyzeFilePathGroups)) {
- return;
- }
- int analyzeIndex = 0;
- for (AiChatMessageDto message : messages) {
- if (!"user".equals(message.getRole()) || analyzeIndex >= analyzeFilePathGroups.size()) {
- continue;
- }
- List<String> filePaths = analyzeFilePathGroups.get(analyzeIndex);
- if (!StringUtils.isEmpty(filePaths)) {
- message.setFilePaths(filePaths);
- }
- analyzeIndex++;
- }
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java b/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
deleted file mode 100644
index e6bcadb..0000000
--- a/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package com.ruoyi.ai.store;
-
-import com.ruoyi.ai.mongodbBean.ChatMessages;
-import dev.langchain4j.data.message.ChatMessage;
-import dev.langchain4j.data.message.ChatMessageDeserializer;
-import dev.langchain4j.data.message.ChatMessageSerializer;
-import dev.langchain4j.store.memory.chat.ChatMemoryStore;
-import lombok.RequiredArgsConstructor;
-import org.springframework.data.mongodb.core.MongoTemplate;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.data.mongodb.core.query.Update;
-import org.springframework.stereotype.Component;
-import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
-
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
-
-@Component
-@RequiredArgsConstructor
-public class MongoChatMemoryStore implements ChatMemoryStore {
-
- private final MongoTemplate mongoTemplate;
-
- @Override
- public List<ChatMessage> getMessages(Object memoryId) {
- ChatMessages chatMessages = findChatMessages(memoryId);
- if (chatMessages == null || chatMessages.getContent() == null) {
- return new LinkedList<>();
- }
- return ChatMessageDeserializer.messagesFromJson(chatMessages.getContent());
- }
-
- @Override
- public void updateMessages(Object memoryId, List<ChatMessage> messages) {
- String memoryIdValue = memoryIdString(memoryId);
- Query query = Query.query(Criteria.where("memoryId").is(memoryIdValue));
- Update update = new Update();
- update.set("memoryId", memoryIdValue);
- update.set("content", ChatMessageSerializer.messagesToJson(messages));
- update.set("updateTime", new Date());
- update.setOnInsert("createTime", new Date());
- mongoTemplate.upsert(query, update, ChatMessages.class);
- }
-
- @Override
- public void deleteMessages(Object memoryId) {
- Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId)));
- mongoTemplate.remove(query, ChatMessages.class);
- }
-
- public void appendMessages(Object memoryId, List<ChatMessage> appendList) {
- List<ChatMessage> messages = new LinkedList<>(getMessages(memoryId));
- messages.addAll(appendList);
- updateMessages(memoryId, messages);
- }
-
- public void appendAnalyzeFileContext(Object memoryId, String userQuestion, List<String> filePaths) {
- String memoryIdValue = memoryIdString(memoryId);
- if (!StringUtils.hasText(memoryIdValue)) {
- return;
- }
- List<String> validFilePaths = new LinkedList<>();
- if (!CollectionUtils.isEmpty(filePaths)) {
- for (String filePath : filePaths) {
- if (StringUtils.hasText(filePath)) {
- validFilePaths.add(filePath);
- }
- }
- }
- if (!StringUtils.hasText(userQuestion) && validFilePaths.isEmpty()) {
- return;
- }
- Query query = Query.query(Criteria.where("memoryId").is(memoryIdValue));
- Update update = new Update();
- update.set("memoryId", memoryIdValue);
- update.set("updateTime", new Date());
- update.setOnInsert("createTime", new Date());
- if (StringUtils.hasText(userQuestion)) {
- update.push("analyzeUserQuestions", userQuestion);
- }
- if (!validFilePaths.isEmpty()) {
- update.push("analyzeFilePaths").each(validFilePaths.toArray());
- update.push("analyzeFilePathGroups", validFilePaths);
- }
- mongoTemplate.upsert(query, update, ChatMessages.class);
- }
-
- public List<String> getAnalyzeUserQuestions(Object memoryId) {
- ChatMessages chatMessages = findChatMessages(memoryId);
- if (chatMessages == null || CollectionUtils.isEmpty(chatMessages.getAnalyzeUserQuestions())) {
- return new LinkedList<>();
- }
- return new LinkedList<>(chatMessages.getAnalyzeUserQuestions());
- }
-
- public List<List<String>> getAnalyzeFilePathGroups(Object memoryId) {
- ChatMessages chatMessages = findChatMessages(memoryId);
- if (chatMessages == null) {
- return new LinkedList<>();
- }
- if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePathGroups())) {
- if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePaths())) {
- return new LinkedList<>();
- }
- List<List<String>> fallback = new LinkedList<>();
- fallback.add(new LinkedList<>(chatMessages.getAnalyzeFilePaths()));
- return fallback;
- }
- List<List<String>> groups = new LinkedList<>();
- for (List<String> group : chatMessages.getAnalyzeFilePathGroups()) {
- if (CollectionUtils.isEmpty(group)) {
- groups.add(new LinkedList<>());
- } else {
- groups.add(new LinkedList<>(group));
- }
- }
- return groups;
- }
-
- private String memoryIdString(Object memoryId) {
- return memoryId == null ? "" : memoryId.toString();
- }
-
- private ChatMessages findChatMessages(Object memoryId) {
- Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId)));
- return mongoTemplate.findOne(query, ChatMessages.class);
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java b/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
deleted file mode 100644
index fec0c21..0000000
--- a/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
+++ /dev/null
@@ -1,1009 +0,0 @@
-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銆乸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,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡� 浠婂ぉ銆佹湰鏈堛�佽繎30澶╋紝鍙笉浼�", required = false) String timeRange) {
-
- LoginUser loginUser = currentLoginUser(memoryId);
- Long userId = loginUser.getUserId();
- Integer statusCode = parseStatus(status);
- String normalizedScope = normalizeScope(scope);
- boolean hasDateFilter = StringUtils.hasText(startDate) || StringUtils.hasText(endDate) || StringUtils.hasText(timeRange);
- DateRange dateRange = hasDateFilter ? resolveDateRange(startDate, endDate, timeRange) : null;
-
- 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);
- }
- }
-
- if (dateRange != null) {
- wrapper.ge(ApproveProcess::getCreateTime, dateRange.start().atStartOfDay())
- .lt(ApproveProcess::getCreateTime, dateRange.end().plusDays(1).atStartOfDay());
- }
-
- 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,
- "timeRange", dateRange == null ? "all" : dateRange.label(),
- "startDate", dateRange == null ? "" : dateRange.start().toString(),
- "endDate", dateRange == null ? "" : dateRange.end().toString()
- ),
- 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 = "鎵ц瀹℃壒鍔ㄤ綔锛宎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);
- }
- 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) {
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/tools/ManufacturingAgentTools.java b/src/main/java/com/ruoyi/ai/tools/ManufacturingAgentTools.java
deleted file mode 100644
index 1ff96c1..0000000
--- a/src/main/java/com/ruoyi/ai/tools/ManufacturingAgentTools.java
+++ /dev/null
@@ -1,1035 +0,0 @@
-package com.ruoyi.ai.tools;
-
-import com.alibaba.fastjson2.JSON;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
-import com.ruoyi.ai.context.AiSessionUserContext;
-import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.device.mapper.DeviceDefectRecordMapper;
-import com.ruoyi.device.mapper.DeviceLedgerMapper;
-import com.ruoyi.device.mapper.DeviceRepairMapper;
-import com.ruoyi.device.pojo.DeviceDefectRecord;
-import com.ruoyi.device.pojo.DeviceLedger;
-import com.ruoyi.device.pojo.DeviceRepair;
-import com.ruoyi.framework.security.LoginUser;
-import com.ruoyi.procurementrecord.mapper.ProcurementExceptionRecordMapper;
-import com.ruoyi.procurementrecord.pojo.ProcurementExceptionRecord;
-import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
-import com.ruoyi.production.mapper.ProductionOrderMapper;
-import com.ruoyi.production.mapper.ProductionPlanMapper;
-import com.ruoyi.production.mapper.ProductionProductMainMapper;
-import com.ruoyi.production.pojo.ProductionOperationTask;
-import com.ruoyi.production.pojo.ProductionOrder;
-import com.ruoyi.production.pojo.ProductionPlan;
-import com.ruoyi.production.pojo.ProductionProductMain;
-import com.ruoyi.quality.mapper.QualityInspectMapper;
-import com.ruoyi.quality.mapper.QualityUnqualifiedMapper;
-import com.ruoyi.quality.pojo.QualityInspect;
-import com.ruoyi.quality.pojo.QualityUnqualified;
-import com.ruoyi.stock.mapper.StockInventoryMapper;
-import com.ruoyi.stock.pojo.StockInventory;
-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.util.StringUtils;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-@Component
-public class ManufacturingAgentTools {
-
- private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
- private static final int DEFAULT_LIMIT = 10;
- private static final int MAX_LIMIT = 30;
- private static final int DEVICE_REPAIR_STATUS_PENDING = 0;
-
- private final ProductionPlanMapper productionPlanMapper;
- private final ProductionOrderMapper productionOrderMapper;
- private final ProductionOperationTaskMapper productionOperationTaskMapper;
- private final ProductionProductMainMapper productionProductMainMapper;
- private final DeviceLedgerMapper deviceLedgerMapper;
- private final DeviceRepairMapper deviceRepairMapper;
- private final DeviceDefectRecordMapper deviceDefectRecordMapper;
- private final QualityInspectMapper qualityInspectMapper;
- private final QualityUnqualifiedMapper qualityUnqualifiedMapper;
- private final StockInventoryMapper stockInventoryMapper;
- private final ProcurementExceptionRecordMapper procurementExceptionRecordMapper;
- private final AiSessionUserContext aiSessionUserContext;
-
- public ManufacturingAgentTools(ProductionPlanMapper productionPlanMapper,
- ProductionOrderMapper productionOrderMapper,
- ProductionOperationTaskMapper productionOperationTaskMapper,
- ProductionProductMainMapper productionProductMainMapper,
- DeviceLedgerMapper deviceLedgerMapper,
- DeviceRepairMapper deviceRepairMapper,
- DeviceDefectRecordMapper deviceDefectRecordMapper,
- QualityInspectMapper qualityInspectMapper,
- QualityUnqualifiedMapper qualityUnqualifiedMapper,
- StockInventoryMapper stockInventoryMapper,
- ProcurementExceptionRecordMapper procurementExceptionRecordMapper,
- AiSessionUserContext aiSessionUserContext) {
- this.productionPlanMapper = productionPlanMapper;
- this.productionOrderMapper = productionOrderMapper;
- this.productionOperationTaskMapper = productionOperationTaskMapper;
- this.productionProductMainMapper = productionProductMainMapper;
- this.deviceLedgerMapper = deviceLedgerMapper;
- this.deviceRepairMapper = deviceRepairMapper;
- this.deviceDefectRecordMapper = deviceDefectRecordMapper;
- this.qualityInspectMapper = qualityInspectMapper;
- this.qualityUnqualifiedMapper = qualityUnqualifiedMapper;
- this.stockInventoryMapper = stockInventoryMapper;
- this.procurementExceptionRecordMapper = procurementExceptionRecordMapper;
- this.aiSessionUserContext = aiSessionUserContext;
- }
-
- @Tool(name = "鏌ヨ鍒堕�犱笟鍔″煙鏁版嵁", value = "鎸変笟鍔″煙鏌ヨ鐢熶骇鐜板満銆佽鍒掋�佸伐鍗曘�佽澶囥�佽川閲忋�佺墿鏂欍�佸紓甯稿鐞嗙浉鍏虫暟鎹��")
- public String queryDomain(@ToolMemoryId String memoryId,
- @P(value = "涓氬姟鍩燂紝site/plan/workorder/device/quality/material/exception") String domain,
- @P(value = "鍏抽敭瀛楋紝鍙笉浼�", required = false) String keyword,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡備粖骞淬�佹湰鏈堛�佽繎30澶�", required = false) String timeRange) {
- LoginUser loginUser = currentLoginUser(memoryId);
- int finalLimit = normalizeLimit(limit);
- DateRange range = resolveDateRange(startDate, endDate, timeRange);
- boolean hasTimeConstraint = hasTimeConstraint(startDate, endDate, timeRange);
- String normalizedDomain = normalizeDomain(domain);
-
- return switch (normalizedDomain) {
- case "site" -> siteSnapshot(loginUser, range);
- case "plan" -> listProductionPlans(loginUser, keyword, finalLimit, range);
- case "workorder" -> listWorkOrders(loginUser, keyword, finalLimit, range);
- case "device" -> isRepairIntent(keyword, timeRange)
- ? listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint)
- : listDevices(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit);
- case "repair" -> listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint);
- case "quality" -> listQualityIssues(loginUser, keyword, finalLimit, range);
- case "material" -> listMaterialInventory(loginUser, keyword, finalLimit);
- case "exception" -> listExceptions(loginUser, keyword, finalLimit, range);
- default -> jsonResponse(false, "manufacturing_query", "涓嶆敮鎸佺殑涓氬姟鍩�: " + safe(domain), Map.of(), Map.of(), Map.of());
- };
- }
-
- @Tool(name = "鍒堕�犻璀︾湅鏉�", value = "璁$畻璁″垝銆佸伐鍗曘�佽澶囥�佽川閲忋�佺墿鏂欍�佸紓甯稿鐞嗙殑棰勮淇℃伅銆�")
- public String getWarningBoard(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡備粖澶┿�佹湰鍛ㄣ�佹湰鏈堛�佽繎30澶�", required = false) String timeRange) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, timeRange);
- LocalDate today = LocalDate.now();
-
- long overduePlanCount = countOverduePlans(loginUser, today);
- long overdueWorkOrderCount = countOverdueWorkOrders(loginUser, today);
- long pendingRepairCount = countPendingRepairs(loginUser);
- long qualityOpenCount = countOpenQualityIssues(loginUser, range);
- long lowStockCount = countLowStock(loginUser);
- long exceptionCount = countExceptionRecords(loginUser, range);
-
- List<Map<String, Object>> warningItems = new ArrayList<>();
- if (overduePlanCount > 0) {
- warningItems.add(warningItem("high", "璁″垝閫炬湡", overduePlanCount, "鏈夌敓浜ц鍒掕秴杩囬渶姹傛棩鏈熶粛鏈畬鎴�"));
- }
- if (overdueWorkOrderCount > 0) {
- warningItems.add(warningItem("high", "宸ュ崟閫炬湡", overdueWorkOrderCount, "鏈夊伐鍗曡鍒掔粨鏉熸棩鏈熷凡杩囦粛鏈畬宸�"));
- }
- if (pendingRepairCount > 0) {
- warningItems.add(warningItem("medium", "璁惧寰呯淮淇�", pendingRepairCount, "瀛樺湪寰呯淮淇�/缁翠慨涓殑璁惧"));
- }
- if (qualityOpenCount > 0) {
- warningItems.add(warningItem("high", "璐ㄩ噺鏈棴鐜�", qualityOpenCount, "瀛樺湪鏈鐞嗗畬鎴愮殑涓嶅悎鏍艰褰�"));
- }
- if (lowStockCount > 0) {
- warningItems.add(warningItem("medium", "鐗╂枡浣庡簱瀛�", lowStockCount, "搴撳瓨鏁伴噺浣庝簬鎴栫瓑浜庨璀﹂槇鍊�"));
- }
- if (exceptionCount > 0) {
- warningItems.add(warningItem("medium", "寮傚父璁板綍", exceptionCount, "鏃堕棿鑼冨洿鍐呭瓨鍦ㄥ紓甯稿鐞嗚褰�"));
- }
-
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("timeRange", range.label());
- summary.put("startDate", range.start().toString());
- summary.put("endDate", range.end().toString());
- summary.put("warningCount", warningItems.size());
- summary.put("overduePlanCount", overduePlanCount);
- summary.put("overdueWorkOrderCount", overdueWorkOrderCount);
- summary.put("pendingRepairCount", pendingRepairCount);
- summary.put("qualityOpenCount", qualityOpenCount);
- summary.put("lowStockCount", lowStockCount);
- summary.put("exceptionCount", exceptionCount);
-
- return jsonResponse(true, "manufacturing_warning", "宸茶繑鍥炲埗閫犻璀︾湅鏉裤��", summary,
- Map.of("items", warningItems), Map.of());
- }
-
- @Tool(name = "鍒堕�犵粡钀ュ垎鏋�", value = "鎸夋椂闂磋寖鍥磋緭鍑哄埗閫犲叧閿寚鏍囷紝鏀寔鏌ャ�侀棶銆佸垎鏋愬満鏅��")
- public String analyzeFactory(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡傛湰鏈堛�佽繎30澶�", required = false) String timeRange) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, timeRange);
-
- long planTotal = countPlans(loginUser, range);
- long planCompleted = countPlansByStatus(loginUser, range, 2);
- long workOrderTotal = countWorkOrders(loginUser, range);
- long workOrderCompleted = countWorkOrdersByStatus(loginUser, range, 2);
- long workOrderInProgress = countWorkOrdersByStatus(loginUser, range, 1);
-
- long outputCount = countOutputs(loginUser, range);
- long deviceTotal = countDevices(loginUser);
- long pendingRepairCount = countPendingRepairs(loginUser);
- long qualityInspectTotal = countQualityInspect(loginUser, range);
- long qualityNgCount = countOpenQualityIssues(loginUser, range);
- long materialSkuCount = countInventorySku(loginUser);
- long lowStockCount = countLowStock(loginUser);
- long exceptionCount = countExceptionRecords(loginUser, range);
-
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("timeRange", range.label());
- summary.put("startDate", range.start().toString());
- summary.put("endDate", range.end().toString());
- summary.put("planTotal", planTotal);
- summary.put("planCompleted", planCompleted);
- summary.put("planCompletionRate", toRate(planCompleted, planTotal));
- summary.put("workOrderTotal", workOrderTotal);
- summary.put("workOrderCompleted", workOrderCompleted);
- summary.put("workOrderInProgress", workOrderInProgress);
- summary.put("workOrderCompletionRate", toRate(workOrderCompleted, workOrderTotal));
- summary.put("outputCount", outputCount);
- summary.put("deviceTotal", deviceTotal);
- summary.put("pendingRepairCount", pendingRepairCount);
- summary.put("qualityInspectTotal", qualityInspectTotal);
- summary.put("qualityNgCount", qualityNgCount);
- summary.put("qualityIssueRate", toRate(qualityNgCount, qualityInspectTotal));
- summary.put("materialSkuCount", materialSkuCount);
- summary.put("lowStockCount", lowStockCount);
- summary.put("exceptionCount", exceptionCount);
-
- List<Map<String, Object>> coreMetrics = List.of(
- metric("璁″垝瀹屾垚鐜�", toRate(planCompleted, planTotal)),
- metric("宸ュ崟瀹屾垚鐜�", toRate(workOrderCompleted, workOrderTotal)),
- metric("璐ㄩ噺寮傚父鐜�", toRate(qualityNgCount, qualityInspectTotal)),
- metric("浣庡簱瀛樺崰姣�", toRate(lowStockCount, materialSkuCount))
- );
-
- Map<String, Object> charts = new LinkedHashMap<>();
- charts.put("domainBarOption", buildDomainBarOption(summary));
- charts.put("qualityPieOption", buildQualityPieOption(qualityInspectTotal, qualityNgCount));
-
- return jsonResponse(true, "manufacturing_analysis", "宸茶繑鍥炲埗閫犲垎鏋愮粨鏋溿��", summary,
- Map.of("coreMetrics", coreMetrics), charts);
- }
-
- @Tool(name = "鐢熸垚鍒堕�犲姙鐞嗗缓璁�", value = "鏍规嵁鐢ㄦ埛闂杈撳嚭鍙墽琛岀殑鍔炵悊鍔ㄤ綔寤鸿锛屽寘鎷洰鏍囦笟鍔℃帴鍙c�佸繀濉瓧娈靛拰绀轰緥銆�")
- public String planActions(@ToolMemoryId String memoryId,
- @P("鐢ㄦ埛璇夋眰鍘熸枃") String userQuery) {
- LoginUser loginUser = currentLoginUser(memoryId);
- List<Map<String, Object>> actionCards = new ArrayList<>();
-
- if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "宸ュ崟", "娲惧伐", "浣滀笟")) {
- actionCards.add(actionCard(
- "workorder_assign",
- "宸ュ崟娲惧伐",
- "POST",
- "/productionOperationTask/assign",
- List.of("id", "userIds"),
- Map.of("id", 10001, "userIds", "12,13"),
- "灏嗗伐鍗曞垎閰嶇粰鎸囧畾浜哄憳锛岄�傜敤浜庣幇鍦鸿皟搴︺��"));
- }
- if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "璁惧", "缁翠慨", "鏁呴殰")) {
- actionCards.add(actionCard(
- "device_repair_create",
- "鍒涘缓璁惧缁翠慨鍗�",
- "POST",
- "/device/repair",
- List.of("deviceLedgerId", "deviceName", "repairName", "remark"),
- Map.of("deviceLedgerId", 1001, "deviceName", "绌哄帇鏈篈-01", "repairName", "寮犱笁", "remark", "寮傚搷骞朵即闅忔俯鍗�"),
- "鏂板缓缁翠慨鍗曪紝杩涘叆璁惧寮傚父澶勭悊闂幆銆�"));
- }
- if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "璐ㄩ噺", "涓嶅悎鏍�", "闂幆")) {
- actionCards.add(actionCard(
- "quality_unqualified_deal",
- "澶勭悊涓嶅悎鏍煎崟",
- "POST",
- "/quality/qualityUnqualified/deal",
- List.of("id", "dealResult", "dealName"),
- Map.of("id", 3001, "dealResult", "杩斿伐鍚庡妫�", "dealName", "鏉庡洓"),
- "瀵逛笉鍚堟牸璁板綍鎵ц澶勭疆骞堕棴鐜��"));
- }
- if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "鐗╂枡", "搴撳瓨", "琛ユ枡")) {
- actionCards.add(actionCard(
- "material_inbound",
- "琛ュ厖搴撳瓨",
- "POST",
- "/stockInventory/addstockInventory",
- List.of("productModelId", "batchNo", "qualitity"),
- Map.of("productModelId", 5001, "batchNo", "B2026051601", "qualitity", 120),
- "褰撲綆搴撳瓨棰勮瑙﹀彂鏃讹紝澧炲姞搴撳瓨鏁伴噺銆�"));
- }
- if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "寮傚父", "閲囪喘寮傚父", "鏉ユ枡寮傚父")) {
- actionCards.add(actionCard(
- "procurement_exception_add",
- "鐧昏寮傚父璁板綍",
- "POST",
- "/procurementExceptionRecord/add",
- List.of("purchaseLedgerId", "exceptionReason", "exceptionNum"),
- Map.of("purchaseLedgerId", 888, "exceptionReason", "鍒版枡鐭己", "exceptionNum", 24),
- "鐧昏閲囪喘/鏉ユ枡寮傚父锛屼究浜庡悗缁拷韪拰鍒嗘瀽銆�"));
- }
-
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("actionCount", actionCards.size());
- summary.put("userId", loginUser.getUserId());
- summary.put("tenantId", loginUser.getTenantId());
-
- return jsonResponse(true, "manufacturing_action_plan", "宸茬敓鎴愬姙鐞嗗缓璁紝璇峰墠绔紩瀵肩敤鎴风‘璁ゅ悗璋冪敤鐩爣涓氬姟鎺ュ彛銆�",
- summary, Map.of("actionCards", actionCards), Map.of());
- }
-
- private String siteSnapshot(LoginUser loginUser, DateRange range) {
- long planTotal = countPlans(loginUser, range);
- long workOrderTotal = countWorkOrders(loginUser, range);
- long outputCount = countOutputs(loginUser, range);
- long deviceTotal = countDevices(loginUser);
- long pendingRepairCount = countPendingRepairs(loginUser);
- long qualityOpenCount = countOpenQualityIssues(loginUser, range);
- long lowStockCount = countLowStock(loginUser);
- long exceptionCount = countExceptionRecords(loginUser, range);
-
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("timeRange", range.label());
- summary.put("startDate", range.start().toString());
- summary.put("endDate", range.end().toString());
- summary.put("planTotal", planTotal);
- summary.put("workOrderTotal", workOrderTotal);
- summary.put("outputCount", outputCount);
- summary.put("deviceTotal", deviceTotal);
- summary.put("pendingRepairCount", pendingRepairCount);
- summary.put("qualityOpenCount", qualityOpenCount);
- summary.put("lowStockCount", lowStockCount);
- summary.put("exceptionCount", exceptionCount);
-
- return jsonResponse(true, "manufacturing_site_snapshot", "宸茶繑鍥炵敓浜х幇鍦烘瑙堛��", summary, Map.of(), Map.of());
- }
-
- private String listProductionPlans(LoginUser loginUser, String keyword, int limit, DateRange range) {
- LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
- wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end());
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(ProductionPlan::getMpsNo, keyword)
- .or().like(ProductionPlan::getRemark, keyword)
- .or().like(ProductionPlan::getSource, keyword));
- }
- wrapper.orderByDesc(ProductionPlan::getRequiredDate, ProductionPlan::getId).last("limit " + limit);
-
- List<Map<String, Object>> items = defaultList(productionPlanMapper.selectList(wrapper)).stream()
- .map(this::toPlanItem)
- .collect(Collectors.toList());
- return jsonResponse(true, "manufacturing_plan_list", "宸茶繑鍥炵敓浜ц鍒掑垪琛ㄣ��",
- rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
- }
-
- private String listWorkOrders(LoginUser loginUser, String keyword, int limit, DateRange range) {
- LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
- wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start())
- .le(ProductionOperationTask::getPlanEndTime, range.end());
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(ProductionOperationTask::getWorkOrderNo, keyword)
- .or().like(ProductionOperationTask::getUserIds, keyword));
- }
- wrapper.orderByDesc(ProductionOperationTask::getPlanEndTime, ProductionOperationTask::getId)
- .last("limit " + limit);
-
- List<Map<String, Object>> items = defaultList(productionOperationTaskMapper.selectList(wrapper)).stream()
- .map(this::toWorkOrderItem)
- .collect(Collectors.toList());
- return jsonResponse(true, "manufacturing_workorder_list", "宸茶繑鍥炲伐鍗曞垪琛ㄣ��",
- rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
- }
-
- private String listDevices(LoginUser loginUser, String keyword, int limit) {
- LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword)
- .or().like(DeviceLedger::getDeviceModel, keyword)
- .or().like(DeviceLedger::getDeviceBrand, keyword));
- }
- wrapper.orderByDesc(DeviceLedger::getId).last("limit " + limit);
-
- Map<Long, Long> pendingRepairMap = pendingRepairCountByDevice(loginUser);
- List<Map<String, Object>> items = defaultList(deviceLedgerMapper.selectList(wrapper)).stream()
- .map(item -> toDeviceItem(item, pendingRepairMap.getOrDefault(item.getId(), 0L)))
- .collect(Collectors.toList());
- return jsonResponse(true, "manufacturing_device_list", "宸茶繑鍥炶澶囧垪琛ㄣ��",
- Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of());
- }
-
- private String listDeviceRepairs(LoginUser loginUser, String keyword, int limit, DateRange range, boolean hasTimeConstraint) {
- LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
- Long currentDeptId = loginUser.getCurrentDeptId();
- if (currentDeptId != null) {
- wrapper.and(w -> w.eq(DeviceRepair::getDeptId, currentDeptId).or().isNull(DeviceRepair::getDeptId));
- }
- if (hasTimeConstraint) {
- wrapper.ge(DeviceRepair::getCreateTime, range.start().atStartOfDay())
- .lt(DeviceRepair::getCreateTime, range.end().plusDays(1).atStartOfDay());
- }
- if (StringUtils.hasText(keyword)) {
- List<Long> matchedDeviceIds = findDeviceLedgerIdsByKeyword(loginUser, keyword);
- wrapper.and(w -> {
- w.like(DeviceRepair::getDeviceName, keyword)
- .or().like(DeviceRepair::getDeviceModel, keyword)
- .or().like(DeviceRepair::getRemark, keyword)
- .or().like(DeviceRepair::getRepairName, keyword)
- .or().like(DeviceRepair::getMaintenanceName, keyword);
- if (!matchedDeviceIds.isEmpty()) {
- w.or().in(DeviceRepair::getDeviceLedgerId, matchedDeviceIds);
- }
- });
- }
- wrapper.orderByDesc(DeviceRepair::getCreateTime, DeviceRepair::getId).last("limit " + limit);
-
- List<Map<String, Object>> items = defaultList(deviceRepairMapper.selectList(wrapper)).stream()
- .map(this::toDeviceRepairItem)
- .collect(Collectors.toList());
- return jsonResponse(true, "manufacturing_device_repair_list", "宸茶繑鍥炶澶囩淮淇褰曘��",
- rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
- }
-
- private String listQualityIssues(LoginUser loginUser, String keyword, int limit, DateRange range) {
- LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId);
- wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start()))
- .lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end()));
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(QualityUnqualified::getProductName, keyword)
- .or().like(QualityUnqualified::getDefectivePhenomena, keyword)
- .or().like(QualityUnqualified::getDealResult, keyword));
- }
- wrapper.orderByDesc(QualityUnqualified::getCheckTime, QualityUnqualified::getId).last("limit " + limit);
-
- List<Map<String, Object>> items = defaultList(qualityUnqualifiedMapper.selectList(wrapper)).stream()
- .map(this::toQualityItem)
- .collect(Collectors.toList());
- return jsonResponse(true, "manufacturing_quality_list", "宸茶繑鍥炶川閲忓紓甯稿垪琛ㄣ��",
- rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
- }
-
- private String listMaterialInventory(LoginUser loginUser, String keyword, int limit) {
- LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(StockInventory::getBatchNo, keyword)
- .or().like(StockInventory::getProductModelId, keyword));
- }
- wrapper.orderByDesc(StockInventory::getId).last("limit " + limit);
-
- List<Map<String, Object>> items = defaultList(stockInventoryMapper.selectList(wrapper)).stream()
- .map(this::toMaterialItem)
- .collect(Collectors.toList());
- return jsonResponse(true, "manufacturing_material_list", "宸茶繑鍥炵墿鏂欏簱瀛樺垪琛ㄣ��",
- Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of());
- }
-
- private String listExceptions(LoginUser loginUser, String keyword, int limit, DateRange range) {
- LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId);
- wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay())
- .lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay());
- if (StringUtils.hasText(keyword)) {
- wrapper.like(ProcurementExceptionRecord::getExceptionReason, keyword);
- }
- wrapper.orderByDesc(ProcurementExceptionRecord::getCreateTime, ProcurementExceptionRecord::getId)
- .last("limit " + limit);
-
- List<Map<String, Object>> items = defaultList(procurementExceptionRecordMapper.selectList(wrapper)).stream()
- .map(this::toExceptionItem)
- .collect(Collectors.toList());
- return jsonResponse(true, "manufacturing_exception_list", "宸茶繑鍥炲紓甯稿鐞嗗垪琛ㄣ��",
- rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
- }
-
- private long countPlans(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
- wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end());
- return productionPlanMapper.selectCount(wrapper);
- }
-
- private long countPlansByStatus(LoginUser loginUser, DateRange range, int status) {
- LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
- wrapper.ge(ProductionPlan::getRequiredDate, range.start())
- .le(ProductionPlan::getRequiredDate, range.end())
- .eq(ProductionPlan::getStatus, status);
- return productionPlanMapper.selectCount(wrapper);
- }
-
- private long countWorkOrders(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
- wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start())
- .le(ProductionOperationTask::getPlanEndTime, range.end());
- return productionOperationTaskMapper.selectCount(wrapper);
- }
-
- private long countWorkOrdersByStatus(LoginUser loginUser, DateRange range, int status) {
- LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
- wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start())
- .le(ProductionOperationTask::getPlanEndTime, range.end())
- .eq(ProductionOperationTask::getStatus, status);
- return productionOperationTaskMapper.selectCount(wrapper);
- }
-
- private long countOutputs(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<ProductionProductMain> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId);
- wrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay())
- .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay());
- return productionProductMainMapper.selectCount(wrapper);
- }
-
- private long countDevices(LoginUser loginUser) {
- LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
- return deviceLedgerMapper.selectCount(wrapper);
- }
-
- private long countPendingRepairs(LoginUser loginUser) {
- LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId);
- wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING);
- return deviceRepairMapper.selectCount(wrapper);
- }
-
- private long countQualityInspect(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<QualityInspect> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), QualityInspect::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityInspect::getDeptId);
- wrapper.ge(QualityInspect::getCheckTime, toDate(range.start()))
- .lt(QualityInspect::getCheckTime, toExclusiveEndDate(range.end()));
- return qualityInspectMapper.selectCount(wrapper);
- }
-
- private long countOpenQualityIssues(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId);
- wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start()))
- .lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end()))
- .ne(QualityUnqualified::getInspectState, 2);
- return qualityUnqualifiedMapper.selectCount(wrapper);
- }
-
- private long countInventorySku(LoginUser loginUser) {
- LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
- return stockInventoryMapper.selectCount(wrapper);
- }
-
- private long countLowStock(LoginUser loginUser) {
- LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
- wrapper.isNotNull(StockInventory::getWarnNum);
- List<StockInventory> stocks = defaultList(stockInventoryMapper.selectList(wrapper));
- return stocks.stream()
- .filter(this::isLowStock)
- .count();
- }
-
- private long countExceptionRecords(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId);
- wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay())
- .lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay());
- return procurementExceptionRecordMapper.selectCount(wrapper);
- }
-
- private long countOverduePlans(LoginUser loginUser, LocalDate today) {
- LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
- wrapper.lt(ProductionPlan::getRequiredDate, today).ne(ProductionPlan::getStatus, 2);
- return productionPlanMapper.selectCount(wrapper);
- }
-
- private long countOverdueWorkOrders(LoginUser loginUser, LocalDate today) {
- LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
- wrapper.lt(ProductionOperationTask::getPlanEndTime, today).ne(ProductionOperationTask::getStatus, 2);
- return productionOperationTaskMapper.selectCount(wrapper);
- }
-
- private Map<Long, Long> pendingRepairCountByDevice(LoginUser loginUser) {
- LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId);
- wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING);
- return defaultList(deviceRepairMapper.selectList(wrapper)).stream()
- .filter(item -> item.getDeviceLedgerId() != null)
- .collect(Collectors.groupingBy(DeviceRepair::getDeviceLedgerId, Collectors.counting()));
- }
-
- private Map<String, Object> toPlanItem(ProductionPlan item) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("mpsNo", safe(item.getMpsNo()));
- map.put("requiredDate", formatDate(item.getRequiredDate()));
- map.put("promisedDeliveryDate", formatDate(item.getPromisedDeliveryDate()));
- map.put("qtyRequired", item.getQtyRequired());
- map.put("quantityIssued", item.getQuantityIssued());
- map.put("status", item.getStatus());
- map.put("source", safe(item.getSource()));
- map.put("remark", safe(item.getRemark()));
- return map;
- }
-
- private Map<String, Object> toWorkOrderItem(ProductionOperationTask item) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("workOrderNo", safe(item.getWorkOrderNo()));
- map.put("productionOrderId", item.getProductionOrderId());
- map.put("planStartTime", formatDate(item.getPlanStartTime()));
- map.put("planEndTime", formatDate(item.getPlanEndTime()));
- map.put("actualStartTime", formatDate(item.getActualStartTime()));
- map.put("actualEndTime", formatDate(item.getActualEndTime()));
- map.put("planQuantity", item.getPlanQuantity());
- map.put("completeQuantity", item.getCompleteQuantity());
- map.put("status", item.getStatus());
- map.put("userIds", safe(item.getUserIds()));
- return map;
- }
-
- private Map<String, Object> toDeviceItem(DeviceLedger item, long pendingRepairCount) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("deviceName", safe(item.getDeviceName()));
- map.put("deviceModel", safe(item.getDeviceModel()));
- map.put("deviceBrand", safe(item.getDeviceBrand()));
- map.put("status", safe(item.getStatus()));
- map.put("storageLocation", safe(item.getStorageLocation()));
- map.put("supplierName", safe(item.getSupplierName()));
- map.put("pendingRepairCount", pendingRepairCount);
- return map;
- }
-
- private Map<String, Object> toDeviceRepairItem(DeviceRepair item) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("deviceLedgerId", item.getDeviceLedgerId());
- map.put("deviceName", safe(item.getDeviceName()));
- map.put("deviceModel", safe(item.getDeviceModel()));
- map.put("repairTime", formatDate(item.getRepairTime()));
- map.put("repairName", safe(item.getRepairName()));
- map.put("maintenanceName", safe(item.getMaintenanceName()));
- map.put("maintenanceTime", formatDateTime(item.getMaintenanceTime()));
- map.put("maintenanceResult", safe(item.getMaintenanceResult()));
- map.put("acceptanceName", safe(item.getAcceptanceName()));
- map.put("acceptanceTime", formatDateTime(item.getAcceptanceTime()));
- map.put("status", item.getStatus());
- map.put("remark", safe(item.getRemark()));
- map.put("createTime", formatDateTime(item.getCreateTime()));
- return map;
- }
-
- private Map<String, Object> toQualityItem(QualityUnqualified item) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("checkTime", formatDate(item.getCheckTime()));
- map.put("inspectState", item.getInspectState());
- map.put("productId", item.getProductId());
- map.put("productName", safe(item.getProductName()));
- map.put("model", safe(item.getModel()));
- map.put("quantity", item.getQuantity());
- map.put("defectivePhenomena", safe(item.getDefectivePhenomena()));
- map.put("dealResult", safe(item.getDealResult()));
- map.put("dealName", safe(item.getDealName()));
- map.put("dealTime", formatDate(item.getDealTime()));
- return map;
- }
-
- private Map<String, Object> toMaterialItem(StockInventory item) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("productModelId", item.getProductModelId());
- map.put("batchNo", safe(item.getBatchNo()));
- map.put("qualitity", item.getQualitity());
- map.put("lockedQuantity", item.getLockedQuantity());
- map.put("warnNum", item.getWarnNum());
- map.put("lowStock", isLowStock(item));
- map.put("remark", safe(item.getRemark()));
- return map;
- }
-
- private Map<String, Object> toExceptionItem(ProcurementExceptionRecord item) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("purchaseLedgerId", item.getPurchaseLedgerId());
- map.put("exceptionReason", safe(item.getExceptionReason()));
- map.put("exceptionNum", item.getExceptionNum());
- map.put("createTime", formatDateTime(item.getCreateTime()));
- return map;
- }
-
- private boolean isLowStock(StockInventory item) {
- BigDecimal quantity = item.getQualitity();
- BigDecimal warnNum = item.getWarnNum();
- if (quantity == null || warnNum == null) {
- return false;
- }
- return quantity.compareTo(warnNum) <= 0;
- }
-
- private Map<String, Object> warningItem(String level, String title, long count, String detail) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("level", level);
- map.put("title", title);
- map.put("count", count);
- map.put("detail", detail);
- return map;
- }
-
- private Map<String, Object> actionCard(String code,
- String name,
- String method,
- String targetApi,
- List<String> requiredFields,
- Map<String, Object> examplePayload,
- String description) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("code", code);
- map.put("name", name);
- map.put("method", method);
- map.put("targetApi", targetApi);
- map.put("requiredFields", requiredFields);
- map.put("examplePayload", examplePayload);
- map.put("description", description);
- return map;
- }
-
- private Map<String, Object> metric(String label, String value) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("label", label);
- map.put("value", value);
- return map;
- }
-
- private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) {
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("timeRange", range.label());
- summary.put("startDate", range.start().toString());
- summary.put("endDate", range.end().toString());
- summary.put("count", count);
- summary.put("keyword", safe(keyword));
- return summary;
- }
-
- private Map<String, Object> buildDomainBarOption(Map<String, Object> summary) {
- List<String> xData = List.of("璁″垝", "宸ュ崟", "璁惧", "璐ㄩ噺", "鐗╂枡", "寮傚父");
- List<Number> yData = List.of(
- numberValue(summary.get("planTotal")),
- numberValue(summary.get("workOrderTotal")),
- numberValue(summary.get("deviceTotal")),
- numberValue(summary.get("qualityNgCount")),
- numberValue(summary.get("lowStockCount")),
- numberValue(summary.get("exceptionCount"))
- );
- 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", xData));
- option.put("yAxis", Map.of("type", "value"));
- option.put("series", List.of(Map.of("name", "鏁伴噺", "type", "bar", "data", yData)));
- return option;
- }
-
- private Map<String, Object> buildQualityPieOption(long inspectTotal, long ngCount) {
- long passCount = Math.max(inspectTotal - ngCount, 0);
- List<Map<String, Object>> data = List.of(
- Map.of("name", "涓嶅悎鏍�", "value", ngCount),
- Map.of("name", "闈炰笉鍚堟牸", "value", passCount)
- );
- Map<String, Object> option = new LinkedHashMap<>();
- option.put("title", Map.of("text", "璐ㄩ噺缁撴灉鍒嗗竷", "left", "center"));
- option.put("tooltip", Map.of("trigger", "item"));
- option.put("series", List.of(Map.of("name", "璐ㄩ噺", "type", "pie", "radius", "60%", "data", data)));
- return option;
- }
-
- private int numberValue(Object value) {
- if (value instanceof Number number) {
- return number.intValue();
- }
- return 0;
- }
-
- private String toRate(long numerator, long denominator) {
- if (denominator <= 0) {
- return "0.00%";
- }
- BigDecimal rate = new BigDecimal(numerator)
- .multiply(new BigDecimal("100"))
- .divide(new BigDecimal(denominator), 2, RoundingMode.HALF_UP);
- return rate.toPlainString() + "%";
- }
-
- private String normalizeDomain(String domain) {
- if (!StringUtils.hasText(domain)) {
- return "";
- }
- String value = domain.trim().toLowerCase();
- return switch (value) {
- case "鐢熶骇鐜板満", "site", "factory", "workshop" -> "site";
- case "璁″垝", "plan", "schedule" -> "plan";
- case "宸ュ崟", "workorder", "work_order", "task" -> "workorder";
- case "璁惧", "device", "equipment" -> "device";
- case "缁翠慨", "repair", "maintenance" -> "repair";
- case "璐ㄩ噺", "quality", "qc" -> "quality";
- case "鐗╂枡", "material", "inventory", "stock" -> "material";
- case "寮傚父", "exception", "abnormal" -> "exception";
- default -> value;
- };
- }
-
- private boolean isRepairIntent(String keyword, String userQuery) {
- String query = safe(userQuery);
- return containsAny(safe(keyword), "缁翠慨", "鎶ヤ慨", "妫�淇�", "缁存姢")
- || containsAny(query, "缁翠慨", "鎶ヤ慨", "妫�淇�", "缁存姢");
- }
-
- private String normalizeDeviceQueryKeyword(String keyword, String userQuery) {
- String source = StringUtils.hasText(keyword) ? keyword : userQuery;
- if (!StringUtils.hasText(source)) {
- return null;
- }
- String cleaned = source
- .replace("鏌ヨ", "")
- .replace("鏌ョ湅", "")
- .replace("甯垜", "")
- .replace("璇�", "")
- .replace("鏌�", "")
- .replace("璁惧", "")
- .replace("缁翠慨璁板綍", "")
- .replace("缁翠慨鎯呭喌", "")
- .replace("鎶ヤ慨璁板綍", "")
- .replace("鎶ヤ慨鎯呭喌", "")
- .replace("缁翠慨", "")
- .replace("鎶ヤ慨", "")
- .replace("鎯呭喌", "")
- .replace("璁板綍", "")
- .replace("淇℃伅", "")
- .replace("鐨�", "")
- .replace("涓�涓�", "")
- .trim();
- return cleaned.length() >= 2 ? cleaned : null;
- }
-
- private List<Long> findDeviceLedgerIdsByKeyword(LoginUser loginUser, String keyword) {
- if (!StringUtils.hasText(keyword)) {
- return List.of();
- }
- LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
- Long currentDeptId = loginUser.getCurrentDeptId();
- if (currentDeptId != null) {
- wrapper.and(w -> w.eq(DeviceLedger::getDeptId, currentDeptId).or().isNull(DeviceLedger::getDeptId));
- }
- wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword)
- .or().like(DeviceLedger::getDeviceModel, keyword)
- .or().like(DeviceLedger::getDeviceBrand, keyword));
- wrapper.orderByDesc(DeviceLedger::getId).last("limit 200");
- return defaultList(deviceLedgerMapper.selectList(wrapper)).stream()
- .map(DeviceLedger::getId)
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
- }
-
- private boolean hasTimeConstraint(String startDate, String endDate, String userQuery) {
- if (StringUtils.hasText(startDate) || StringUtils.hasText(endDate)) {
- return true;
- }
- if (!StringUtils.hasText(userQuery)) {
- return false;
- }
- String text = userQuery.trim();
- return containsAny(text, "浠婂ぉ", "鏄ㄥぉ", "鏈懆", "涓婂懆", "鏈湀", "涓婃湀", "浠婂勾", "鍘诲勾", "杩�", "鏈�杩�");
- }
-
- private DateRange resolveDateRange(String startDate, String endDate, String timeRange) {
- LocalDate today = LocalDate.now();
- LocalDate start = parseLocalDate(startDate);
- LocalDate end = parseLocalDate(endDate);
- if (start != null || end != null) {
- LocalDate s = start != null ? start : end;
- LocalDate e = end != null ? end : start;
- if (s.isAfter(e)) {
- LocalDate temp = s;
- s = e;
- e = temp;
- }
- return new DateRange(s, e, s + "鑷�" + e);
- }
- if (!StringUtils.hasText(timeRange)) {
- return new DateRange(today.minusDays(29), today, "杩�30澶�");
- }
- String text = timeRange.trim();
- if (text.contains("浠婂ぉ")) {
- return new DateRange(today, today, "浠婂ぉ");
- }
- if (text.contains("鏈懆")) {
- LocalDate startOfWeek = today.minusDays(today.getDayOfWeek().getValue() - 1L);
- return new DateRange(startOfWeek, today, "鏈懆");
- }
- if (text.contains("鏈湀")) {
- return new DateRange(today.withDayOfMonth(1), today, "鏈湀");
- }
- if (text.contains("鏈勾") || text.contains("浠婂勾")) {
- return new DateRange(today.withDayOfYear(1), today, "浠婂勾");
- }
- if (text.contains("鍘诲勾")) {
- LocalDate firstDay = today.minusYears(1).withDayOfYear(1);
- LocalDate lastDay = today.minusYears(1).withMonth(12).withDayOfMonth(31);
- return new DateRange(firstDay, lastDay, "鍘诲勾");
- }
- if (text.contains("涓婃湀")) {
- LocalDate startOfLastMonth = today.minusMonths(1).withDayOfMonth(1);
- return new DateRange(startOfLastMonth, startOfLastMonth.withDayOfMonth(startOfLastMonth.lengthOfMonth()), "涓婃湀");
- }
- java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(杩憒鏈�杩�)(\\d+)(澶﹟鍛▅涓湀|鏈坾骞�)").matcher(text);
- if (matcher.find()) {
- int amount = Integer.parseInt(matcher.group(2));
- String unit = matcher.group(3);
- LocalDate relativeStart = 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(29);
- };
- return new DateRange(relativeStart, today, "杩�" + amount + unit);
- }
- return new DateRange(today.minusDays(29), today, "杩�30澶�");
- }
-
- private LocalDate parseLocalDate(String text) {
- if (!StringUtils.hasText(text)) {
- return null;
- }
- return LocalDate.parse(text.trim(), DATE_FMT);
- }
-
- private Date toDate(LocalDate date) {
- return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
- }
-
- private Date toExclusiveEndDate(LocalDate date) {
- return Date.from(date.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
- }
-
- private String formatDate(LocalDate date) {
- return date == null ? "" : DATE_FMT.format(date);
- }
-
- private String formatDate(Date date) {
- if (date == null) {
- return "";
- }
- return DATE_FMT.format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
- }
-
- private String formatDateTime(LocalDateTime time) {
- if (time == null) {
- return "";
- }
- return time.truncatedTo(ChronoUnit.SECONDS).toString().replace('T', ' ');
- }
-
- private int normalizeLimit(Integer limit) {
- if (limit == null || limit <= 0) {
- return DEFAULT_LIMIT;
- }
- return Math.min(limit, MAX_LIMIT);
- }
-
- private boolean containsAny(String text, String... values) {
- for (String value : values) {
- if (text.contains(value)) {
- return true;
- }
- }
- return false;
- }
-
- private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, SFunction<T, Long> field) {
- if (tenantId != null) {
- wrapper.eq(field, tenantId);
- }
- }
-
- private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, SFunction<T, Long> field) {
- if (deptId != null) {
- wrapper.eq(field, deptId);
- }
- }
-
- private LoginUser currentLoginUser(String memoryId) {
- LoginUser loginUser = aiSessionUserContext.get(memoryId);
- if (loginUser != null) {
- return loginUser;
- }
- return SecurityUtils.getLoginUser();
- }
-
- private String safe(Object value) {
- return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ');
- }
-
- private <T> List<T> defaultList(List<T> list) {
- return list == null ? List.of() : list;
- }
-
- 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 record DateRange(LocalDate start, LocalDate end, String label) {
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java b/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
deleted file mode 100644
index 17b6868..0000000
--- a/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
+++ /dev/null
@@ -1,643 +0,0 @@
-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.common.utils.SecurityUtils;
-import com.ruoyi.framework.security.LoginUser;
-import com.ruoyi.purchase.mapper.InvoicePurchaseMapper;
-import com.ruoyi.purchase.mapper.PaymentRegistrationMapper;
-import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
-import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
-import com.ruoyi.purchase.pojo.InvoicePurchase;
-import com.ruoyi.purchase.pojo.PaymentRegistration;
-import com.ruoyi.purchase.pojo.PurchaseLedger;
-import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
-import com.ruoyi.procurementrecord.mapper.InboundManagementMapper;
-import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
-import com.ruoyi.procurementrecord.pojo.InboundManagement;
-import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
-import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
-import com.ruoyi.sales.pojo.SalesLedgerProduct;
-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.util.StringUtils;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Comparator;
-import java.util.stream.Collectors;
-
-@Component
-public class PurchaseAgentTools {
-
- private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
- private static final int DEFAULT_LIMIT = 10;
- private static final int MAX_LIMIT = 30;
-
- private final PurchaseLedgerMapper purchaseLedgerMapper;
- private final PaymentRegistrationMapper paymentRegistrationMapper;
- private final InvoicePurchaseMapper invoicePurchaseMapper;
- private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper;
- private final SalesLedgerProductMapper salesLedgerProductMapper;
- private final ProcurementRecordMapper procurementRecordMapper;
- private final InboundManagementMapper inboundManagementMapper;
- private final AiSessionUserContext aiSessionUserContext;
-
- public PurchaseAgentTools(PurchaseLedgerMapper purchaseLedgerMapper,
- PaymentRegistrationMapper paymentRegistrationMapper,
- InvoicePurchaseMapper invoicePurchaseMapper,
- PurchaseReturnOrdersMapper purchaseReturnOrdersMapper,
- SalesLedgerProductMapper salesLedgerProductMapper,
- ProcurementRecordMapper procurementRecordMapper,
- InboundManagementMapper inboundManagementMapper,
- AiSessionUserContext aiSessionUserContext) {
- this.purchaseLedgerMapper = purchaseLedgerMapper;
- this.paymentRegistrationMapper = paymentRegistrationMapper;
- this.invoicePurchaseMapper = invoicePurchaseMapper;
- this.purchaseReturnOrdersMapper = purchaseReturnOrdersMapper;
- this.salesLedgerProductMapper = salesLedgerProductMapper;
- this.procurementRecordMapper = procurementRecordMapper;
- this.inboundManagementMapper = inboundManagementMapper;
- this.aiSessionUserContext = aiSessionUserContext;
- }
-
- @Tool(name = "鏌ヨ閲囪喘鍙拌处鍒楄〃", value = "鎸夊叧閿瓧鍜屾椂闂磋寖鍥存煡璇㈤噰璐彴璐︼紝鏀寔杩斿洖鏈�杩慛鏉�")
- public String listPurchaseLedgers(@ToolMemoryId String memoryId,
- @P(value = "鍏抽敭瀛楋紝鍙尮閰嶉噰璐悎鍚屽彿/渚涘簲鍟�/椤圭洰鍚�", required = false) String keyword,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- LocalDate start = parseLocalDate(startDate);
- LocalDate end = parseLocalDate(endDate);
- int finalLimit = normalizeLimit(limit);
-
- LambdaQueryWrapper<PurchaseLedger> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), PurchaseLedger::getTenantId);
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(PurchaseLedger::getPurchaseContractNumber, keyword)
- .or().like(PurchaseLedger::getSupplierName, keyword)
- .or().like(PurchaseLedger::getProjectName, keyword));
- }
- if (start != null) {
- wrapper.ge(PurchaseLedger::getEntryDate, toDate(start));
- }
- if (end != null) {
- wrapper.lt(PurchaseLedger::getEntryDate, toExclusiveEndDate(end));
- }
- wrapper.orderByDesc(PurchaseLedger::getEntryDate, PurchaseLedger::getId).last("limit " + finalLimit);
-
- List<PurchaseLedger> rows = defaultList(purchaseLedgerMapper.selectList(wrapper));
- List<Map<String, Object>> items = rows.stream().map(this::toLedgerItem).collect(Collectors.toList());
- return jsonResponse(true, "purchase_ledger_list", "宸茶繑鍥為噰璐彴璐﹀垪琛�",
- Map.of("count", items.size(), "limit", finalLimit, "keyword", safe(keyword)),
- Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ閲囪喘鍙拌处璇︽儏", value = "鎸夐噰璐彴璐D鏌ヨ璇︽儏")
- public String getPurchaseLedgerDetail(@ToolMemoryId String memoryId, @P("閲囪喘鍙拌处ID") Long ledgerId) {
- if (ledgerId == null) {
- return jsonResponse(false, "purchase_ledger_detail", "閲囪喘鍙拌处ID涓嶈兘涓虹┖", Map.of(), Map.of(), Map.of());
- }
- LoginUser loginUser = currentLoginUser(memoryId);
- PurchaseLedger ledger = purchaseLedgerMapper.selectById(ledgerId);
- if (ledger == null || !tenantMatched(ledger.getTenantId(), loginUser.getTenantId())) {
- return jsonResponse(false, "purchase_ledger_detail", "鏈壘鍒拌閲囪喘鍙拌处鎴栨棤鏉冮檺璁块棶", Map.of("ledgerId", ledgerId), Map.of(), Map.of());
- }
- return jsonResponse(true, "purchase_ledger_detail", "宸茶繑鍥為噰璐彴璐﹁鎯�",
- Map.of("ledgerId", ledgerId),
- Map.of("detail", toLedgerItem(ledger)),
- Map.of());
- }
-
- @Tool(name = "缁熻閲囪喘鏁版嵁", value = "缁熻鏃堕棿鑼冨洿鍐呴噰璐悎鍚屾暟銆佸悎鍚岄噾棰濄�佷粯娆鹃噾棰濄�佸彂绁ㄩ噾棰濄�侀��璐ч噾棰�")
- public String getPurchaseStats(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡備粖骞淬�佹湰鏈堛�佽繎30澶�", required = false) String timeRange) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, timeRange);
-
- List<PurchaseLedger> ledgers = queryLedgers(loginUser, range);
- List<PaymentRegistration> payments = queryPayments(loginUser, range);
- List<InvoicePurchase> invoices = queryInvoices(loginUser, range);
- List<PurchaseReturnOrders> returns = queryReturns(loginUser, range);
-
- BigDecimal contractAmount = ledgers.stream()
- .map(PurchaseLedger::getContractAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal paymentAmount = payments.stream()
- .map(PaymentRegistration::getCurrentPaymentAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal invoiceAmount = invoices.stream()
- .map(InvoicePurchase::getInvoiceAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal returnAmount = returns.stream()
- .map(PurchaseReturnOrders::getTotalAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
-
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("timeRange", range.label());
- summary.put("startDate", range.start().toString());
- summary.put("endDate", range.end().toString());
- summary.put("ledgerCount", ledgers.size());
- summary.put("paymentCount", payments.size());
- summary.put("invoiceCount", invoices.size());
- summary.put("returnCount", returns.size());
- summary.put("contractAmount", contractAmount);
- summary.put("paymentAmount", paymentAmount);
- summary.put("invoiceAmount", invoiceAmount);
- summary.put("returnAmount", returnAmount);
-
- return jsonResponse(true, "purchase_stats", "宸茶繑鍥為噰璐粺璁℃暟鎹�", summary, Map.of(), Map.of());
- }
-
- @Tool(name = "閲囪喘鐗╂枡閲戦鎺掕", value = "鎸夋椂闂磋寖鍥寸粺璁¢噰璐墿鏂欓噾棰濇帓琛岋紝鍙洖绛旀湰鏈堥噰璐噾棰濇帓鍚嶉潬鍓嶇殑鐗╂枡銆�")
- public String rankPurchaseMaterials(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡傛湰鏈堛�佽繎7澶┿�佽繎30澶�", required = false) String timeRange,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, timeRange);
- List<Long> ledgerIds = queryLedgers(loginUser, range).stream()
- .map(PurchaseLedger::getId)
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
- if (ledgerIds.isEmpty()) {
- return jsonResponse(true, "purchase_material_rank", "褰撳墠鏃堕棿鑼冨洿鍐呮病鏈夐噰璐墿鏂欐暟鎹��",
- rangeSummary(range, 0), Map.of("items", List.of()), Map.of());
- }
-
- List<SalesLedgerProduct> products = defaultList(salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>()
- .eq(SalesLedgerProduct::getType, 2)
- .in(SalesLedgerProduct::getSalesLedgerId, ledgerIds)));
-
- Map<String, MaterialRankItem> grouped = new LinkedHashMap<>();
- for (SalesLedgerProduct product : products) {
- String name = safe(product.getProductCategory());
- String model = safe(product.getSpecificationModel());
- String key = name + "|" + model;
- MaterialRankItem item = grouped.computeIfAbsent(key, ignored -> new MaterialRankItem(name, model, safe(product.getUnit())));
- item.quantity = item.quantity.add(defaultDecimal(product.getQuantity()));
- item.amount = item.amount.add(defaultDecimal(product.getTaxInclusiveTotalPrice()));
- }
-
- List<Map<String, Object>> items = grouped.values().stream()
- .sorted(Comparator.comparing((MaterialRankItem item) -> item.amount).reversed())
- .limit(normalizeLimit(limit))
- .map(MaterialRankItem::toMap)
- .collect(Collectors.toList());
-
- return jsonResponse(true, "purchase_material_rank", "宸茶繑鍥為噰璐墿鏂欓噾棰濇帓琛屻��",
- rangeSummary(range, items.size()), Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ鏈叆搴撻噰璐鍗�", value = "鏌ヨ閲囪喘璁㈠崟涓嬩粛鏈夊緟鍏ュ簱鏁伴噺鐨勭墿鏂欐槑缁嗐��")
- public String listUnstockedPurchaseOrders(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鍏抽敭瀛楋紝鍙尮閰嶉噰璐悎鍚屽彿/渚涘簲鍟�/鐗╂枡", required = false) String keyword,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, null);
- List<PurchaseLedger> ledgers = queryLedgers(loginUser, range).stream()
- .filter(ledger -> matchLedgerKeyword(ledger, keyword))
- .collect(Collectors.toList());
- Map<Long, PurchaseLedger> ledgerMap = ledgers.stream()
- .filter(ledger -> ledger.getId() != null)
- .collect(Collectors.toMap(PurchaseLedger::getId, ledger -> ledger, (a, b) -> a, LinkedHashMap::new));
- if (ledgerMap.isEmpty()) {
- return jsonResponse(true, "purchase_unstocked_list", "鏈煡璇㈠埌绗﹀悎鏉′欢鐨勯噰璐鍗曘��",
- rangeSummary(range, 0), Map.of("items", List.of()), Map.of());
- }
-
- List<SalesLedgerProduct> products = defaultList(salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>()
- .eq(SalesLedgerProduct::getType, 2)
- .in(SalesLedgerProduct::getSalesLedgerId, ledgerMap.keySet())));
-
- List<Map<String, Object>> items = products.stream()
- .filter(product -> matchProductKeyword(product, keyword))
- .map(product -> toUnstockedItem(product, ledgerMap.get(product.getSalesLedgerId())))
- .filter(Objects::nonNull)
- .limit(normalizeLimit(limit))
- .collect(Collectors.toList());
-
- return jsonResponse(true, "purchase_unstocked_list", "宸茶繑鍥炴湭鍏ュ簱閲囪喘璁㈠崟銆�",
- rangeSummary(range, items.size()), Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ閲囪喘鍒拌揣寮傚父", value = "鏌ヨ鍒拌揣鐘舵�佸紓甯告垨澶囨敞鍖呭惈寮傚父淇℃伅鐨勫埌璐ц褰曘��")
- public String listArrivalExceptions(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡傝繎7澶┿�佹湰鏈�", required = false) String timeRange,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, timeRange);
- LambdaQueryWrapper<InboundManagement> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), InboundManagement::getTenantId);
- wrapper.ge(InboundManagement::getArrivalTime, toDate(range.start()))
- .lt(InboundManagement::getArrivalTime, toExclusiveEndDate(range.end()))
- .and(w -> w.notLike(InboundManagement::getStatus, "姝e父")
- .notLike(InboundManagement::getStatus, "瀹屾垚")
- .notLike(InboundManagement::getStatus, "宸插埌璐�")
- .or().like(InboundManagement::getStatus, "寮傚父")
- .or().like(InboundManagement::getRemark, "寮傚父")
- .or().like(InboundManagement::getRemark, "闂")
- .or().like(InboundManagement::getRemark, "寤惰繜")
- .or().like(InboundManagement::getRemark, "鐭己"));
- wrapper.orderByDesc(InboundManagement::getArrivalTime).last("limit " + normalizeLimit(limit));
-
- List<Map<String, Object>> items = defaultList(inboundManagementMapper.selectList(wrapper)).stream()
- .map(this::toArrivalItem)
- .collect(Collectors.toList());
- return jsonResponse(true, "purchase_arrival_exception_list", "宸茶繑鍥為噰璐埌璐у紓甯歌褰曘��",
- rangeSummary(range, items.size()), Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ寰呬粯娆鹃噰璐崟", value = "鏌ヨ鍚堝悓閲戦澶т簬宸蹭粯娆鹃噾棰濈殑閲囪喘鍗曘��")
- public String listPendingPaymentOrders(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鍏抽敭瀛楋紝鍙尮閰嶉噰璐悎鍚屽彿/渚涘簲鍟�/椤圭洰鍚�", required = false) String keyword,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, null);
- List<Map<String, Object>> items = queryLedgers(loginUser, range).stream()
- .filter(ledger -> matchLedgerKeyword(ledger, keyword))
- .map(ledger -> toPendingPaymentItem(loginUser, ledger))
- .filter(Objects::nonNull)
- .sorted(Comparator.comparing(item -> (BigDecimal) item.get("pendingAmount"), Comparator.reverseOrder()))
- .limit(normalizeLimit(limit))
- .collect(Collectors.toList());
- return jsonResponse(true, "purchase_pending_payment_list", "宸茶繑鍥炲緟浠樻閲囪喘鍗曘��",
- rangeSummary(range, items.size()), Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ閲囪喘閫�璐ф儏鍐�", value = "鎸夋椂闂磋寖鍥存煡璇㈤噰璐��璐у崟鍒楄〃鍜岄��璐ч噾棰濄��")
- public String listPurchaseReturns(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鍏抽敭瀛楋紝鍙尮閰嶉��璐у崟鍙�/澶囨敞", required = false) String keyword,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, null);
- LambdaQueryWrapper<PurchaseReturnOrders> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), PurchaseReturnOrders::getDeptId);
- wrapper.ge(PurchaseReturnOrders::getPreparedAt, range.start())
- .le(PurchaseReturnOrders::getPreparedAt, range.end());
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(PurchaseReturnOrders::getNo, keyword)
- .or().like(PurchaseReturnOrders::getRemark, keyword)
- .or().like(PurchaseReturnOrders::getReturnUserName, keyword));
- }
- wrapper.orderByDesc(PurchaseReturnOrders::getPreparedAt).last("limit " + normalizeLimit(limit));
-
- List<PurchaseReturnOrders> returns = defaultList(purchaseReturnOrdersMapper.selectList(wrapper));
- BigDecimal totalAmount = returns.stream()
- .map(PurchaseReturnOrders::getTotalAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- Map<String, Object> summary = rangeSummary(range, returns.size());
- summary.put("returnAmount", totalAmount);
-
- return jsonResponse(true, "purchase_return_list", "宸茶繑鍥為噰璐��璐ф儏鍐点��",
- summary,
- Map.of("items", returns.stream().map(this::toReturnItem).collect(Collectors.toList())),
- Map.of());
- }
-
- private List<PurchaseLedger> queryLedgers(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<PurchaseLedger> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), PurchaseLedger::getTenantId);
- wrapper.ge(PurchaseLedger::getEntryDate, toDate(range.start()))
- .lt(PurchaseLedger::getEntryDate, toExclusiveEndDate(range.end()));
- return defaultList(purchaseLedgerMapper.selectList(wrapper));
- }
-
- private Map<String, Object> rangeSummary(DateRange range, int count) {
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("timeRange", range.label());
- summary.put("startDate", range.start().toString());
- summary.put("endDate", range.end().toString());
- summary.put("count", count);
- return summary;
- }
-
- private boolean matchLedgerKeyword(PurchaseLedger ledger, String keyword) {
- if (!StringUtils.hasText(keyword)) {
- return true;
- }
- String text = keyword.trim();
- return safe(ledger.getPurchaseContractNumber()).contains(text)
- || safe(ledger.getSupplierName()).contains(text)
- || safe(ledger.getProjectName()).contains(text);
- }
-
- private boolean matchProductKeyword(SalesLedgerProduct product, String keyword) {
- if (!StringUtils.hasText(keyword)) {
- return true;
- }
- String text = keyword.trim();
- return safe(product.getProductCategory()).contains(text)
- || safe(product.getSpecificationModel()).contains(text);
- }
-
- private Map<String, Object> toUnstockedItem(SalesLedgerProduct product, PurchaseLedger ledger) {
- if (product == null || ledger == null || product.getId() == null) {
- return null;
- }
- BigDecimal orderedQuantity = defaultDecimal(product.getQuantity());
- BigDecimal inboundQuantity = sumInboundQuantity(product.getId());
- BigDecimal pendingQuantity = orderedQuantity.subtract(inboundQuantity);
- if (pendingQuantity.compareTo(BigDecimal.ZERO) <= 0) {
- return null;
- }
- Map<String, Object> item = new LinkedHashMap<>();
- item.put("purchaseLedgerId", ledger.getId());
- item.put("purchaseContractNumber", safe(ledger.getPurchaseContractNumber()));
- item.put("supplierName", safe(ledger.getSupplierName()));
- item.put("productCategory", safe(product.getProductCategory()));
- item.put("specificationModel", safe(product.getSpecificationModel()));
- item.put("unit", safe(product.getUnit()));
- item.put("orderedQuantity", orderedQuantity);
- item.put("inboundQuantity", inboundQuantity);
- item.put("pendingInboundQuantity", pendingQuantity);
- item.put("entryDate", formatDate(ledger.getEntryDate()));
- return item;
- }
-
- private BigDecimal sumInboundQuantity(Long salesLedgerProductId) {
- List<ProcurementRecordStorage> records = defaultList(procurementRecordMapper.selectList(new LambdaQueryWrapper<ProcurementRecordStorage>()
- .eq(ProcurementRecordStorage::getType, 1)
- .eq(ProcurementRecordStorage::getSalesLedgerProductId, salesLedgerProductId)));
- return records.stream()
- .map(ProcurementRecordStorage::getInboundNum)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- }
-
- private Map<String, Object> toArrivalItem(InboundManagement item) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("orderNo", safe(item.getOrderNo()));
- map.put("arrivalNo", safe(item.getArrivalNo()));
- map.put("supplierName", safe(item.getSupplierName()));
- map.put("status", safe(item.getStatus()));
- map.put("arrivalTime", formatDate(item.getArrivalTime()));
- map.put("arrivalQuantity", safe(item.getArrivalQuantity()));
- map.put("remark", safe(item.getRemark()));
- return map;
- }
-
- private Map<String, Object> toPendingPaymentItem(LoginUser loginUser, PurchaseLedger ledger) {
- BigDecimal contractAmount = defaultDecimal(ledger.getContractAmount());
- BigDecimal paidAmount = sumPaymentAmount(loginUser, ledger.getId());
- BigDecimal pendingAmount = contractAmount.subtract(paidAmount);
- if (pendingAmount.compareTo(BigDecimal.ZERO) <= 0) {
- return null;
- }
- Map<String, Object> item = toLedgerItem(ledger);
- item.put("paidAmount", paidAmount);
- item.put("pendingAmount", pendingAmount);
- return item;
- }
-
- private BigDecimal sumPaymentAmount(LoginUser loginUser, Long purchaseLedgerId) {
- LambdaQueryWrapper<PaymentRegistration> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), PaymentRegistration::getTenantId);
- wrapper.eq(PaymentRegistration::getPurchaseLedgerId, purchaseLedgerId);
- return defaultList(paymentRegistrationMapper.selectList(wrapper)).stream()
- .map(PaymentRegistration::getCurrentPaymentAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- }
-
- private Map<String, Object> toReturnItem(PurchaseReturnOrders item) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("no", safe(item.getNo()));
- map.put("returnType", item.getReturnType());
- map.put("purchaseLedgerId", item.getPurchaseLedgerId());
- map.put("preparedAt", item.getPreparedAt() == null ? "" : item.getPreparedAt().toString());
- map.put("returnUserName", safe(item.getReturnUserName()));
- map.put("totalAmount", item.getTotalAmount());
- map.put("remark", safe(item.getRemark()));
- return map;
- }
-
- private BigDecimal defaultDecimal(BigDecimal value) {
- return value == null ? BigDecimal.ZERO : value;
- }
-
- private List<PaymentRegistration> queryPayments(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<PaymentRegistration> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), PaymentRegistration::getTenantId);
- wrapper.ge(PaymentRegistration::getPaymentDate, toDate(range.start()))
- .lt(PaymentRegistration::getPaymentDate, toExclusiveEndDate(range.end()));
- return defaultList(paymentRegistrationMapper.selectList(wrapper));
- }
-
- private List<InvoicePurchase> queryInvoices(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<InvoicePurchase> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), InvoicePurchase::getTenantId);
- wrapper.ge(InvoicePurchase::getIssueDate, range.start())
- .le(InvoicePurchase::getIssueDate, range.end());
- return defaultList(invoicePurchaseMapper.selectList(wrapper));
- }
-
- private List<PurchaseReturnOrders> queryReturns(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<PurchaseReturnOrders> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), PurchaseReturnOrders::getDeptId);
- wrapper.ge(PurchaseReturnOrders::getPreparedAt, range.start())
- .le(PurchaseReturnOrders::getPreparedAt, range.end());
- return defaultList(purchaseReturnOrdersMapper.selectList(wrapper));
- }
-
- private Map<String, Object> toLedgerItem(PurchaseLedger item) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("purchaseContractNumber", safe(item.getPurchaseContractNumber()));
- map.put("supplierName", safe(item.getSupplierName()));
- map.put("projectName", safe(item.getProjectName()));
- map.put("entryDate", formatDate(item.getEntryDate()));
- map.put("contractAmount", item.getContractAmount());
- map.put("approvalStatus", item.getApprovalStatus());
- map.put("paymentMethod", safe(item.getPaymentMethod()));
- return map;
- }
-
- private DateRange resolveDateRange(String startDate, String endDate, String timeRange) {
- LocalDate today = LocalDate.now();
- LocalDate start = parseLocalDate(startDate);
- LocalDate end = parseLocalDate(endDate);
- if (start != null || end != null) {
- LocalDate s = start != null ? start : end;
- LocalDate e = end != null ? end : start;
- if (s.isAfter(e)) {
- LocalDate temp = s;
- s = e;
- e = temp;
- }
- return new DateRange(s, e, s + "鑷�" + e);
- }
- if (!StringUtils.hasText(timeRange)) {
- return new DateRange(today.minusDays(29), today, "杩�30澶�");
- }
- String text = timeRange.trim();
- if (text.contains("浠婂勾") || text.contains("鏈勾")) {
- return new DateRange(today.withDayOfYear(1), today, "浠婂勾");
- }
- if (text.contains("鏈湀")) {
- return new DateRange(today.withDayOfMonth(1), today, "鏈湀");
- }
- if (text.contains("涓婃湀")) {
- LocalDate first = today.minusMonths(1).withDayOfMonth(1);
- LocalDate last = first.withDayOfMonth(first.lengthOfMonth());
- return new DateRange(first, last, "涓婃湀");
- }
- if (text.contains("杩戝崐骞�") || text.contains("鏈�杩戝崐骞�")) {
- return new DateRange(today.minusMonths(6).plusDays(1), today, "杩戝崐骞�");
- }
- if (text.contains("杩戝崐涓湀") || text.contains("鏈�杩戝崐涓湀") || text.contains("鍗婁釜鏈�")) {
- return new DateRange(today.minusDays(14), today, "杩戝崐涓湀");
- }
- java.util.regex.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 relativeStart = 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(29);
- };
- return new DateRange(relativeStart, today, "杩�" + amount + unit);
- }
- return new DateRange(today.minusDays(29), today, "杩�30澶�");
- }
-
- private LocalDate parseLocalDate(String text) {
- if (!StringUtils.hasText(text)) {
- return null;
- }
- return LocalDate.parse(text.trim(), DATE_FMT);
- }
-
- private Date toDate(LocalDate localDate) {
- return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
- }
-
- private Date toExclusiveEndDate(LocalDate localDate) {
- return Date.from(localDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
- }
-
- private String formatDate(Date date) {
- if (date == null) {
- return "";
- }
- return DATE_FMT.format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
- }
-
- private boolean tenantMatched(Long dataTenantId, Long userTenantId) {
- if (userTenantId == null) {
- return true;
- }
- return Objects.equals(dataTenantId, userTenantId);
- }
-
- private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, com.baomidou.mybatisplus.core.toolkit.support.SFunction<T, Long> field) {
- if (tenantId != null) {
- wrapper.eq(field, tenantId);
- }
- }
-
- private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, com.baomidou.mybatisplus.core.toolkit.support.SFunction<T, Long> field) {
- if (deptId != null) {
- wrapper.eq(field, deptId);
- }
- }
-
- private LoginUser currentLoginUser(String memoryId) {
- LoginUser loginUser = aiSessionUserContext.get(memoryId);
- if (loginUser != null) {
- return loginUser;
- }
- return SecurityUtils.getLoginUser();
- }
-
- private int normalizeLimit(Integer limit) {
- if (limit == null || limit <= 0) {
- return DEFAULT_LIMIT;
- }
- return Math.min(limit, MAX_LIMIT);
- }
-
- private String safe(Object value) {
- return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ');
- }
-
- private <T> List<T> defaultList(List<T> list) {
- return list == null ? List.of() : list;
- }
-
- 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 record DateRange(LocalDate start, LocalDate end, String label) {
- }
-
- private static class MaterialRankItem {
- private final String productCategory;
- private final String specificationModel;
- private final String unit;
- private BigDecimal quantity = BigDecimal.ZERO;
- private BigDecimal amount = BigDecimal.ZERO;
-
- private MaterialRankItem(String productCategory, String specificationModel, String unit) {
- this.productCategory = productCategory;
- this.specificationModel = specificationModel;
- this.unit = unit;
- }
-
- private Map<String, Object> toMap() {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("productCategory", productCategory);
- map.put("specificationModel", specificationModel);
- map.put("unit", unit);
- map.put("quantity", quantity);
- map.put("amount", amount);
- return map;
- }
- }
-}
diff --git a/src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java b/src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java
deleted file mode 100644
index b56144b..0000000
--- a/src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java
+++ /dev/null
@@ -1,1475 +0,0 @@
-package com.ruoyi.ai.tools;
-
-import com.alibaba.fastjson2.JSON;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
-import com.ruoyi.account.mapper.SalesReceiptReturnMapper;
-import com.ruoyi.account.pojo.SalesReceiptReturn;
-import com.ruoyi.ai.context.AiSessionUserContext;
-import com.ruoyi.basic.dto.CustomerDto;
-import com.ruoyi.basic.mapper.CustomerMapper;
-import com.ruoyi.basic.vo.CustomerVo;
-import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.framework.security.LoginUser;
-import com.ruoyi.sales.dto.InvoiceLedgerDto;
-import com.ruoyi.sales.mapper.InvoiceLedgerMapper;
-import com.ruoyi.sales.mapper.ReceiptPaymentMapper;
-import com.ruoyi.sales.mapper.SalesLedgerMapper;
-import com.ruoyi.sales.mapper.SalesQuotationMapper;
-import com.ruoyi.sales.mapper.ShippingInfoMapper;
-import com.ruoyi.sales.pojo.ReceiptPayment;
-import com.ruoyi.sales.pojo.SalesLedger;
-import com.ruoyi.sales.pojo.SalesQuotation;
-import com.ruoyi.sales.pojo.ShippingInfo;
-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.util.StringUtils;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.YearMonth;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-@Component
-public class SalesAgentTools {
-
- private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
- private static final int DEFAULT_LIMIT = 10;
- private static final int MAX_LIMIT = 30;
- private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
- private static final Pattern RELATIVE_PATTERN = Pattern.compile("(杩憒鏈�杩�)?\\s*(\\d+)\\s*(澶﹟鍛▅涓湀|鏈坾骞�)");
- private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
-
- private final CustomerMapper customerMapper;
- private final SalesLedgerMapper salesLedgerMapper;
- private final SalesQuotationMapper salesQuotationMapper;
- private final ShippingInfoMapper shippingInfoMapper;
- private final ReceiptPaymentMapper receiptPaymentMapper;
- private final InvoiceLedgerMapper invoiceLedgerMapper;
- private final SalesReceiptReturnMapper salesReceiptReturnMapper;
- private final AiSessionUserContext aiSessionUserContext;
-
- public SalesAgentTools(CustomerMapper customerMapper,
- SalesLedgerMapper salesLedgerMapper,
- SalesQuotationMapper salesQuotationMapper,
- ShippingInfoMapper shippingInfoMapper,
- ReceiptPaymentMapper receiptPaymentMapper,
- InvoiceLedgerMapper invoiceLedgerMapper,
- SalesReceiptReturnMapper salesReceiptReturnMapper,
- AiSessionUserContext aiSessionUserContext) {
- this.customerMapper = customerMapper;
- this.salesLedgerMapper = salesLedgerMapper;
- this.salesQuotationMapper = salesQuotationMapper;
- this.shippingInfoMapper = shippingInfoMapper;
- this.receiptPaymentMapper = receiptPaymentMapper;
- this.invoiceLedgerMapper = invoiceLedgerMapper;
- this.salesReceiptReturnMapper = salesReceiptReturnMapper;
- this.aiSessionUserContext = aiSessionUserContext;
- }
-
- @Tool(name = "鏌ヨ瀹㈡埛妗f", value = "鎸夌娴�/鍏捣绫诲瀷鍜屽叧閿瘝鏌ヨ瀹㈡埛妗f鍒楄〃")
- public String listCustomerProfiles(@ToolMemoryId String memoryId,
- @P(value = "瀹㈡埛姹犵被鍨嬶紝鍙�� private/public", required = false) String seaType,
- @P(value = "鍏抽敭璇嶏紝鍙尮閰嶅鎴峰悕绉�/鑱旂郴浜�/鐢佃瘽", required = false) String keyword,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- CustomerDto customerDto = new CustomerDto();
- customerDto.setType(normalizeSeaType(seaType));
- customerDto.setUsageStatus(1L);
-
- List<CustomerVo> rows = defaultList(customerMapper.list(customerDto, loginUser.getUserId()));
- List<CustomerVo> filtered = rows.stream()
- .filter(item -> matchCustomerKeyword(item, keyword))
- .sorted(Comparator.comparing(CustomerVo::getId, Comparator.nullsLast(Comparator.reverseOrder())))
- .limit(normalizeLimit(limit))
- .collect(Collectors.toList());
-
- List<Map<String, Object>> items = filtered.stream().map(item -> {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("customerName", safe(item.getCustomerName()));
- map.put("customerType", safe(item.getCustomerType()));
- map.put("contactPerson", safe(item.getContactPerson()));
- map.put("contactPhone", safe(item.getContactPhone()));
- map.put("companyPhone", safe(item.getCompanyPhone()));
- map.put("maintainer", safe(item.getMaintainer()));
- map.put("maintenanceTime", formatDate(item.getMaintenanceTime()));
- map.put("usageUserName", safe(item.getUsageUserName()));
- map.put("seaType", customerSeaTypeName(item.getType()));
- map.put("isAssigned", item.getIsAssigned());
- return map;
- }).collect(Collectors.toList());
-
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("count", items.size());
- summary.put("seaType", seaType == null ? "all" : seaType);
- summary.put("keyword", safe(keyword));
- summary.put("userId", loginUser.getUserId());
-
- return jsonResponse(true, "sales_customer_profile_list", "宸茶繑鍥炲鎴锋。妗堝垪琛�", summary, Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ閿�鍞姤浠�", value = "鎸夊叧閿瘝鍜屾椂闂磋寖鍥存煡璇㈤攢鍞姤浠峰崟")
- public String listSalesQuotations(@ToolMemoryId String memoryId,
- @P(value = "鍏抽敭璇嶏紝鍙尮閰嶆姤浠峰崟鍙�/瀹㈡埛/涓氬姟鍛�/鐘舵��", required = false) String keyword,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, null);
- LambdaQueryWrapper<SalesQuotation> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), SalesQuotation::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesQuotation::getDeptId);
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(SalesQuotation::getQuotationNo, keyword)
- .or().like(SalesQuotation::getCustomer, keyword)
- .or().like(SalesQuotation::getSalesperson, keyword)
- .or().like(SalesQuotation::getStatus, keyword));
- }
- wrapper.ge(SalesQuotation::getQuotationDate, range.start())
- .le(SalesQuotation::getQuotationDate, range.end())
- .orderByDesc(SalesQuotation::getQuotationDate, SalesQuotation::getId)
- .last("limit " + normalizeLimit(limit));
-
- List<SalesQuotation> rows = defaultList(salesQuotationMapper.selectList(wrapper));
- BigDecimal quotationAmountTotal = rows.stream()
- .map(SalesQuotation::getTotalAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
-
- List<Map<String, Object>> items = rows.stream().map(item -> {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("quotationNo", safe(item.getQuotationNo()));
- map.put("customer", safe(item.getCustomer()));
- map.put("salesperson", safe(item.getSalesperson()));
- map.put("quotationDate", formatDate(item.getQuotationDate()));
- map.put("validDate", formatDate(item.getValidDate()));
- map.put("status", safe(item.getStatus()));
- map.put("paymentMethod", safe(item.getPaymentMethod()));
- map.put("deliveryPeriod", safe(item.getDeliveryPeriod()));
- map.put("totalAmount", item.getTotalAmount());
- return map;
- }).collect(Collectors.toList());
-
- Map<String, Object> summary = rangeSummary(range, items.size(), keyword);
- summary.put("quotationAmountTotal", quotationAmountTotal);
- return jsonResponse(true, "sales_quotation_list", "宸茶繑鍥為攢鍞姤浠峰垪琛�", summary, Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ閿�鍞彴璐�", value = "鎸夊叧閿瘝鍜屾椂闂磋寖鍥存煡璇㈤攢鍞彴璐︼紝骞惰繑鍥炲紑绁ㄥ洖娆句笌鍙戣揣鐘舵��")
- public String listSalesLedgers(@ToolMemoryId String memoryId,
- @P(value = "鍏抽敭璇嶏紝鍙尮閰嶉攢鍞悎鍚屽彿/瀹㈡埛鍚堝悓鍙�/瀹㈡埛/椤圭洰", required = false) String keyword,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, null);
- LambdaQueryWrapper<SalesLedger> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), SalesLedger::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesLedger::getDeptId);
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(SalesLedger::getSalesContractNo, keyword)
- .or().like(SalesLedger::getCustomerContractNo, keyword)
- .or().like(SalesLedger::getCustomerName, keyword)
- .or().like(SalesLedger::getProjectName, keyword)
- .or().like(SalesLedger::getSalesman, keyword));
- }
- wrapper.ge(SalesLedger::getEntryDate, toDate(range.start()))
- .lt(SalesLedger::getEntryDate, toExclusiveEndDate(range.end()))
- .orderByDesc(SalesLedger::getEntryDate, SalesLedger::getId)
- .last("limit " + normalizeLimit(limit));
- List<SalesLedger> rows = defaultList(salesLedgerMapper.selectList(wrapper));
- if (rows.isEmpty()) {
- return jsonResponse(true, "sales_ledger_list", "鏈煡璇㈠埌绗﹀悎鏉′欢鐨勯攢鍞彴璐�", rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
- }
-
- List<Long> ledgerIds = rows.stream().map(SalesLedger::getId).filter(Objects::nonNull).collect(Collectors.toList());
- Map<Long, BigDecimal> invoiceAmountByLedgerId = sumInvoiceAmounts(ledgerIds);
- Map<Long, BigDecimal> receiptAmountByLedgerId = sumReceiptAmounts(loginUser, ledgerIds);
- Map<Long, List<ShippingInfo>> shippingByLedgerId = queryShippingsByLedgerIds(loginUser, ledgerIds).stream()
- .collect(Collectors.groupingBy(ShippingInfo::getSalesLedgerId));
-
- BigDecimal contractAmountTotal = BigDecimal.ZERO;
- BigDecimal invoicedAmountTotal = BigDecimal.ZERO;
- BigDecimal receivedAmountTotal = BigDecimal.ZERO;
- BigDecimal pendingAmountTotal = BigDecimal.ZERO;
-
- List<Map<String, Object>> items = new ArrayList<>();
- for (SalesLedger ledger : rows) {
- BigDecimal contractAmount = defaultDecimal(ledger.getContractAmount());
- BigDecimal invoicedAmount = invoiceAmountByLedgerId.getOrDefault(ledger.getId(), BigDecimal.ZERO);
- BigDecimal receivedAmount = receiptAmountByLedgerId.getOrDefault(ledger.getId(), BigDecimal.ZERO);
- BigDecimal unbilledAmount = maxZero(contractAmount.subtract(invoicedAmount));
- BigDecimal pendingAmount = maxZero(invoicedAmount.subtract(receivedAmount));
-
- contractAmountTotal = contractAmountTotal.add(contractAmount);
- invoicedAmountTotal = invoicedAmountTotal.add(invoicedAmount);
- receivedAmountTotal = receivedAmountTotal.add(receivedAmount);
- pendingAmountTotal = pendingAmountTotal.add(pendingAmount);
-
- Map<String, Object> item = new LinkedHashMap<>();
- item.put("id", ledger.getId());
- item.put("salesContractNo", safe(ledger.getSalesContractNo()));
- item.put("customerContractNo", safe(ledger.getCustomerContractNo()));
- item.put("customerName", safe(ledger.getCustomerName()));
- item.put("projectName", safe(ledger.getProjectName()));
- item.put("salesman", safe(ledger.getSalesman()));
- item.put("entryDate", formatDate(ledger.getEntryDate()));
- item.put("executionDate", formatDate(ledger.getExecutionDate()));
- item.put("deliveryDate", formatDate(ledger.getDeliveryDate()));
- item.put("contractAmount", contractAmount);
- item.put("invoicedAmount", invoicedAmount);
- item.put("receivedAmount", receivedAmount);
- item.put("unbilledAmount", unbilledAmount);
- item.put("pendingAmount", pendingAmount);
- item.put("shippingStatus", calcLedgerShippingStatus(shippingByLedgerId.get(ledger.getId())));
- items.add(item);
- }
-
- Map<String, Object> summary = rangeSummary(range, items.size(), keyword);
- summary.put("contractAmountTotal", contractAmountTotal);
- summary.put("invoicedAmountTotal", invoicedAmountTotal);
- summary.put("receivedAmountTotal", receivedAmountTotal);
- summary.put("pendingAmountTotal", pendingAmountTotal);
- return jsonResponse(true, "sales_ledger_list", "宸茶繑鍥為攢鍞彴璐﹀垪琛�", summary, Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ閿�鍞��璐�", value = "鎸夋椂闂磋寖鍥村拰鍏抽敭璇嶆煡璇㈤攢鍞��璐ц褰�")
- public String listSalesReturns(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鍏抽敭璇嶏紝鍙尮閰嶉��娆惧崟鍙�/浜ゆ槗鍙�/浠樻璐︽埛", required = false) String keyword,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, null);
- LambdaQueryWrapper<SalesReceiptReturn> wrapper = new LambdaQueryWrapper<>();
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesReceiptReturn::getDeptId);
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(SalesReceiptReturn::getRefundId, keyword)
- .or().like(SalesReceiptReturn::getTransactionNo, keyword)
- .or().like(SalesReceiptReturn::getPaymentAccountName, keyword));
- }
- wrapper.ge(SalesReceiptReturn::getCreateTime, range.start().atStartOfDay())
- .le(SalesReceiptReturn::getCreateTime, range.end().atTime(23, 59, 59))
- .orderByDesc(SalesReceiptReturn::getCreateTime, SalesReceiptReturn::getId)
- .last("limit " + normalizeLimit(limit));
- List<SalesReceiptReturn> rows = defaultList(salesReceiptReturnMapper.selectList(wrapper));
-
- BigDecimal returnAmount = rows.stream()
- .map(SalesReceiptReturn::getActualAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
-
- List<Map<String, Object>> items = rows.stream().map(item -> {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("refundId", safe(item.getRefundId()));
- map.put("paymentAccount", safe(item.getPaymentAccount()));
- map.put("paymentAccountName", safe(item.getPaymentAccountName()));
- map.put("paymentMethod", item.getPaymentMethod());
- map.put("actualAmount", item.getActualAmount());
- map.put("fee", item.getFee());
- map.put("discountAmount", item.getDiscountAmount());
- map.put("transactionNo", safe(item.getTransactionNo()));
- map.put("createTime", formatDateTime(item.getCreateTime()));
- return map;
- }).collect(Collectors.toList());
-
- Map<String, Object> summary = rangeSummary(range, items.size(), keyword);
- summary.put("returnAmount", returnAmount);
- return jsonResponse(true, "sales_return_list", "宸茶繑鍥為攢鍞��璐ц褰�", summary, Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ瀹㈡埛寰�鏉�", value = "鎸夋椂闂磋寖鍥村拰鍏抽敭璇嶆煡璇㈠鎴峰洖娆惧線鏉ユ槑缁�")
- public String listCustomerInteractions(@ToolMemoryId String memoryId,
- @P(value = "鍏抽敭璇嶏紝鍙尮閰嶅鎴峰悕绉�/閿�鍞悎鍚屽彿/椤圭洰鍚�", required = false) String keyword,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, null);
- LambdaQueryWrapper<ReceiptPayment> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), ReceiptPayment::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ReceiptPayment::getDeptId);
- wrapper.ge(ReceiptPayment::getReceiptPaymentDate, range.start())
- .le(ReceiptPayment::getReceiptPaymentDate, range.end())
- .orderByDesc(ReceiptPayment::getReceiptPaymentDate, ReceiptPayment::getId);
- List<ReceiptPayment> payments = defaultList(receiptPaymentMapper.selectList(wrapper));
- if (payments.isEmpty()) {
- return jsonResponse(true, "sales_customer_interaction_list", "鏈煡璇㈠埌瀹㈡埛寰�鏉ヨ褰�", rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
- }
-
- List<Long> ledgerIds = payments.stream()
- .map(ReceiptPayment::getSalesLedgerId)
- .filter(Objects::nonNull)
- .distinct()
- .collect(Collectors.toList());
- Map<Long, SalesLedger> ledgerMap = defaultList(salesLedgerMapper.selectBatchIds(ledgerIds)).stream()
- .filter(ledger -> tenantMatched(ledger.getTenantId(), loginUser.getTenantId()))
- .collect(Collectors.toMap(SalesLedger::getId, item -> item, (a, b) -> a, LinkedHashMap::new));
-
- List<ReceiptPayment> filtered = payments.stream()
- .filter(item -> matchInteractionKeyword(item, ledgerMap.get(item.getSalesLedgerId()), keyword))
- .limit(normalizeLimit(limit))
- .collect(Collectors.toList());
-
- BigDecimal totalReceiptAmount = filtered.stream()
- .map(ReceiptPayment::getReceiptPaymentAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
-
- List<Map<String, Object>> items = filtered.stream().map(item -> {
- SalesLedger ledger = ledgerMap.get(item.getSalesLedgerId());
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("salesLedgerId", item.getSalesLedgerId());
- map.put("salesContractNo", ledger == null ? "" : safe(ledger.getSalesContractNo()));
- map.put("customerName", ledger == null ? "" : safe(ledger.getCustomerName()));
- map.put("projectName", ledger == null ? "" : safe(ledger.getProjectName()));
- map.put("receiptPaymentDate", formatDate(item.getReceiptPaymentDate()));
- map.put("receiptPaymentAmount", item.getReceiptPaymentAmount());
- map.put("receiptPaymentType", safe(item.getReceiptPaymentType()));
- map.put("registrant", safe(item.getRegistrant()));
- return map;
- }).collect(Collectors.toList());
-
- Map<String, Object> summary = rangeSummary(range, items.size(), keyword);
- summary.put("totalReceiptAmount", totalReceiptAmount);
- summary.put("customerCount", items.stream().map(item -> String.valueOf(item.get("customerName"))).filter(StringUtils::hasText).distinct().count());
- return jsonResponse(true, "sales_customer_interaction_list", "宸茶繑鍥炲鎴峰線鏉ユ槑缁�", summary, Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ鍙戣揣鍙拌处", value = "鎸夊叧閿瘝鍜屾椂闂磋寖鍥存煡璇㈠彂璐у彴璐�")
- public String listShippingLedgers(@ToolMemoryId String memoryId,
- @P(value = "鍏抽敭璇嶏紝鍙尮閰嶅彂璐у崟鍙�/蹇�掑崟鍙�/鐗╂祦鍏徃/杞︾墝鍙�", required = false) String keyword,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, null);
- LambdaQueryWrapper<ShippingInfo> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), ShippingInfo::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ShippingInfo::getDeptId);
- if (StringUtils.hasText(keyword)) {
- wrapper.and(w -> w.like(ShippingInfo::getShippingNo, keyword)
- .or().like(ShippingInfo::getExpressNumber, keyword)
- .or().like(ShippingInfo::getExpressCompany, keyword)
- .or().like(ShippingInfo::getShippingCarNumber, keyword)
- .or().like(ShippingInfo::getStatus, keyword));
- }
- wrapper.ge(ShippingInfo::getShippingDate, toDate(range.start()))
- .lt(ShippingInfo::getShippingDate, toExclusiveEndDate(range.end()))
- .orderByDesc(ShippingInfo::getShippingDate, ShippingInfo::getId)
- .last("limit " + normalizeLimit(limit));
- List<ShippingInfo> rows = defaultList(shippingInfoMapper.selectList(wrapper));
- if (rows.isEmpty()) {
- return jsonResponse(true, "sales_shipping_list", "鏈煡璇㈠埌鍙戣揣鍙拌处璁板綍", rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
- }
-
- List<Long> ledgerIds = rows.stream().map(ShippingInfo::getSalesLedgerId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
- Map<Long, SalesLedger> ledgerMap = defaultList(salesLedgerMapper.selectBatchIds(ledgerIds)).stream()
- .filter(ledger -> tenantMatched(ledger.getTenantId(), loginUser.getTenantId()))
- .collect(Collectors.toMap(SalesLedger::getId, item -> item, (a, b) -> a, LinkedHashMap::new));
-
- long shippedCount = rows.stream().filter(item -> isShippedStatus(item.getStatus())).count();
- List<Map<String, Object>> items = rows.stream().map(item -> {
- SalesLedger ledger = ledgerMap.get(item.getSalesLedgerId());
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("id", item.getId());
- map.put("salesLedgerId", item.getSalesLedgerId());
- map.put("salesContractNo", ledger == null ? "" : safe(ledger.getSalesContractNo()));
- map.put("customerName", ledger == null ? "" : safe(ledger.getCustomerName()));
- map.put("shippingNo", safe(item.getShippingNo()));
- map.put("status", safe(item.getStatus()));
- map.put("shippingDate", formatDate(item.getShippingDate()));
- map.put("type", safe(item.getType()));
- map.put("shippingCarNumber", safe(item.getShippingCarNumber()));
- map.put("expressCompany", safe(item.getExpressCompany()));
- map.put("expressNumber", safe(item.getExpressNumber()));
- return map;
- }).collect(Collectors.toList());
-
- Map<String, Object> summary = rangeSummary(range, items.size(), keyword);
- summary.put("shippingCount", rows.size());
- summary.put("shippedCount", shippedCount);
- summary.put("pendingCount", Math.max(rows.size() - shippedCount, 0));
- return jsonResponse(true, "sales_shipping_list", "宸茶繑鍥炲彂璐у彴璐﹁褰�", summary, Map.of("items", items), Map.of());
- }
-
- @Tool(name = "鏌ヨ閿�鍞寚鏍囩粺璁�", value = "鎸夋椂闂磋寖鍥寸粺璁¢攢鍞悎鍚屻�佹姤浠枫�佸彂璐с�佸洖娆剧瓑鍏抽敭鎸囨爣")
- public String getSalesDashboard(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽鏈湀銆佹湰骞淬�佽繎30澶�", required = false) String timeRange) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, timeRange);
-
- List<SalesLedger> ledgers = querySalesLedgers(loginUser, range);
- List<SalesQuotation> quotations = querySalesQuotations(loginUser, range);
- List<ShippingInfo> shippings = queryShippings(loginUser, range);
- List<ReceiptPayment> receipts = queryReceipts(loginUser, range);
-
- BigDecimal contractAmountTotal = ledgers.stream()
- .map(SalesLedger::getContractAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal quotationAmountTotal = quotations.stream()
- .map(SalesQuotation::getTotalAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal receivedAmountTotal = receipts.stream()
- .map(ReceiptPayment::getReceiptPaymentAmount)
- .filter(Objects::nonNull)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal pendingAmountTotal = maxZero(contractAmountTotal.subtract(receivedAmountTotal));
-
- long shippingCount = shippings.size();
- long shippedCount = shippings.stream().filter(item -> isShippedStatus(item.getStatus())).count();
- String shipRate = toRate(shippedCount, shippingCount);
-
- List<Map<String, Object>> topCustomers = buildTopCustomers(ledgers);
- TrendData trendData = buildContractTrendData(ledgers, range);
-
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("timeRange", range.label());
- summary.put("startDate", range.start().toString());
- summary.put("endDate", range.end().toString());
- summary.put("orderCount", ledgers.size());
- summary.put("quotationCount", quotations.size());
- summary.put("shippingCount", shippingCount);
- summary.put("shippedCount", shippedCount);
- summary.put("shipRate", shipRate);
- summary.put("contractAmountTotal", contractAmountTotal);
- summary.put("quotationAmountTotal", quotationAmountTotal);
- summary.put("receivedAmountTotal", receivedAmountTotal);
- summary.put("pendingAmountTotal", pendingAmountTotal);
-
- Map<String, Object> charts = new LinkedHashMap<>();
- charts.put("amountBarOption", buildAmountBarOption(contractAmountTotal, quotationAmountTotal, receivedAmountTotal, pendingAmountTotal));
- charts.put("shippingPieOption", buildShippingPieOption(shippedCount, Math.max(shippingCount - shippedCount, 0)));
- charts.put("customerTopBarOption", buildCustomerTopBarOption(topCustomers));
- charts.put("contractTrendLineOption", buildContractTrendLineOption(trendData.labels(), trendData.values()));
-
- Map<String, Object> data = new LinkedHashMap<>();
- data.put("topCustomers", topCustomers);
- data.put("contractTrend", trendData.toItemList());
-
- return jsonResponse(true, "sales_dashboard", "宸茶繑鍥為攢鍞寚鏍囩粺璁�", summary, data, charts);
- }
-
- @Tool(name = "瀹㈡埛娴佸け椋庨櫓鍒嗘瀽", value = "鎸夊鎴风淮搴﹁瘎浼版祦澶遍闄╋紝杈撳嚭椋庨櫓鍒嗙骇銆佸師鍥犲拰寤鸿浼樺厛绾�")
- public String analyzeCustomerChurnRisk(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽杩�90澶┿�佹湰骞�", required = false) String timeRange,
- @P(value = "鍏抽敭璇嶏紝鍙尮閰嶅鎴峰悕绉�", required = false) String keyword,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, StringUtils.hasText(timeRange) ? timeRange : "杩�180澶�");
- List<CustomerRiskMetric> metrics = buildCustomerRiskMetrics(loginUser, range, keyword);
- if (metrics.isEmpty()) {
- return jsonResponse(true, "sales_customer_churn_risk", "褰撳墠鑼冨洿鍐呮湭鏌ヨ鍒板彲鍒嗘瀽鐨勫鎴锋暟鎹�",
- rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
- }
-
- List<CustomerRiskMetric> sorted = metrics.stream()
- .sorted(Comparator.comparing(CustomerRiskMetric::getRiskScore).reversed()
- .thenComparing(CustomerRiskMetric::getPendingAmount, Comparator.reverseOrder()))
- .limit(normalizeLimit(limit))
- .collect(Collectors.toList());
-
- long highCount = sorted.stream().filter(item -> "high".equals(item.getRiskLevel())).count();
- long mediumCount = sorted.stream().filter(item -> "medium".equals(item.getRiskLevel())).count();
- long lowCount = sorted.stream().filter(item -> "low".equals(item.getRiskLevel())).count();
-
- List<Map<String, Object>> items = sorted.stream().map(this::toRiskItem).collect(Collectors.toList());
- Map<String, Object> summary = rangeSummary(range, items.size(), keyword);
- summary.put("highRiskCount", highCount);
- summary.put("mediumRiskCount", mediumCount);
- summary.put("lowRiskCount", lowCount);
-
- Map<String, Object> charts = new LinkedHashMap<>();
- charts.put("riskLevelPieOption", buildRiskLevelPieOption(highCount, mediumCount, lowCount));
- charts.put("riskScoreBarOption", buildRiskScoreBarOption(sorted));
-
- return jsonResponse(true, "sales_customer_churn_risk", "宸插畬鎴愬鎴锋祦澶遍闄╁垎鏋�", summary, Map.of("items", items), charts);
- }
-
- @Tool(name = "鍥炴涓庢姤浠风瓥鐣ュ缓璁�", value = "鍩轰簬瀹㈡埛椋庨櫓銆佸洖娆惧拰鎶ヤ环鎯呭喌鐢熸垚鍙墽琛岀殑璺熻繘绛栫暐")
- public String suggestCollectionAndQuotationStrategy(@ToolMemoryId String memoryId,
- @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
- @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
- @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽杩�90澶┿�佹湰鏈�", required = false) String timeRange,
- @P(value = "鍏抽敭璇嶏紝鍙尮閰嶅鎴峰悕绉�", required = false) String keyword,
- @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit,
- @P(value = "鏄惁浼樺厛楂橀闄╁鎴凤紝true 琛ㄧず楂橀闄╀紭鍏�", required = false) Boolean prioritizeHighRisk) {
- LoginUser loginUser = currentLoginUser(memoryId);
- DateRange range = resolveDateRange(startDate, endDate, StringUtils.hasText(timeRange) ? timeRange : "杩�90澶�");
- List<CustomerRiskMetric> metrics = buildCustomerRiskMetrics(loginUser, range, keyword);
- if (metrics.isEmpty()) {
- return jsonResponse(true, "sales_collection_quote_strategy", "褰撳墠鑼冨洿鍐呮湭鏌ヨ鍒板彲鐢熸垚绛栫暐鐨勫鎴锋暟鎹�",
- rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
- }
-
- boolean highRiskFirst = Boolean.TRUE.equals(prioritizeHighRisk);
- Comparator<CustomerRiskMetric> sortComparator;
- if (highRiskFirst) {
- sortComparator = Comparator
- .comparingInt((CustomerRiskMetric metric) -> riskLevelRank(metric.getRiskLevel())).reversed()
- .thenComparing(CustomerRiskMetric::getRiskScore, Comparator.reverseOrder())
- .thenComparing(CustomerRiskMetric::getPendingAmount, Comparator.reverseOrder());
- } else {
- sortComparator = Comparator
- .comparing(CustomerRiskMetric::getPendingAmount, Comparator.reverseOrder())
- .thenComparing(CustomerRiskMetric::getRiskScore, Comparator.reverseOrder());
- }
-
- List<CustomerRiskMetric> sorted = metrics.stream()
- .sorted(sortComparator)
- .limit(normalizeLimit(limit))
- .collect(Collectors.toList());
-
- List<Map<String, Object>> items = sorted.stream().map(this::toStrategyItem).collect(Collectors.toList());
- long highPriorityCount = items.stream().filter(item -> "high".equals(item.get("priority"))).count();
- long mediumPriorityCount = items.stream().filter(item -> "medium".equals(item.get("priority"))).count();
- long lowPriorityCount = items.stream().filter(item -> "low".equals(item.get("priority"))).count();
-
- Map<String, Object> summary = rangeSummary(range, items.size(), keyword);
- summary.put("highPriorityCount", highPriorityCount);
- summary.put("mediumPriorityCount", mediumPriorityCount);
- summary.put("lowPriorityCount", lowPriorityCount);
- summary.put("prioritizeHighRisk", highRiskFirst);
- summary.put("priorityMode", highRiskFirst ? "high_risk_first" : "pending_amount_first");
-
- Map<String, Object> charts = new LinkedHashMap<>();
- charts.put("pendingAmountBarOption", buildPendingAmountBarOption(sorted));
- charts.put("priorityPieOption", buildPriorityPieOption(highPriorityCount, mediumPriorityCount, lowPriorityCount));
-
- return jsonResponse(true, "sales_collection_quote_strategy", "宸茬敓鎴愬洖娆句笌鎶ヤ环绛栫暐寤鸿", summary, Map.of("items", items), charts);
- }
-
- private List<CustomerRiskMetric> buildCustomerRiskMetrics(LoginUser loginUser, DateRange range, String keyword) {
- List<SalesLedger> ledgers = querySalesLedgers(loginUser, range).stream()
- .filter(item -> matchLedgerCustomerKeyword(item, keyword))
- .collect(Collectors.toList());
- if (ledgers.isEmpty()) {
- return List.of();
- }
-
- Map<String, CustomerRiskMetric> metricMap = new LinkedHashMap<>();
- for (SalesLedger ledger : ledgers) {
- String customerName = StringUtils.hasText(ledger.getCustomerName()) ? ledger.getCustomerName().trim() : "鏈煡瀹㈡埛";
- CustomerRiskMetric metric = metricMap.computeIfAbsent(customerName, CustomerRiskMetric::new);
- metric.setOrderCount(metric.getOrderCount() + 1);
- metric.setContractAmount(metric.getContractAmount().add(defaultDecimal(ledger.getContractAmount())));
- metric.setTopSingleOrderAmount(metric.getTopSingleOrderAmount().max(defaultDecimal(ledger.getContractAmount())));
- LocalDate entryDate = toLocalDate(ledger.getEntryDate());
- if (entryDate != null && (metric.getLastOrderDate() == null || entryDate.isAfter(metric.getLastOrderDate()))) {
- metric.setLastOrderDate(entryDate);
- }
- if (ledger.getId() != null) {
- metric.getLedgerIds().add(ledger.getId());
- if (ledger.getDeliveryDate() != null) {
- metric.getDeliveryDateByLedgerId().put(ledger.getId(), ledger.getDeliveryDate());
- }
- }
- }
-
- List<Long> allLedgerIds = metricMap.values().stream()
- .flatMap(metric -> metric.getLedgerIds().stream())
- .distinct()
- .collect(Collectors.toList());
- Map<Long, BigDecimal> receiptAmountByLedgerId = sumReceiptAmounts(loginUser, allLedgerIds);
- Map<Long, List<ShippingInfo>> shippingByLedgerId = queryShippingsByLedgerIds(loginUser, allLedgerIds).stream()
- .collect(Collectors.groupingBy(ShippingInfo::getSalesLedgerId));
-
- List<SalesQuotation> quotations = querySalesQuotations(loginUser, range);
- for (SalesQuotation quotation : quotations) {
- String customerName = safe(quotation.getCustomer());
- CustomerRiskMetric metric = metricMap.get(customerName);
- if (metric == null) {
- continue;
- }
- metric.setQuoteCount(metric.getQuoteCount() + 1);
- metric.setQuoteAmount(metric.getQuoteAmount().add(defaultDecimal(quotation.getTotalAmount())));
- }
-
- LocalDate today = LocalDate.now();
- for (CustomerRiskMetric metric : metricMap.values()) {
- BigDecimal receivedAmount = BigDecimal.ZERO;
- long overdueDeliveryCount = 0;
- for (Long ledgerId : metric.getLedgerIds()) {
- receivedAmount = receivedAmount.add(receiptAmountByLedgerId.getOrDefault(ledgerId, BigDecimal.ZERO));
- LocalDate deliveryDate = metric.getDeliveryDateByLedgerId().get(ledgerId);
- if (deliveryDate != null && deliveryDate.isBefore(today) && !isLedgerFullyShipped(ledgerId, shippingByLedgerId)) {
- overdueDeliveryCount++;
- }
- }
- metric.setReceivedAmount(receivedAmount);
- metric.setPendingAmount(maxZero(metric.getContractAmount().subtract(receivedAmount)));
- if (metric.getContractAmount().compareTo(BigDecimal.ZERO) > 0) {
- metric.setPendingRate(metric.getPendingAmount()
- .divide(metric.getContractAmount(), 4, RoundingMode.HALF_UP));
- } else {
- metric.setPendingRate(BigDecimal.ZERO);
- }
- metric.setOverdueDeliveryCount(overdueDeliveryCount);
- if (metric.getLastOrderDate() == null) {
- metric.setDaysSinceLastOrder(999);
- } else {
- metric.setDaysSinceLastOrder(Math.max(today.toEpochDay() - metric.getLastOrderDate().toEpochDay(), 0));
- }
- evaluateRiskMetric(metric);
- }
- return new ArrayList<>(metricMap.values());
- }
-
- private void evaluateRiskMetric(CustomerRiskMetric metric) {
- int score = 0;
- List<String> reasons = new ArrayList<>();
- if (metric.getDaysSinceLastOrder() >= 90) {
- score += 35;
- reasons.add("杩�90澶╂棤鏂板璁㈠崟");
- } else if (metric.getDaysSinceLastOrder() >= 60) {
- score += 25;
- reasons.add("杩�60澶╄鍗曟椿璺冨害涓嬮檷");
- } else if (metric.getDaysSinceLastOrder() >= 30) {
- score += 12;
- reasons.add("杩�30澶╄鍗曟尝鍔ㄥ亸寮�");
- }
-
- if (metric.getPendingRate().compareTo(new BigDecimal("0.60")) >= 0) {
- score += 30;
- reasons.add("寰呭洖娆惧崰姣旈珮浜�60%");
- } else if (metric.getPendingRate().compareTo(new BigDecimal("0.30")) >= 0) {
- score += 20;
- reasons.add("寰呭洖娆惧崰姣旈珮浜�30%");
- } else if (metric.getPendingRate().compareTo(new BigDecimal("0.10")) >= 0) {
- score += 10;
- reasons.add("瀛樺湪寰呭洖娆鹃闄�");
- }
-
- if (metric.getOverdueDeliveryCount() > 0) {
- score += Math.min((int) metric.getOverdueDeliveryCount() * 6, 20);
- reasons.add("瀛樺湪浜ゆ湡閫炬湡璁㈠崟");
- }
-
- if (metric.getOrderCount() <= 1) {
- score += 8;
- reasons.add("璁㈠崟鍩烘暟鍋忎綆");
- }
-
- if (metric.getQuoteCount() > 0 && metric.getOrderCount() == 0) {
- score += 10;
- reasons.add("鎶ヤ环鏈舰鎴愯鍗曡浆鍖�");
- }
-
- score = Math.min(score, 100);
- metric.setRiskScore(score);
- if (score >= 70) {
- metric.setRiskLevel("high");
- } else if (score >= 40) {
- metric.setRiskLevel("medium");
- } else {
- metric.setRiskLevel("low");
- }
- metric.setRiskReasons(reasons);
- }
-
- private Map<String, Object> toRiskItem(CustomerRiskMetric metric) {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("customerName", metric.getCustomerName());
- map.put("riskLevel", metric.getRiskLevel());
- map.put("riskScore", metric.getRiskScore());
- map.put("contractAmount", metric.getContractAmount());
- map.put("receivedAmount", metric.getReceivedAmount());
- map.put("pendingAmount", metric.getPendingAmount());
- map.put("pendingRate", toPercent(metric.getPendingRate()));
- map.put("orderCount", metric.getOrderCount());
- map.put("quoteCount", metric.getQuoteCount());
- map.put("overdueDeliveryCount", metric.getOverdueDeliveryCount());
- map.put("daysSinceLastOrder", metric.getDaysSinceLastOrder());
- map.put("lastOrderDate", formatDate(metric.getLastOrderDate()));
- map.put("riskReasons", metric.getRiskReasons());
- return map;
- }
-
- private Map<String, Object> toStrategyItem(CustomerRiskMetric metric) {
- String priority = strategyPriority(metric);
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("customerName", metric.getCustomerName());
- map.put("riskLevel", metric.getRiskLevel());
- map.put("riskScore", metric.getRiskScore());
- map.put("priority", priority);
- map.put("pendingAmount", metric.getPendingAmount());
- map.put("pendingRate", toPercent(metric.getPendingRate()));
- map.put("quoteCount", metric.getQuoteCount());
- map.put("orderCount", metric.getOrderCount());
- map.put("quoteConversionRate", toRate(metric.getOrderCount(), Math.max(metric.getQuoteCount(), 1)));
- map.put("collectionStrategy", buildCollectionStrategy(metric));
- map.put("quotationStrategy", buildQuotationStrategy(metric));
- map.put("nextAction", buildNextAction(priority));
- map.put("topSingleOrderAmount", metric.getTopSingleOrderAmount());
- return map;
- }
-
- private String buildCollectionStrategy(CustomerRiskMetric metric) {
- if (metric.getPendingAmount().compareTo(BigDecimal.ZERO) <= 0) {
- return "淇濇寔姝e父鏈堝害瀵硅处涓庡洖娆剧‘璁わ紝缁存寔瀹㈡埛鍥炴鑺傚銆�";
- }
- if (metric.getPendingRate().compareTo(new BigDecimal("0.60")) >= 0) {
- return "浼樺厛閿佸畾鍥炴璁″垝锛屾寜鍛ㄦ媶鍒嗗洖娆捐妭鐐瑰苟缁戝畾鍙戣揣鏉′欢锛岄伩鍏嶆柊澧炰俊鐢ㄦ暈鍙c��";
- }
- if (metric.getPendingRate().compareTo(new BigDecimal("0.30")) >= 0) {
- return "寤鸿鎵ц鍙屽懆鍌敹鏈哄埗锛屽悓姝ヨ储鍔′笌涓氬姟鑱斿悎璺熻繘閲嶇偣鍚堝悓銆�";
- }
- return "淇濇寔姝e父鍌敹鑺傚锛屾寜鍚堝悓鑺傜偣鎻愬墠3澶╂彁閱掑鎴蜂粯娆俱��";
- }
-
- private String buildQuotationStrategy(CustomerRiskMetric metric) {
- if ("high".equals(metric.getRiskLevel())) {
- return "鎶ヤ环浼樺厛淇濇瘺鍒╀笌鍥炴鏉℃锛屽噺灏戣秴闀胯处鏈燂紝蹇呰鏃堕噰鐢ㄥ垎闃舵鎶ヤ环銆�";
- }
- if (metric.getQuoteCount() > 0 && metric.getOrderCount() < metric.getQuoteCount()) {
- return "浼樺寲鎶ヤ环缁撴瀯锛屽缓璁彁渚涘熀纭�鐗�+鍗囩骇鐗堢粍鍚堟姤浠凤紝鎻愰珮杞寲鐜囥��";
- }
- if (metric.getOrderCount() <= 1) {
- return "鍔犲己闇�姹傛寲鎺橈紝鍥寸粫瀹㈡埛鍦烘櫙琛ュ厖澧炲�奸」涓庝氦浠樹繚闅滄潯娆俱��";
- }
- return "淇濇寔褰撳墠鎶ヤ环绛栫暐锛岄噸鐐瑰洿缁曚氦鏈熷拰鏈嶅姟鑳藉姏鍋氬樊寮傚寲鍛堢幇銆�";
- }
-
- private String buildNextAction(String priority) {
- return switch (priority) {
- case "high" -> "48灏忔椂鍐呭畬鎴愬鎴峰洖璁匡紝纭鍥炴璁″垝骞跺鏍告姤浠锋湁鏁堟湡銆�";
- case "medium" -> "鏈懆鍐呭畬鎴愬鎴烽渶姹傚鐩橈紝鏇存柊鎶ヤ环鐗堟湰骞跺悓姝ュ洖娆捐妭鐐广��";
- default -> "淇濇寔鏈堝害渚嬭璺熻繘锛屾寔缁拷韪鎴烽噰璐鍒掑彉鍖栥��";
- };
- }
-
- private String strategyPriority(CustomerRiskMetric metric) {
- if ("high".equals(metric.getRiskLevel()) || metric.getPendingRate().compareTo(new BigDecimal("0.50")) >= 0) {
- return "high";
- }
- if ("medium".equals(metric.getRiskLevel()) || metric.getPendingRate().compareTo(new BigDecimal("0.30")) >= 0) {
- return "medium";
- }
- return "low";
- }
-
- private int riskLevelRank(String riskLevel) {
- if ("high".equals(riskLevel)) {
- return 3;
- }
- if ("medium".equals(riskLevel)) {
- return 2;
- }
- return 1;
- }
-
- private List<Map<String, Object>> buildTopCustomers(List<SalesLedger> ledgers) {
- Map<String, BigDecimal> grouped = new LinkedHashMap<>();
- for (SalesLedger ledger : ledgers) {
- String customerName = StringUtils.hasText(ledger.getCustomerName()) ? ledger.getCustomerName().trim() : "鏈煡瀹㈡埛";
- grouped.merge(customerName, defaultDecimal(ledger.getContractAmount()), BigDecimal::add);
- }
- return grouped.entrySet().stream()
- .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
- .limit(5)
- .map(entry -> {
- Map<String, Object> map = new LinkedHashMap<>();
- map.put("customerName", entry.getKey());
- map.put("contractAmount", entry.getValue());
- return map;
- })
- .collect(Collectors.toList());
- }
-
- private TrendData buildContractTrendData(List<SalesLedger> ledgers, DateRange range) {
- Map<String, BigDecimal> amountByMonth = new LinkedHashMap<>();
- YearMonth startMonth = YearMonth.from(range.start());
- YearMonth endMonth = YearMonth.from(range.end());
- for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) {
- amountByMonth.put(month.toString(), BigDecimal.ZERO);
- }
- for (SalesLedger ledger : ledgers) {
- LocalDate entryDate = toLocalDate(ledger.getEntryDate());
- if (entryDate == null) {
- continue;
- }
- String monthKey = YearMonth.from(entryDate).toString();
- if (!amountByMonth.containsKey(monthKey)) {
- continue;
- }
- amountByMonth.put(monthKey, amountByMonth.get(monthKey).add(defaultDecimal(ledger.getContractAmount())));
- }
- return new TrendData(new ArrayList<>(amountByMonth.keySet()), new ArrayList<>(amountByMonth.values()));
- }
-
- private List<SalesLedger> querySalesLedgers(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<SalesLedger> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), SalesLedger::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesLedger::getDeptId);
- if (range != null) {
- wrapper.ge(SalesLedger::getEntryDate, toDate(range.start()))
- .lt(SalesLedger::getEntryDate, toExclusiveEndDate(range.end()));
- }
- return defaultList(salesLedgerMapper.selectList(wrapper));
- }
-
- private List<SalesQuotation> querySalesQuotations(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<SalesQuotation> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), SalesQuotation::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesQuotation::getDeptId);
- if (range != null) {
- wrapper.ge(SalesQuotation::getQuotationDate, range.start())
- .le(SalesQuotation::getQuotationDate, range.end());
- }
- return defaultList(salesQuotationMapper.selectList(wrapper));
- }
-
- private List<ShippingInfo> queryShippings(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<ShippingInfo> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), ShippingInfo::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ShippingInfo::getDeptId);
- if (range != null) {
- wrapper.ge(ShippingInfo::getShippingDate, toDate(range.start()))
- .lt(ShippingInfo::getShippingDate, toExclusiveEndDate(range.end()));
- }
- return defaultList(shippingInfoMapper.selectList(wrapper));
- }
-
- private List<ReceiptPayment> queryReceipts(LoginUser loginUser, DateRange range) {
- LambdaQueryWrapper<ReceiptPayment> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), ReceiptPayment::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ReceiptPayment::getDeptId);
- if (range != null) {
- wrapper.ge(ReceiptPayment::getReceiptPaymentDate, range.start())
- .le(ReceiptPayment::getReceiptPaymentDate, range.end());
- }
- return defaultList(receiptPaymentMapper.selectList(wrapper));
- }
-
- private List<ReceiptPayment> queryReceiptsByLedgerIds(LoginUser loginUser, List<Long> ledgerIds) {
- if (ledgerIds == null || ledgerIds.isEmpty()) {
- return List.of();
- }
- LambdaQueryWrapper<ReceiptPayment> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), ReceiptPayment::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ReceiptPayment::getDeptId);
- wrapper.in(ReceiptPayment::getSalesLedgerId, ledgerIds);
- return defaultList(receiptPaymentMapper.selectList(wrapper));
- }
-
- private List<ShippingInfo> queryShippingsByLedgerIds(LoginUser loginUser, List<Long> ledgerIds) {
- if (ledgerIds == null || ledgerIds.isEmpty()) {
- return List.of();
- }
- LambdaQueryWrapper<ShippingInfo> wrapper = new LambdaQueryWrapper<>();
- applyTenantFilter(wrapper, loginUser.getTenantId(), ShippingInfo::getTenantId);
- applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ShippingInfo::getDeptId);
- wrapper.in(ShippingInfo::getSalesLedgerId, ledgerIds);
- return defaultList(shippingInfoMapper.selectList(wrapper));
- }
-
- private Map<Long, BigDecimal> sumInvoiceAmounts(List<Long> ledgerIds) {
- if (ledgerIds == null || ledgerIds.isEmpty()) {
- return Map.of();
- }
- Map<Long, BigDecimal> result = new HashMap<>();
- for (InvoiceLedgerDto item : defaultList(invoiceLedgerMapper.invoicedTotal(ledgerIds))) {
- if (item.getSalesLedgerId() == null) {
- continue;
- }
- result.merge(item.getSalesLedgerId().longValue(), defaultDecimal(item.getInvoiceTotal()), BigDecimal::add);
- }
- return result;
- }
-
- private Map<Long, BigDecimal> sumReceiptAmounts(LoginUser loginUser, List<Long> ledgerIds) {
- Map<Long, BigDecimal> result = new HashMap<>();
- for (ReceiptPayment item : queryReceiptsByLedgerIds(loginUser, ledgerIds)) {
- if (item.getSalesLedgerId() == null) {
- continue;
- }
- result.merge(item.getSalesLedgerId(), defaultDecimal(item.getReceiptPaymentAmount()), BigDecimal::add);
- }
- return result;
- }
-
- private boolean isLedgerFullyShipped(Long ledgerId, Map<Long, List<ShippingInfo>> shippingByLedgerId) {
- List<ShippingInfo> shippingInfos = shippingByLedgerId.get(ledgerId);
- if (shippingInfos == null || shippingInfos.isEmpty()) {
- return false;
- }
- return shippingInfos.stream().allMatch(item -> isShippedStatus(item.getStatus()));
- }
-
- private String calcLedgerShippingStatus(List<ShippingInfo> shippingInfos) {
- if (shippingInfos == null || shippingInfos.isEmpty()) {
- return "鏈彂璐�";
- }
- long shippedCount = shippingInfos.stream().filter(item -> isShippedStatus(item.getStatus())).count();
- if (shippedCount == 0) {
- return "寰呭彂璐�";
- }
- if (shippedCount == shippingInfos.size()) {
- return "宸插彂璐�";
- }
- return "閮ㄥ垎鍙戣揣";
- }
-
- private boolean isShippedStatus(String status) {
- return StringUtils.hasText(status) && status.contains("宸插彂璐�");
- }
-
- private boolean matchCustomerKeyword(CustomerVo customer, String keyword) {
- if (!StringUtils.hasText(keyword)) {
- return true;
- }
- String text = keyword.trim();
- return safe(customer.getCustomerName()).contains(text)
- || safe(customer.getContactPerson()).contains(text)
- || safe(customer.getContactPhone()).contains(text)
- || safe(customer.getCompanyPhone()).contains(text)
- || safe(customer.getUsageUserName()).contains(text);
- }
-
- private boolean matchInteractionKeyword(ReceiptPayment payment, SalesLedger ledger, String keyword) {
- if (!StringUtils.hasText(keyword)) {
- return true;
- }
- String text = keyword.trim();
- return safe(payment.getRegistrant()).contains(text)
- || (ledger != null && (safe(ledger.getCustomerName()).contains(text)
- || safe(ledger.getSalesContractNo()).contains(text)
- || safe(ledger.getProjectName()).contains(text)));
- }
-
- private boolean matchLedgerCustomerKeyword(SalesLedger ledger, String keyword) {
- if (!StringUtils.hasText(keyword)) {
- return true;
- }
- String text = keyword.trim();
- return safe(ledger.getCustomerName()).contains(text)
- || safe(ledger.getSalesContractNo()).contains(text)
- || safe(ledger.getProjectName()).contains(text);
- }
-
- private Integer normalizeSeaType(String seaType) {
- if (!StringUtils.hasText(seaType)) {
- return null;
- }
- String value = seaType.trim().toLowerCase(Locale.ROOT);
- return switch (value) {
- case "private", "绉佹捣", "0" -> 0;
- case "public", "鍏捣", "1" -> 1;
- default -> null;
- };
- }
-
- private String customerSeaTypeName(Integer type) {
- if (type == null) {
- return "鏈煡";
- }
- return type == 1 ? "鍏捣" : "绉佹捣";
- }
-
- private int normalizeLimit(Integer limit) {
- if (limit == null || limit <= 0) {
- return DEFAULT_LIMIT;
- }
- return Math.min(limit, MAX_LIMIT);
- }
-
- private boolean tenantMatched(Long dataTenantId, Long userTenantId) {
- if (userTenantId == null) {
- return true;
- }
- return Objects.equals(dataTenantId, userTenantId);
- }
-
- private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, SFunction<T, Long> field) {
- if (tenantId != null) {
- wrapper.eq(field, tenantId);
- }
- }
-
- private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, SFunction<T, Long> field) {
- if (deptId != null) {
- wrapper.eq(field, deptId);
- }
- }
-
- private LoginUser currentLoginUser(String memoryId) {
- LoginUser loginUser = aiSessionUserContext.get(memoryId);
- if (loginUser != null) {
- return loginUser;
- }
- return SecurityUtils.getLoginUser();
- }
-
- private DateRange resolveDateRange(String startDate, String endDate, String timeRange) {
- LocalDate today = LocalDate.now();
- LocalDate explicitStart = parseLocalDate(startDate);
- LocalDate explicitEnd = parseLocalDate(endDate);
- 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(29), today, "杩�30澶�");
- }
- 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("鏈湀")) {
- return new DateRange(today.withDayOfMonth(1), today, "鏈湀");
- }
- if (text.contains("涓婃湀")) {
- YearMonth lastMonth = YearMonth.from(today).minusMonths(1);
- return new DateRange(lastMonth.atDay(1), lastMonth.atEndOfMonth(), "涓婃湀");
- }
- if (text.contains("浠婂勾") || text.contains("鏈勾")) {
- return new DateRange(today.withDayOfYear(1), 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 = RELATIVE_PATTERN.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(29);
- };
- return new DateRange(start, today, "杩�" + amount + unit);
- }
- Matcher dateMatcher = DATE_PATTERN.matcher(text);
- if (dateMatcher.find()) {
- LocalDate start = parseLocalDate(dateMatcher.group(1));
- LocalDate end = dateMatcher.find() ? parseLocalDate(dateMatcher.group(1)) : start;
- if (start != null && end != null) {
- if (start.isAfter(end)) {
- LocalDate temp = start;
- start = end;
- end = temp;
- }
- return new DateRange(start, end, start + "鑷�" + end);
- }
- }
- return new DateRange(today.minusDays(29), today, "杩�30澶�");
- }
-
- private LocalDate parseLocalDate(String text) {
- if (!StringUtils.hasText(text)) {
- return null;
- }
- try {
- return LocalDate.parse(text.trim(), DATE_FMT);
- } catch (Exception ignored) {
- return null;
- }
- }
-
- private Date toDate(LocalDate localDate) {
- return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
- }
-
- private Date toExclusiveEndDate(LocalDate localDate) {
- return Date.from(localDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
- }
-
- private LocalDate toLocalDate(Date date) {
- if (date == null) {
- return null;
- }
- return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
- }
-
- private String formatDate(Date date) {
- LocalDate localDate = toLocalDate(date);
- return formatDate(localDate);
- }
-
- private String formatDate(LocalDate date) {
- return date == null ? "" : date.format(DATE_FMT);
- }
-
- private String formatDateTime(LocalDateTime time) {
- return time == null ? "" : time.toString().replace('T', ' ');
- }
-
- private BigDecimal defaultDecimal(BigDecimal value) {
- return value == null ? BigDecimal.ZERO : value;
- }
-
- private BigDecimal maxZero(BigDecimal value) {
- return value == null || value.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : value;
- }
-
- private String toRate(long numerator, long denominator) {
- if (denominator <= 0) {
- return "0.00%";
- }
- BigDecimal rate = new BigDecimal(numerator)
- .multiply(ONE_HUNDRED)
- .divide(new BigDecimal(denominator), 2, RoundingMode.HALF_UP);
- return rate.toPlainString() + "%";
- }
-
- private String toPercent(BigDecimal decimal) {
- if (decimal == null) {
- return "0.00%";
- }
- BigDecimal rate = decimal.multiply(ONE_HUNDRED).setScale(2, RoundingMode.HALF_UP);
- return rate.toPlainString() + "%";
- }
-
- private String safe(Object value) {
- return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ').trim();
- }
-
- private <T> List<T> defaultList(List<T> list) {
- return list == null ? List.of() : list;
- }
-
- private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) {
- Map<String, Object> summary = new LinkedHashMap<>();
- summary.put("timeRange", range.label());
- summary.put("startDate", range.start().toString());
- summary.put("endDate", range.end().toString());
- summary.put("count", count);
- summary.put("keyword", safe(keyword));
- return summary;
- }
-
- private Map<String, Object> buildAmountBarOption(BigDecimal contractAmount,
- BigDecimal quotationAmount,
- BigDecimal receivedAmount,
- BigDecimal pendingAmount) {
- List<String> xData = List.of("鍚堝悓棰�", "鎶ヤ环棰�", "鍥炴棰�", "寰呭洖娆�");
- List<BigDecimal> yData = List.of(contractAmount, quotationAmount, receivedAmount, pendingAmount);
- 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", xData));
- option.put("yAxis", Map.of("type", "value"));
- option.put("series", List.of(Map.of("name", "閲戦", "type", "bar", "data", yData)));
- return option;
- }
-
- private Map<String, Object> buildShippingPieOption(long shippedCount, long pendingCount) {
- List<Map<String, Object>> data = List.of(
- Map.of("name", "宸插彂璐�", "value", shippedCount),
- Map.of("name", "鏈彂璐�", "value", pendingCount)
- );
- Map<String, Object> option = new LinkedHashMap<>();
- option.put("title", Map.of("text", "鍙戣揣鐘舵�佸垎甯�", "left", "center"));
- option.put("tooltip", Map.of("trigger", "item"));
- option.put("series", List.of(Map.of("type", "pie", "radius", "60%", "data", data)));
- return option;
- }
-
- private Map<String, Object> buildCustomerTopBarOption(List<Map<String, Object>> topCustomers) {
- List<String> xData = new ArrayList<>();
- List<BigDecimal> yData = new ArrayList<>();
- for (Map<String, Object> item : topCustomers) {
- xData.add(String.valueOf(item.get("customerName")));
- yData.add((BigDecimal) item.get("contractAmount"));
- }
- Map<String, Object> option = new LinkedHashMap<>();
- option.put("title", Map.of("text", "瀹㈡埛鍚堝悓棰漈OP5", "left", "center"));
- option.put("tooltip", Map.of("trigger", "axis"));
- option.put("xAxis", Map.of("type", "category", "data", xData));
- option.put("yAxis", Map.of("type", "value"));
- option.put("series", List.of(Map.of("name", "鍚堝悓棰�", "type", "bar", "data", yData)));
- return option;
- }
-
- private Map<String, Object> buildContractTrendLineOption(List<String> labels, List<BigDecimal> values) {
- 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", labels));
- option.put("yAxis", Map.of("type", "value"));
- option.put("series", List.of(Map.of("name", "鍚堝悓棰�", "type", "line", "smooth", true, "data", values)));
- return option;
- }
-
- private Map<String, Object> buildRiskLevelPieOption(long highCount, long mediumCount, long lowCount) {
- List<Map<String, Object>> data = List.of(
- Map.of("name", "楂橀闄�", "value", highCount),
- Map.of("name", "涓闄�", "value", mediumCount),
- Map.of("name", "浣庨闄�", "value", lowCount)
- );
- Map<String, Object> option = new LinkedHashMap<>();
- option.put("title", Map.of("text", "瀹㈡埛椋庨櫓绛夌骇鍒嗗竷", "left", "center"));
- option.put("tooltip", Map.of("trigger", "item"));
- option.put("series", List.of(Map.of("name", "椋庨櫓绛夌骇", "type", "pie", "radius", "60%", "data", data)));
- return option;
- }
-
- private Map<String, Object> buildRiskScoreBarOption(List<CustomerRiskMetric> metrics) {
- List<String> xData = metrics.stream().map(CustomerRiskMetric::getCustomerName).collect(Collectors.toList());
- List<Integer> yData = metrics.stream().map(CustomerRiskMetric::getRiskScore).collect(Collectors.toList());
- 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", xData));
- option.put("yAxis", Map.of("type", "value", "max", 100));
- option.put("series", List.of(Map.of("name", "椋庨櫓鍒嗗��", "type", "bar", "data", yData)));
- return option;
- }
-
- private Map<String, Object> buildPendingAmountBarOption(List<CustomerRiskMetric> metrics) {
- List<String> xData = metrics.stream().map(CustomerRiskMetric::getCustomerName).collect(Collectors.toList());
- List<BigDecimal> yData = metrics.stream().map(CustomerRiskMetric::getPendingAmount).collect(Collectors.toList());
- 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", xData));
- option.put("yAxis", Map.of("type", "value"));
- option.put("series", List.of(Map.of("name", "寰呭洖娆�", "type", "bar", "data", yData)));
- return option;
- }
-
- private Map<String, Object> buildPriorityPieOption(long high, long medium, long low) {
- List<Map<String, Object>> data = List.of(
- Map.of("name", "楂樹紭鍏堢骇", "value", high),
- Map.of("name", "涓紭鍏堢骇", "value", medium),
- Map.of("name", "浣庝紭鍏堢骇", "value", low)
- );
- Map<String, Object> option = new LinkedHashMap<>();
- option.put("title", Map.of("text", "绛栫暐浼樺厛绾у垎甯�", "left", "center"));
- option.put("tooltip", Map.of("trigger", "item"));
- option.put("series", List.of(Map.of("name", "浼樺厛绾�", "type", "pie", "radius", "60%", "data", data)));
- return option;
- }
-
- 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 record DateRange(LocalDate start, LocalDate end, String label) {
- }
-
- private record TrendData(List<String> labels, List<BigDecimal> values) {
- private List<Map<String, Object>> toItemList() {
- List<Map<String, Object>> items = new LinkedList<>();
- for (int i = 0; i < labels.size(); i++) {
- Map<String, Object> item = new LinkedHashMap<>();
- item.put("month", labels.get(i));
- item.put("amount", values.get(i));
- items.add(item);
- }
- return items;
- }
- }
-
- private static class CustomerRiskMetric {
- private final String customerName;
- private final List<Long> ledgerIds = new ArrayList<>();
- private final Map<Long, LocalDate> deliveryDateByLedgerId = new HashMap<>();
- private BigDecimal contractAmount = BigDecimal.ZERO;
- private BigDecimal receivedAmount = BigDecimal.ZERO;
- private BigDecimal pendingAmount = BigDecimal.ZERO;
- private BigDecimal pendingRate = BigDecimal.ZERO;
- private BigDecimal quoteAmount = BigDecimal.ZERO;
- private BigDecimal topSingleOrderAmount = BigDecimal.ZERO;
- private int orderCount;
- private int quoteCount;
- private LocalDate lastOrderDate;
- private long daysSinceLastOrder;
- private long overdueDeliveryCount;
- private int riskScore;
- private String riskLevel = "low";
- private List<String> riskReasons = new ArrayList<>();
-
- private CustomerRiskMetric(String customerName) {
- this.customerName = customerName;
- }
-
- private String getCustomerName() {
- return customerName;
- }
-
- private List<Long> getLedgerIds() {
- return ledgerIds;
- }
-
- private Map<Long, LocalDate> getDeliveryDateByLedgerId() {
- return deliveryDateByLedgerId;
- }
-
- private BigDecimal getContractAmount() {
- return contractAmount;
- }
-
- private void setContractAmount(BigDecimal contractAmount) {
- this.contractAmount = contractAmount;
- }
-
- private BigDecimal getReceivedAmount() {
- return receivedAmount;
- }
-
- private void setReceivedAmount(BigDecimal receivedAmount) {
- this.receivedAmount = receivedAmount;
- }
-
- private BigDecimal getPendingAmount() {
- return pendingAmount;
- }
-
- private void setPendingAmount(BigDecimal pendingAmount) {
- this.pendingAmount = pendingAmount;
- }
-
- private BigDecimal getPendingRate() {
- return pendingRate;
- }
-
- private void setPendingRate(BigDecimal pendingRate) {
- this.pendingRate = pendingRate;
- }
-
- private BigDecimal getQuoteAmount() {
- return quoteAmount;
- }
-
- private void setQuoteAmount(BigDecimal quoteAmount) {
- this.quoteAmount = quoteAmount;
- }
-
- private BigDecimal getTopSingleOrderAmount() {
- return topSingleOrderAmount;
- }
-
- private void setTopSingleOrderAmount(BigDecimal topSingleOrderAmount) {
- this.topSingleOrderAmount = topSingleOrderAmount;
- }
-
- private int getOrderCount() {
- return orderCount;
- }
-
- private void setOrderCount(int orderCount) {
- this.orderCount = orderCount;
- }
-
- private int getQuoteCount() {
- return quoteCount;
- }
-
- private void setQuoteCount(int quoteCount) {
- this.quoteCount = quoteCount;
- }
-
- private LocalDate getLastOrderDate() {
- return lastOrderDate;
- }
-
- private void setLastOrderDate(LocalDate lastOrderDate) {
- this.lastOrderDate = lastOrderDate;
- }
-
- private long getDaysSinceLastOrder() {
- return daysSinceLastOrder;
- }
-
- private void setDaysSinceLastOrder(long daysSinceLastOrder) {
- this.daysSinceLastOrder = daysSinceLastOrder;
- }
-
- private long getOverdueDeliveryCount() {
- return overdueDeliveryCount;
- }
-
- private void setOverdueDeliveryCount(long overdueDeliveryCount) {
- this.overdueDeliveryCount = overdueDeliveryCount;
- }
-
- private int getRiskScore() {
- return riskScore;
- }
-
- private void setRiskScore(int riskScore) {
- this.riskScore = riskScore;
- }
-
- private String getRiskLevel() {
- return riskLevel;
- }
-
- private void setRiskLevel(String riskLevel) {
- this.riskLevel = riskLevel;
- }
-
- private List<String> getRiskReasons() {
- return riskReasons;
- }
-
- private void setRiskReasons(List<String> riskReasons) {
- this.riskReasons = riskReasons;
- }
- }
-}
diff --git a/src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java b/src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java
index c331756..609c8f0 100644
--- a/src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java
+++ b/src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java
@@ -50,7 +50,7 @@
private static final String DEFAULT_APP_PAGE = "pages/index";
- @PostConstruct
+// @PostConstruct
public void init() {
GtApiConfiguration config = new GtApiConfiguration();
config.setAppId(getuiConfig.getAppId());
@@ -215,4 +215,4 @@
pushChannel.setAndroid(androidDTO);
return pushChannel;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/resources/application-jhy.yml b/src/main/resources/application-jhy.yml
index 8b99569..dcb952e 100644
--- a/src/main/resources/application-jhy.yml
+++ b/src/main/resources/application-jhy.yml
@@ -142,8 +142,8 @@
enabled: false
# redis 閰嶇疆
data:
- mongodb:
- uri: mongodb://114.132.189.42:9028/chat_memory_db
+# mongodb:
+# uri: mongodb://114.132.189.42:9028/chat_memory_db
# redis 閰嶇疆
redis:
# 鍦板潃
--
Gitblit v1.9.3