yaowanxin
14 小时以前 6f888b234148d1a4dbd1e6c28a8d9ed85ebfda09
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
@@ -2,8 +2,11 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
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;
@@ -12,10 +15,10 @@
import com.ruoyi.basic.mapper.SupplierManageMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.pojo.SupplierManage;
import com.ruoyi.collaborativeApproval.mapper.NoticeMapper;
import com.ruoyi.collaborativeApproval.pojo.Notice;
import com.ruoyi.common.enums.ApproveTypeEnum;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.device.mapper.DeviceRepairMapper;
import com.ruoyi.device.pojo.DeviceRepair;
@@ -32,9 +35,11 @@
import com.ruoyi.production.mapper.ProductWorkOrderMapper;
import com.ruoyi.production.pojo.ProductWorkOrder;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.domain.SysUserDept;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserDeptMapper;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.purchase.mapper.PaymentRegistrationMapper;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PaymentRegistration;
@@ -44,8 +49,6 @@
import com.ruoyi.sales.mapper.ReceiptPaymentMapper;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.sales.pojo.ReceiptPayment;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
@@ -83,6 +86,9 @@
    @Autowired
    private SalesLedgerProductMapper salesLedgerProductMapper;
    @Autowired
    private StockInventoryMapper stockInventoryMapper;
    @Autowired
    private ProcurementRecordMapper procurementRecordStorageMapper;
@@ -124,7 +130,9 @@
    private SysUserMapper sysUserMapper;
    @Autowired
    private SysUserDeptMapper sysUserDeptMapper;
    private StockInventoryMapper stockInventoryMapper;
    @Autowired
    private HomeMapper homeMapper;
    @Override
    public HomeBusinessDto business() {
@@ -178,38 +186,13 @@
            homeBusinessDto.setMonthPurchaseHaveMoney(unReceiptPaymentAmount.setScale(2, RoundingMode.HALF_UP).toString());
        }
        // 统计库存
//        List<ProcurementRecordStorage> procurementRecordStorages = procurementRecordStorageMapper.selectList(null);
//        BigDecimal stockAmount = procurementRecordStorages.stream()
//                .map(ProcurementRecordStorage::getInboundNum)
//                .filter(Objects::nonNull)
//                .reduce(BigDecimal.ZERO, BigDecimal::add);
//        // 自定义库存
//        List<CustomStorage> customStorages = customStorageMapper.selectList(null);
//        BigDecimal customStockAmount = customStorages.stream()
//                .map(CustomStorage::getInboundNum)
//                .filter(Objects::nonNull)
//                .reduce(BigDecimal.ZERO, BigDecimal::add);
//        List<ProcurementRecordOut> procurementRecordOuts = procurementRecordOutMapper.selectList(null);
//        BigDecimal outboundAmount = procurementRecordOuts.stream()
//                .map(ProcurementRecordOut::getInboundNum)
//                .filter(Objects::nonNull)
//                .reduce(BigDecimal.ZERO, BigDecimal::add);
//        BigDecimal stock = stockAmount.add(customStockAmount).subtract(outboundAmount);
        BigDecimal stockQuantityTotal = stockInventoryMapper.selectTotal();
        homeBusinessDto.setInventoryNum(stockQuantityTotal.setScale(2, RoundingMode.HALF_UP).toString());
        // 获取当天入库数量
        LambdaQueryWrapper<ProcurementRecordStorage> procurementRecordStorageLambdaQueryWrapper = new LambdaQueryWrapper<>();
        procurementRecordStorageLambdaQueryWrapper.ge(ProcurementRecordStorage::getCreateTime, now)  // 大于等于当天
                .lt(ProcurementRecordStorage::getCreateTime, now.plusDays(1)); // 小于明天
        List<ProcurementRecordStorage> procurementRecordStorages1 = procurementRecordStorageMapper.selectList(procurementRecordStorageLambdaQueryWrapper);
        BigDecimal stockAmount1 = procurementRecordStorages1.stream()
                .map(ProcurementRecordStorage::getInboundNum)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
//        homeBusinessDto.setInventoryNum(stock.setScale(2, RoundingMode.HALF_UP).toString());
        homeBusinessDto.setTodayInventoryNum(stockAmount1.setScale(2, RoundingMode.HALF_UP).toString());
        BigDecimal bigDecimal = stockInventoryMapper.selectTotalByDate(LocalDate.now());
        homeBusinessDto.setTodayInventoryNum(bigDecimal.setScale(2, RoundingMode.HALF_UP).toString());
        return homeBusinessDto;
    }
