From 05853ea02897a6242b01a16d6fa89da5fcfcd02a Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期五, 22 五月 2026 09:10:53 +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/PurchaseAgentTools.java | 603 +++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 567 insertions(+), 36 deletions(-)
diff --git a/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java b/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
index 92b739f..a294b68 100644
--- a/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
+++ b/src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
@@ -2,17 +2,29 @@
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ruoyi.account.mapper.purchase.AccountPaymentApplicationMapper;
+import com.ruoyi.account.mapper.purchase.AccountPurchaseInvoiceMapper;
+import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper;
+import com.ruoyi.account.pojo.purchase.AccountPaymentApplication;
+import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice;
+import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
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.procurementrecord.mapper.InboundManagementMapper;
+import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
+import com.ruoyi.procurementrecord.pojo.InboundManagement;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
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.quality.mapper.QualityInspectMapper;
+import com.ruoyi.quality.pojo.QualityInspect;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import com.ruoyi.stock.mapper.StockInRecordMapper;
+import com.ruoyi.stock.pojo.StockInRecord;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.ToolMemoryId;
@@ -21,38 +33,52 @@
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.*;
import java.util.stream.Collectors;
@Component
public class PurchaseAgentTools {
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai");
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 AccountPurchasePaymentMapper accountPurchasePaymentMapper;
+ private final AccountPaymentApplicationMapper accountPaymentApplicationMapper;
+ private final AccountPurchaseInvoiceMapper accountPurchaseInvoiceMapper;
+ private final StockInRecordMapper stockInRecordMapper;
+ private final QualityInspectMapper qualityInspectMapper;
private final AiSessionUserContext aiSessionUserContext;
public PurchaseAgentTools(PurchaseLedgerMapper purchaseLedgerMapper,
- PaymentRegistrationMapper paymentRegistrationMapper,
- InvoicePurchaseMapper invoicePurchaseMapper,
PurchaseReturnOrdersMapper purchaseReturnOrdersMapper,
+ SalesLedgerProductMapper salesLedgerProductMapper,
+ ProcurementRecordMapper procurementRecordMapper,
+ InboundManagementMapper inboundManagementMapper,
+ AccountPurchasePaymentMapper accountPurchasePaymentMapper,
+ AccountPaymentApplicationMapper accountPaymentApplicationMapper,
+ AccountPurchaseInvoiceMapper accountPurchaseInvoiceMapper,
+ StockInRecordMapper stockInRecordMapper,
+ QualityInspectMapper qualityInspectMapper,
AiSessionUserContext aiSessionUserContext) {
this.purchaseLedgerMapper = purchaseLedgerMapper;
- this.paymentRegistrationMapper = paymentRegistrationMapper;
- this.invoicePurchaseMapper = invoicePurchaseMapper;
this.purchaseReturnOrdersMapper = purchaseReturnOrdersMapper;
+ this.salesLedgerProductMapper = salesLedgerProductMapper;
+ this.procurementRecordMapper = procurementRecordMapper;
+ this.inboundManagementMapper = inboundManagementMapper;
+ this.accountPurchasePaymentMapper = accountPurchasePaymentMapper;
+ this.accountPaymentApplicationMapper = accountPaymentApplicationMapper;
+ this.accountPurchaseInvoiceMapper = accountPurchaseInvoiceMapper;
+ this.stockInRecordMapper = stockInRecordMapper;
+ this.qualityInspectMapper = qualityInspectMapper;
this.aiSessionUserContext = aiSessionUserContext;
}
@@ -78,7 +104,7 @@
wrapper.ge(PurchaseLedger::getEntryDate, toDate(start));
}
if (end != null) {
- wrapper.le(PurchaseLedger::getEntryDate, toDate(end));
+ wrapper.lt(PurchaseLedger::getEntryDate, toExclusiveEndDate(end));
}
wrapper.orderByDesc(PurchaseLedger::getEntryDate, PurchaseLedger::getId).last("limit " + finalLimit);
@@ -114,8 +140,8 @@
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<AccountPurchasePayment> payments = queryPayments(loginUser, range);
+ List<AccountPurchaseInvoice> invoices = queryInvoices(loginUser, range);
List<PurchaseReturnOrders> returns = queryReturns(loginUser, range);
BigDecimal contractAmount = ledgers.stream()
@@ -123,11 +149,11 @@
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal paymentAmount = payments.stream()
- .map(PaymentRegistration::getCurrentPaymentAmount)
+ .map(AccountPurchasePayment::getPaymentAmount)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal invoiceAmount = invoices.stream()
- .map(InvoicePurchase::getInvoiceAmount)
+ .map(this::invoiceAmountOf)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal returnAmount = returns.stream()
@@ -151,29 +177,473 @@
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<PurchaseLedger> matchedLedgers = queryLedgers(loginUser, range).stream()
+ .filter(ledger -> matchLedgerKeyword(ledger, keyword))
+ .collect(Collectors.toList());
+ Map<Long, BigDecimal> paidAmountByLedgerId = sumPaymentAmountByLedgerId(loginUser, matchedLedgers.stream()
+ .map(PurchaseLedger::getId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ List<Map<String, Object>> items = matchedLedgers.stream()
+ .map(ledger -> toPendingPaymentItem(ledger, paidAmountByLedgerId.getOrDefault(ledger.getId(), BigDecimal.ZERO)))
+ .filter(Objects::nonNull)
+ .sorted(Comparator.comparing(item -> (BigDecimal) item.get("pendingAmount"), Comparator.reverseOrder()))
+ .limit(normalizeLimit(limit))
+ .collect(Collectors.toList());
+
+ BigDecimal totalContractAmount = items.stream()
+ .map(item -> asBigDecimal(item.get("contractAmount")))
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ BigDecimal totalPaidAmount = items.stream()
+ .map(item -> asBigDecimal(item.get("paidAmount")))
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ BigDecimal totalPendingAmount = items.stream()
+ .map(item -> asBigDecimal(item.get("pendingAmount")))
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ Map<String, Object> summary = rangeSummary(range, items.size());
+ summary.put("pendingOrderCount", items.size());
+ summary.put("totalContractAmount", totalContractAmount);
+ summary.put("totalPaidAmount", totalPaidAmount);
+ summary.put("totalPendingAmount", totalPendingAmount);
+
+ return jsonResponse(true, "purchase_pending_payment_list", "宸茶繑鍥炲緟浠樻閲囪喘鍗曘��",
+ summary, 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()))
- .le(PurchaseLedger::getEntryDate, toDate(range.end()));
+ .lt(PurchaseLedger::getEntryDate, toExclusiveEndDate(range.end()));
return defaultList(purchaseLedgerMapper.selectList(wrapper));
}
- 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()))
- .le(PaymentRegistration::getPaymentDate, toDate(range.end()));
- return defaultList(paymentRegistrationMapper.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 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 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(PurchaseLedger ledger, BigDecimal paidAmount) {
+ BigDecimal contractAmount = defaultDecimal(ledger.getContractAmount());
+ BigDecimal safePaidAmount = defaultDecimal(paidAmount);
+ BigDecimal pendingAmount = contractAmount.subtract(safePaidAmount);
+ if (pendingAmount.compareTo(BigDecimal.ZERO) <= 0) {
+ return null;
+ }
+ Map<String, Object> item = toLedgerItem(ledger);
+ item.put("paidAmount", safePaidAmount);
+ item.put("pendingAmount", pendingAmount);
+ return item;
+ }
+
+ private Map<Long, BigDecimal> sumPaymentAmountByLedgerId(LoginUser loginUser, List<Long> purchaseLedgerIds) {
+ if (purchaseLedgerIds == null || purchaseLedgerIds.isEmpty()) {
+ return Map.of();
+ }
+ List<AccountPurchasePayment> payments = queryPayments(loginUser);
+ if (payments.isEmpty()) {
+ return Map.of();
+ }
+
+ Map<Integer, AccountPaymentApplication> applicationById = queryPaymentApplications(payments);
+ if (applicationById.isEmpty()) {
+ return Map.of();
+ }
+
+ Map<Long, StockInRecord> stockInRecordById = queryStockInRecords(applicationById.values());
+ Map<Long, Long> purchaseLedgerIdByQualityInspectId = queryPurchaseLedgerIdByQualityInspectId(stockInRecordById.values());
+ Set<Long> targetLedgerIdSet = new HashSet<>(purchaseLedgerIds);
+ Map<Long, BigDecimal> result = new HashMap<>();
+
+ for (AccountPurchasePayment payment : payments) {
+ if (payment.getAccountPaymentApplicationId() == null) {
+ continue;
+ }
+ AccountPaymentApplication application = applicationById.get(payment.getAccountPaymentApplicationId());
+ if (application == null) {
+ continue;
+ }
+ Set<Long> ledgerIds = resolvePurchaseLedgerIds(application, stockInRecordById, purchaseLedgerIdByQualityInspectId);
+ if (ledgerIds.isEmpty()) {
+ continue;
+ }
+ BigDecimal amount = defaultDecimal(payment.getPaymentAmount());
+ for (Long ledgerId : ledgerIds) {
+ if (targetLedgerIdSet.contains(ledgerId)) {
+ result.merge(ledgerId, amount, BigDecimal::add);
+ }
+ }
+ }
+ return result;
+ }
+
+ 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 BigDecimal asBigDecimal(Object value) {
+ if (value == null) {
+ return BigDecimal.ZERO;
+ }
+ if (value instanceof BigDecimal decimal) {
+ return decimal;
+ }
+ if (value instanceof Number number) {
+ return new BigDecimal(String.valueOf(number));
+ }
+ try {
+ return new BigDecimal(String.valueOf(value));
+ } catch (Exception ignored) {
+ return BigDecimal.ZERO;
+ }
+ }
+
+ private BigDecimal invoiceAmountOf(AccountPurchaseInvoice invoice) {
+ if (invoice == null) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal amount = defaultDecimal(invoice.getTaxInclusivePrice());
+ if (amount.compareTo(BigDecimal.ZERO) > 0) {
+ return amount;
+ }
+ return defaultDecimal(invoice.getTaxExclusivelPrice()).add(defaultDecimal(invoice.getTaxPrice()));
+ }
+
+ 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())
+ .orderByDesc(AccountPurchasePayment::getPaymentDate, AccountPurchasePayment::getId);
+ return defaultList(accountPurchasePaymentMapper.selectList(wrapper));
+ }
+
+ private List<AccountPurchasePayment> queryPayments(LoginUser loginUser) {
+ LambdaQueryWrapper<AccountPurchasePayment> wrapper = new LambdaQueryWrapper<>();
+ applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchasePayment::getDeptId);
+ return defaultList(accountPurchasePaymentMapper.selectList(wrapper));
+ }
+
+ private List<AccountPurchaseInvoice> queryInvoices(LoginUser loginUser, DateRange range) {
+ LambdaQueryWrapper<AccountPurchaseInvoice> wrapper = new LambdaQueryWrapper<>();
+ applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchaseInvoice::getDeptId);
+ wrapper.ge(AccountPurchaseInvoice::getIssueDate, range.start())
+ .le(AccountPurchaseInvoice::getIssueDate, range.end())
+ .orderByDesc(AccountPurchaseInvoice::getIssueDate, AccountPurchaseInvoice::getId);
+ return defaultList(accountPurchaseInvoiceMapper.selectList(wrapper));
+ }
+
+ private Map<Integer, AccountPaymentApplication> queryPaymentApplications(List<AccountPurchasePayment> payments) {
+ List<Integer> ids = payments.stream()
+ .map(AccountPurchasePayment::getAccountPaymentApplicationId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toList());
+ if (ids.isEmpty()) {
+ return Map.of();
+ }
+ return defaultList(accountPaymentApplicationMapper.selectBatchIds(ids)).stream()
+ .filter(item -> item.getId() != null)
+ .collect(Collectors.toMap(AccountPaymentApplication::getId, item -> item, (a, b) -> a));
+ }
+
+ private Map<Long, StockInRecord> queryStockInRecords(Collection<AccountPaymentApplication> applications) {
+ Set<Long> stockInRecordIds = new HashSet<>();
+ for (AccountPaymentApplication application : applications) {
+ stockInRecordIds.addAll(parseLongIds(application.getStockInRecordIds()));
+ }
+ if (stockInRecordIds.isEmpty()) {
+ return Map.of();
+ }
+ return defaultList(stockInRecordMapper.selectBatchIds(stockInRecordIds)).stream()
+ .filter(item -> item.getId() != null)
+ .collect(Collectors.toMap(StockInRecord::getId, item -> item, (a, b) -> a));
+ }
+
+ 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())))
+ .map(StockInRecord::getRecordId)
+ .collect(Collectors.toSet());
+ if (qualityInspectIds.isEmpty()) {
+ return Map.of();
+ }
+ return defaultList(qualityInspectMapper.selectBatchIds(qualityInspectIds)).stream()
+ .filter(item -> item.getId() != null && item.getPurchaseLedgerId() != null)
+ .collect(Collectors.toMap(QualityInspect::getId, QualityInspect::getPurchaseLedgerId, (a, b) -> a));
+ }
+
+ private Set<Long> resolvePurchaseLedgerIds(AccountPaymentApplication application,
+ Map<Long, StockInRecord> stockInRecordById,
+ Map<Long, Long> purchaseLedgerIdByQualityInspectId) {
+ Set<Long> result = new LinkedHashSet<>();
+ for (Long stockInRecordId : parseLongIds(application.getStockInRecordIds())) {
+ StockInRecord stockInRecord = stockInRecordById.get(stockInRecordId);
+ if (stockInRecord == null || stockInRecord.getRecordId() == null) {
+ continue;
+ }
+ if (stockInRecord.getApprovalStatus() != null && stockInRecord.getApprovalStatus() != 1) {
+ continue;
+ }
+ String recordType = safe(stockInRecord.getRecordType());
+ if ("7".equals(recordType)) {
+ result.add(stockInRecord.getRecordId());
+ } else if ("10".equals(recordType)) {
+ Long purchaseLedgerId = purchaseLedgerIdByQualityInspectId.get(stockInRecord.getRecordId());
+ if (purchaseLedgerId != null) {
+ result.add(purchaseLedgerId);
+ }
+ }
+ }
+ return result;
+ }
+
+ private List<Long> parseLongIds(String raw) {
+ if (!StringUtils.hasText(raw)) {
+ return List.of();
+ }
+ List<Long> result = new ArrayList<>();
+ for (String part : raw.split(",")) {
+ if (!StringUtils.hasText(part)) {
+ continue;
+ }
+ try {
+ result.add(Long.parseLong(part.trim()));
+ } catch (Exception ignored) {
+ }
+ }
+ return result;
+ }
+
private List<PurchaseReturnOrders> queryReturns(LoginUser loginUser, DateRange range) {
LambdaQueryWrapper<PurchaseReturnOrders> wrapper = new LambdaQueryWrapper<>();
@@ -197,7 +667,7 @@
}
private DateRange resolveDateRange(String startDate, String endDate, String timeRange) {
- LocalDate today = LocalDate.now();
+ LocalDate today = LocalDate.now(CHINA_ZONE_ID);
LocalDate start = parseLocalDate(startDate);
LocalDate end = parseLocalDate(endDate);
if (start != null || end != null) {
@@ -214,6 +684,22 @@
return new DateRange(today.minusDays(29), today, "杩�30澶�");
}
String text = timeRange.trim();
+ if (text.contains("浠婂ぉ")) {
+ return new DateRange(today, today, "浠婂ぉ");
+ }
+ if (text.contains("鏄ㄥぉ")) {
+ LocalDate yesterday = today.minusDays(1);
+ return new DateRange(yesterday, yesterday, "鏄ㄥぉ");
+ }
+ if (text.contains("鏈懆")) {
+ LocalDate startOfWeek = today.minusDays(today.getDayOfWeek().getValue() - 1L);
+ return new DateRange(startOfWeek, today, "鏈懆");
+ }
+ if (text.contains("涓婂懆")) {
+ LocalDate thisWeekStart = today.minusDays(today.getDayOfWeek().getValue() - 1L);
+ LocalDate startOfLastWeek = thisWeekStart.minusWeeks(1);
+ return new DateRange(startOfLastWeek, startOfLastWeek.plusDays(6), "涓婂懆");
+ }
if (text.contains("浠婂勾") || text.contains("鏈勾")) {
return new DateRange(today.withDayOfYear(1), today, "浠婂勾");
}
@@ -231,6 +717,19 @@
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澶�");
}
@@ -238,11 +737,19 @@
if (!StringUtils.hasText(text)) {
return null;
}
- return LocalDate.parse(text.trim(), DATE_FMT);
+ 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 String formatDate(Date date) {
@@ -312,4 +819,28 @@
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;
+ }
+ }
}
--
Gitblit v1.9.3