From 62fe432c21e9fd87d3fc7558c8c5a2863e220a98 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期四, 29 一月 2026 18:00:34 +0800
Subject: [PATCH] feat: 财务分析大屏接口

---
 src/main/resources/mapper/sales/SalesLedgerMapper.xml                         |   13 +
 src/main/java/com/ruoyi/home/controller/HomeController.java                   |   35 ++
 src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java             |    5 
 src/main/resources/mapper/account/AccountExpenseMapper.xml                    |   22 +
 src/main/resources/mapper/account/AccountIncomeMapper.xml                     |   13 +
 src/main/java/com/ruoyi/home/dto/MonthlyExpenditureDto.java                   |   31 ++
 src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml                   |   21 +
 src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java              |    5 
 src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java                |  381 +++++++++++++++++++++++++++++++
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java |    2 
 src/main/java/com/ruoyi/home/dto/MonthlyIncomeDto.java                        |   31 ++
 src/main/resources/mapper/sales/SalesLedgerProductMapper.xml                  |   21 +
 src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java               |    4 
 src/main/java/com/ruoyi/home/service/HomeService.java                         |   10 
 src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java                   |    5 
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java        |   62 +---
 src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java            |    2 
 17 files changed, 618 insertions(+), 45 deletions(-)

diff --git a/src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java b/src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java
index 8b6a644..3cd3fc8 100644
--- a/src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java
+++ b/src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java
@@ -10,6 +10,7 @@
 import com.ruoyi.dto.DateQueryDto;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
+import com.ruoyi.home.dto.IncomeExpenseAnalysisDto;
 
 import java.math.BigDecimal;
 import java.util.List;
@@ -24,4 +25,8 @@
 
     List<AccountDto2> report(@Param("dateQueryDto") DateQueryDto dateQueryDto);
     BigDecimal report1(@Param("dateQueryDto") DateQueryDto dateQueryDto, @Param("dictValue") String dictValue);
+
+    List<IncomeExpenseAnalysisDto> selectAccountExpenseStats(@Param("startDate") String startDate, @Param("endDate") String endDate, @Param("dateFormat") String dateFormat);
+
+    List<com.ruoyi.dto.MapDto> selectExpenseComposition(@Param("startDate") String startDate, @Param("endDate") String endDate);
 }
diff --git a/src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java b/src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java
index 76a3c55..9a9f951 100644
--- a/src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java
+++ b/src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java
@@ -7,6 +7,7 @@
 import com.ruoyi.account.pojo.AccountFile;
 import com.ruoyi.account.pojo.AccountIncome;
 import com.ruoyi.dto.DateQueryDto;
+import com.ruoyi.home.dto.IncomeExpenseAnalysisDto;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
@@ -23,4 +24,7 @@
 
     List<AccountDto2> report(@Param("dateQueryDto") DateQueryDto dateQueryDto);
     BigDecimal report1(@Param("dateQueryDto") DateQueryDto dateQueryDto, @Param("dictValue") String dictValue);
+
+    List<IncomeExpenseAnalysisDto> selectIncomeStats(@Param("startDate") String startDate, @Param("endDate") String endDate, @Param("dateFormat") String dateFormat);
+
 }
diff --git a/src/main/java/com/ruoyi/home/controller/HomeController.java b/src/main/java/com/ruoyi/home/controller/HomeController.java
index 82dc3b3..bf64b65 100644
--- a/src/main/java/com/ruoyi/home/controller/HomeController.java
+++ b/src/main/java/com/ruoyi/home/controller/HomeController.java
@@ -132,6 +132,41 @@
         return AjaxResult.success(list);
     }
 
+    @GetMapping("/incomeExpenseAnalysis")
+    @ApiOperation("鏀敹瀵规瘮鍒嗘瀽")
+    public AjaxResult incomeExpenseAnalysis(@RequestParam(value = "type", defaultValue = "1") Integer type) {
+        List<Map<String, Object>> result = homeService.incomeExpenseAnalysis(type);
+        return AjaxResult.success(result);
+    }
+
+    @GetMapping("/profitTrendAnalysis")
+    @ApiOperation("鍒╂鼎瓒嬪娍鍒嗘瀽")
+    public AjaxResult profitTrendAnalysis(){
+        List<MapDto> list = homeService.profitTrendAnalysis();
+        return AjaxResult.success(list);
+    }
+
+    @GetMapping("/expenseCompositionAnalysis")
+    @ApiOperation("鏋勬垚鍒嗘瀽")
+    public AjaxResult expenseCompositionAnalysis(@RequestParam(value = "type", defaultValue = "1") Integer type) {
+        List<MapDto> list = homeService.expenseCompositionAnalysis(type);
+        return AjaxResult.success(list);
+    }
+
+    @GetMapping("/monthlyIncome")
+    @ApiOperation("鏈堝害鏀跺叆")
+    public AjaxResult monthlyIncome(){
+        MonthlyIncomeDto dto = homeService.monthlyIncome();
+        return AjaxResult.success(dto);
+    }
+
+   @GetMapping("/monthlyExpenditure")
+   @ApiOperation("鏈堝害鏀嚭")
+   public AjaxResult monthlyExpenditure(){
+        MonthlyExpenditureDto dto = homeService.monthlyExpenditure();
+        return AjaxResult.success(dto);
+   }
+
     /********************************************************钀ラ攢閲囪喘绫�**************************************************/
     @GetMapping("/business")
     @Log(title = "閿�鍞�-閲囪喘-搴撳瓨鏁版嵁", businessType = BusinessType.OTHER)
