浏览代码

fix: 修复数据统计多项准确性问题

- getAvgDuration/getAvgFrequency 查询条件 gt→lt,修复返回空数据问题
- getAvgFrequency distinct()移到map()之后,修复用户去重失效
- totalRegisteredMembers 按站点过滤,与其他指标一致
- avgOrderDuration 除数改为仅含有效 totalSeconds 的订单
- Dashboard 收入改为从 SplitRecord 计算充值+跨店收入,与统计表口径一致
- 所有日期范围查询统一使用 plusDays(1)/plusMonths(1) 上界模式

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 10 小时之前
父节点
当前提交
14b8a472ed

+ 4 - 4
car-wash-service/src/main/java/com/kym/service/impl/SplitRecordServiceImpl.java

@@ -236,10 +236,10 @@ public class SplitRecordServiceImpl extends MyBaseServiceImpl<SplitRecordMapper,
     }
 
     /**
-     * 获取日期结束时间
+     * 获取日期结束时间(次日零点,配合 lt 使用)
      */
     private LocalDateTime getEndOfDay(LocalDate date) {
-        return date.atTime(LocalTime.MAX);
+        return date.plusDays(1).atStartOfDay();
     }
 
     /**
@@ -250,10 +250,10 @@ public class SplitRecordServiceImpl extends MyBaseServiceImpl<SplitRecordMapper,
     }
 
     /**
-     * 获取月份结束时间
+     * 获取月份结束时间(下月首日零点,配合 lt 使用)
      */
     private LocalDateTime getEndOfMonth(LocalDate date) {
-        return date.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);
+        return date.with(TemporalAdjusters.firstDayOfMonth()).plusMonths(1).atStartOfDay();
     }
 
 }

+ 32 - 12
car-wash-service/src/main/java/com/kym/service/impl/StatServiceImpl.java

@@ -3,6 +3,8 @@ package com.kym.service.impl;
 import cn.dev33.satoken.stp.StpUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.kym.common.utils.CommUtil;
+import com.kym.entity.SplitRecord;
+import com.kym.entity.User;
 import com.kym.entity.WashDevice;
 import com.kym.entity.WashOrder;
 import com.kym.entity.queryParams.StatQueryParam;
