From e93af9a26dfdedfa47c982151e8f8f0b3d65645b Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 22 五月 2026 09:20:06 +0800
Subject: [PATCH] fix(ai): 解决记录类型字符串前后空格导致的数据映射问题

---
 doc/20260522_财务升级AI模块前端变更联调文档.md                         |  150 ++++++++++++++++++++++++++++++
 src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java |    4 
 src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java    |  114 ----------------------
 3 files changed, 153 insertions(+), 115 deletions(-)

diff --git "a/doc/20260522_\350\264\242\345\212\241\345\215\207\347\272\247AI\346\250\241\345\235\227\345\211\215\347\253\257\345\217\230\346\233\264\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260522_\350\264\242\345\212\241\345\215\207\347\272\247AI\346\250\241\345\235\227\345\211\215\347\253\257\345\217\230\346\233\264\350\201\224\350\260\203\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..e011374
--- /dev/null
+++ "b/doc/20260522_\350\264\242\345\212\241\345\215\207\347\272\247AI\346\250\241\345\235\227\345\211\215\347\253\257\345\217\230\346\233\264\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -0,0 +1,150 @@
+# 璐㈠姟妯″潡鍗囩骇鍚� AI 妯″潡鍓嶇鍙樻洿鑱旇皟鏂囨。锛堥噰璐�/閿�鍞�/鐢熶骇/寰呭姙锛�
+
+鏇存柊鏃ユ湡锛�2026-05-22  
+閫傜敤鑼冨洿锛歚/sales-ai`銆乣/purchase-ai`銆乣/manufacturing-ai`銆乣/xiaozhi`锛堝鎵瑰緟鍔烇級
+
+## 1. 鍙樻洿鎬昏
+
+| 妯″潡 | 瀵瑰鎺ュ彛 | 鏄惁闇�瑕佸墠绔敼閫� | 缁撹 |
+| --- | --- | --- | --- |
+| 閿�鍞� AI | `POST /sales-ai/chat` | 鏄� | 璐㈠姟鍙e緞鍒囨崲鍒版柊鏀舵妯″瀷锛岄儴鍒� `type` 鐨勫瓧娈佃涔夊彉鍖� |
+| 閲囪喘 AI | `POST /purchase-ai/chat` | 鏄� | 浠樻/鍙戠エ/寰呬粯娆捐绠楀垏鎹㈠埌鏂拌储鍔¢摼璺紝缁熻鍊间粠鍗犱綅鏀逛负鐪熷疄鍊� |
+| 鐢熶骇 AI | `POST /manufacturing-ai/chat` | 鍚� | 宸叉牳鏌ワ紝鏃犳棫璐㈠姟閫昏緫渚濊禆锛屾棤瀛楁鍙樻洿 |
+| 寰呭姙 AI | `POST /xiaozhi/chat` | 鍚� | 宸叉牳鏌ワ紝鏃犳棫璐㈠姟閫昏緫渚濊禆锛屾棤瀛楁鍙樻洿 |
+
+## 2. 閿�鍞� AI 鍙樻洿锛坄/sales-ai/chat`锛�
+
+### 2.1 `type = sales_return_list`锛堥攢鍞��娆�/鍥炴璁板綍锛�
+
+褰撳墠杩斿洖鏁版嵁鏉ユ簮缁熶竴涓烘柊璐㈠姟琛� `account_sales_collection`锛屼笉鍐嶈蛋鏃ф敹娆鹃��璐ч�昏緫銆�
+
+`data.items[]` 鍏抽敭瀛楁锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+| --- | --- | --- |
+| id | number | 鏀舵璁板綍ID |
+| refundId | string | 鏄犲皠 `collectionNumber`锛屽墠绔彲缁х画浣滀负鈥滈��娆�/鍥炴鍗曞彿鈥濆睍绀� |
+| collectionNumber | string | 鏀舵鍗曞彿 |
+| paymentMethod | string | 鏀舵鏂瑰紡 |
+| actualAmount | number | 鏀舵閲戦锛堜笌 `collectionAmount` 鍚屽�硷級 |
+| collectionAmount | number | 鏀舵閲戦锛堟帹鑽愪富灞曠ず瀛楁锛� |
+| customerId | number | 瀹㈡埛ID |
+| remark | string | 澶囨敞 |
+| createTime | string | 鏀舵鏃ユ湡锛坹yyy-MM-dd锛� |
+
+`summary` 澧為噺鍏虫敞锛�
+- `returnAmount`锛氭椂闂磋寖鍥村唴閲戦姹囨�伙紙鎸� `collectionAmount` 缁熻锛�
+
+### 2.2 `type = sales_customer_interaction_list`锛堝鎴峰線鏉ワ級
+
+褰撳墠杩斿洖鍩轰簬鏂伴摼璺細
+`account_sales_collection.stock_out_record_ids -> stock_out_record(record_type=13) -> shipping_info -> sales_ledger`
+
+杩斿洖绾﹀畾锛�
+- 鏃犳暟鎹椂锛歚description = "no_customer_interactions"`
+- 鏈夋暟鎹椂锛歚description = "ok"`
+
+`summary` 鍏抽敭瀛楁锛�
+- `totalReceiptAmount`
+- `customerCount`
+
+`data.items[]` 鍏抽敭瀛楁锛�
+- `salesLedgerId`
+- `salesContractNo`
+- `customerName`
+- `projectName`
+- `receiptPaymentDate`
+- `receiptPaymentAmount`
+- `receiptPaymentType`
+- `collectionNumber`
+- `registrant`
+- `remark`
+
+### 2.3 `type = sales_ledger_list`锛堥攢鍞彴璐︼級
+
+瀛楁缁撴瀯涓嶅彉锛屼絾閲戦鍙e緞宸插垏鎹細
+- `receivedAmount` 鐢辨柊鏀舵妯″瀷姹囨�诲緱鍒帮紱
+- `pendingAmount = max(0, invoicedAmount - receivedAmount)`锛�
+- 鑻ユ敹娆捐褰曟湭鏄惧紡鍏宠仈鍙拌处锛屽垯鎸夊鎴风淮搴﹀厹搴曞綊闆嗐��
+
+鍓嶇鏀归�犲缓璁細
+- 涓嶆敼瀛楁鍚嶏紱
+- 閲嶇偣鍥炲綊鈥滃凡鏀堕噾棰�/寰呭洖娆鹃噾棰濃�濇槸鍚︿笌璐㈠姟鍙拌处涓�鑷淬��
+
+## 3. 閲囪喘 AI 鍙樻洿锛坄/purchase-ai/chat`锛�
+
+### 3.1 `type = purchase_stats`锛堥噰璐粺璁★級
+
+浠ヤ笅瀛楁宸蹭粠鍗犱綅鍊兼敼涓虹湡瀹炵粺璁″�硷細
+- `summary.paymentCount`
+- `summary.invoiceCount`
+- `summary.paymentAmount`
+- `summary.invoiceAmount`
+
+鍙戠エ閲戦鍙e緞锛�
+- 浼樺厛 `taxInclusivePrice`
+- 鑻ヤ负绌�/0锛屽垯浣跨敤 `taxExclusivelPrice + taxPrice`
+
+### 3.2 `type = purchase_pending_payment_list`锛堝緟浠樻閲囪喘鍗曪級
+
+鏍稿績璁$畻宸插垏鎹㈠埌鏂拌储鍔¢摼璺細
+
+`account_purchase_payment -> account_payment_application -> stock_in_record -> (purchase_ledger / quality_inspect) -> purchase_ledger_id`
+
+鏄犲皠瑙勫垯锛�
+1. `stock_in_record.record_type = 7`锛歚record_id` 鐩存帴瑙嗕负 `purchase_ledger_id`
+2. `stock_in_record.record_type = 10`锛氶�氳繃 `quality_inspect.id = record_id` 鍙� `quality_inspect.purchase_ledger_id`
+
+閲戦瀛楁鍙e緞锛�
+- `paidAmount`锛氭柊閾捐矾绱宸蹭粯娆鹃噾棰�
+- `pendingAmount = contractAmount - paidAmount`锛�<=0 鐨勮褰曚笉杩斿洖锛�
+
+`summary` 鍏抽敭瀛楁锛堝潎涓虹湡瀹炲�硷級锛�
+- `pendingOrderCount`
+- `totalContractAmount`
+- `totalPaidAmount`
+- `totalPendingAmount`
+
+### 3.3 鏁版嵁娓呮礂淇
+
+宸蹭慨澶� `record_type` 甯︾┖鏍煎鑷寸殑鏄犲皠涓㈠け闂锛堝悗绔粺涓� `trim()` 鍚庡啀鍒ゆ柇 `7/10`锛夈��
+
+## 4. 鐢熶骇 AI / 寰呭姙 AI 鏍告煡缁撹
+
+宸叉牳鏌ヤ互涓嬫ā鍧椾唬鐮侊紝鏈彂鐜版棫璐㈠姟閫昏緫鑰﹀悎鐐癸細
+- `ManufacturingAgentTools`锛堢敓浜э級
+- `ApproveTodoTools`锛堝緟鍔炲鎵癸級
+
+缁撹锛�
+- 瀵瑰 `type` 涓庡瓧娈电粨鏋勬棤鍙樻洿锛�
+- 鍓嶇鏃犻渶鍋氬吋瀹规敼閫狅紝浠呴渶鍋氫竴娆″洖褰掗獙璇併��
+
+## 5. 鍓嶇鑱旇皟瑕佺偣
+
+1. `/sales-ai/chat`銆乣/purchase-ai/chat` 缁х画鎸� SSE 鏂囨湰娴佹嫾鎺ュ悗鍋� JSON 瑙f瀽銆�  
+2. 鎸� `type` 璺敱娓叉煋锛屼笉瑕佷粎渚濊禆 `description` 鏂囨銆�  
+3. `sales_customer_interaction_list` 闇�鍏煎 `description` 鏋氫妇锛歚ok` / `no_customer_interactions`銆�  
+4. `sales_return_list` 閲戦灞曠ず缁熶竴鐢� `collectionAmount`锛坄actualAmount` 淇濈暀鍏煎锛夈��  
+5. `purchase_pending_payment_list` 鐨勬眹鎬诲崱鐗囪鐩存帴璇诲彇 `summary.totalPendingAmount` 绛夊瓧娈碉紝涓嶅啀鍓嶇浜屾浼扮畻銆�  
+
+## 6. 鍥炲綊娓呭崟锛堝缓璁級
+
+### 閿�鍞�
+1. 鎻愰棶锛氣�滆繎30澶╁摢涓鍗曞洖娆炬渶灏戔��  
+   - 鏍¢獙 `sales_ledger_list` 鐨� `receivedAmount/pendingAmount`銆�  
+2. 鎻愰棶锛氣�滄煡璇㈡湰鏈堥攢鍞��娆锯��  
+   - 鏍¢獙 `sales_return_list` 鐨� `collectionNumber/collectionAmount/returnAmount`銆�  
+3. 鎻愰棶锛氣�滄煡璇㈡湰鏈堝鎴峰線鏉モ��  
+   - 鏍¢獙 `sales_customer_interaction_list` 鐨� `totalReceiptAmount/customerCount`銆�  
+
+### 閲囪喘
+1. 鎻愰棶锛氣�滅粺璁℃湰鏈堥噰璐暟鎹��  
+   - 鏍¢獙 `purchase_stats` 鐨� `paymentCount/invoiceCount/paymentAmount/invoiceAmount` 闈炲浐瀹�0銆�  
+2. 鎻愰棶锛氣�滃垪鍑哄緟浠樻閲囪喘鍗曗��  
+   - 鏍¢獙 `purchase_pending_payment_list` 鐨� `paidAmount/pendingAmount` 涓庤储鍔″疄闄呬竴鑷淬��  
+
+### 鐢熶骇/寰呭姙
+1. 鐢熶骇鎻愰棶锛氣�滄煡璇㈡湰鍛ㄨ澶囩淮淇褰曗��  
+2. 寰呭姙鎻愰棶锛氣�滄煡璇㈡垜鐨勫緟瀹℃壒鍒楄〃鈥�  
+   - 鏍¢獙杩斿洖缁撴瀯涓庡崌绾у墠涓�鑷达紙鏃犲瓧娈电牬鍧忥級銆�
+
diff --git a/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java b/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
index a294b68..bdd1f1f 100644
--- a/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
+++ b/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
@@ -591,7 +591,7 @@
     private Map<Long, Long> queryPurchaseLedgerIdByQualityInspectId(Collection<StockInRecord> stockInRecords) {
         Set<Long> qualityInspectIds = stockInRecords.stream()
                 .filter(Objects::nonNull)
-                .filter(item -> item.getRecordId() != null && "10".equals(safe(item.getRecordType())))
+                .filter(item -> item.getRecordId() != null && "10".equals(safe(item.getRecordType()).trim()))
                 .map(StockInRecord::getRecordId)
                 .collect(Collectors.toSet());
         if (qualityInspectIds.isEmpty()) {
@@ -614,7 +614,7 @@
             if (stockInRecord.getApprovalStatus() != null && stockInRecord.getApprovalStatus() != 1) {
                 continue;
             }
-            String recordType = safe(stockInRecord.getRecordType());
+            String recordType = safe(stockInRecord.getRecordType()).trim();
             if ("7".equals(recordType)) {
                 result.add(stockInRecord.getRecordId());
             } else if ("10".equals(recordType)) {
diff --git a/src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java b/src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java
index 8a63db7..0f7e586 100644
--- a/src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java
+++ b/src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java
@@ -121,60 +121,6 @@
                                       @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit) {
         LoginUser loginUser = currentLoginUser(memoryId);
         DateRange range = resolveDateRange(startDate, endDate, null);
-        /*
-        List<AccountSalesCollection> collections = queryCollections(loginUser, range);
-        if (collections.isEmpty()) {
-            return jsonResponse(true, "sales_customer_interaction_list", "閺堫亝鐓$拠銏犲煂鐎广垺鍩涘鈧弶銉唶瑜�?, rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
-        }
-
-        Map<Integer, Set<Long>> ledgerIdsByCollectionId = mapCollectionLedgerIds(loginUser, collections);
-        Set<Long> ledgerIds = ledgerIdsByCollectionId.values().stream()
-                .flatMap(Collection::stream)
-                .collect(Collectors.toSet());
-        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));
-
-        int finalLimit = normalizeLimit(limit);
-        List<Map<String, Object>> items = new ArrayList<>();
-        for (AccountSalesCollection collection : collections) {
-            Set<Long> relatedLedgerIds = ledgerIdsByCollectionId.get(collection.getId());
-            if (relatedLedgerIds == null || relatedLedgerIds.isEmpty()) {
-                if (!matchInteractionKeyword(collection, null, keyword)) {
-                    continue;
-                }
-                items.add(toInteractionItem(collection, null));
-                if (items.size() >= finalLimit) {
-                    break;
-                }
-                continue;
-            }
-            for (Long ledgerId : relatedLedgerIds) {
-                SalesLedger ledger = ledgerMap.get(ledgerId);
-                if (ledger == null || !matchInteractionKeyword(collection, ledger, keyword)) {
-                    continue;
-                }
-                items.add(toInteractionItem(collection, ledger));
-                if (items.size() >= finalLimit) {
-                    break;
-                }
-            }
-            if (items.size() >= finalLimit) {
-                break;
-            }
-        }
-
-        BigDecimal totalReceiptAmount = items.stream()
-                .map(item -> asBigDecimal(item.get("receiptPaymentAmount")))
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-        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());
-        */
         LambdaQueryWrapper<SalesQuotation> wrapper = new LambdaQueryWrapper<>();
         applyTenantFilter(wrapper, loginUser.getTenantId(), SalesQuotation::getTenantId);
         applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesQuotation::getDeptId);
@@ -398,59 +344,7 @@
                 .filter(StringUtils::hasText)
                 .distinct()
                 .count());
-        if (summary.size() >= 0) {
-            return jsonResponse(true, "sales_customer_interaction_list", "ok", summary, Map.of("items", items), Map.of());
-        }
-//        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());
-        return jsonResponse(true, "sales_customer_interaction_list", "宸茶繑鍥炲鎴峰線鏉ユ槑缁�", null, Map.of("items", List.of()), Map.of());
+        return jsonResponse(true, "sales_customer_interaction_list", "ok", summary, Map.of("items", items), Map.of());
     }
 
     @Tool(name = "鏌ヨ鍙戣揣鍙拌处", value = "鎸夊叧閿瘝鍜屾椂闂磋寖鍥存煡璇㈠彂璐у彴璐�")
@@ -965,12 +859,6 @@
             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;
     }
 

--
Gitblit v1.9.3