diff --git a/src/main/java/com/ruoyi/home/dto/MonthlyExpenditureDto.java b/src/main/java/com/ruoyi/home/dto/MonthlyExpenditureDto.java
new file mode 100644
index 0000000..f4e5b9f
--- /dev/null
+++ b/src/main/java/com/ruoyi/home/dto/MonthlyExpenditureDto.java
@@ -0,0 +1,31 @@
+package com.ruoyi.home.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 鏈堝害鏀嚭 DTO
+ */
+@Data
+public class MonthlyExpenditureDto {
+    /**
+     * 鏈堝害鎬绘敮鍑�
+     */
+    private BigDecimal monthlyExpenditure = BigDecimal.ZERO;
+
+    /**
+     * 浠樻鐜� (鐧惧垎姣�)
+     */
+    private String paymentRate = "0.00";
+
+    /**
+     * 姣涘埄娑� (閲戦)
+     */
+    private BigDecimal grossProfit = BigDecimal.ZERO;
+
+    /**
+     * 鍒╂鼎鐜� (鐧惧垎姣�)
+     */
+    private String profitMarginRate = "0.00";
+}
diff --git a/src/main/java/com/ruoyi/home/dto/MonthlyIncomeDto.java b/src/main/java/com/ruoyi/home/dto/MonthlyIncomeDto.java
new file mode 100644
index 0000000..f79b4bf
--- /dev/null
+++ b/src/main/java/com/ruoyi/home/dto/MonthlyIncomeDto.java
@@ -0,0 +1,31 @@
+package com.ruoyi.home.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 鏈堝害鏀跺叆 DTO
+ */
+@Data
+public class MonthlyIncomeDto {
+    /**
+     * 鏈堝害鏀跺叆
+     */
+    private BigDecimal monthlyIncome = BigDecimal.ZERO;
+
+    /**
+     * 鍥炴鐜�
+     */
+    private String collectionRate = "0.00";
+
+    /**
+     * 閫炬湡鏁�
+     */
+    private BigDecimal overdueNum = BigDecimal.ZERO;
+
+    /**
+     * 閫炬湡鐜�
+     */
+    private String overdueRate = "0.00";
+}
diff --git a/src/main/java/com/ruoyi/home/service/HomeService.java b/src/main/java/com/ruoyi/home/service/HomeService.java
index 67cd4f3..5bb6b17 100644
--- a/src/main/java/com/ruoyi/home/service/HomeService.java
+++ b/src/main/java/com/ruoyi/home/service/HomeService.java
@@ -54,4 +54,14 @@
     List<Map<String, Object>> productInOutAnalysis(Integer type);
 
     List<MapDto> productTurnoverDays();
+
+    List<Map<String, Object>> incomeExpenseAnalysis(Integer type);
+
+    List<MapDto> profitTrendAnalysis();
+
+    List<MapDto> expenseCompositionAnalysis(Integer type);
+
+    MonthlyIncomeDto monthlyIncome();
+
+    MonthlyExpenditureDto monthlyExpenditure();
 }
diff --git a/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java b/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
index 137c5fb..828d6ae 100644
--- a/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
+++ b/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
@@ -3,7 +3,10 @@
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.account.mapper.AccountIncomeMapper;
+import com.ruoyi.account.pojo.AccountExpense;
 import com.ruoyi.home.mapper.HomeMapper;
+import com.ruoyi.account.mapper.AccountExpenseMapper;
 import com.ruoyi.approve.mapper.ApproveProcessMapper;
 import com.ruoyi.approve.pojo.ApproveProcess;
 import com.ruoyi.basic.mapper.CustomerMapper;
@@ -1098,4 +1101,382 @@
     public List<MapDto> productTurnoverDays() {
         return homeMapper.productTurnoverDays();
     }