@@ -29,10 +31,13 @@ public class StatServiceImpl implements StatService {
     private final UserService userService;
     private final WashOrderService washOrderService;
     private final WashDeviceService washDeviceService;
-    public StatServiceImpl(UserService userService, WashOrderService washOrderService, WashDeviceService washDeviceService) {
+    private final SplitRecordService splitRecordService;
+    public StatServiceImpl(UserService userService, WashOrderService washOrderService, WashDeviceService washDeviceService,
+                           SplitRecordService splitRecordService) {
         this.userService = userService;
         this.washOrderService = washOrderService;
         this.washDeviceService = washDeviceService;
+        this.splitRecordService = splitRecordService;
     }
 
     @Override
@@ -80,7 +85,7 @@ public class StatServiceImpl implements StatService {
         var endTime = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
         LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.ge(WashOrder::getStartTime, startTime);
-        wrapper.gt(WashOrder::getStartTime, endTime);
+        wrapper.lt(WashOrder::getStartTime, endTime);
         var list = washOrderService.list(wrapper);
         if (!list.isEmpty()) {
             var totalSeconds = list.stream().mapToLong(WashOrder::getTotalSeconds).sum();
@@ -101,10 +106,10 @@ public class StatServiceImpl implements StatService {
         var endTime = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
         LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.ge(WashOrder::getStartTime, startTime);
-        wrapper.gt(WashOrder::getStartTime, endTime);
+        wrapper.lt(WashOrder::getStartTime, endTime);
         var list = washOrderService.list(wrapper);
         // 洗车人数
-        var users = list.stream().distinct().map(WashOrder::getUserId).count();
+        var users = list.stream().map(WashOrder::getUserId).distinct().count();
         return users == 0 ? 0 : (double) Math.round((double) list.size() / users * 100) / 100;
     }
 
@@ -121,17 +126,24 @@ public class StatServiceImpl implements StatService {
                 .ge(WashOrder::getStartTime, LocalDateTime.of(LocalDate.now(), LocalTime.MIN))
                 .list();
 
-        // 今日收入:V2结算方案从订单中计算
-        var todayIncome = todayOrders.stream().filter(o -> o.getAmountReceived() != null).mapToInt(WashOrder::getAmountReceived).sum();
+        // 今日收入:充值收入 + 跨店收入(按 split_record 创建时间,与日统计表口径一致)
+        var todayIncome = splitRecordService.lambdaQuery()
+                .eq(SplitRecord::getToStationId, stationId)
+                .in(SplitRecord::getType, SplitRecord.TYPE_RECHARGE, SplitRecord.TYPE_CROSS_INCOME)
+                .ge(SplitRecord::getCreateTime, LocalDateTime.of(LocalDate.now(), LocalTime.MIN))
+                .lt(SplitRecord::getCreateTime, LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.MIN))
+                .list().stream().mapToInt(SplitRecord::getAmount).sum();
 
         // 站点今日注册人数
         var todayRegistrations = userService.countDailyRegister(LocalDate.now(), stationId);
 
         // 平均洗车消费单价(分)
-       var avgOrderPrice = todayOrders.isEmpty() ? 0 : (int)todayIncome/todayOrders.size();
+        var todayConsumptionAmount = todayOrders.stream().filter(o -> o.getAmount() != null).mapToInt(WashOrder::getAmount).sum();
+        var avgOrderPrice = todayOrders.isEmpty() ? 0 : todayConsumptionAmount / todayOrders.size();
        
         // 平均洗车时长(秒)
-       var avgOrderDuration = todayOrders.isEmpty() ? 0 : (int) (todayOrders.stream().filter(o -> o.getTotalSeconds() != null).mapToLong(WashOrder::getTotalSeconds).sum() / todayOrders.size());
+        var ordersWithDuration = todayOrders.stream().filter(o -> o.getTotalSeconds() != null).toList();
+       var avgOrderDuration = ordersWithDuration.isEmpty() ? 0 : (int) (ordersWithDuration.stream().mapToLong(WashOrder::getTotalSeconds).sum() / ordersWithDuration.size());
 
         // 月度数据
         var monthOrders = washOrderService.lambdaQuery()
@@ -140,7 +152,12 @@ public class StatServiceImpl implements StatService {
                 .le(WashOrder::getStartTime, LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX))
                 .list();
         var monthConsumptionAmount = monthOrders.stream().filter(o -> o.getAmount() != null).mapToInt(WashOrder::getAmount).sum();
-        var monthIncome = monthOrders.stream().filter(o -> o.getAmountReceived() != null).mapToInt(WashOrder::getAmountReceived).sum();
+        var monthIncome = splitRecordService.lambdaQuery()
+                .eq(SplitRecord::getToStationId, stationId)
+                .in(SplitRecord::getType, SplitRecord.TYPE_RECHARGE, SplitRecord.TYPE_CROSS_INCOME)
+                .ge(SplitRecord::getCreateTime, LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN))
+                .lt(SplitRecord::getCreateTime, LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()).plusMonths(1).atTime(LocalTime.MIN))
+                .list().stream().mapToInt(SplitRecord::getAmount).sum();
         var monthRegistrations = userService.countMonthRegister(LocalDate.now());
         var monthOrdersMap = washOrderService.countMonthOrders(LocalDate.now());
 
@@ -148,13 +165,16 @@ public class StatServiceImpl implements StatService {
         var allOrders = washOrderService.lambdaQuery()
                 .eq(WashOrder::getStationId, stationId)
                 .list();
-        var totalIncome = allOrders.stream().filter(o -> o.getAmountReceived() != null).mapToInt(WashOrder::getAmountReceived).sum();
+        var totalIncome = splitRecordService.lambdaQuery()
+                .eq(SplitRecord::getToStationId, stationId)
+                .in(SplitRecord::getType, SplitRecord.TYPE_RECHARGE, SplitRecord.TYPE_CROSS_INCOME)
+                .list().stream().mapToInt(SplitRecord::getAmount).sum();
         var totalConsumptionAmount = allOrders.stream().filter(o -> o.getAmount() != null).mapToInt(WashOrder::getAmount).sum();
-        var totalMembers = (int) userService.count();
+        var totalMembers = (int) userService.lambdaQuery().eq(User::getStationId, stationId).count();
 
         DashboardVo dashboardVo = new DashboardVo();
         dashboardVo.setTodayIncome(todayIncome);
-        dashboardVo.setTodayConsumptionAmount(todayOrders.stream().filter(o -> o.getAmount() != null).mapToInt(WashOrder::getAmount).sum());
+        dashboardVo.setTodayConsumptionAmount(todayConsumptionAmount);
         dashboardVo.setTodayWashOrders(todayOrders.size());
         dashboardVo.setTodayRegisteredMembers(CommUtil.isEmptyOrNull(todayRegistrations) ? 0 : todayRegistrations.getOrDefault(stationId, 0));
 

+ 2 - 2
car-wash-service/src/main/java/com/kym/service/impl/UserServiceImpl.java

@@ -337,7 +337,7 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
     public Map<String, Integer> countDailyRegister(LocalDate statDay, String... stationId) {
         LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
         wrapper.ge(User::getCreateTime, LocalDateTime.of(statDay, LocalTime.MIN));
-        wrapper.lt(User::getCreateTime, LocalDateTime.of(statDay, LocalTime.MAX));
+        wrapper.lt(User::getCreateTime, LocalDateTime.of(statDay.plusDays(1), LocalTime.MIN));
         if (stationId.length > 0) {
             wrapper.in(User::getStationId, Arrays.asList(stationId));
         }
@@ -354,7 +354,7 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
     @Override
     public Map<String, Integer> countMonthRegister(LocalDate statDay) {
         var startTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN);
-        var endTime = statDay.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);
+        var endTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).plusMonths(1).atTime(LocalTime.MIN);
         LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
         wrapper.ge(User::getCreateTime, startTime);
         wrapper.lt(User::getCreateTime, endTime);

+ 5 - 5
car-wash-service/src/main/java/com/kym/service/impl/WashOrderServiceImpl.java

@@ -427,7 +427,7 @@ public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, Was
     @Override
     public Map<String, Integer> sumMonthAmount(LocalDate statDay) {
         var startTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN);
-        var endTime = statDay.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);
+        var endTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).plusMonths(1).atTime(LocalTime.MIN);
         LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.select(WashOrder::getAmount, WashOrder::getStationId);
         wrapper.ge(WashOrder::getStartTime, startTime);
