MrShi
8 小时以前 59bfd0b8bbbf0ee94ec68e4a3a1a6e536d0ad8fd
server/services/src/main/java/com/doumee/service/business/impl/DataBoardServiceImpl.java
@@ -6,6 +6,8 @@
import com.doumee.dao.business.*;
import com.doumee.dao.business.model.*;
import com.doumee.dao.dto.DataBoardQueryDTO;
import com.doumee.dao.dto.FinanceQueryDTO;
import com.doumee.dao.dto.TrendQueryDTO;
import com.doumee.dao.vo.*;
import com.doumee.service.business.DataBoardService;
import lombok.extern.slf4j.Slf4j;
@@ -37,14 +39,17 @@
    private OtherOrdersMapper otherOrdersMapper;
    @Autowired
    private OrdersRefundMapper ordersRefundMapper;
    @Autowired
    private RevenueMapper revenueMapper;
    @Override
    public DataBoardVO overview(DataBoardQueryDTO query) {
        query.resolveDateRange();
        DataBoardVO vo = new DataBoardVO();
        // 会员总数(不受时间/门店影响)
        // 会员总数(不受时间/门店影响,userType=0 会员身份)
        vo.setMemberCount(memberMapper.selectCount(new QueryWrapper<Member>().lambda()
                .eq(Member::getDeleted, Constants.ZERO)
                .eq(Member::getStatus, Constants.ZERO)));
                .eq(Member::getUserType, Constants.ZERO)));
        // 门店总数(auditStatus=3 正式版本)
        vo.setShopCount(shopInfoMapper.selectCount(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getAuditStatus, Constants.THREE)
@@ -89,69 +94,85 @@
        // 行李类型占比
        List<Integer> orderIds = orders.stream().map(Orders::getId).collect(Collectors.toList());
        List<LuggageTypeItem> luggageTypeList = new ArrayList<>();
        if (!orderIds.isEmpty()) {
            List<OrdersDetail> details = ordersDetailMapper.selectList(new QueryWrapper<OrdersDetail>().lambda()
                    .in(OrdersDetail::getOrderId, orderIds)
                    .eq(OrdersDetail::getDeleted, Constants.ZERO));
            Map<String, List<OrdersDetail>> grouped = details.stream()
                    .filter(d -> StringUtils.isNotBlank(d.getLuggageName()))
                    .collect(Collectors.groupingBy(OrdersDetail::getLuggageName));
            for (Map.Entry<String, List<OrdersDetail>> entry : grouped.entrySet()) {
                LuggageTypeItem item = new LuggageTypeItem();
                item.setLuggageName(entry.getKey());
                item.setOrderCount((long) entry.getValue().stream().map(OrdersDetail::getOrderId).distinct().count());
                item.setLuggageCount((long) entry.getValue().stream()
                        .mapToInt(d -> d.getNum() != null ? d.getNum() : 0).sum());
                luggageTypeList.add(item);
            }
        vo.setLuggageTypeList(buildLuggageTypeList(orderIds));
        // 门店入驻率:auditStatus=3 的门店数 / 全部门店数
        long totalShopCount = shopInfoMapper.selectCount(new QueryWrapper<ShopInfo>().lambda()
                .eq(ShopInfo::getVersionType, Constants.ZERO)
                .eq(ShopInfo::getDeleted, Constants.ZERO));
        if (totalShopCount > 0) {
            vo.setShopSettlementRate(BigDecimal.valueOf(vo.getShopCount())
                    .multiply(BigDecimal.valueOf(100))
                    .divide(BigDecimal.valueOf(totalShopCount), 2, RoundingMode.HALF_UP));
        } else {
            vo.setShopSettlementRate(BigDecimal.ZERO);
        }
        vo.setLuggageTypeList(luggageTypeList);
        // 司机通过率:auditStatus=3 的司机数 / 全部司机数
        long totalDriverCount = driverInfoMapper.selectCount(new QueryWrapper<DriverInfo>().lambda()
                .eq(DriverInfo::getVersionType, Constants.ZERO)
                .eq(DriverInfo::getDeleted, Constants.ZERO));
        if (totalDriverCount > 0) {
            vo.setDriverPassRate(BigDecimal.valueOf(vo.getDriverCount())
                    .multiply(BigDecimal.valueOf(100))
                    .divide(BigDecimal.valueOf(totalDriverCount), 2, RoundingMode.HALF_UP));
        } else {
            vo.setDriverPassRate(BigDecimal.ZERO);
        }
        // 周期退款单数
        if (!orderIds.isEmpty()) {
            QueryWrapper<OrdersRefund> refundQw = new QueryWrapper<>();
            refundQw.lambda()
                    .in(OrdersRefund::getOrderId, orderIds)
                    .eq(OrdersRefund::getStatus, Constants.ONE)
                    .eq(OrdersRefund::getDeleted, Constants.ZERO);
            List<OrdersRefund> refundRecords = ordersRefundMapper.selectList(refundQw);
            vo.setRefundOrderCount(refundRecords.stream()
                    .map(OrdersRefund::getOrderId).distinct().count());
        } else {
            vo.setRefundOrderCount(0L);
        }
        return vo;
    }
    @Override
    public List<MemberTrendVO> memberTrend() {
        Date startDate = get30DaysAgoStart();
    public List<MemberTrendVO> memberTrend(TrendQueryDTO query) {
        TrendDateRange range = parseTrendDateRange(query);
        List<Member> members = memberMapper.selectList(new QueryWrapper<Member>().lambda()
                .eq(Member::getDeleted, Constants.ZERO)
                .eq(Member::getStatus, Constants.ZERO)
                .ge(Member::getCreateTime, startDate));
                .eq(Member::getUserType, Constants.ZERO)
                .ge(Member::getCreateTime, range.startDate)
                .le(Member::getCreateTime, range.endDate));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sdf = new SimpleDateFormat(range.pattern);
        Map<String, Long> map = members.stream()
                .collect(Collectors.groupingBy(m -> sdf.format(m.getCreateTime()), Collectors.counting()));
        return buildDailyList(startDate, (date) -> {
        return buildTrendList(range, (key, label) -> {
            MemberTrendVO vo = new MemberTrendVO();
            vo.setDate(date);
            vo.setCount(map.getOrDefault(date, 0L));
            vo.setDate(label);
            vo.setCount(map.getOrDefault(key, 0L));
            return vo;
        });
    }
    @Override
    public List<OrderTrendVO> orderTrend() {
        Date startDate = get30DaysAgoStart();
    public List<OrderTrendVO> orderTrend(TrendQueryDTO query) {
        TrendDateRange range = parseTrendDateRange(query);
        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())
                .ge(Orders::getCreateTime, startDate));
                .ge(Orders::getCreateTime, range.startDate)
                .le(Orders::getCreateTime, range.endDate));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sdf = new SimpleDateFormat(range.pattern);
        Map<String, List<Orders>> grouped = orders.stream()
                .collect(Collectors.groupingBy(o -> sdf.format(o.getCreateTime())));
        return buildDailyList(startDate, (date) -> {
            List<Orders> dayOrders = grouped.getOrDefault(date, Collections.emptyList());
        return buildTrendList(range, (key, label) -> {
            List<Orders> dayOrders = grouped.getOrDefault(key, Collections.emptyList());
            OrderTrendVO vo = new OrderTrendVO();
            vo.setDate(date);
            vo.setDate(label);
            vo.setLocalCount(dayOrders.stream().filter(o -> Constants.equalsInteger(o.getType(), Constants.ZERO)).count());
            vo.setRemoteCount(dayOrders.stream().filter(o -> Constants.equalsInteger(o.getType(), Constants.ONE)).count());
            return vo;
@@ -159,35 +180,45 @@
    }
    @Override
    public List<RevenueTrendVO> revenueTrend() {
        Date startDate = get30DaysAgoStart();
    public List<RevenueTrendVO> revenueTrend(TrendQueryDTO query) {
        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())
                .ge(Orders::getCreateTime, startDate));
                        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<OtherOrders> otherOrders = otherOrdersMapper.selectList(new QueryWrapper<OtherOrders>().lambda()
                .eq(OtherOrders::getType, 2)
                .eq(OtherOrders::getPayStatus, Constants.ONE)
                .eq(OtherOrders::getDeleted, Constants.ZERO)
                .ge(OtherOrders::getPayTime, startDate));
        // 根据筛选出的订单主键查询已支付的逾期费用
        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);
            }
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Map<String, Long> localOrderRevenue = new HashMap<>();
        Map<String, Long> remoteOrderRevenue = 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 = (o.getOverdueStatus() != null && o.getOverdueStatus() == 2 && o.getOverdueAmount() != null)
                    ? o.getOverdueAmount() : 0L;
            long overdue = overduePaidMap.getOrDefault(o.getId(), 0L);
            long rev = pay - refund + overdue;
            if (Constants.equalsInteger(o.getType(), Constants.ZERO)) {
                localOrderRevenue.merge(date, rev, Long::sum);
@@ -196,18 +227,11 @@
            }
        }
        Map<String, Long> otherRevenueMap = new HashMap<>();
        for (OtherOrders oo : otherOrders) {
            String date = sdf.format(oo.getPayTime());
            long amt = oo.getPayAccount() != null ? oo.getPayAccount() : 0L;
            otherRevenueMap.merge(date, amt, Long::sum);
        }
        return buildDailyList(startDate, (date) -> {
        return buildTrendList(range, (key, label) -> {
            RevenueTrendVO vo = new RevenueTrendVO();
            vo.setDate(date);
            long local = localOrderRevenue.getOrDefault(date, 0L) + otherRevenueMap.getOrDefault(date, 0L);
            long remote = remoteOrderRevenue.getOrDefault(date, 0L);
            vo.setDate(label);
            long local = localOrderRevenue.getOrDefault(key, 0L);
            long remote = remoteOrderRevenue.getOrDefault(key, 0L);
            vo.setLocalRevenue(BigDecimal.valueOf(local).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
            vo.setRemoteRevenue(BigDecimal.valueOf(remote).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
            return vo;
@@ -216,6 +240,7 @@
    @Override
    public ShopPerformanceVO shopPerformance(DataBoardQueryDTO query) {
        query.resolveDateRange();
        ShopPerformanceVO vo = new ShopPerformanceVO();
        // 1. 总订单数 + 营收(status 1-6)
@@ -282,6 +307,295 @@
        return vo;
    }
    @Override
    public List<LuggageTypeItem> luggageTypeList(DataBoardQueryDTO query) {
        query.resolveDateRange();
        List<Orders> orders = ordersMapper.selectList(buildOrderQueryWrapper(query));
        List<Integer> orderIds = orders.stream().map(Orders::getId).collect(Collectors.toList());
        return buildLuggageTypeList(orderIds);
    }
    @Override
    public List<LuggageTypeItem> buildLuggageTypeList(List<Integer> orderIds) {
        List<LuggageTypeItem> luggageTypeList = new ArrayList<>();
        if (orderIds != null && !orderIds.isEmpty()) {
            List<OrdersDetail> details = ordersDetailMapper.selectList(new QueryWrapper<OrdersDetail>().lambda()
                    .in(OrdersDetail::getOrderId, orderIds)
                    .eq(OrdersDetail::getDeleted, Constants.ZERO));
            Map<String, List<OrdersDetail>> grouped = details.stream()
                    .filter(d -> StringUtils.isNotBlank(d.getLuggageName()))
                    .collect(Collectors.groupingBy(OrdersDetail::getLuggageName));
            for (Map.Entry<String, List<OrdersDetail>> entry : grouped.entrySet()) {
                LuggageTypeItem item = new LuggageTypeItem();
                item.setLuggageName(entry.getKey());
                item.setOrderCount((long) entry.getValue().stream().map(OrdersDetail::getOrderId).distinct().count());
                item.setLuggageCount((long) entry.getValue().stream()
                        .mapToInt(d -> d.getNum() != null ? d.getNum() : 0).sum());
                luggageTypeList.add(item);
            }
        }
        return luggageTypeList;
    }
    @Override
    public List<FinanceOverviewVO> financeOverview(FinanceQueryDTO query) {
        // 补齐日期:startDate 取月初,endDate 取月末
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(query.getStartDate());
        startCal.set(Calendar.DAY_OF_MONTH, 1);
        startCal.set(Calendar.HOUR_OF_DAY, 0);
        startCal.set(Calendar.MINUTE, 0);
        startCal.set(Calendar.SECOND, 0);
        startCal.set(Calendar.MILLISECOND, 0);
        query.setStartDate(startCal.getTime());
        Calendar endCal = Calendar.getInstance();
        endCal.setTime(query.getEndDate());
        endCal.set(Calendar.DAY_OF_MONTH, endCal.getActualMaximum(Calendar.DAY_OF_MONTH));
        endCal.set(Calendar.HOUR_OF_DAY, 23);
        endCal.set(Calendar.MINUTE, 59);
        endCal.set(Calendar.SECOND, 59);
        endCal.set(Calendar.MILLISECOND, 999);
        query.setEndDate(endCal.getTime());
        List<Orders> orders = ordersMapper.selectList(buildFinanceOrderQueryWrapper(query));
        // 根据筛选出的订单主键查询已支付的逾期费用
        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);
            }
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
        Map<String, List<Orders>> grouped = orders.stream()
                .collect(Collectors.groupingBy(o -> sdf.format(o.getCreateTime())));
        // 生成完整年月列表
        List<String> months = new ArrayList<>();
        Calendar loop = Calendar.getInstance();
        loop.setTime(query.getStartDate());
        loop.set(Calendar.DAY_OF_MONTH, 1);
        Calendar monthEnd = Calendar.getInstance();
        monthEnd.setTime(query.getEndDate());
        while (!loop.after(monthEnd)) {
            months.add(sdf.format(loop.getTime()));
            loop.add(Calendar.MONTH, 1);
        }
        BigDecimal hundred = BigDecimal.valueOf(100);
        List<FinanceOverviewVO> result = new ArrayList<>();
        for (String month : months) {
            List<Orders> monthOrders = grouped.getOrDefault(month, Collections.emptyList());
            long storageRev = 0L, deliveryRev = 0L, shopFee = 0L, driverFeeTotal = 0L, refundTotal = 0L, overdueTotal = 0L;
            for (Orders o : monthOrders) {
                long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
                long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
                long overdue = overduePaidMap.getOrDefault(o.getId(), 0L);
                long actual = pay - refund + overdue;
                long depFee = o.getDepositShopFee() != null ? o.getDepositShopFee() : 0L;
                long takeFee = o.getTakeShopFee() != null ? o.getTakeShopFee() : 0L;
                long dFee = Constants.equalsInteger(o.getType(),Constants.ONE)&&o.getDriverFee() != null ? o.getDriverFee() : 0L;
                if (Constants.equalsInteger(o.getType(), Constants.ZERO)) {
                    storageRev += actual;
                    shopFee += depFee;
                } else {
                    deliveryRev += actual;
                    shopFee += depFee + takeFee;
                }
                driverFeeTotal += dFee;
                refundTotal += refund;
                overdueTotal += overdue;
            }
            long totalRev = storageRev + deliveryRev;
            long netRev = totalRev - shopFee - driverFeeTotal;
            FinanceOverviewVO vo = new FinanceOverviewVO();
            vo.setDate(month);
            vo.setStorageRevenue(BigDecimal.valueOf(storageRev).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setDeliveryRevenue(BigDecimal.valueOf(deliveryRev).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setTotalRevenue(BigDecimal.valueOf(totalRev).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setShopFeeTotal(BigDecimal.valueOf(shopFee).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setDriverFeeTotal(BigDecimal.valueOf(driverFeeTotal).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setRefundAmount(BigDecimal.valueOf(refundTotal).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setOverdueAmount(BigDecimal.valueOf(overdueTotal).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setNetRevenue(BigDecimal.valueOf(netRev).divide(hundred, 2, RoundingMode.HALF_UP));
            result.add(vo);
        }
        return result;
    }
    @Override
    public List<ShopTopVO> shopTop(TrendQueryDTO query) {
        TrendDateRange range = parseTrendDateRange(query);
        List<Orders> orders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
                .eq(Orders::getDeleted, Constants.ZERO)
                .eq(Orders::getStatus, 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);
            }
        }
        // 按门店ID归集: key = shopId
        Map<Integer, long[]> map = new LinkedHashMap<>();
        for (Orders o : orders) {
            long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
            long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
            long overdue = overduePaidMap.getOrDefault(o.getId(), 0L);
            boolean hasRefund = refund > 0;
            long rev = pay - refund + overdue;
            // 存件门店
            if (o.getDepositShopId() != null) {
                long depFee = o.getDepositShopFee() != null ? o.getDepositShopFee() : 0L;
                long[] arr = map.computeIfAbsent(o.getDepositShopId(), k -> new long[4]); // [count, revenue, fee, refundCount]
                arr[0]++;
                arr[1] += rev;
                arr[2] += depFee;
                if (hasRefund) arr[3]++;
            }
            // 异地且有取件门店
            if (Constants.equalsInteger(o.getType(), Constants.ONE) && o.getTakeShopId() != null) {
                long takeFee = o.getTakeShopFee() != null ? o.getTakeShopFee() : 0L;
                long[] arr = map.computeIfAbsent(o.getTakeShopId(), k -> new long[4]);
                arr[0]++;
                arr[1] += rev;
                arr[2] += takeFee;
                if (hasRefund) arr[3]++;
            }
        }
        // 批量查门店名称
        Set<Integer> shopIds = map.keySet();
        Map<Integer, String> shopNameMap = new HashMap<>();
        if (!shopIds.isEmpty()) {
            List<ShopInfo> shops = shopInfoMapper.selectList(new QueryWrapper<ShopInfo>().lambda()
                    .in(ShopInfo::getId, shopIds)
                    .select(ShopInfo::getId, ShopInfo::getName));
            for (ShopInfo s : shops) {
                shopNameMap.put(s.getId(), s.getName());
            }
        }
        BigDecimal hundred = BigDecimal.valueOf(100);
        List<ShopTopVO> result = new ArrayList<>();
        for (Map.Entry<Integer, long[]> entry : map.entrySet()) {
            long[] arr = entry.getValue();
            ShopTopVO vo = new ShopTopVO();
            vo.setShopId(entry.getKey());
            vo.setShopName(shopNameMap.getOrDefault(entry.getKey(), ""));
            vo.setCompletedCount(arr[0]);
            vo.setTotalRevenue(BigDecimal.valueOf(arr[1]).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setShopFeeTotal(BigDecimal.valueOf(arr[2]).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setRefundCount(arr[3]);
            vo.setPenaltyAmount(BigDecimal.ZERO);
            result.add(vo);
        }
        result.sort((a, b) -> Long.compare(b.getCompletedCount(), a.getCompletedCount()));
        return result.size() > 10 ? result.subList(0, 10) : result;
    }
    @Override
    public List<DriverTopVO> driverTop(TrendQueryDTO query) {
        TrendDateRange range = parseTrendDateRange(query);
        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.finished.getKey())
                .eq(Orders::getType, Constants.ONE)
                .ge(Orders::getCreateTime, range.startDate)
                .le(Orders::getCreateTime, range.endDate));
        System.out.println(
                orders.stream().map(i->i.getDriverFee()).reduce(0L,Long::sum)
        );
        // 根据筛选出的订单主键查询已支付的逾期费用
        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);
            }
        }
        // 按司机归集: key = acceptDriver
        Map<Integer, long[]> map = new LinkedHashMap<>();
        for (Orders o : orders) {
            if (o.getAcceptDriver() == null) continue;
            long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
            long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
            long overdue = overduePaidMap.getOrDefault(o.getId(), 0L);
            long dFee = o.getDriverFee() != null ? o.getDriverFee() : 0L;
            long[] arr = map.computeIfAbsent(o.getAcceptDriver(), k -> new long[4]); // [count, revenue, fee, refundCount]
            arr[0]++;
            arr[1] += pay - refund + overdue;
            arr[2] += dFee;
            if (refund > 0) arr[3]++;
        }
        // 批量查司机姓名
        Set<Integer> driverIds = map.keySet();
        Map<Integer, String> driverNameMap = new HashMap<>();
        if (!driverIds.isEmpty()) {
            List<DriverInfo> drivers = driverInfoMapper.selectList(new QueryWrapper<DriverInfo>().lambda()
                    .in(DriverInfo::getId, driverIds)
                    .select(DriverInfo::getMemberId, DriverInfo::getName));
            for (DriverInfo d : drivers) {
                driverNameMap.put(d.getMemberId(), d.getName());
            }
        }
        BigDecimal hundred = BigDecimal.valueOf(100);
        List<DriverTopVO> result = new ArrayList<>();
        for (Map.Entry<Integer, long[]> entry : map.entrySet()) {
            long[] arr = entry.getValue();
            DriverTopVO vo = new DriverTopVO();
            vo.setDriverId(entry.getKey());
            vo.setDriverName(driverNameMap.getOrDefault(entry.getKey(), "未知"));
            vo.setCompletedCount(arr[0]);
            vo.setTotalRevenue(BigDecimal.valueOf(arr[1]).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setDriverFeeTotal(BigDecimal.valueOf(arr[2]).divide(hundred, 2, RoundingMode.HALF_UP));
            vo.setRefundCount(arr[3]);
            vo.setPenaltyAmount(BigDecimal.ZERO);
            result.add(vo);
        }
        result.sort((a, b) -> Long.compare(b.getCompletedCount(), a.getCompletedCount()));
        return result.size() > 10 ? result.subList(0, 10) : result;
    }
    // ========== 私有方法 ==========
    private QueryWrapper<Orders> buildOrderQueryWrapper(DataBoardQueryDTO query) {
@@ -294,7 +608,7 @@
                        Constants.OrderStatus.accepted.getKey(),
                        Constants.OrderStatus.delivering.getKey(),
                        Constants.OrderStatus.arrived.getKey(),
                        Constants.OrderStatus.overdue.getKey());
                        Constants.OrderStatus.finished.getKey());
        if (query.getStartDate() != null) {
            qw.lambda().ge(Orders::getCreateTime, query.getStartDate());
        }
@@ -308,30 +622,130 @@
        return qw;
    }
    private Date get30DaysAgoStart() {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DAY_OF_MONTH, -29);
        return cal.getTime();
    private QueryWrapper<Orders> buildFinanceOrderQueryWrapper(FinanceQueryDTO query) {
        QueryWrapper<Orders> qw = new QueryWrapper<>();
        qw.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.finished.getKey());
        if (query.getStartDate() != null) {
            qw.lambda().ge(Orders::getCreateTime, query.getStartDate());
        }
        if (query.getEndDate() != null) {
            qw.lambda().le(Orders::getCreateTime, Utils.Date.getEnd(query.getEndDate()));
        }
        return qw;
    }
    private TrendDateRange parseTrendDateRange(TrendQueryDTO query) {
        Calendar now = Calendar.getInstance();
        TrendDateRange range = new TrendDateRange();
        if (StringUtils.isNotBlank(query.getMonth())) {
            // 按月查询:yyyy-MM
            SimpleDateFormat parseSdf = new SimpleDateFormat("yyyy-MM");
            Date monthDate;
            try {
                monthDate = parseSdf.parse(query.getMonth());
            } catch (Exception e) {
                throw new IllegalArgumentException("month 格式错误,应为 yyyy-MM");
            }
            Calendar monthCal = Calendar.getInstance();
            monthCal.setTime(monthDate);
            // 当月1日起始
            monthCal.set(Calendar.DAY_OF_MONTH, 1);
            monthCal.set(Calendar.HOUR_OF_DAY, 0);
            monthCal.set(Calendar.MINUTE, 0);
            monthCal.set(Calendar.SECOND, 0);
            monthCal.set(Calendar.MILLISECOND, 0);
            range.startDate = monthCal.getTime();
            // 月末
            monthCal.set(Calendar.DAY_OF_MONTH, monthCal.getActualMaximum(Calendar.DAY_OF_MONTH));
            monthCal.set(Calendar.HOUR_OF_DAY, 23);
            monthCal.set(Calendar.MINUTE, 59);
            monthCal.set(Calendar.SECOND, 59);
            monthCal.set(Calendar.MILLISECOND, 999);
            Date monthEnd = monthCal.getTime();
            // 不能超过当前时间
            range.endDate = monthEnd.after(now.getTime()) ? now.getTime() : monthEnd;
            range.byMonth = true;
            range.pattern = "MM-dd";
        } else if (StringUtils.isNotBlank(query.getYear())) {
            // 按年查询:yyyy
            int year = Integer.parseInt(query.getYear());
            Calendar yearStart = Calendar.getInstance();
            yearStart.set(Calendar.YEAR, year);
            yearStart.set(Calendar.MONTH, Calendar.JANUARY);
            yearStart.set(Calendar.DAY_OF_MONTH, 1);
            yearStart.set(Calendar.HOUR_OF_DAY, 0);
            yearStart.set(Calendar.MINUTE, 0);
            yearStart.set(Calendar.SECOND, 0);
            yearStart.set(Calendar.MILLISECOND, 0);
            range.startDate = yearStart.getTime();
            Calendar yearEnd = Calendar.getInstance();
            yearEnd.set(Calendar.YEAR, year);
            yearEnd.set(Calendar.MONTH, Calendar.DECEMBER);
            yearEnd.set(Calendar.DAY_OF_MONTH, 31);
            yearEnd.set(Calendar.HOUR_OF_DAY, 23);
            yearEnd.set(Calendar.MINUTE, 59);
            yearEnd.set(Calendar.SECOND, 59);
            yearEnd.set(Calendar.MILLISECOND, 999);
            Date end = yearEnd.getTime();
            range.endDate = end.after(now.getTime()) ? now.getTime() : end;
            range.byMonth = false;
            range.pattern = "MM";
        } else {
            throw new IllegalArgumentException("请传入 month 或 year 参数");
        }
        return range;
    }
    private static class TrendDateRange {
        Date startDate;
        Date endDate;
        boolean byMonth; // true=按月查(按日返回),false=按年查(按月返回)
        String pattern;  // SimpleDateFormat 格式
    }
    @FunctionalInterface
    private interface DailyVOBuilder<T> {
        T build(String date);
    private interface TrendVOBuilder<T> {
        T build(String key, String label);
    }
    private <T> List<T> buildDailyList(Date startDate, DailyVOBuilder<T> builder) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    private <T> List<T> buildTrendList(TrendDateRange range, TrendVOBuilder<T> builder) {
        List<T> result = new ArrayList<>();
        Calendar loop = Calendar.getInstance();
        loop.setTime(startDate);
        Calendar end = Calendar.getInstance();
        while (!loop.after(end)) {
            result.add(builder.build(sdf.format(loop.getTime())));
            loop.add(Calendar.DAY_OF_MONTH, 1);
        SimpleDateFormat sdf = new SimpleDateFormat(range.pattern);
        if (range.byMonth) {
            // 按日循环
            Calendar loop = Calendar.getInstance();
            loop.setTime(range.startDate);
            Calendar end = Calendar.getInstance();
            end.setTime(range.endDate);
            while (!loop.after(end)) {
                String dateStr = sdf.format(loop.getTime());
                result.add(builder.build(dateStr, dateStr));
                loop.add(Calendar.DAY_OF_MONTH, 1);
            }
        } else {
            // 按月循环 1~12,不能超过 endDate 所在月
            Calendar endCal = Calendar.getInstance();
            endCal.setTime(range.endDate);
            int endMonth = endCal.get(Calendar.MONTH); // 0-based
            for (int m = 0; m <= endMonth; m++) {
                String key = String.format("%02d", m + 1);
                String label = (m + 1) + "月";
                result.add(builder.build(key, label));
            }
        }
        return result;
    }