+
+    @Autowired
+    private AccountExpenseMapper accountExpenseMapper;
+
+    @Autowired
+    private AccountIncomeMapper accountIncomeMapper;
+
+
+    public List<Map<String, Object>> incomeExpenseAnalysis(Integer type) {
+
+        LocalDate today = LocalDate.now();
+        LocalDate startDate;
+        LocalDate endDate;
+        String dateFormat;
+        DateTimeFormatter formatter;
+        List<String> xAxis = new ArrayList<>();
+
+        // 1. 鏃堕棿鑼冨洿 & X 杞�
+        if (type == 3) {
+            // 鏈搴︼紙鎸夋湀锛�
+            Month currentMonth = today.getMonth();
+            Month firstMonthOfQuarter = currentMonth.firstMonthOfQuarter();
+            startDate = today.withMonth(firstMonthOfQuarter.getValue())
+                    .with(TemporalAdjusters.firstDayOfMonth());
+            endDate = startDate.plusMonths(2)
+                    .with(TemporalAdjusters.lastDayOfMonth());
+
+            dateFormat = "%Y-%m";
+            formatter = DateTimeFormatter.ofPattern("yyyy-MM");
+
+            LocalDate tmp = startDate;
+            while (!tmp.isAfter(endDate)) {
+                xAxis.add(tmp.format(formatter));
+                tmp = tmp.plusMonths(1);
+            }
+
+        } else {
+            // 鍛� / 鏈堬紙鎸夊ぉ锛�
+            dateFormat = "%Y-%m-%d";
+            formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+            if (type == 1) {
+                startDate = today.with(DayOfWeek.MONDAY);
+                endDate = today.with(DayOfWeek.SUNDAY);
+            } else {
+                startDate = today.with(TemporalAdjusters.firstDayOfMonth());
+                endDate = today.with(TemporalAdjusters.lastDayOfMonth());
+            }
+
+            LocalDate tmp = startDate;
+            while (!tmp.isAfter(endDate)) {
+                xAxis.add(tmp.format(formatter));
+                tmp = tmp.plusDays(1);
+            }
+        }
+
+        String startStr = startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        String endStr = endDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+        // 2. 鏌ヨ鏁版嵁
+        List<IncomeExpenseAnalysisDto> incomeList = accountIncomeMapper.selectIncomeStats(startStr, endStr, dateFormat);
+
+//        List<IncomeExpenseAnalysisDto> purchaseList =
+//                purchaseLedgerMapper.selectPurchaseStats(startStr, endStr, dateFormat);
+
+        List<IncomeExpenseAnalysisDto> expenseList = accountExpenseMapper.selectAccountExpenseStats(startStr, endStr, dateFormat);
+
+        // 3. 杞� Map锛堣嚜鍔ㄥ悎骞讹級
+        Map<String, BigDecimal> incomeMap = incomeList.stream()
+                .collect(Collectors.toMap(
+                        IncomeExpenseAnalysisDto::getDateStr,
+                        IncomeExpenseAnalysisDto::getAmount,
+                        BigDecimal::add));
+
+//        Map<String, BigDecimal> purchaseMap = purchaseList.stream()
+//                .collect(Collectors.toMap(
+//                        IncomeExpenseAnalysisDto::getDateStr,
+//                        IncomeExpenseAnalysisDto::getAmount,
+//                        BigDecimal::add));
+
+        Map<String, BigDecimal> expenseMap = expenseList.stream()
+                .collect(Collectors.toMap(
+                        IncomeExpenseAnalysisDto::getDateStr,
+                        IncomeExpenseAnalysisDto::getAmount,
+                        BigDecimal::add));
+
+        // 4. 缁勮杩斿洖
+        List<Map<String, Object>> result = new ArrayList<>();
+
+        for (String dateStr : xAxis) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("date", dateStr);
+
+            // 鏀跺叆
+            BigDecimal income = incomeMap.getOrDefault(dateStr, BigDecimal.ZERO);
+            item.put("income", income.setScale(2, RoundingMode.HALF_UP));
+
+            // 鏀嚭 = 閲囪喘 + 璐㈠姟鏀嚭
+//            BigDecimal purchase = purchaseMap.getOrDefault(dateStr, BigDecimal.ZERO);
+            BigDecimal expense = expenseMap.getOrDefault(dateStr, BigDecimal.ZERO);
+//            BigDecimal totalExpense = purchase.add(expense);
+            BigDecimal totalExpense = expense;
+
+            item.put("expense", totalExpense.setScale(2, RoundingMode.HALF_UP));
+
+            result.add(item);
+        }
+
+        return result;
+    }
+
+
+    @Override
+    public List<MapDto> profitTrendAnalysis() {
+        LocalDate today = LocalDate.now();
+        LocalDate startDate = today.minusMonths(11).with(TemporalAdjusters.firstDayOfMonth());
+        LocalDate endDate = today.with(TemporalAdjusters.lastDayOfMonth());
+
+        String dateFormat = "%Y-%m";
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
+
+        List<String> months = new ArrayList<>();
+        LocalDate temp = startDate;
+        while (!temp.isAfter(endDate)) {
+            months.add(temp.format(formatter));
+            temp = temp.plusMonths(1);
+        }
+
+        String startStr = startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        String endStr = endDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+//        List<IncomeExpenseAnalysisDto> incomeList = salesLedgerMapper.selectIncomeStats(startStr, endStr, dateFormat);
+        List<IncomeExpenseAnalysisDto> incomeList = new ArrayList<>();
+        List<IncomeExpenseAnalysisDto> purchaseList = purchaseLedgerMapper.selectPurchaseStats(startStr, endStr, dateFormat);
+        List<IncomeExpenseAnalysisDto> expenseList = accountExpenseMapper.selectAccountExpenseStats(startStr, endStr, dateFormat);
+
+        Map<String, BigDecimal> incomeMap = incomeList.stream().collect(Collectors.toMap(IncomeExpenseAnalysisDto::getDateStr, IncomeExpenseAnalysisDto::getAmount, BigDecimal::add));
+        Map<String, BigDecimal> purchaseMap = purchaseList.stream().collect(Collectors.toMap(IncomeExpenseAnalysisDto::getDateStr, IncomeExpenseAnalysisDto::getAmount, BigDecimal::add));
+        Map<String, BigDecimal> expenseMap = expenseList.stream().collect(Collectors.toMap(IncomeExpenseAnalysisDto::getDateStr, IncomeExpenseAnalysisDto::getAmount, BigDecimal::add));
+
+        List<MapDto> result = new ArrayList<>();
+
+        for (String month : months) {
+            MapDto dto = new MapDto();
+            dto.setName(month);
+
+            BigDecimal income = incomeMap.getOrDefault(month, BigDecimal.ZERO);
+
+            BigDecimal purchase = purchaseMap.getOrDefault(month, BigDecimal.ZERO);
+             income = BigDecimal.ZERO;
+            BigDecimal expense = expenseMap.getOrDefault(month, BigDecimal.ZERO);
+            BigDecimal totalExpense = purchase.add(expense);
+
+            BigDecimal profit = income.subtract(totalExpense);
+
+            dto.setValue(profit.setScale(2, RoundingMode.HALF_UP).toString());
+            result.add(dto);
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<MapDto> expenseCompositionAnalysis(Integer type) {
+        List<MapDto> result = new ArrayList<>();
+
+        if (type == 2) {
+            List<MapDto> customerList = salesLedgerMapper.selectCustomerSalesComposition();
+            if (customerList != null) {
+                result.addAll(customerList);
+            }
+        } else {
+            BigDecimal rawMaterialAmount = salesLedgerProductMapper.selectRawMaterialExpense();
+            MapDto rawMaterialDto = new MapDto();
+            rawMaterialDto.setName("鍘熸潗鏂�");
+            rawMaterialDto.setValue(rawMaterialAmount != null ? rawMaterialAmount.toString() : "0");
+            result.add(rawMaterialDto);
+
+            List<MapDto> expenseList = accountExpenseMapper.selectExpenseComposition(null, null);
+            if (expenseList != null) {
+                result.addAll(expenseList);
+            }
+        }
+
+        BigDecimal total = BigDecimal.ZERO;
+        for (MapDto dto : result) {
+            if (dto.getValue() != null) {
+                total = total.add(new BigDecimal(dto.getValue()));
+            }
+        }
+
+        if (total.compareTo(BigDecimal.ZERO) > 0) {
+            for (MapDto dto : result) {
+                if (dto.getValue() != null) {
+                    BigDecimal val = new BigDecimal(dto.getValue());
+                    String rate = val.divide(total, 4, RoundingMode.HALF_UP)
+                            .multiply(new BigDecimal("100"))
+                            .setScale(2, RoundingMode.HALF_UP)
+                            .toString();
+                    dto.setRate(rate);
+                } else {
+                    dto.setRate("0.00");
+                }
+            }
+        } else {
+            for (MapDto dto : result) {
+                dto.setRate("0.00");
+            }
+        }
+
+        return result;
+    }
+
+
+    @Override
+    public MonthlyIncomeDto monthlyIncome() {
+        MonthlyIncomeDto dto = new MonthlyIncomeDto();
+        LocalDate now = LocalDate.now();
+        YearMonth currentMonth = YearMonth.from(now);
+        LocalDateTime startOfMonth = currentMonth.atDay(1).atStartOfDay();
+        LocalDateTime endOfMonth = currentMonth.atEndOfMonth().atTime(23, 59, 59);
+
+        LambdaQueryWrapper<SalesLedgerProduct> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SalesLedgerProduct::getType, 1);
+        wrapper.ge(SalesLedgerProduct::getRegisterDate, startOfMonth);
+        wrapper.le(SalesLedgerProduct::getRegisterDate, endOfMonth);
+
+        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(wrapper);
+
+        if (CollectionUtils.isEmpty(products)) {
+            return dto;
+        }
+
+        BigDecimal collected = products.stream()
+                .map(SalesLedgerProduct::getInvoiceTotal)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        dto.setMonthlyIncome(collected);
+
+        BigDecimal overdue = products.stream()
+                .map(SalesLedgerProduct::getPendingInvoiceTotal)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        dto.setOverdueNum(overdue);
+
+        BigDecimal total = collected.add(overdue);
+
+        if (total.compareTo(BigDecimal.ZERO) > 0) {
+            String collectionRate = collected.divide(total, 4, RoundingMode.HALF_UP)
+                    .multiply(new BigDecimal("100"))
+                    .setScale(2, RoundingMode.HALF_UP)
+                    .toString();
+            dto.setCollectionRate(collectionRate);
+
+            String overdueRate = overdue.divide(total, 4, RoundingMode.HALF_UP)
+                    .multiply(new BigDecimal("100"))
+                    .setScale(2, RoundingMode.HALF_UP)
+                    .toString();
+            dto.setOverdueRate(overdueRate);
+        }
+
+        return dto;
+    }
+
+    public MonthlyExpenditureDto monthlyExpenditure() {
+
+        MonthlyExpenditureDto dto = new MonthlyExpenditureDto();
+
+        //  褰撴湀鏃堕棿鑼冨洿
+        LocalDate now = LocalDate.now();
+        YearMonth currentMonth = YearMonth.from(now);
+        LocalDateTime startOfMonth = currentMonth.atDay(1).atStartOfDay();
+        LocalDateTime endOfMonth = currentMonth.atEndOfMonth().atTime(23, 59, 59);
+
+        //  閲囪喘鍙拌处锛坱ype = 2锛�
+        LambdaQueryWrapper<SalesLedgerProduct> purchaseWrapper = new LambdaQueryWrapper<>();
+        purchaseWrapper.eq(SalesLedgerProduct::getType, 2);
+        purchaseWrapper.ge(SalesLedgerProduct::getRegisterDate, startOfMonth);
+        purchaseWrapper.le(SalesLedgerProduct::getRegisterDate, endOfMonth);
+
+        List<SalesLedgerProduct> purchaseProducts =
+                salesLedgerProductMapper.selectList(purchaseWrapper);
+
+        BigDecimal rawMaterialCost = BigDecimal.ZERO;   // 鍘熸潗鏂欐垚鏈�
+        BigDecimal paidAmount = BigDecimal.ZERO;        // 宸蹭粯娆鹃噾棰�
+        BigDecimal pendingAmount = BigDecimal.ZERO;     // 寰呬粯娆鹃噾棰�
+
+        if (!CollectionUtils.isEmpty(purchaseProducts)) {
+            for (SalesLedgerProduct p : purchaseProducts) {
+
+                if (p.getTaxInclusiveTotalPrice() != null) {
+                    rawMaterialCost = rawMaterialCost.add(p.getTaxInclusiveTotalPrice());
+                }
+
+                if (p.getTicketsTotal() != null) {
+                    paidAmount = paidAmount.add(p.getTicketsTotal());
+                }
+
+                if (p.getPendingTicketsTotal() != null) {
+                    pendingAmount = pendingAmount.add(p.getPendingTicketsTotal());
+                }
+            }
+        }
+
+        //  鍏朵粬璐圭敤
+        LambdaQueryWrapper<AccountExpense> expenseWrapper = new LambdaQueryWrapper<>();
+        expenseWrapper.ge(AccountExpense::getExpenseDate,
+                java.sql.Date.valueOf(currentMonth.atDay(1)));
+        expenseWrapper.le(AccountExpense::getExpenseDate,
+                java.sql.Date.valueOf(currentMonth.atEndOfMonth()));
+
+        List<AccountExpense> expenses =
+                accountExpenseMapper.selectList(expenseWrapper);
+
+        BigDecimal otherExpense = BigDecimal.ZERO;
+        if (!CollectionUtils.isEmpty(expenses)) {
+            for (AccountExpense e : expenses) {
+                if (e.getExpenseMoney() != null) {
+                    otherExpense = otherExpense.add(e.getExpenseMoney());
+                }
+            }
+        }
+
+        //  鏈堝害鎬绘敮鍑�
+//        BigDecimal monthlyExpenditure = rawMaterialCost.add(otherExpense);
+        BigDecimal monthlyExpenditure = otherExpense;
+        dto.setMonthlyExpenditure(monthlyExpenditure);
+
+        // 宸蹭粯娆� 梅锛堝凡浠樻 + 寰呬粯娆撅級
+        BigDecimal totalPayable = paidAmount.add(pendingAmount);
+        if (totalPayable.compareTo(BigDecimal.ZERO) > 0) {
+            String paymentRate = paidAmount
+                    .divide(totalPayable, 4, RoundingMode.HALF_UP)
+                    .multiply(BigDecimal.valueOf(100))
+                    .setScale(2, RoundingMode.HALF_UP)
+                    .toString();
+            dto.setPaymentRate(paymentRate);
+        }
+
+        // 鍞彴璐︼紙type = 1锛�
+        LambdaQueryWrapper<SalesLedgerProduct> salesWrapper = new LambdaQueryWrapper<>();
+        salesWrapper.eq(SalesLedgerProduct::getType, 1);
+        salesWrapper.ge(SalesLedgerProduct::getRegisterDate, startOfMonth);
+        salesWrapper.le(SalesLedgerProduct::getRegisterDate, endOfMonth);
+
+        List<SalesLedgerProduct> salesProducts =
+                salesLedgerProductMapper.selectList(salesWrapper);
+
+        BigDecimal revenue = BigDecimal.ZERO;
+        if (!CollectionUtils.isEmpty(salesProducts)) {
+            for (SalesLedgerProduct s : salesProducts) {
+                if (s.getInvoiceAmount() != null) {
+                    revenue = revenue.add(s.getInvoiceAmount());
+                }
+            }
+        }
+
+        //  姣涘埄娑� & 鍒╂鼎鐜�
+        if (revenue.compareTo(BigDecimal.ZERO) > 0) {
+
+            // 姣涘埄娑� = 閿�鍞敹鍏� - 鍘熸潗鏂欐垚鏈�
+            BigDecimal grossProfit = revenue.subtract(rawMaterialCost);
+            dto.setGrossProfit(grossProfit);
+
+            // 鍒╂鼎鐜� = (閿�鍞敹鍏� - 鏈堝害鎬绘敮鍑�) / 閿�鍞敹鍏�
+            BigDecimal profit = revenue.subtract(monthlyExpenditure);
+            String profitMarginRate = profit
+                    .divide(revenue, 4, RoundingMode.HALF_UP)
+                    .multiply(BigDecimal.valueOf(100))
+                    .setScale(2, RoundingMode.HALF_UP)
+                    .toString();
+
+            dto.setProfitMarginRate(profitMarginRate);
+        }
+
+        return dto;
+    }
+
 }
