rk
8 小时以前 9f8b3700ffbcc616a97e7ee2ea283ef4df3d666a
server/services/src/main/java/com/doumee/service/business/impl/DataBoardServiceImpl.java
@@ -13,7 +13,11 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
@@ -430,6 +434,7 @@
            vo.setNetRevenue(BigDecimal.valueOf(netRev).divide(hundred, 2, RoundingMode.HALF_UP));
            result.add(vo);
        }
        Collections.reverse(result);
        return result;
    }
@@ -642,6 +647,184 @@
        return qw;
    }
    @Override
    public void revenueTrendExport(TrendQueryDTO query, HttpServletResponse response) {
        TrendDateRange range = parseTrendDateRange(query);
        SimpleDateFormat sdf = new SimpleDateFormat(range.pattern);
        // 查询订单
        List<Orders> orders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getDeleted, Constants.ZERO)
                .in(Orders::getStatus,
                        Constants.OrderStatus.waitDeposit.getKey(),
                        Constants.OrderStatus.deposited.getKey(),
                        Constants.OrderStatus.accepted.getKey(),
                        Constants.OrderStatus.delivering.getKey(),
                        Constants.OrderStatus.arrived.getKey(),
                        Constants.OrderStatus.overdue.getKey(),
                        Constants.OrderStatus.finished.getKey())
                .ge(Orders::getCreateTime, range.startDate)
                .le(Orders::getCreateTime, range.endDate));
        // 查询逾期费用
        List<Integer> orderIds = orders.stream().map(Orders::getId).collect(Collectors.toList());
        Map<Integer, Long> overduePaidMap = new HashMap<>();
        if (!orderIds.isEmpty()) {
            List<OtherOrders> overdueOrders = otherOrdersMapper.selectList(new QueryWrapper<OtherOrders>().lambda()
                    .eq(OtherOrders::getType, 2)
                    .eq(OtherOrders::getPayStatus, Constants.ONE)
                    .eq(OtherOrders::getDeleted, Constants.ZERO)
                    .in(OtherOrders::getOrderId, orderIds));
            for (OtherOrders oo : overdueOrders) {
                long amt = oo.getPayAccount() != null ? oo.getPayAccount() : 0L;
                overduePaidMap.merge(oo.getOrderId(), amt, Long::sum);
            }
        }
        // 生成日期列头
        List<String> dateKeys = new ArrayList<>();
        List<String> dateLabels = new ArrayList<>();
        if (range.byMonth) {
            Calendar loop = Calendar.getInstance();
            loop.setTime(range.startDate);
            Calendar end = Calendar.getInstance();
            end.setTime(range.endDate);
            while (!loop.after(end)) {
                dateKeys.add(sdf.format(loop.getTime()));
                dateLabels.add(sdf.format(loop.getTime()));
                loop.add(Calendar.DAY_OF_MONTH, 1);
            }
        } else {
            Calendar endCal = Calendar.getInstance();
            endCal.setTime(range.endDate);
            int endMonth = endCal.get(Calendar.MONTH);
            for (int m = 0; m <= endMonth; m++) {
                dateKeys.add(String.format("%02d", m + 1));
                dateLabels.add((m + 1) + "月");
            }
        }
        // 收集所有门店
        Map<Integer, String> shopNameMap = new LinkedHashMap<>();
        for (Orders o : orders) {
            if (o.getDepositShopId() != null && !shopNameMap.containsKey(o.getDepositShopId())) {
                ShopInfo shop = shopInfoMapper.selectById(o.getDepositShopId());
                shopNameMap.put(o.getDepositShopId(), shop != null ? shop.getName() : "未知门店");
            }
        }
        // 按门店+日期分组计算营收
        // key = shopId + "_" + dateKey
        Map<String, Long> revenueMap = new HashMap<>();
        for (Orders o : orders) {
            String date = sdf.format(o.getCreateTime());
            long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
            long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
            long overdue = overduePaidMap.getOrDefault(o.getId(), 0L);
            long rev = pay - refund + overdue;
            Integer shopId = o.getDepositShopId();
            if (shopId != null) {
                String key = shopId + "_" + date;
                revenueMap.merge(key, rev, Long::sum);
            }
        }
        // 构建Excel
        try (SXSSFWorkbook wb = new SXSSFWorkbook()) {
            Sheet sheet = wb.createSheet("营收趋势");
            // 创建样式
            CellStyle titleStyle = wb.createCellStyle();
            Font titleFont = wb.createFont();
            titleFont.setBold(true);
            titleFont.setFontHeightInPoints((short) 16);
            titleStyle.setFont(titleFont);
            titleStyle.setAlignment(HorizontalAlignment.CENTER);
            CellStyle headerStyle = wb.createCellStyle();
            Font headerFont = wb.createFont();
            headerFont.setBold(true);
            headerFont.setFontHeightInPoints((short) 12);
            headerStyle.setFont(headerFont);
            headerStyle.setAlignment(HorizontalAlignment.CENTER);
            headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
            headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            CellStyle dataStyle = wb.createCellStyle();
            dataStyle.setAlignment(HorizontalAlignment.RIGHT);
            // 生成标题
            String title;
            int totalCols = dateLabels.size() + 1;
            if (range.byMonth) {
                String yearMonth = query.getMonth(); // yyyy-MM
                String[] ym = yearMonth.split("-");
                title = "行李寄存门店" + ym[0] + "年" + Integer.valueOf(ym[1]) + "月日报表";
            } else {
                title = "行李寄存门店" + query.getYear() + "年月报表";
            }
            // 写标题行(第0行,合并所有列)
            Row titleRow = sheet.createRow(0);
            titleRow.setHeight((short) 600);
            Cell titleCell = titleRow.createCell(0);
            titleCell.setCellValue(title);
            titleCell.setCellStyle(titleStyle);
            sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, totalCols - 1));
            // 写列头(第1行)
            Row headerRow = sheet.createRow(1);
            Cell shopCell = headerRow.createCell(0);
            shopCell.setCellValue("门店名称");
            shopCell.setCellStyle(headerStyle);
            sheet.setColumnWidth(0, 20 * 256);
            for (int i = 0; i < dateLabels.size(); i++) {
                Cell cell = headerRow.createCell(i + 1);
                cell.setCellValue(dateLabels.get(i));
                cell.setCellStyle(headerStyle);
                sheet.setColumnWidth(i + 1, 14 * 256);
            }
            // 写数据行(第2行起)
            BigDecimal hundred = BigDecimal.valueOf(100);
            int rowIndex = 2;
            for (Map.Entry<Integer, String> entry : shopNameMap.entrySet()) {
                Row row = sheet.createRow(rowIndex++);
                row.createCell(0).setCellValue(entry.getValue());
                for (int i = 0; i < dateKeys.size(); i++) {
                    String mapKey = entry.getKey() + "_" + dateKeys.get(i);
                    long rev = revenueMap.getOrDefault(mapKey, 0L);
                    Cell cell = row.createCell(i + 1);
                    if (rev > 0) {
                        cell.setCellValue(BigDecimal.valueOf(rev).divide(hundred, 2, RoundingMode.HALF_UP).toPlainString());
                    } else {
                        cell.setCellValue("");
                    }
                    cell.setCellStyle(dataStyle);
                }
            }
            // 输出
            String fileName;
            if (StringUtils.isNotBlank(query.getMonth())) {
                fileName = "营收趋势导出_" + query.getMonth();
            } else {
                fileName = "营收趋势导出_" + query.getYear();
            }
            String encodeFileName = java.net.URLEncoder.encode(fileName, "UTF-8") + ".xlsx";
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment;filename=" + encodeFileName);
            response.setHeader("doumee-opera-type", "download");
            response.setHeader("doumee-download-filename", encodeFileName);
            wb.write(response.getOutputStream());
            response.getOutputStream().flush();
        } catch (Exception e) {
            log.error("营收趋势导出异常", e);
            throw new RuntimeException("导出失败: " + e.getMessage());
        }
    }
    private TrendDateRange parseTrendDateRange(TrendQueryDTO query) {
        Calendar now = Calendar.getInstance();
        TrendDateRange range = new TrendDateRange();