From 2b66259ce096acedfbef751199dab50076b1c571 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期三, 27 五月 2026 11:10:11 +0800
Subject: [PATCH] Merge branch 'dev_New_pro' of http://114.132.189.42:9002/r/product-inventory-management-after into dev_New_pro

---
 src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java         |  156 +++++++++++++++++++-----------
 src/main/resources/financial-agent-prompt.txt                     |   12 +-
 src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java |   76 +++++++++-----
 3 files changed, 152 insertions(+), 92 deletions(-)

diff --git a/src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java b/src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java
index 9afadff..f402fc2 100644
--- a/src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java
+++ b/src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java
@@ -14,9 +14,10 @@
 public class FinancialIntentExecutor {
 
     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 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 static final Pattern RELATIVE_DAY_PATTERN = Pattern.compile("(?:杩憒鏈�杩�)\\s*(\\d{1,3})\\s*澶�");
+    private static final Pattern FUTURE_MONTH_PATTERN = Pattern.compile("(?:鏈潵|鍚庣画|鎺ヤ笅鏉�)\\s*(\\d{1,2})\\s*(?:涓�)?鏈�");
 
     private final FinancialAgentTools financialAgentTools;
 
@@ -42,18 +43,18 @@
         String endDate = dateRange.endDate();
         String timeRange = dateRange.label();
 
-        if (containsAny(text, "鎴愭湰鏍哥畻", "浜у搧鎴愭湰", "宸ュ簭鎴愭湰", "浜哄伐鎴愭湰", "鎶樻棫", "鏉愭枡鎹熻��")) {
+        if (containsAny(text, "鎴愭湰鏍哥畻", "浜у搧鎴愭湰", "宸ュ簭鎴愭湰", "浜哄伐鎴愭湰", "鎶樻棫", "鏉愭枡鎹熻��", "鎴愭湰鏈�楂�")) {
             return financialAgentTools.calculateIntelligentCost(memoryId, startDate, endDate, timeRange, keyword, limit);
         }
-        if (containsAny(text, "鍒╂鼎鍒嗘瀽", "璁㈠崟鍒╂鼎", "浜忔崯璁㈠崟", "浣庡埄娑�",
-                "鏈�璧氶挶瀹㈡埛", "鍝釜瀹㈡埛鏈�璧氶挶", "瀹㈡埛鏈�璧氶挶", "鍒╂鼎鏈�楂樺鎴�", "鍒╂鼎璐$尞鏈�楂樺鎴�", "鍒╂鼎涓嬮檷")) {
+        if (containsAny(text, "鍒╂鼎鍒嗘瀽", "璁㈠崟鍒╂鼎", "浜忔崯璁㈠崟", "浣庡埄娑�", "鏈�璧氶挶瀹㈡埛", "鍝釜瀹㈡埛鏈�璧氶挶",
+                "瀹㈡埛鏈�璧氶挶", "鍒╂鼎鏈�楂樺鎴�", "鍒╂鼎璐$尞鏈�楂�", "鍒╂鼎涓嬮檷")) {
             return financialAgentTools.analyzeOrderProfit(memoryId, startDate, endDate, timeRange, keyword, limit);
         }
         if (containsAny(text, "搴撳瓨璧勯噾", "搴撳瓨绉帇", "鍛嗘粸搴撳瓨", "璧勯噾鍗犵敤", "鍛ㄨ浆鐜�", "搴撳瓨鍛ㄨ浆")) {
             return financialAgentTools.analyzeInventoryCapital(memoryId, startDate, endDate, timeRange, keyword, limit);
         }
         if (containsAny(text, "鐜伴噾娴�", "鍥炴椋庨櫓", "浠樻鍘嬪姏", "璧勯噾缂哄彛", "搴旀敹", "搴斾粯", "鍥炴棰勬祴")) {
-            return financialAgentTools.forecastCashFlow(memoryId, startDate, endDate, timeRange, limit);
+            return financialAgentTools.forecastCashFlow(memoryId, startDate, endDate, timeRange, extractForecastMonths(text));
         }
         if (containsAny(text, "寮傚父棰勮", "缁忚惀寮傚父", "椋庨櫓棰勮", "鎴愭湰寮傚父", "鍒╂鼎寮傚父", "鍥炴寮傚父", "璁㈠崟椋庨櫓")) {
             return financialAgentTools.detectBusinessAnomalies(memoryId, startDate, endDate, timeRange, limit);
@@ -73,30 +74,35 @@
 
     private String tryExecuteQuickPrompt(String memoryId, String text) {
         String normalized = normalizeForMatch(text);
+        if ("鐢熸垚鏈懆缁忚惀鍛ㄦ姤鍒╂鼎涓庣幇閲戞祦".equals(normalized) || "鐢熸垚鏈懆缁忚惀鍛ㄦ姤".equals(normalized) || "鐢熸垚鍛ㄦ姤".equals(normalized)) {
+            DateRange range = weekRange();
+            return financialAgentTools.generateOperationReport(memoryId, range.startDate(), range.endDate(), range.label(), "weekly");
+        }
+        if ("鍒嗘瀽鏈湀鍒╂鼎涓嬮檷鍘熷洜".equals(normalized)) {
+            DateRange range = monthRange();
+            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
+        }
+        if ("杩�30澶╁摢涓鎴峰埄娑﹁础鐚渶楂�".equals(normalized)) {
+            DateRange range = recentDaysRange(30);
+            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
+        }
         if ("鏌ョ湅鏈湀缁忚惀椹鹃┒鑸�".equals(normalized) || "鏌ョ湅缁忚惀椹鹃┒鑸�".equals(normalized)) {
             DateRange range = monthRange();
             return financialAgentTools.getBusinessCockpit(memoryId, range.startDate(), range.endDate(), range.label());
         }
-        if ("鏌ヨ杩�30澶╀簭鎹熻鍗�".equals(normalized) || "鍝釜璁㈠崟浜忔崯".equals(normalized)) {
+        if ("鏌ヨ杩�30澶╀簭鎹熻鍗�".equals(normalized)) {
             DateRange range = recentDaysRange(30);
-            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, 20);
+            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
         }
-        if ("鐢熸垚鏈懆缁忚惀鍛ㄦ姤".equals(normalized) || "鐢熸垚鍛ㄦ姤".equals(normalized)) {
-            DateRange range = weekRange();
-            return financialAgentTools.generateOperationReport(memoryId, range.startDate(), range.endDate(), range.label(), "weekly");
+        if ("鍒嗘瀽杩�30澶╁簱瀛樿祫閲戝崰鐢�".equals(normalized)) {
+            DateRange range = recentDaysRange(30);
+            return financialAgentTools.analyzeInventoryCapital(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
         }
-        if ("涓轰粈涔堝埄娑︿笅闄�".equals(normalized)) {
-            DateRange range = monthRange();
-            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, 20);
+        if ("棰勬祴鏈潵3涓湀鐜伴噾娴�".equals(normalized)) {
+            return financialAgentTools.forecastCashFlow(memoryId, null, null, null, 3);
         }
-        if ("鍝釜瀹㈡埛鏈�璧氶挶".equals(normalized)
-                || "鏈�杩戝摢涓鎴锋渶璧氶挶".equals(normalized)
-                || "鏈湀鍝釜瀹㈡埛鏈�璧氶挶".equals(normalized)
-                || "杩�30澶╁摢涓鎴锋渶璧氶挶".equals(normalized)
-                || "鍝釜瀹㈡埛鍒╂鼎鏈�楂�".equals(normalized)
-                || "鍝釜瀹㈡埛鍒╂鼎璐$尞鏈�楂�".equals(normalized)) {
-            DateRange range = extractDateRange(text);
-            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, 20);
+        if ("鍝釜宸ュ簭鎴愭湰鏈�楂�".equals(normalized)) {
+            return financialAgentTools.calculateIntelligentCost(memoryId, null, null, null, null, null);
         }
         return null;
     }
@@ -112,7 +118,12 @@
 
     private Integer extractLimit(String text) {
         Matcher matcher = LIMIT_PATTERN.matcher(text);
-        return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10;
+        return matcher.find() ? Integer.parseInt(matcher.group(1)) : null;
+    }
+
+    private Integer extractForecastMonths(String text) {
+        Matcher matcher = FUTURE_MONTH_PATTERN.matcher(text);
+        return matcher.find() ? Integer.parseInt(matcher.group(1)) : null;
     }
 
     private DateRange extractDateRange(String text) {
@@ -128,7 +139,7 @@
         if (text.contains("涓婃湀")) {
             return lastMonthRange();
         }
-        if (text.contains("鏈勾") || text.contains("浠婂勾")) {
+        if (text.contains("浠婂勾") || text.contains("鏈勾")) {
             return yearRange();
         }
         if (text.contains("鏈懆")) {
@@ -136,17 +147,17 @@
         }
         Matcher relativeDayMatcher = RELATIVE_DAY_PATTERN.matcher(text);
         if (relativeDayMatcher.find()) {
-            int days = Integer.parseInt(relativeDayMatcher.group(2));
+            int days = Integer.parseInt(relativeDayMatcher.group(1));
             return recentDaysRange(days);
         }
-        return new DateRange(null, null, "杩�30澶�");
+        return new DateRange(null, null, null);
     }
 
     private DateRange buildDateRange(String start, String end, String label) {
         LocalDate startDate = parseDate(start);
         LocalDate endDate = parseDate(end);
         if (startDate == null || endDate == null) {
-            return new DateRange(null, null, "杩�30澶�");
+            return new DateRange(null, null, null);
         }
         if (startDate.isAfter(endDate)) {
             LocalDate temp = startDate;
@@ -200,7 +211,11 @@
         if (!StringUtils.hasText(text)) {
             return "";
         }
-        return text.replace("锛�", "")
+        return text.replace("锛�", "")
+                .replace("锛�", "")
+                .replace("(", "")
+                .replace(")", "")
+                .replace("锛�", "")
                 .replace(",", "")
                 .replace("銆�", "")
                 .replace(".", "")
@@ -218,13 +233,16 @@
 
     private String extractKeyword(String text) {
         String cleaned = text
+                .replaceAll("\\d{4}-\\d{2}-\\d{2}", "")
+                .replaceAll("(?:杩憒鏈�杩�)\\s*\\d{1,3}\\s*澶�", "")
+                .replaceAll("(?:鍓峾鏈�杩憒灞曠ず|杩斿洖)?\\s*\\d{1,2}\\s*(?:鏉涓獆鍚�)", "")
                 .replace("鏌ヨ", "")
                 .replace("鏌ョ湅", "")
                 .replace("鐪嬩笅", "")
                 .replace("鐪嬬湅", "")
                 .replace("甯垜", "")
                 .replace("璇�", "")
-                .replace("涓�涓�", "")
+                .replace("涓�涓�", "")
                 .replace("涓轰粈涔�", "")
                 .replace("鍝釜瀹㈡埛鏈�璧氶挶", "")
                 .replace("鏈�杩戝摢涓鎴锋渶璧氶挶", "")
diff --git a/src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java b/src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java
index 900242b..082b2cd 100644
--- a/src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java
+++ b/src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java
@@ -199,8 +199,8 @@
 
         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("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
         summary.put("orderCount", bundle.orderMetrics().size());
         summary.put("totalRevenue", bundle.totalRevenue());
         summary.put("totalMaterialCost", bundle.totalMaterialCost());
@@ -268,8 +268,8 @@
 
         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("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
         summary.put("orderCount", metrics.size());
         summary.put("lossOrderCount", metrics.stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count());
         summary.put("lowProfitOrderCount", riskyOrders.size());
@@ -394,8 +394,8 @@
 
         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("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
         summary.put("actualIncomeTotal", collections.stream().map(AccountSalesCollection::getCollectionAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
         summary.put("actualExpenseTotal", payments.stream().map(AccountPurchasePayment::getPaymentAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
         summary.put("receivableBalance", receivableTotal);
@@ -481,8 +481,8 @@
         List<Map<String, Object>> topAnomalies = anomalyItems.stream().limit(finalLimit).toList();
         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("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
         summary.put("anomalyCount", topAnomalies.size());
         summary.put("highRiskCount", topAnomalies.stream().filter(item -> "high".equals(item.get("riskLevel"))).count());
         summary.put("mediumRiskCount", topAnomalies.stream().filter(item -> "medium".equals(item.get("riskLevel"))).count());
@@ -521,8 +521,8 @@
 
         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("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
         summary.put("outputValue", outputValue);
         summary.put("profit", profitBundle.totalProfit());
         summary.put("profitRate", toPercent(profitRate));
@@ -604,8 +604,8 @@
         Map<String, Object> summary = new LinkedHashMap<>();
         summary.put("reportType", type);
         summary.put("timeRange", range.label());
-        summary.put("startDate", range.start().toString());
-        summary.put("endDate", range.end().toString());
+        summary.put("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
         summary.put("orderCount", bundle.orderMetrics().size());
         summary.put("lossOrderCount", lossCount);
         summary.put("riskSuggestionCount", riskSuggestions.size());
@@ -721,9 +721,11 @@
         applyTenantFilter(outWrapper, loginUser.getTenantId(), ProcurementRecordOut::getTenantId);
         applyDeptFilter(outWrapper, loginUser.getCurrentDeptId(), ProcurementRecordOut::getDeptId);
         outWrapper.eq(ProcurementRecordOut::getType, 2)
-                .in(ProcurementRecordOut::getSalesLedgerProductId, ledgerProductIds)
-                .ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
-                .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
+                .in(ProcurementRecordOut::getSalesLedgerProductId, ledgerProductIds);
+        if (range.hasDateFilter()) {
+            outWrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
+                    .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        }
         List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(outWrapper));
 
         Set<Integer> storageIds = outList.stream()
@@ -778,8 +780,10 @@
 
         LambdaQueryWrapper<ProductionOrder> orderWrapper = new LambdaQueryWrapper<>();
         applyDeptFilter(orderWrapper, loginUser.getCurrentDeptId(), ProductionOrder::getDeptId);
-        orderWrapper.ge(ProductionOrder::getCreateTime, range.start().atStartOfDay().minusMonths(2))
-                .lt(ProductionOrder::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
+        if (range.hasDateFilter()) {
+            orderWrapper.ge(ProductionOrder::getCreateTime, range.start().atStartOfDay().minusMonths(2))
+                    .lt(ProductionOrder::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
+        }
         List<ProductionOrder> orders = defaultList(productionOrderMapper.selectList(orderWrapper));
 
         Map<Long, Set<Long>> orderIdToLedgerIds = new HashMap<>();
@@ -815,9 +819,11 @@
 
         LambdaQueryWrapper<ProductionProductMain> mainWrapper = new LambdaQueryWrapper<>();
         applyDeptFilter(mainWrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId);
-        mainWrapper.in(ProductionProductMain::getProductionOperationTaskId, taskIdToOrderId.keySet())
-                .ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay().minusMonths(2))
-                .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
+        mainWrapper.in(ProductionProductMain::getProductionOperationTaskId, taskIdToOrderId.keySet());
+        if (range.hasDateFilter()) {
+            mainWrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay().minusMonths(2))
+                    .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
+        }
         List<ProductionProductMain> mainList = defaultList(productionProductMainMapper.selectList(mainWrapper));
         Map<Long, Set<Long>> mainIdToLedgers = new HashMap<>();
         for (ProductionProductMain main : mainList) {
@@ -837,9 +843,11 @@
 
         LambdaQueryWrapper<ProductionAccount> accountWrapper = new LambdaQueryWrapper<>();
         applyDeptFilter(accountWrapper, loginUser.getCurrentDeptId(), ProductionAccount::getDeptId);
-        accountWrapper.in(ProductionAccount::getProductionProductMainId, mainIdToLedgers.keySet())
-                .ge(ProductionAccount::getSchedulingDate, range.start().atStartOfDay())
-                .lt(ProductionAccount::getSchedulingDate, range.end().plusDays(1).atStartOfDay());
+        accountWrapper.in(ProductionAccount::getProductionProductMainId, mainIdToLedgers.keySet());
+        if (range.hasDateFilter()) {
+            accountWrapper.ge(ProductionAccount::getSchedulingDate, range.start().atStartOfDay())
+                    .lt(ProductionAccount::getSchedulingDate, range.end().plusDays(1).atStartOfDay());
+        }
         List<ProductionAccount> accountList = defaultList(productionAccountMapper.selectList(accountWrapper));
 
         Map<String, BigDecimal> salaryQuotaByOperation = defaultList(technologyOperationMapper.selectList(new LambdaQueryWrapper<TechnologyOperation>()
@@ -868,9 +876,11 @@
 
         LambdaQueryWrapper<ProductionProductOutput> outputWrapper = new LambdaQueryWrapper<>();
         applyDeptFilter(outputWrapper, loginUser.getCurrentDeptId(), ProductionProductOutput::getDeptId);
-        outputWrapper.in(ProductionProductOutput::getProductionProductMainId, mainIdToLedgers.keySet())
-                .ge(ProductionProductOutput::getCreateTime, range.start().atStartOfDay())
-                .lt(ProductionProductOutput::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        outputWrapper.in(ProductionProductOutput::getProductionProductMainId, mainIdToLedgers.keySet());
+        if (range.hasDateFilter()) {
+            outputWrapper.ge(ProductionProductOutput::getCreateTime, range.start().atStartOfDay())
+                    .lt(ProductionProductOutput::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        }
         List<ProductionProductOutput> outputList = defaultList(productionProductOutputMapper.selectList(outputWrapper));
         Map<Long, BigDecimal> scrapCostByLedger = new HashMap<>();
         for (ProductionProductOutput output : outputList) {
@@ -909,10 +919,14 @@
                     .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));
+        if (range.hasDateFilter()) {
+            wrapper.ge(SalesLedger::getEntryDate, toDate(range.start()))
+                    .lt(SalesLedger::getEntryDate, toExclusiveEndDate(range.end()));
+        }
+        wrapper.orderByDesc(SalesLedger::getEntryDate, SalesLedger::getId);
+        if (limit != null && limit > 0) {
+            wrapper.last("limit " + normalizeLimit(limit));
+        }
         return defaultList(salesLedgerMapper.selectList(wrapper));
     }
 
@@ -1002,8 +1016,10 @@
         if (productModelIds != null && !productModelIds.isEmpty()) {
             wrapper.in(ProcurementRecordOut::getProductModelId, productModelIds);
         }
-        wrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
-                .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        if (range.hasDateFilter()) {
+            wrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
+                    .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        }
         List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(wrapper));
         if (outList.isEmpty()) {
             return OutboundStats.empty();
@@ -1098,18 +1114,22 @@
     private List<AccountSalesCollection> queryCollections(LoginUser loginUser, DateRange range) {
         LambdaQueryWrapper<AccountSalesCollection> wrapper = new LambdaQueryWrapper<>();
         applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountSalesCollection::getDeptId);
-        wrapper.ge(AccountSalesCollection::getCollectionDate, range.start())
-                .le(AccountSalesCollection::getCollectionDate, range.end())
-                .orderByAsc(AccountSalesCollection::getCollectionDate);
+        if (range.hasDateFilter()) {
+            wrapper.ge(AccountSalesCollection::getCollectionDate, range.start())
+                    .le(AccountSalesCollection::getCollectionDate, range.end());
+        }
+        wrapper.orderByAsc(AccountSalesCollection::getCollectionDate);
         return defaultList(accountSalesCollectionMapper.selectList(wrapper));
     }
 
     private List<AccountPurchasePayment> queryPayments(LoginUser loginUser, DateRange range) {
         LambdaQueryWrapper<AccountPurchasePayment> wrapper = new LambdaQueryWrapper<>();
         applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchasePayment::getDeptId);
-        wrapper.ge(AccountPurchasePayment::getPaymentDate, range.start())
-                .le(AccountPurchasePayment::getPaymentDate, range.end())
-                .orderByAsc(AccountPurchasePayment::getPaymentDate);
+        if (range.hasDateFilter()) {
+            wrapper.ge(AccountPurchasePayment::getPaymentDate, range.start())
+                    .le(AccountPurchasePayment::getPaymentDate, range.end());
+        }
+        wrapper.orderByAsc(AccountPurchasePayment::getPaymentDate);
         return defaultList(accountPurchasePaymentMapper.selectList(wrapper));
     }
 
@@ -1118,8 +1138,12 @@
                                                        List<AccountPurchasePayment> payments) {
         Map<YearMonth, BigDecimal> incomeByMonth = new LinkedHashMap<>();
         Map<YearMonth, BigDecimal> expenseByMonth = new LinkedHashMap<>();
-        YearMonth startMonth = YearMonth.from(range.start());
-        YearMonth endMonth = YearMonth.from(range.end());
+        DateRange monthlyRange = range.hasDateFilter() ? range : inferCashFlowRange(collections, payments);
+        if (!monthlyRange.hasDateFilter()) {
+            return List.of();
+        }
+        YearMonth startMonth = YearMonth.from(monthlyRange.start());
+        YearMonth endMonth = YearMonth.from(monthlyRange.end());
         for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) {
             incomeByMonth.put(month, BigDecimal.ZERO);
             expenseByMonth.put(month, BigDecimal.ZERO);
@@ -1151,6 +1175,27 @@
             result.add(new MonthlyCashFlow(month.toString(), income, expense, income.subtract(expense)));
         }
         return result;
+    }
+
+    private DateRange inferCashFlowRange(List<AccountSalesCollection> collections,
+                                         List<AccountPurchasePayment> payments) {
+        LocalDate min = null;
+        LocalDate max = null;
+        for (AccountSalesCollection row : defaultList(collections)) {
+            if (row.getCollectionDate() == null) {
+                continue;
+            }
+            min = min == null || row.getCollectionDate().isBefore(min) ? row.getCollectionDate() : min;
+            max = max == null || row.getCollectionDate().isAfter(max) ? row.getCollectionDate() : max;
+        }
+        for (AccountPurchasePayment row : defaultList(payments)) {
+            if (row.getPaymentDate() == null) {
+                continue;
+            }
+            min = min == null || row.getPaymentDate().isBefore(min) ? row.getPaymentDate() : min;
+            max = max == null || row.getPaymentDate().isAfter(max) ? row.getPaymentDate() : max;
+        }
+        return min == null || max == null ? new DateRange(null, null, "鍏ㄩ儴") : new DateRange(min, max, "鍏ㄩ儴");
     }
 
     private List<MonthlyCashFlow> forecastMonthlyCashFlow(List<MonthlyCashFlow> actual, int forecastMonths) {
@@ -1352,6 +1397,9 @@
     }
 
     private DateRange previousSameLengthRange(DateRange range) {
+        if (!range.hasDateFilter()) {
+            return new DateRange(null, null, "鍏ㄩ儴");
+        }
         long days = daysBetween(range.start(), range.end()) + 1L;
         LocalDate prevEnd = range.start().minusDays(1);
         LocalDate prevStart = prevEnd.minusDays(days - 1L);
@@ -1770,20 +1818,7 @@
         }
 
         if (!StringUtils.hasText(timeRange)) {
-            if ("浠婂ぉ".equals(defaultLabel)) {
-                return new DateRange(today, today, "浠婂ぉ");
-            }
-            if ("鏈懆".equals(defaultLabel)) {
-                LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L);
-                return new DateRange(start, today, "鏈懆");
-            }
-            if ("鏈湀".equals(defaultLabel)) {
-                return new DateRange(today.withDayOfMonth(1), today, "鏈湀");
-            }
-            if ("杩�90澶�".equals(defaultLabel)) {
-                return new DateRange(today.minusDays(89), today, "杩�90澶�");
-            }
-            return new DateRange(today.minusDays(29), today, defaultLabel);
+            return new DateRange(null, null, "鍏ㄩ儴");
         }
 
         String text = timeRange.trim();
@@ -1840,7 +1875,7 @@
                 return new DateRange(start, end, start + "鑷�" + end);
             }
         }
-        return new DateRange(today.minusDays(29), today, "杩�30澶�");
+        return new DateRange(null, null, "鍏ㄩ儴");
     }
 
     private LocalDate parseLocalDate(String text) {
@@ -1870,6 +1905,10 @@
     }
 
     private String formatDate(LocalDate date) {
+        return date == null ? "" : date.format(DATE_FMT);
+    }
+
+    private String displayDate(LocalDate date) {
         return date == null ? "" : date.format(DATE_FMT);
     }
 
@@ -1996,8 +2035,8 @@
     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("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
         summary.put("count", count);
         summary.put("keyword", safe(keyword));
         return summary;
@@ -2080,6 +2119,9 @@
     }
 
     private record DateRange(LocalDate start, LocalDate end, String label) {
+        private boolean hasDateFilter() {
+            return start != null && end != null;
+        }
     }
 
     private record OrderProfitMetric(Long ledgerId,
diff --git a/src/main/resources/financial-agent-prompt.txt b/src/main/resources/financial-agent-prompt.txt
index c7a4a3f..5b5fb02 100644
--- a/src/main/resources/financial-agent-prompt.txt
+++ b/src/main/resources/financial-agent-prompt.txt
@@ -2,10 +2,10 @@
 褰撳墠鏃ユ湡锛歿{currentDate}}锛堜腑鍥芥椂鍖猴級銆�
 
 宸ヤ綔瑙勫垯锛�
-1. 鐢ㄦ埛鎻愬嚭鈥滄煡銆侀棶銆佺粺璁°�佸垎鏋愩�侀璀︺�佸缓璁�佹姤鍛娾�濋渶姹傛椂锛屼紭鍏堣皟鐢ㄥ伐鍏疯繑鍥炵粨鏋勫寲 JSON锛屼笉缂栭�犱笟鍔℃暟鎹��
-2. 鍛戒腑鎴愭湰銆佸埄娑︺�佸簱瀛樿祫閲戙�佺幇閲戞祦銆侀璀︺�侀┚椹惰埍銆佹棩鎶ュ懆鎶ュ満鏅椂锛屼紭鍏堣皟鐢ㄥ搴斿伐鍏枫��
+1. 鐢ㄦ埛鎻愬嚭鈥滄煡銆侀棶銆佺粺璁°�佸垎鏋愩�侀璀︺�佸缓璁�佹姤鍛娾�濈被闇�姹傛椂锛屼紭鍏堣皟鐢ㄥ伐鍏疯繑鍥炵粨鏋勫寲 JSON锛屼笉缂栭�犱笟鍔℃暟鎹��
+2. 鍛戒腑鎴愭湰銆佸埄娑︺�佸簱瀛樿祫閲戙�佺幇閲戞祦銆侀璀︺�侀┚椹惰埍銆佹棩鎶ャ�佸懆鎶ョ瓑鍦烘櫙鏃讹紝浼樺厛璋冪敤瀵瑰簲宸ュ叿銆�
 3. 宸ュ叿杩斿洖 JSON 鏃讹紝鐩存帴杈撳嚭鍘熷 JSON 瀛楃涓诧紝涓嶈棰濆鍖呰9 Markdown锛屼篃涓嶈鍦ㄥ墠鍚庤拷鍔犺В閲婃枃鏈��
-4. 褰撶敤鎴烽棶棰樼己灏戞椂闂磋寖鍥存椂锛岄粯璁や娇鐢ㄥ伐鍏峰唴缃彛寰勶紙濡傝繎30澶┿�佹湰鏈堛�佽繎90澶╋級锛屽苟鍦ㄥ悗缁彲鎻愰啋鐢ㄦ埛琛ュ厖鑼冨洿銆�
-5. 鐢ㄦ埛闂�滀负浠�涔堝埄娑︿笅闄嶁�濃�滃摢涓鍗曚簭鎹熲�濃�滃摢涓鎴锋渶璧氶挶鈥濃�滃摢涓鎴峰埄娑﹁础鐚渶楂樷�濃�滃摢涓溅闂�/宸ュ簭鎴愭湰鏈�楂樷�濈瓑闂鏃讹紝浼樺厛鍩轰簬璁㈠崟鍒╂鼎涓庡伐搴忔垚鏈垎鏋愬伐鍏蜂綔绛斻��
-6. 鍥炵瓟蹇呴』浣跨敤涓枃锛涜嫢鏁版嵁涓嶈冻浠ュ緱鍑虹粨璁猴紝鏄庣‘鎸囧嚭缂哄皯鍝簺鍏抽敭瀛楁鎴栫瓫閫夋潯浠躲��
-7. 鐢ㄦ埛鎻愬埌鈥滀粖骞�/鏈湀/浠婂ぉ/鏈�杩�/涓婃湀/鍘诲勾鈥濈瓑鐩稿鏃堕棿鏃讹紝蹇呴』涓ユ牸鍩轰簬鈥滃綋鍓嶆棩鏈熲�濇崲绠楋紝绂佹鑷鍋囪骞翠唤銆�
+4. 鐢ㄦ埛娌℃湁鏄庣‘缁欏嚭鏃堕棿銆佸鎴枫�佷緵搴斿晢銆佷骇鍝併�佽鍗曘�佹暟閲忕瓑绛涢�夋潯浠舵椂锛屼笉瑕佽嚜琛岃ˉ鍏呮潯浠讹紱宸ュ叿鍙傛暟淇濇寔涓虹┖锛岀敱宸ュ叿鎸夊綋鍓嶄笟鍔″彛寰勬煡璇€��
+5. 鐢ㄦ埛鎻愬埌鈥滄湰鍛ㄣ�佹湰鏈堛�佷粖骞淬�佷粖澶┿�佹渶杩戙�佽繎30澶┿�佷笂鏈堛�佸幓骞粹�濈瓑鐩稿鏃堕棿鏃讹紝蹇呴』涓ユ牸鍩轰簬鈥滃綋鍓嶆棩鏈熲�濇崲绠楋紝绂佹鑷鍋囪骞翠唤銆�
+6. 鐢ㄦ埛闂�滀负浠�涔堝埄娑︿笅闄嶁�濃�滃摢涓鍗曚簭鎹熲�濃�滃摢涓鎴锋渶璧氶挶鈥濃�滃摢涓鎴峰埄娑﹁础鐚渶楂樷�濃�滃摢涓伐搴忔垚鏈渶楂樷�濈瓑闂鏃讹紝浼樺厛鍩轰簬璁㈠崟鍒╂鼎涓庡伐搴忔垚鏈垎鏋愬伐鍏蜂綔绛斻��
+7. 鍥炵瓟蹇呴』浣跨敤涓枃锛涜嫢鏁版嵁涓嶈冻浠ュ緱鍑虹粨璁猴紝鏄庣‘鎸囧嚭缂哄皯鍝簺鍏抽敭瀛楁鎴栫瓫閫夋潯浠躲��

--
Gitblit v1.9.3