diff --git a/src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java b/src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java
index da04f8d..c3201e0 100644
--- a/src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java
+++ b/src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java
@@ -7,6 +7,7 @@
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
 import com.ruoyi.purchase.pojo.PurchaseLedger;
 import org.apache.ibatis.annotations.Param;
+import com.ruoyi.home.dto.IncomeExpenseAnalysisDto;
 
 import java.math.BigDecimal;
 import java.util.List;
@@ -24,4 +25,8 @@
     IPage<PurchaseLedgerDto> selectPurchaseLedgerListPage(IPage ipage, @Param("c") PurchaseLedgerDto purchaseLedger);
 
     List<PaymentRegistrationDto> getPaymentRegistrationDtoById(Long id);
+
+    List<IncomeExpenseAnalysisDto> selectPurchaseStats(@Param("startDate") String startDate, @Param("endDate") String endDate, @Param("dateFormat") String dateFormat);
+
+    BigDecimal selectTotalPurchaseAmount(@Param("startDate") String startDate, @Param("endDate") String endDate);
 }
diff --git a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
index 6e4aee6..3100a55 100644
--- a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
+++ b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
@@ -5,6 +5,7 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.home.dto.IncomeExpenseAnalysisDto;
 import com.ruoyi.sales.dto.SalesLedgerDto;
 import com.ruoyi.sales.dto.SalesTrendDto;
 import com.ruoyi.sales.dto.StatisticsTableDto;