@@ -399,6 +382,12 @@
        if (CollectionUtils.isEmpty(approveProcesses)) {
            approveProcesses = new ArrayList<>();
        }
        approveProcesses.forEach(a -> {
            if (a.getApproveType() != null) {
                a.setApproveTypeName(ApproveTypeEnum.getNameByCode(a.getApproveType()));
            }
        });
//        // 查询未领用劳保记录
//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//
@@ -416,6 +405,8 @@
//                approveProcesses.add(approveProcess);
//            }
//        }
        return approveProcesses;
    }
@@ -695,15 +686,15 @@
        LocalDate startDate;
        LocalDate endDate;
        switch (type) {
            case 0: // 周
            case 1: // 周
                startDate = today.with(DayOfWeek.MONDAY);
                endDate = today.with(DayOfWeek.SUNDAY);
                break;
            case 1: // 月
            case 2: // 月
                startDate = today.with(TemporalAdjusters.firstDayOfMonth());
                endDate = today.with(TemporalAdjusters.lastDayOfMonth());
                break;
            case 2: // 季度
            case 3: // 季度
                Month currentMonth = today.getMonth();
                Month firstMonthOfQuarter = currentMonth.firstMonthOfQuarter();
                Month lastMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
@@ -745,15 +736,15 @@
        boolean groupByMonth = false;
        switch (type) {
            case 0: // 周
            case 1: // 周
                start = today.with(DayOfWeek.MONDAY);
                end = today.with(DayOfWeek.SUNDAY);
                break;
            case 1: // 月
            case 2: // 月
                start = today.with(TemporalAdjusters.firstDayOfMonth());
                end = today.with(TemporalAdjusters.lastDayOfMonth());
                break;
            case 2: // 季度
            case 3: // 季度
                Month firstMonthOfQuarter = today.getMonth().firstMonthOfQuarter();
                start = today.withMonth(firstMonthOfQuarter.getValue()).with(TemporalAdjusters.firstDayOfMonth());
                end = today.withMonth(firstMonthOfQuarter.plus(2).getValue()).with(TemporalAdjusters.lastDayOfMonth());
@@ -814,109 +805,54 @@
        ProductCategoryDistributionDto dto = new ProductCategoryDistributionDto();
        List<ProductCategoryDistributionDto.MajorCategoryDto> majorItems = new ArrayList<>();
        //  所有的产品大类和小类
        List<Product> allProducts = productMapper.selectList(new LambdaQueryWrapper<Product>());
        // 查询所有的产品类别
        List<Product> allProducts = productMapper.selectList(new LambdaQueryWrapper<>());
        if (CollectionUtils.isEmpty(allProducts)) {
            dto.setItems(majorItems);
            return dto;
        }
        List<Product> majorCategories = allProducts.stream().filter(p -> p.getParentId() == null).collect(Collectors.toList());
        List<Product> minorCategories = allProducts.stream().filter(p -> p.getParentId() != null).collect(Collectors.toList());
        // 分离大类和小类
        List<Product> majorCategories = allProducts.stream()
                .filter(p -> p.getParentId() == null)
                .collect(Collectors.toList());
        List<Product> minorCategories = allProducts.stream()
                .filter(p -> p.getParentId() != null)
                .collect(Collectors.toList());
        //  从 sales_ledger_product 拿到每个产品的型号库存
        // 需包含规格型号以支持三级分布
        List<Map<String, Object>> quantityMaps = salesLedgerProductMapper.selectMaps(new QueryWrapper<SalesLedgerProduct>()
                .select("product_id", "specification_model", "type", "SUM(quantity) as sum_qty")
                .isNotNull("product_id")
                .groupBy("product_id", "specification_model", "type"));
        // 按父类ID对小类进行分组
        Map<Long, List<Product>> minorGroupMap = minorCategories.stream()
                .collect(Collectors.groupingBy(Product::getParentId));
        Map<Long, Map<String, Map<Integer, BigDecimal>>> modelStockGroups = new HashMap<>();
        for (Map<String, Object> map : quantityMaps) {
            Long productId = Long.parseLong(map.get("product_id").toString());
            String model = map.get("specification_model") != null ? map.get("specification_model").toString() : "未知型号";
            Integer type = Integer.parseInt(map.get("type").toString());
            BigDecimal sum = map.get("sum_qty") != null ? new BigDecimal(map.get("sum_qty").toString()) : BigDecimal.ZERO;
        int totalMinorCount = minorCategories.size();
            modelStockGroups.computeIfAbsent(productId, k -> new HashMap<>())
                    .computeIfAbsent(model, k -> new HashMap<>())
                    .put(type, sum);
        }
        //  库存并汇总
        Map<Long, List<MapDto>> productModelsMap = new HashMap<>();
        Map<Long, BigDecimal> productTotalStockMap = new HashMap<>();
        for (Long pid : modelStockGroups.keySet()) {
            Map<String, Map<Integer, BigDecimal>> models = modelStockGroups.get(pid);
            BigDecimal productStock = BigDecimal.ZERO;
            List<MapDto> modelDtos = new ArrayList<>();
            for (String modelName : models.keySet()) {
                Map<Integer, BigDecimal> types = models.get(modelName);
                BigDecimal procurement = types.getOrDefault(2, BigDecimal.ZERO);
                BigDecimal sales = types.getOrDefault(1, BigDecimal.ZERO);
                BigDecimal stock = procurement.subtract(sales);
                if (stock.compareTo(BigDecimal.ZERO) < 0) stock = BigDecimal.ZERO;
                MapDto modelDto = new MapDto();
                modelDto.setName(modelName);
                modelDto.setValue(stock.stripTrailingZeros().toPlainString());
                modelDtos.add(modelDto);
                productStock = productStock.add(stock);
            }
            productModelsMap.put(pid, modelDtos);
            productTotalStockMap.put(pid, productStock);
        }
        BigDecimal totalInventory = productTotalStockMap.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        //  型号占比
        if (totalInventory.compareTo(BigDecimal.ZERO) > 0) {
            for (List<MapDto> dtos : productModelsMap.values()) {
                for (MapDto m : dtos) {
                    BigDecimal val = new BigDecimal(m.getValue());
                    m.setRate(val.multiply(new BigDecimal("100"))
                            .divide(totalInventory, 2, RoundingMode.HALF_UP).toString());
                }
            }
        }
        //  分层数据
        for (Product major : majorCategories) {
            ProductCategoryDistributionDto.MajorCategoryDto majorDto = new ProductCategoryDistributionDto.MajorCategoryDto();
            majorDto.setName(major.getProductName());
            List<ProductCategoryDistributionDto.MinorCategoryDto> minorDtos = new ArrayList<>();
            BigDecimal majorStock = BigDecimal.ZERO;
            List<Product> children = minorGroupMap.getOrDefault(major.getId(), new ArrayList<>());
            int childrenSize = children.size();
            // 设置小类的总数
            majorDto.setValue(String.valueOf(childrenSize));
            for (Product minor : minorCategories) {
                if (major.getId().equals(minor.getParentId())) {
                    BigDecimal stock = productTotalStockMap.getOrDefault(minor.getId(), BigDecimal.ZERO);
                    ProductCategoryDistributionDto.MinorCategoryDto minorDto = new ProductCategoryDistributionDto.MinorCategoryDto();
                    minorDto.setName(minor.getProductName());
                    minorDto.setValue(stock.stripTrailingZeros().toPlainString());
                    if (totalInventory.compareTo(BigDecimal.ZERO) > 0) {
                        minorDto.setRate(stock.multiply(new BigDecimal("100"))
                                .divide(totalInventory, 2, RoundingMode.HALF_UP).toString());
                    } else {
                        minorDto.setRate("0.00");
                    }
                    minorDto.setChildren(productModelsMap.getOrDefault(minor.getId(), new ArrayList<>()));
                    minorDtos.add(minorDto);
                    majorStock = majorStock.add(stock);
                }
            }
            majorDto.setValue(majorStock.stripTrailingZeros().toPlainString());
            if (totalInventory.compareTo(BigDecimal.ZERO) > 0) {
                majorDto.setRate(majorStock.multiply(new BigDecimal("100"))
                        .divide(totalInventory, 2, RoundingMode.HALF_UP).toString());
            // 计算占比
            if (totalMinorCount > 0) {
                BigDecimal rate = BigDecimal.valueOf(childrenSize)
                        .multiply(new BigDecimal("100"))
                        .divide(BigDecimal.valueOf(totalMinorCount), 2, RoundingMode.HALF_UP);
                majorDto.setRate(rate.toString());
            } else {
                majorDto.setRate("0.00");
            }
            List<ProductCategoryDistributionDto.MinorCategoryDto> minorDtos = new ArrayList<>();
            for (Product minor : children) {
                ProductCategoryDistributionDto.MinorCategoryDto minorDto = new ProductCategoryDistributionDto.MinorCategoryDto();
                minorDto.setName(minor.getProductName());
                minorDto.setValue("0");
                minorDto.setRate("0.00");
                minorDto.setChildren(new ArrayList<>());
                minorDtos.add(minorDto);
            }
            majorDto.setChildren(minorDtos);
            majorItems.add(majorDto);
@@ -932,15 +868,15 @@
        LocalDate startDate = null;
        LocalDate endDate = null;
        switch (type) {
            case 0:
            case 1:
                startDate = today.with(DayOfWeek.MONDAY);
                endDate = today.with(DayOfWeek.SUNDAY);
                break;
            case 1:
            case 2:
                startDate = today.with(TemporalAdjusters.firstDayOfMonth());
                endDate = today.with(TemporalAdjusters.lastDayOfMonth());
                break;
            case 2:
            case 3:
                Month currentMonth = today.getMonth();
                Month firstMonthOfQuarter = currentMonth.firstMonthOfQuarter();
                Month lastMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
@@ -970,4 +906,577 @@
        }
        return result;
    }
    @Override
    public List<MapDto> productSalesAnalysis() {
        // 获取所有产品大类的销售额
        List<Map<String, Object>> analysisResults = salesLedgerProductMapper.selectProductSalesAnalysis();
        if (CollectionUtils.isEmpty(analysisResults)) {
            return new ArrayList<>();
        }
        // 计算总销售额
        BigDecimal totalSalesAmount = analysisResults.stream()
                .map(r -> (BigDecimal) r.get("value"))
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        return analysisResults.stream()
                .map(result -> {
                    MapDto mapDto = new MapDto();
                    String name = (String) result.get("name");
                    BigDecimal value = (BigDecimal) result.get("value");
                    mapDto.setName(name);
                    mapDto.setValue(value != null ? value.setScale(2, RoundingMode.HALF_UP).toString() : "0.00");
                    if (totalSalesAmount.compareTo(BigDecimal.ZERO) == 0 || value == null) {
                        mapDto.setRate("0.00");
                    } else {
                        String rate = value.divide(totalSalesAmount, 4, RoundingMode.HALF_UP)
                                .multiply(new BigDecimal("100"))
                                .setScale(2, RoundingMode.HALF_UP)
                                .toString();
                        mapDto.setRate(rate);
                    }
                    return mapDto;
                })
                .collect(Collectors.toList());
    }
    @Override
    public List<MapDto> rawMaterialPurchaseAmountRatio() {
        // 获取所有原材料的采购额
        List<Map<String, Object>> analysisResults = salesLedgerProductMapper.selectRawMaterialPurchaseAnalysis();
        if (CollectionUtils.isEmpty(analysisResults)) {
            return new ArrayList<>();
        }
        // 计算总采购额
        BigDecimal totalPurchaseAmount = analysisResults.stream()
                .map(r -> (BigDecimal) r.get("value"))
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        return analysisResults.stream()
                .map(result -> {
                    MapDto mapDto = new MapDto();
                    String name = (String) result.get("name");
                    BigDecimal value = (BigDecimal) result.get("value");
                    mapDto.setName(name);
                    mapDto.setValue(value != null ? value.setScale(2, RoundingMode.HALF_UP).toString() : "0.00");
                    if (totalPurchaseAmount.compareTo(BigDecimal.ZERO) == 0 || value == null) {
                        mapDto.setRate("0.00");
                    } else {
                        String rate = value.divide(totalPurchaseAmount, 4, RoundingMode.HALF_UP)
                                .multiply(new BigDecimal("100"))
                                .setScale(2, RoundingMode.HALF_UP)
                                .toString();
                        mapDto.setRate(rate);
                    }
                    return mapDto;
                })
                .collect(Collectors.toList());
    }
    @Override
    public List<MapDto> salesPurchaseStorageProductCount() {
        LocalDate now = LocalDate.now();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String currentMonthStart = now.with(TemporalAdjusters.firstDayOfMonth()).format(dtf);
        String currentMonthNow = now.format(dtf);
        LocalDate lastMonth = now.minusMonths(1);
        String lastMonthStart = lastMonth.with(TemporalAdjusters.firstDayOfMonth()).format(dtf);
        String lastMonthEnd = lastMonth.with(TemporalAdjusters.lastDayOfMonth()).format(dtf);
        // 销售
        int currentSales = salesLedgerProductMapper.selectProductCountByTypeAndDate(1, currentMonthStart, currentMonthNow);
        int lastSales = salesLedgerProductMapper.selectProductCountByTypeAndDate(1, lastMonthStart, lastMonthEnd);
        // 采购
        int currentPurchase = salesLedgerProductMapper.selectProductCountByTypeAndDate(2, currentMonthStart, currentMonthNow);
        int lastPurchase = salesLedgerProductMapper.selectProductCountByTypeAndDate(2, lastMonthStart, lastMonthEnd);
        // 储存
        int currentStorage = stockInventoryMapper.selectStorageProductCountByDate(currentMonthStart, currentMonthNow);
        int lastStorage = stockInventoryMapper.selectStorageProductCountByDate(lastMonthStart, lastMonthEnd);
        List<MapDto> list = new ArrayList<>();
        list.add(createMapDto("销售产品数", currentSales, lastSales));
        list.add(createMapDto("采购产品数", currentPurchase, lastPurchase));
        list.add(createMapDto("储存产品数", currentStorage, lastStorage));
        return list;
    }
    private MapDto createMapDto(String name, int current, int last) {
        MapDto dto = new MapDto();
        dto.setName(name);
        dto.setValue(String.valueOf(current));
        if (last == 0) {
            dto.setRate(current > 0 ? "100.00" : "0.00");
        } else {
            BigDecimal curDec = new BigDecimal(current);
            BigDecimal lastDec = new BigDecimal(last);
            // 增长率
            String rate = curDec.subtract(lastDec)
                    .divide(lastDec, 4, RoundingMode.HALF_UP)
                    .multiply(new BigDecimal("100"))
                    .setScale(2, RoundingMode.HALF_UP)
                    .toString();
            dto.setRate(rate);
        }
        return dto;
    }
    @Override
    public List<Map<String, Object>> productInOutAnalysis(Integer type) {
        String targetName;
        if (type == 1) {
            targetName = "原材料";
        } else if (type == 2) {
            targetName = "成品";
        } else if (type == 3) {
            targetName = "半成品";
        } else {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Product::getProductName, targetName).isNull(Product::getParentId);
        Product rootCategory = productMapper.selectOne(queryWrapper);
        if (rootCategory == null) {
            log.warn("未找到名称为 {} 的顶级产品分类", targetName);
            return new ArrayList<>();
        }
        Long rootCategoryId = rootCategory.getId();
        LocalDate now = LocalDate.now();
        DateTimeFormatter displayDtf = DateTimeFormatter.ofPattern("M/d");
        String startDate = now.minusDays(6).toString();
        String endDate = now.toString();
        List<Map<String, Object>> inCounts = stockInventoryMapper.selectDailyStockInCounts(rootCategoryId, startDate, endDate + " 23:59:59");
        List<Map<String, Object>> outCounts = stockInventoryMapper.selectDailyStockOutCounts(rootCategoryId, startDate, endDate + " 23:59:59");
        Map<LocalDate, BigDecimal> inMap = inCounts.stream()
                .collect(Collectors.toMap(
                        m -> ((java.sql.Date) m.get("date")).toLocalDate(),
                        m -> (BigDecimal) m.get("count"),
                        BigDecimal::add
                ));
        Map<LocalDate, BigDecimal> outMap = outCounts.stream()
                .collect(Collectors.toMap(
                        m -> ((java.sql.Date) m.get("date")).toLocalDate(),
                        m -> (BigDecimal) m.get("count"),
                        BigDecimal::add
                ));
        List<Map<String, Object>> result = new ArrayList<>();
        for (int i = 6; i >= 0; i--) {
            LocalDate date = now.minusDays(i);
            Map<String, Object> dataPoint = new LinkedHashMap<>();
            dataPoint.put("date", date.format(displayDtf)); // 1/23
            dataPoint.put("inCount", inMap.getOrDefault(date, BigDecimal.ZERO));
            dataPoint.put("outCount", outMap.getOrDefault(date, BigDecimal.ZERO));
            result.add(dataPoint);
        }
        return result;
    }
    @Override
    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);
        //  采购台账(type = 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;
    }
}