@@ -447,7 +447,7 @@ public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, Was
     public Map<String, Integer> countDailyOrders(LocalDate statDay) {
         LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.ge(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MIN));
-        wrapper.lt(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MAX));
+        wrapper.lt(WashOrder::getStartTime, LocalDateTime.of(statDay.plusDays(1), LocalTime.MIN));
         // 按照站点分组统计订单数量
         return list(wrapper).stream().collect(Collectors.groupingBy(WashOrder::getStationId, Collectors.summingInt(o -> 1)));
     }
@@ -461,7 +461,7 @@ public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, Was
     @Override
     public Map<String, Integer> countMonthOrders(LocalDate statDay) {
         var startTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN);
-        var endTime = statDay.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);
+        var endTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).plusMonths(1).atTime(LocalTime.MIN);
         LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.select(WashOrder::getStationId);
         wrapper.ge(WashOrder::getStartTime, startTime);
@@ -475,7 +475,7 @@ public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, Was
         LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.select(WashOrder::getUserId, WashOrder::getStationId);
         wrapper.ge(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MIN));
-        wrapper.lt(WashOrder::getStartTime, LocalDateTime.of(statDay, LocalTime.MAX));
+        wrapper.lt(WashOrder::getStartTime, LocalDateTime.of(statDay.plusDays(1), LocalTime.MIN));
         wrapper.groupBy(WashOrder::getStationId, WashOrder::getUserId);
         return list(wrapper).stream()
                 .collect(Collectors.groupingBy(WashOrder::getStationId, Collectors.summingInt(o -> 1)));
@@ -484,7 +484,7 @@ public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, Was
     @Override
     public Map<String, Integer> countMonthActiveUsers(LocalDate statDay) {
         var startTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN);
-        var endTime = statDay.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);
+        var endTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).plusMonths(1).atTime(LocalTime.MIN);
         LambdaQueryWrapper<WashOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.select(WashOrder::getUserId, WashOrder::getStationId);
         wrapper.ge(WashOrder::getStartTime, startTime);