@@ -23,6 +24,10 @@
  * @date 2025-05-08
  */
 public interface SalesLedgerMapper extends BaseMapper<SalesLedger> {
+
+    List<IncomeExpenseAnalysisDto> selectIncomeStats(@Param("startDate") String startDate, @Param("endDate") String endDate, @Param("dateFormat") String dateFormat);
+
+    List<com.ruoyi.dto.MapDto> selectCustomerSalesComposition();
     /**
      * 鏌ヨ鎸囧畾鏃ユ湡鐨勬墍鏈夊悎鍚屽簭鍒楀彿
      * @param datePart 鏃ユ湡閮ㄥ垎锛堟牸寮忥細yyyyMMdd锛�
diff --git a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
index 2117d96..94e9892 100644
--- a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
+++ b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
@@ -40,4 +40,6 @@
     List<Map<String, Object>> selectRawMaterialPurchaseAnalysis();
 
     int selectProductCountByTypeAndDate(@Param("type") Integer type, @Param("startDate") String startDate, @Param("endDate") String endDate);
+
+    BigDecimal selectRawMaterialExpense();
 }
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
index f87ebd8..d2118d1 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -42,6 +42,7 @@
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.function.Function;
@@ -217,6 +218,7 @@
         int result;
         Long salesLedgerId = salesLedgerProduct.getSalesLedgerId();
         if (salesLedgerProduct.getId() == null) {
+            salesLedgerProduct.setRegisterDate(LocalDateTime.now());
             result = salesLedgerProductMapper.insert(salesLedgerProduct);
             addProductionData(salesLedgerProduct);
         } else {
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
index af21c1c..f62f179 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -263,75 +263,51 @@
 
     @Override
     public List<MonthlyAmountDto> getAmountHalfYear(Integer type) {
+
         LocalDate now = LocalDate.now();
-        LocalDateTime currentDateTime = LocalDateTime.now();
-
-        // 鏍规嵁 type 纭畾鏌ヨ鐨勬椂闂撮棿闅旓紙澶╂暟锛夊拰鎬绘煡璇㈠ぉ鏁�
-        int daysPerPeriod;
-        int totalDays;
-        switch (type) {
-            case 1:
-                daysPerPeriod = 5;   // 姣�5澶╂煡涓�娆�
-                totalDays = 30;       // 6娆� 脳 5澶� = 30澶�
-                break;
-            case 2:
-                daysPerPeriod = 15;    // 姣�15澶╂煡涓�娆�
-                totalDays = 90;       // 6娆� 脳 15澶� = 90澶�
-                break;
-            case 3:
-                daysPerPeriod = 30;   // 姣�30澶╂煡涓�娆�
-                totalDays = 180;      // 6娆� 脳 30澶� = 180澶�
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid type value: " + type);
-        }
-
         List<MonthlyAmountDto> result = new ArrayList<>();
 
-        // 寰幆6娆★紝姣忔鏌ヨ涓�涓椂闂存鐨勬暟鎹�
-        for (int i = 0; i < 6; i++) {
-            // 璁$畻褰撳墠鏃堕棿娈电殑璧峰鍜岀粨鏉熸椂闂�
-            LocalDateTime endTime = currentDateTime.minusDays(i * daysPerPeriod);
-            LocalDateTime startTime = endTime.minusDays(daysPerPeriod);
+        for (int i = 5; i >= 0; i--) {
+            YearMonth yearMonth = YearMonth.from(now.minusMonths(i));
+            LocalDateTime startTime = yearMonth.atDay(1).atStartOfDay();
+            LocalDateTime endTime = yearMonth.atEndOfMonth().atTime(23, 59, 59);
 
-            // 鏌ヨ鍥炴閲戦
+            //  鍥炴閲戦
             LambdaQueryWrapper<ReceiptPayment> receiptPaymentQuery = new LambdaQueryWrapper<>();
             receiptPaymentQuery
                     .ge(ReceiptPayment::getCreateTime, startTime)
-                    .lt(ReceiptPayment::getCreateTime, endTime);
-            List<ReceiptPayment> receiptPayments = receiptPaymentMapper.selectList(receiptPaymentQuery);
+                    .le(ReceiptPayment::getCreateTime, endTime);
 
-            // 鏌ヨ寮�绁ㄩ噾棰�
-            LambdaQueryWrapper<InvoiceLedger> invoiceLedgerQuery = new LambdaQueryWrapper<>();
-            invoiceLedgerQuery
-                    .ge(InvoiceLedger::getCreateTime, startTime)
-                    .lt(InvoiceLedger::getCreateTime, endTime);
-            List<InvoiceLedger> invoiceLedgers = invoiceLedgerMapper.selectList(invoiceLedgerQuery);
+            List<ReceiptPayment> receiptPayments =
+                    receiptPaymentMapper.selectList(receiptPaymentQuery);
 
-            // 璁$畻鍥炴鎬婚
             BigDecimal receiptAmount = receiptPayments.stream()
                     .map(ReceiptPayment::getReceiptPaymentAmount)
                     .filter(Objects::nonNull)
                     .reduce(BigDecimal.ZERO, BigDecimal::add);
 
-            // 璁$畻寮�绁ㄦ�婚
+            //  寮�绁ㄩ噾棰�
+            LambdaQueryWrapper<InvoiceLedger> invoiceLedgerQuery = new LambdaQueryWrapper<>();
+            invoiceLedgerQuery
+                    .ge(InvoiceLedger::getCreateTime, startTime)
+                    .le(InvoiceLedger::getCreateTime, endTime);
+
+            List<InvoiceLedger> invoiceLedgers =
+                    invoiceLedgerMapper.selectList(invoiceLedgerQuery);
+
             BigDecimal invoiceAmount = invoiceLedgers.stream()
                     .map(InvoiceLedger::getInvoiceTotal)
                     .filter(Objects::nonNull)
                     .reduce(BigDecimal.ZERO, BigDecimal::add);
 
-            // 鏋勯�犺繑鍥炵殑 DTO
             MonthlyAmountDto dto = new MonthlyAmountDto();
-            dto.setMonth(startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " ~ " +
-                    endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
+            dto.setMonth(yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM")));
             dto.setReceiptAmount(receiptAmount);
             dto.setInvoiceAmount(invoiceAmount);
 
             result.add(dto);
         }
 
-        // 鍙嶈浆鍒楄〃锛屼娇鏃堕棿椤哄簭浠庢棭鍒版櫄
-        Collections.reverse(result);
         return result;
     }
 
diff --git a/src/main/resources/mapper/account/AccountExpenseMapper.xml b/src/main/resources/mapper/account/AccountExpenseMapper.xml
index 3b37674..1a8d34b 100644
--- a/src/main/resources/mapper/account/AccountExpenseMapper.xml
+++ b/src/main/resources/mapper/account/AccountExpenseMapper.xml
@@ -70,4 +70,26 @@
         </if>
     </select>
 
+    <select id="selectAccountExpenseStats" resultType="com.ruoyi.home.dto.IncomeExpenseAnalysisDto">
+        SELECT DATE_FORMAT(expense_date, #{dateFormat}) as dateStr, IFNULL(SUM(expense_money), 0) as amount
+        FROM account_expense
+        WHERE expense_date BETWEEN #{startDate} AND #{endDate}
+        GROUP BY dateStr
+    </select>
+
+    <select id="selectExpenseComposition" resultType="com.ruoyi.dto.MapDto">
+        SELECT sdd.dict_label as name, CAST(IFNULL(SUM(ae.expense_money), 0) AS CHAR) as value
+        FROM account_expense ae
+        LEFT JOIN sys_dict_data sdd ON ae.expense_type = sdd.dict_value AND sdd.dict_type = 'expense_types'
+        <where>
+            <if test="startDate != null and startDate != ''">
+                AND ae.expense_date &gt;= #{startDate}
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND ae.expense_date &lt;= #{endDate}
+            </if>
+        </where>
+        GROUP BY ae.expense_type, sdd.dict_label
+    </select>
+
 </mapper>
diff --git a/src/main/resources/mapper/account/AccountIncomeMapper.xml b/src/main/resources/mapper/account/AccountIncomeMapper.xml
index 7400904..4acb504 100644
--- a/src/main/resources/mapper/account/AccountIncomeMapper.xml
+++ b/src/main/resources/mapper/account/AccountIncomeMapper.xml
@@ -69,4 +69,17 @@
             AND income_date &lt;= DATE_FORMAT(#{dateQueryDto.entryDateEnd},'%Y-%m-%d')
         </if>
     </select>
+
+    <select id="selectIncomeStats"
+            resultType="com.ruoyi.home.dto.IncomeExpenseAnalysisDto">
+        SELECT DATE_FORMAT(income_date, #{dateFormat}) AS dateStr,
+               IFNULL(SUM(income_money), 0)            AS amount
+        FROM account_income
+        WHERE income_date BETWEEN #{startDate} AND #{endDate}
+          AND business_type = 1
+        GROUP BY dateStr
+        ORDER BY dateStr
+
+    </select>
+
 </mapper>
diff --git a/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml b/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
index 0ed5eb4..6cbad29 100644
--- a/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
+++ b/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
@@ -83,4 +83,25 @@
         WHERE
             T1.sales_ledger_product_id = #{id}
     </select>
+
+
+    <select id="selectPurchaseStats" resultType="com.ruoyi.home.dto.IncomeExpenseAnalysisDto">
+        SELECT DATE_FORMAT(entry_date, #{dateFormat}) as dateStr, IFNULL(SUM(contract_amount), 0) as amount
+        FROM purchase_ledger
+        WHERE entry_date BETWEEN #{startDate} AND #{endDate}
+        GROUP BY dateStr
+    </select>
+
+    <select id="selectTotalPurchaseAmount" resultType="java.math.BigDecimal">
+        SELECT IFNULL(SUM(contract_amount), 0)
+        FROM purchase_ledger
+        <where>
+            <if test="startDate != null and startDate != ''">
+                AND entry_date &gt;= #{startDate}
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND entry_date &lt;= #{endDate}
+            </if>
+        </where>
+    </select>
 </mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/sales/SalesLedgerMapper.xml b/src/main/resources/mapper/sales/SalesLedgerMapper.xml
index 7bc2d2d..215f0a7 100644
--- a/src/main/resources/mapper/sales/SalesLedgerMapper.xml
+++ b/src/main/resources/mapper/sales/SalesLedgerMapper.xml
@@ -88,4 +88,17 @@
         </where>
     order by T1.entry_date desc
     </select>
+
+    <select id="selectIncomeStats" resultType="com.ruoyi.home.dto.IncomeExpenseAnalysisDto">
+        SELECT DATE_FORMAT(entry_date, #{dateFormat}) as dateStr, IFNULL(SUM(contract_amount), 0) as amount
+        FROM sales_ledger
+        WHERE entry_date BETWEEN #{startDate} AND #{endDate}
+        GROUP BY dateStr
+    </select>
+
+    <select id="selectCustomerSalesComposition" resultType="com.ruoyi.dto.MapDto">
+        SELECT customer_name as name, CAST(IFNULL(SUM(contract_amount), 0) AS CHAR) as value
+        FROM sales_ledger
+        GROUP BY customer_name
+    </select>
 </mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml b/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
index 06063b3..01f0e89 100644
--- a/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
+++ b/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -162,7 +162,7 @@
         left join product p on pm.product_id = p.id
     </select>
 
-    <select id="selectProductSalesAnalysis" resultType="java.util.LinkedHashMap">
+    <select id="selectProductSalesAnalysis" resultType="java.util.Map">
         SELECT
             product_category as name,
             SUM( tax_inclusive_total_price ) AS value
@@ -177,7 +177,7 @@
         LIMIT 5
     </select>
 
-    <select id="selectRawMaterialPurchaseAnalysis" resultType="java.util.LinkedHashMap">
+    <select id="selectRawMaterialPurchaseAnalysis" resultType="java.util.Map">
         SELECT
             pr.product_name AS name,
             SUM( slp.tax_inclusive_total_price ) AS value
@@ -203,4 +203,21 @@
         AND register_date &lt;= #{endDate}
     </select>
 
+    <select id="selectRawMaterialExpense" resultType="java.math.BigDecimal">
+        WITH RECURSIVE product_tree AS (SELECT id
+                                        FROM product
+                                        WHERE product_name = '鍘熸潗鏂�'
+
+                                        UNION ALL
+
+                                        SELECT p.id
+                                        FROM product p
+                                                 INNER JOIN product_tree pt ON p.parent_id = pt.id)
+        SELECT IFNULL(SUM(slp.tax_inclusive_total_price), 0)
+        FROM sales_ledger_product slp
+        WHERE slp.type = 2
+          AND slp.product_id IN (SELECT id
+                                 FROM product_tree);
+
+    </select>
 </mapper>

--
Gitblit v1.9.3