| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 = "æå
³é®ååæ¶é´èå´æ¥è¯¢éè´å°è´¦ï¼æ¯æè¿åæè¿Næ¡") |
| | | 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 = "æéè´å°è´¦IDæ¥è¯¢è¯¦æ
") |
| | | 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, "æ£å¸¸") |
| | | .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; |
| | | } |
| | | } |
| | | } |