| doc/20260522_财务升级AI模块前端变更联调文档.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
doc/20260522_²ÆÎñÉý¼¶AIÄ£¿éǰ¶Ë±ä¸üÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,150 @@ # è´¢å¡æ¨¡åå级å AI 模ååç«¯åæ´èè°ææ¡£ï¼éè´/éå®/ç产/å¾ åï¼ æ´æ°æ¥æï¼2026-05-22 éç¨èå´ï¼`/sales-ai`ã`/purchase-ai`ã`/manufacturing-ai`ã`/xiaozhi`ï¼å®¡æ¹å¾ åï¼ ## 1. åæ´æ»è§ | 模å | 坹夿¥å£ | æ¯å¦éè¦å端æ¹é | ç»è®º | | --- | --- | --- | --- | | éå® AI | `POST /sales-ai/chat` | æ¯ | è´¢å¡å£å¾åæ¢å°æ°æ¶æ¬¾æ¨¡åï¼é¨å `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 | æ¶æ¬¾æ¥æï¼yyyy-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`ï¼éå®å°è´¦ï¼ åæ®µç»æä¸åï¼ä½éé¢å£å¾å·²åæ¢ï¼ - `receivedAmount` ç±æ°æ¶æ¬¾æ¨¡åæ±æ»å¾å°ï¼ - `pendingAmount = max(0, invoicedAmount - receivedAmount)`ï¼ - è¥æ¶æ¬¾è®°å½æªæ¾å¼å ³èå°è´¦ï¼åæå®¢æ·ç»´åº¦å åºå½éã å端æ¹é å»ºè®®ï¼ - 䏿¹å段åï¼ - éç¹åå½âå·²æ¶éé¢/å¾ åæ¬¾éé¢âæ¯å¦ä¸è´¢å¡å°è´¦ä¸è´ã ## 3. éè´ AI åæ´ï¼`/purchase-ai/chat`ï¼ ### 3.1 `type = purchase_stats`ï¼éè´ç»è®¡ï¼ 以ä¸å段已ä»å ä½å¼æ¹ä¸ºçå®ç»è®¡å¼ï¼ - `summary.paymentCount` - `summary.invoiceCount` - `summary.paymentAmount` - `summary.invoiceAmount` å票éé¢å£å¾ï¼ - ä¼å `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` éé¢å段å£å¾ï¼ - `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 è§£æã 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. å¾ åæé®ï¼âæ¥è¯¢æçå¾ å®¡æ¹å表â - æ ¡éªè¿åç»æä¸å级åä¸è´ï¼æ åæ®µç ´åï¼ã 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)) { 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()); } @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; }