19 小时以前 261a7d2645877369a8928d573e0fa0d978577ef3
src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java
@@ -199,8 +199,8 @@
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", range.start().toString());
        summary.put("endDate", range.end().toString());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("orderCount", bundle.orderMetrics().size());
        summary.put("totalRevenue", bundle.totalRevenue());
        summary.put("totalMaterialCost", bundle.totalMaterialCost());
@@ -268,8 +268,8 @@
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", range.start().toString());
        summary.put("endDate", range.end().toString());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("orderCount", metrics.size());
        summary.put("lossOrderCount", metrics.stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count());
        summary.put("lowProfitOrderCount", riskyOrders.size());
@@ -394,8 +394,8 @@
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", range.start().toString());
        summary.put("endDate", range.end().toString());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("actualIncomeTotal", collections.stream().map(AccountSalesCollection::getCollectionAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
        summary.put("actualExpenseTotal", payments.stream().map(AccountPurchasePayment::getPaymentAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
        summary.put("receivableBalance", receivableTotal);
@@ -481,8 +481,8 @@
        List<Map<String, Object>> topAnomalies = anomalyItems.stream().limit(finalLimit).toList();
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", range.start().toString());
        summary.put("endDate", range.end().toString());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("anomalyCount", topAnomalies.size());
        summary.put("highRiskCount", topAnomalies.stream().filter(item -> "high".equals(item.get("riskLevel"))).count());
        summary.put("mediumRiskCount", topAnomalies.stream().filter(item -> "medium".equals(item.get("riskLevel"))).count());
@@ -521,8 +521,8 @@
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", range.start().toString());
        summary.put("endDate", range.end().toString());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("outputValue", outputValue);
        summary.put("profit", profitBundle.totalProfit());
        summary.put("profitRate", toPercent(profitRate));
@@ -604,8 +604,8 @@
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("reportType", type);
        summary.put("timeRange", range.label());
        summary.put("startDate", range.start().toString());
        summary.put("endDate", range.end().toString());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("orderCount", bundle.orderMetrics().size());
        summary.put("lossOrderCount", lossCount);
        summary.put("riskSuggestionCount", riskSuggestions.size());
@@ -721,9 +721,11 @@
        applyTenantFilter(outWrapper, loginUser.getTenantId(), ProcurementRecordOut::getTenantId);
        applyDeptFilter(outWrapper, loginUser.getCurrentDeptId(), ProcurementRecordOut::getDeptId);
        outWrapper.eq(ProcurementRecordOut::getType, 2)
                .in(ProcurementRecordOut::getSalesLedgerProductId, ledgerProductIds)
                .ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
                .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
                .in(ProcurementRecordOut::getSalesLedgerProductId, ledgerProductIds);
        if (range.hasDateFilter()) {
            outWrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
                    .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
        }
        List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(outWrapper));
        Set<Integer> storageIds = outList.stream()
@@ -778,8 +780,10 @@
        LambdaQueryWrapper<ProductionOrder> orderWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(orderWrapper, loginUser.getCurrentDeptId(), ProductionOrder::getDeptId);
        orderWrapper.ge(ProductionOrder::getCreateTime, range.start().atStartOfDay().minusMonths(2))
                .lt(ProductionOrder::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
        if (range.hasDateFilter()) {
            orderWrapper.ge(ProductionOrder::getCreateTime, range.start().atStartOfDay().minusMonths(2))
                    .lt(ProductionOrder::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
        }
        List<ProductionOrder> orders = defaultList(productionOrderMapper.selectList(orderWrapper));
        Map<Long, Set<Long>> orderIdToLedgerIds = new HashMap<>();
@@ -815,9 +819,11 @@
        LambdaQueryWrapper<ProductionProductMain> mainWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(mainWrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId);
        mainWrapper.in(ProductionProductMain::getProductionOperationTaskId, taskIdToOrderId.keySet())
                .ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay().minusMonths(2))
                .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
        mainWrapper.in(ProductionProductMain::getProductionOperationTaskId, taskIdToOrderId.keySet());
        if (range.hasDateFilter()) {
            mainWrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay().minusMonths(2))
                    .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
        }
        List<ProductionProductMain> mainList = defaultList(productionProductMainMapper.selectList(mainWrapper));
        Map<Long, Set<Long>> mainIdToLedgers = new HashMap<>();
        for (ProductionProductMain main : mainList) {
@@ -837,9 +843,11 @@
        LambdaQueryWrapper<ProductionAccount> accountWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(accountWrapper, loginUser.getCurrentDeptId(), ProductionAccount::getDeptId);
        accountWrapper.in(ProductionAccount::getProductionProductMainId, mainIdToLedgers.keySet())
                .ge(ProductionAccount::getSchedulingDate, range.start().atStartOfDay())
                .lt(ProductionAccount::getSchedulingDate, range.end().plusDays(1).atStartOfDay());
        accountWrapper.in(ProductionAccount::getProductionProductMainId, mainIdToLedgers.keySet());
        if (range.hasDateFilter()) {
            accountWrapper.ge(ProductionAccount::getSchedulingDate, range.start().atStartOfDay())
                    .lt(ProductionAccount::getSchedulingDate, range.end().plusDays(1).atStartOfDay());
        }
        List<ProductionAccount> accountList = defaultList(productionAccountMapper.selectList(accountWrapper));
        Map<String, BigDecimal> salaryQuotaByOperation = defaultList(technologyOperationMapper.selectList(new LambdaQueryWrapper<TechnologyOperation>()
@@ -868,9 +876,11 @@
        LambdaQueryWrapper<ProductionProductOutput> outputWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(outputWrapper, loginUser.getCurrentDeptId(), ProductionProductOutput::getDeptId);
        outputWrapper.in(ProductionProductOutput::getProductionProductMainId, mainIdToLedgers.keySet())
                .ge(ProductionProductOutput::getCreateTime, range.start().atStartOfDay())
                .lt(ProductionProductOutput::getCreateTime, range.end().plusDays(1).atStartOfDay());
        outputWrapper.in(ProductionProductOutput::getProductionProductMainId, mainIdToLedgers.keySet());
        if (range.hasDateFilter()) {
            outputWrapper.ge(ProductionProductOutput::getCreateTime, range.start().atStartOfDay())
                    .lt(ProductionProductOutput::getCreateTime, range.end().plusDays(1).atStartOfDay());
        }
        List<ProductionProductOutput> outputList = defaultList(productionProductOutputMapper.selectList(outputWrapper));
        Map<Long, BigDecimal> scrapCostByLedger = new HashMap<>();
        for (ProductionProductOutput output : outputList) {
@@ -909,10 +919,14 @@
                    .or().like(SalesLedger::getProjectName, keyword)
                    .or().like(SalesLedger::getSalesman, keyword));
        }
        wrapper.ge(SalesLedger::getEntryDate, toDate(range.start()))
                .lt(SalesLedger::getEntryDate, toExclusiveEndDate(range.end()))
                .orderByDesc(SalesLedger::getEntryDate, SalesLedger::getId)
                .last("limit " + normalizeLimit(limit));
        if (range.hasDateFilter()) {
            wrapper.ge(SalesLedger::getEntryDate, toDate(range.start()))
                    .lt(SalesLedger::getEntryDate, toExclusiveEndDate(range.end()));
        }
        wrapper.orderByDesc(SalesLedger::getEntryDate, SalesLedger::getId);
        if (limit != null && limit > 0) {
            wrapper.last("limit " + normalizeLimit(limit));
        }
        return defaultList(salesLedgerMapper.selectList(wrapper));
    }
@@ -1002,8 +1016,10 @@
        if (productModelIds != null && !productModelIds.isEmpty()) {
            wrapper.in(ProcurementRecordOut::getProductModelId, productModelIds);
        }
        wrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
                .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
        if (range.hasDateFilter()) {
            wrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
                    .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
        }
        List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(wrapper));
        if (outList.isEmpty()) {
            return OutboundStats.empty();
@@ -1098,18 +1114,22 @@
    private List<AccountSalesCollection> queryCollections(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<AccountSalesCollection> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountSalesCollection::getDeptId);
        wrapper.ge(AccountSalesCollection::getCollectionDate, range.start())
                .le(AccountSalesCollection::getCollectionDate, range.end())
                .orderByAsc(AccountSalesCollection::getCollectionDate);
        if (range.hasDateFilter()) {
            wrapper.ge(AccountSalesCollection::getCollectionDate, range.start())
                    .le(AccountSalesCollection::getCollectionDate, range.end());
        }
        wrapper.orderByAsc(AccountSalesCollection::getCollectionDate);
        return defaultList(accountSalesCollectionMapper.selectList(wrapper));
    }
    private List<AccountPurchasePayment> queryPayments(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<AccountPurchasePayment> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchasePayment::getDeptId);
        wrapper.ge(AccountPurchasePayment::getPaymentDate, range.start())
                .le(AccountPurchasePayment::getPaymentDate, range.end())
                .orderByAsc(AccountPurchasePayment::getPaymentDate);
        if (range.hasDateFilter()) {
            wrapper.ge(AccountPurchasePayment::getPaymentDate, range.start())
                    .le(AccountPurchasePayment::getPaymentDate, range.end());
        }
        wrapper.orderByAsc(AccountPurchasePayment::getPaymentDate);
        return defaultList(accountPurchasePaymentMapper.selectList(wrapper));
    }
@@ -1118,8 +1138,12 @@
                                                       List<AccountPurchasePayment> payments) {
        Map<YearMonth, BigDecimal> incomeByMonth = new LinkedHashMap<>();
        Map<YearMonth, BigDecimal> expenseByMonth = new LinkedHashMap<>();
        YearMonth startMonth = YearMonth.from(range.start());
        YearMonth endMonth = YearMonth.from(range.end());
        DateRange monthlyRange = range.hasDateFilter() ? range : inferCashFlowRange(collections, payments);
        if (!monthlyRange.hasDateFilter()) {
            return List.of();
        }
        YearMonth startMonth = YearMonth.from(monthlyRange.start());
        YearMonth endMonth = YearMonth.from(monthlyRange.end());
        for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) {
            incomeByMonth.put(month, BigDecimal.ZERO);
            expenseByMonth.put(month, BigDecimal.ZERO);
@@ -1151,6 +1175,27 @@
            result.add(new MonthlyCashFlow(month.toString(), income, expense, income.subtract(expense)));
        }
        return result;
    }
    private DateRange inferCashFlowRange(List<AccountSalesCollection> collections,
                                         List<AccountPurchasePayment> payments) {
        LocalDate min = null;
        LocalDate max = null;
        for (AccountSalesCollection row : defaultList(collections)) {
            if (row.getCollectionDate() == null) {
                continue;
            }
            min = min == null || row.getCollectionDate().isBefore(min) ? row.getCollectionDate() : min;
            max = max == null || row.getCollectionDate().isAfter(max) ? row.getCollectionDate() : max;
        }
        for (AccountPurchasePayment row : defaultList(payments)) {
            if (row.getPaymentDate() == null) {
                continue;
            }
            min = min == null || row.getPaymentDate().isBefore(min) ? row.getPaymentDate() : min;
            max = max == null || row.getPaymentDate().isAfter(max) ? row.getPaymentDate() : max;
        }
        return min == null || max == null ? new DateRange(null, null, "全部") : new DateRange(min, max, "全部");
    }
    private List<MonthlyCashFlow> forecastMonthlyCashFlow(List<MonthlyCashFlow> actual, int forecastMonths) {
@@ -1352,6 +1397,9 @@
    }
    private DateRange previousSameLengthRange(DateRange range) {
        if (!range.hasDateFilter()) {
            return new DateRange(null, null, "全部");
        }
        long days = daysBetween(range.start(), range.end()) + 1L;
        LocalDate prevEnd = range.start().minusDays(1);
        LocalDate prevStart = prevEnd.minusDays(days - 1L);
@@ -1770,20 +1818,7 @@
        }
        if (!StringUtils.hasText(timeRange)) {
            if ("今天".equals(defaultLabel)) {
                return new DateRange(today, today, "今天");
            }
            if ("本周".equals(defaultLabel)) {
                LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L);
                return new DateRange(start, today, "本周");
            }
            if ("本月".equals(defaultLabel)) {
                return new DateRange(today.withDayOfMonth(1), today, "本月");
            }
            if ("近90天".equals(defaultLabel)) {
                return new DateRange(today.minusDays(89), today, "近90天");
            }
            return new DateRange(today.minusDays(29), today, defaultLabel);
            return new DateRange(null, null, "全部");
        }
        String text = timeRange.trim();
@@ -1840,7 +1875,7 @@
                return new DateRange(start, end, start + "至" + end);
            }
        }
        return new DateRange(today.minusDays(29), today, "近30天");
        return new DateRange(null, null, "全部");
    }
    private LocalDate parseLocalDate(String text) {
@@ -1870,6 +1905,10 @@
    }
    private String formatDate(LocalDate date) {
        return date == null ? "" : date.format(DATE_FMT);
    }
    private String displayDate(LocalDate date) {
        return date == null ? "" : date.format(DATE_FMT);
    }
@@ -1996,8 +2035,8 @@
    private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) {
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", range.start().toString());
        summary.put("endDate", range.end().toString());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("count", count);
        summary.put("keyword", safe(keyword));
        return summary;
@@ -2080,6 +2119,9 @@
    }
    private record DateRange(LocalDate start, LocalDate end, String label) {
        private boolean hasDateFilter() {
            return start != null && end != null;
        }
    }
    private record OrderProfitMetric(Long ledgerId,