Browse Source

CRM:优化【客户统计】的代码实现

YunaiV 1 year ago
parent
commit
90f82b157d
21 changed files with 497 additions and 614 deletions
  1. 13 15
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java
  2. 0 39
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
  3. 140 4
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
  4. 0 1
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
  5. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.http
  6. 9 9
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.java
  7. 0 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerByUserBaseRespVO.java
  8. 6 25
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerContractSummaryRespVO.java
  9. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByDateRespVO.java
  10. 2 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByUserRespVO.java
  11. 4 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerReqVO.java
  12. 2 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByDateRespVO.java
  13. 4 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByUserRespVO.java
  14. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByDateRespVO.java
  15. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByTypeRespVO.java
  16. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByUserRespVO.java
  17. 1 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankRespVO.java
  18. 12 12
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java
  19. 3 5
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java
  20. 146 336
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java
  21. 144 144
      yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml

+ 13 - 15
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.common.enums;
 
+import cn.hutool.core.util.ArrayUtil;
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -7,7 +8,7 @@ import lombok.Getter;
 import java.util.Arrays;
 
 /**
- * 时间间隔类型枚举
+ * 时间间隔枚举
  *
  * @author dhb52
  */
@@ -15,26 +16,19 @@ import java.util.Arrays;
 @AllArgsConstructor
 public enum DateIntervalEnum implements IntArrayValuable {
 
-    TODAY(1, "今天"),
-    YESTERDAY(2, "昨天"),
-    THIS_WEEK(3, "本周"),
-    LAST_WEEK(4, "上周"),
-    THIS_MONTH(5, "本月"),
-    LAST_MONTH(6, "上月"),
-    THIS_QUARTER(7, "本季度"),
-    LAST_QUARTER(8, "上季度"),
-    THIS_YEAR(9, "本年"),
-    LAST_YEAR(10, "去年"),
-    CUSTOMER(11, "自定义"),
+    DAY(1, "天"),
+    WEEK(2, "周"),
+    MONTH(3, "月"),
+    QUARTER(4, "季度"),
+    YEAR(5, "年")
     ;
 
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getType).toArray();
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getInterval).toArray();
 
     /**
      * 类型
      */
-    private final Integer type;
-
+    private final Integer interval;
     /**
      * 名称
      */
@@ -45,4 +39,8 @@ public enum DateIntervalEnum implements IntArrayValuable {
         return ARRAYS;
     }
 
+    public static DateIntervalEnum valueOf(Integer interval) {
+        return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values());
+    }
+
 }

+ 0 - 39
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java

@@ -65,19 +65,11 @@ public class DateUtils {
         return new Date(System.currentTimeMillis() + duration.toMillis());
     }
 
-    public static boolean isExpired(Date time) {
-        return System.currentTimeMillis() > time.getTime();
-    }
-
     public static boolean isExpired(LocalDateTime time) {
         LocalDateTime now = LocalDateTime.now();
         return now.isAfter(time);
     }
 
-    public static long diff(Date endTime, Date startTime) {
-        return endTime.getTime() - startTime.getTime();
-    }
-
     /**
      * 创建指定时间
      *
@@ -134,37 +126,6 @@ public class DateUtils {
         return a.isAfter(b) ? a : b;
     }
 
-    /**
-     * 计算当期时间相差的日期
-     *
-     * @param field  日历字段.<br/>eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,<br/>Calendar.HOUR_OF_DAY等.
-     * @param amount 相差的数值
-     * @return 计算后的日志
-     */
-    public static Date addDate(int field, int amount) {
-        return addDate(null, field, amount);
-    }
-
-    /**
-     * 计算当期时间相差的日期
-     *
-     * @param date   设置时间
-     * @param field  日历字段 例如说,{@link Calendar#DAY_OF_MONTH} 等
-     * @param amount 相差的数值
-     * @return 计算后的日志
-     */
-    public static Date addDate(Date date, int field, int amount) {
-        if (amount == 0) {
-            return date;
-        }
-        Calendar c = Calendar.getInstance();
-        if (date != null) {
-            c.setTime(date);
-        }
-        c.add(field, amount);
-        return c.getTime();
-    }
-
     /**
      * 是否今天
      *

+ 140 - 4
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java

@@ -1,13 +1,18 @@
 package cn.iocoder.yudao.framework.common.util.date;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DatePattern;
 import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
 
-import java.time.Duration;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
+import java.time.*;
+import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * 时间工具类,用于 {@link java.time.LocalDateTime}
@@ -21,6 +26,22 @@ public class LocalDateTimeUtils {
      */
     public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
 
+    /**
+     * 解析时间
+     *
+     * 相比 {@link LocalDateTimeUtil#parse(CharSequence)} 方法来说,会尽量去解析,直到成功
+     *
+     * @param time 时间
+     * @return 时间字符串
+     */
+    public static LocalDateTime parse(String time) {
+        try {
+            return LocalDateTimeUtil.parse(time, DatePattern.NORM_DATE_PATTERN);
+        } catch (DateTimeParseException e) {
+            return LocalDateTimeUtil.parse(time);
+        }
+    }
+
     public static LocalDateTime addTime(Duration duration) {
         return LocalDateTime.now().plus(duration);
     }
@@ -54,6 +75,21 @@ public class LocalDateTimeUtils {
         return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
     }
 
+    /**
+     * 判指定断时间,是否在该时间范围内
+     *
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @param time 指定时间
+     * @return 是否
+     */
+    public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, String time) {
+        if (startTime == null || endTime == null || time == null) {
+            return false;
+        }
+        return LocalDateTimeUtil.isIn(parse(time), startTime, endTime);
+    }
+
     /**
      * 判断当前时间是否在该时间范围内
      *
@@ -122,6 +158,16 @@ public class LocalDateTimeUtils {
         return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
     }
 
+    /**
+     * 获得指定日期所在季度
+     *
+     * @param date 日期
+     * @return 所在季度
+     */
+    public static int getQuarterOfYear(LocalDateTime date) {
+        return (date.getMonthValue() - 1) / 3 + 1;
+    }
+
     /**
      * 获取指定日期到现在过了几天,如果指定日期在当前日期之后,获取结果为负
      *
@@ -168,4 +214,94 @@ public class LocalDateTimeUtils {
         return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);
     }
 
+    public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime,
+                                                         LocalDateTime endTime,
+                                                         Integer interval) {
+        // 1.1 找到枚举
+        DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
+        Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
+        // 1.2 将时间对齐
+        startTime = LocalDateTimeUtil.beginOfDay(startTime);
+        endTime = LocalDateTimeUtil.endOfDay(endTime);
+
+        // 2. 循环,生成时间范围
+        List<LocalDateTime[]> timeRanges = new ArrayList<>();
+        switch (intervalEnum) {
+            case DateIntervalEnum.DAY:
+                while (startTime.isBefore(endTime)) {
+                    timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});
+                    startTime = startTime.plusDays(1);
+                }
+                break;
+            case DateIntervalEnum.WEEK:
+                while (startTime.isBefore(endTime)) {
+                    LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1);
+                    timeRanges.add(new LocalDateTime[]{startTime, endOfWeek});
+                    startTime = endOfWeek.plusNanos(1);
+                }
+                break;
+            case DateIntervalEnum.MONTH:
+                while (startTime.isBefore(endTime)) {
+                    LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1);
+                    timeRanges.add(new LocalDateTime[]{startTime, endOfMonth});
+                    startTime = endOfMonth.plusNanos(1);
+                }
+                break;
+            case DateIntervalEnum.QUARTER:
+                while (startTime.isBefore(endTime)) {
+                    LocalDateTime quarterEnd = startTime.withMonth(getQuarterOfYear(startTime) * 3 + 1)
+                            .withDayOfMonth(1).minusNanos(1);
+                    timeRanges.add(new LocalDateTime[]{startTime, quarterEnd});
+                    startTime = quarterEnd.plusNanos(1);
+                }
+                break;
+            case DateIntervalEnum.YEAR:
+                while (startTime.isBefore(endTime)) {
+                    LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1);
+                    timeRanges.add(new LocalDateTime[]{startTime, endOfYear});
+                    startTime = endOfYear.plusNanos(1);
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid interval: " + interval);
+        }
+        // 3. 兜底,最后一个时间,需要保持在 endTime 之前
+        LocalDateTime[] lastTimeRange = CollUtil.getLast(timeRanges);
+        if (lastTimeRange != null) {
+            lastTimeRange[1] = endTime;
+        }
+        return timeRanges;
+    }
+
+    /**
+     * 格式化时间范围
+     *
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @param interval  时间间隔
+     * @return 时间范围
+     */
+    public static String formatDateRange(LocalDateTime startTime, LocalDateTime endTime, Integer interval) {
+        // 1. 找到枚举
+        DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
+        Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
+
+        // 2. 循环,生成时间范围
+        switch (intervalEnum) {
+            case DateIntervalEnum.DAY:
+                return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);
+            case DateIntervalEnum.WEEK:
+                return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)
+                        + StrUtil.format("(第 {} 周)", LocalDateTimeUtil.weekOfYear(startTime));
+            case DateIntervalEnum.MONTH:
+                return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN);
+            case DateIntervalEnum.QUARTER:
+                return StrUtil.format("{}-Q{}", startTime.getYear(), getQuarterOfYear(startTime));
+            case DateIntervalEnum.YEAR:
+                return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN);
+            default:
+                throw new IllegalArgumentException("Invalid interval: " + interval);
+        }
+    }
+
 }

+ 0 - 1
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java

@@ -104,6 +104,5 @@ public interface ErrorCodeConstants {
     ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限");
 
     // ========== 数据统计 1_020_014_000 ==========
-    ErrorCode STATISTICS_CUSTOMER_TIMES_NOT_SET = new ErrorCode(1_020_014_000, "自定义时间间隔,必须输入时间区间");
 
 }

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.http

@@ -1,6 +1,6 @@
 # == 1. 客户总量分析 ==
 ### 1.1 客户总量分析(按日)
-GET {{baseUrl}}/crm/statistics-customer/get-customer-summary-by-date?deptId=100&intervalType=11&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
+GET {{baseUrl}}/crm/statistics-customer/get-customer-summary-by-date?deptId=100&interval=1&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
 

+ 9 - 9
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.java

@@ -40,25 +40,25 @@ public class CrmStatisticsCustomerController {
         return success(customerService.getCustomerSummaryByUser(reqVO));
     }
 
-    @GetMapping("/get-followup-summary-by-date")
+    @GetMapping("/get-follow-up-summary-by-date")
     @Operation(summary = "获取客户跟进次数分析(按日期)")
     @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
-    public CommonResult<List<CrmStatisticsFollowupSummaryByDateRespVO>> getFollowupSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
-        return success(customerService.getFollowupSummaryByDate(reqVO));
+    public CommonResult<List<CrmStatisticsFollowUpSummaryByDateRespVO>> getFollowupSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getFollowUpSummaryByDate(reqVO));
     }
 
-    @GetMapping("/get-followup-summary-by-user")
+    @GetMapping("/get-follow-up-summary-by-user")
     @Operation(summary = "获取客户跟进次数分析(按用户)")
     @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
-    public CommonResult<List<CrmStatisticsFollowupSummaryByUserRespVO>> getFollowupSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
-        return success(customerService.getFollowupSummaryByUser(reqVO));
+    public CommonResult<List<CrmStatisticsFollowUpSummaryByUserRespVO>> getFollowUpSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getFollowUpSummaryByUser(reqVO));
     }
 
-    @GetMapping("/get-followup-summary-by-type")
+    @GetMapping("/get-follow-up-summary-by-type")
     @Operation(summary = "获取客户跟进次数分析(按类型)")
     @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
-    public CommonResult<List<CrmStatisticsFollowupSummaryByTypeRespVO>> getFollowupSummaryByType(@Valid CrmStatisticsCustomerReqVO reqVO) {
-        return success(customerService.getFollowupSummaryByType(reqVO));
+    public CommonResult<List<CrmStatisticsFollowUpSummaryByTypeRespVO>> getFollowUpSummaryByType(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getFollowUpSummaryByType(reqVO));
     }
 
     @GetMapping("/get-contract-summary")

+ 0 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerByUserBaseRespVO.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -13,7 +12,6 @@ import lombok.Data;
 public class CrmStatisticsCustomerByUserBaseRespVO {
 
     @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @JsonIgnore
     private Long ownerUserId;
 
     @Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")

+ 6 - 25
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerContractSummaryRespVO.java

@@ -1,18 +1,12 @@
 package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
 
 
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.math.BigDecimal;
-import java.time.LocalDate;
 import java.time.LocalDateTime;
 
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
-
 @Schema(description = "管理后台 - CRM 客户转化率分析 VO")
 @Data
 public class CrmStatisticsCustomerContractSummaryRespVO {
@@ -29,31 +23,19 @@ public class CrmStatisticsCustomerContractSummaryRespVO {
     @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1200.00")
     private BigDecimal receivablePrice;
 
-    @Schema(description = "客户行业ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    @JsonIgnore
-    private String industryId;
-
-    @Schema(description = "客户行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "金融")
-    private String industryName;
-
-    @Schema(description = "客户来源ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @JsonIgnore
-    private String source;
+    @Schema(description = "客户行业编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    private Integer industryId;
 
-    @Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "外呼")
-    private String sourceName;
+    @Schema(description = "客户来源编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer source;
 
     @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @JsonIgnore
     private Long ownerUserId;
-
     @Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
     private String ownerUserName;
 
     @Schema(description = "创建人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    @JsonIgnore
-    private String creatorUserId;
-
+    private String creator;
     @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED, example = "源码")
     private String creatorUserName;
 
@@ -61,7 +43,6 @@ public class CrmStatisticsCustomerContractSummaryRespVO {
     private LocalDateTime createTime;
 
     @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02 00:00:00")
-    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY, timezone = TIME_ZONE_DEFAULT)
-    private LocalDate orderDate;
+    private LocalDateTime orderDate;
 
 }

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByDateRespVO.java

@@ -11,6 +11,6 @@ public class CrmStatisticsCustomerDealCycleByDateRespVO {
     private String time;
 
     @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
-    private Double customerDealCycle = 0.0;
+    private Double customerDealCycle;
 
 }

+ 2 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByUserRespVO.java

@@ -8,9 +8,9 @@ import lombok.Data;
 public class CrmStatisticsCustomerDealCycleByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
 
     @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
-    private Double customerDealCycle = 0.0;
+    private Double customerDealCycle;
 
     @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer customerDealCount = 0;
+    private Integer customerDealCount;
 
 }

+ 4 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerReqVO.java

@@ -30,12 +30,12 @@ public class CrmStatisticsCustomerReqVO {
      * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
      * 后续,可能会支持选择部分用户进行查询
      */
-    @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
+    @Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
     private List<Long> userIds;
 
     @Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
-    private Integer intervalType;
+    private Integer interval;
 
     /**
      * 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
@@ -49,7 +49,8 @@ public class CrmStatisticsCustomerReqVO {
      * group by DATE_FORMAT(field, #{dateFormat})
      * 非前端传递, 由Service计算后传递给Mapper的参数
      */
-    @Schema(description = "Group By 日期格式", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "%Y%m")
+    @Deprecated
+    @Schema(description = "Group By 日期格式", hidden = true, example = "%Y%m")
     private String sqlDateFormat;
 
 }

+ 2 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByDateRespVO.java

@@ -11,9 +11,9 @@ public class CrmStatisticsCustomerSummaryByDateRespVO {
     private String time;
 
     @Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer customerCreateCount = 0;
+    private Integer customerCreateCount;
 
     @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer customerDealCount = 0;
+    private Integer customerDealCount;
 
 }

+ 4 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByUserRespVO.java

@@ -10,15 +10,15 @@ import java.math.BigDecimal;
 public class CrmStatisticsCustomerSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
 
     @Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer customerCreateCount = 0;
+    private Integer customerCreateCount;
 
     @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer customerDealCount = 0;
+    private Integer customerDealCount;
 
     @Schema(description = "合同总金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
-    private BigDecimal contractPrice = BigDecimal.ZERO;
+    private BigDecimal contractPrice;
 
     @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
-    private BigDecimal receivablePrice = BigDecimal.ZERO;
+    private BigDecimal receivablePrice;
 
 }

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowupSummaryByDateRespVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByDateRespVO.java

@@ -5,15 +5,15 @@ import lombok.Data;
 
 @Schema(description = "管理后台 - CRM 跟进次数分析(按日期) VO")
 @Data
-public class CrmStatisticsFollowupSummaryByDateRespVO {
+public class CrmStatisticsFollowUpSummaryByDateRespVO {
 
     @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
     private String time;
 
     @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer followupRecordCount = 0;
+    private Integer followUpRecordCount;
 
     @Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer followupCustomerCount = 0;
+    private Integer followUpCustomerCount;
 
 }

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowupSummaryByTypeRespVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByTypeRespVO.java

@@ -6,12 +6,12 @@ import lombok.Data;
 
 @Schema(description = "管理后台 - CRM 跟进次数分析(按类型) VO")
 @Data
-public class CrmStatisticsFollowupSummaryByTypeRespVO {
+public class CrmStatisticsFollowUpSummaryByTypeRespVO {
 
     @Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private String followupType;
+    private Integer followUpType;
 
     @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer followupRecordCount = 0;
+    private Integer followUpRecordCount;
 
 }

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowupSummaryByUserRespVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByUserRespVO.java

@@ -5,12 +5,12 @@ import lombok.Data;
 
 @Schema(description = "管理后台 - CRM 跟进次数分析(按用户) VO")
 @Data
-public class CrmStatisticsFollowupSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
+public class CrmStatisticsFollowUpSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
 
     @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer followupRecordCount = 0;
+    private Integer followUpRecordCount;
 
     @Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer followupCustomerCount = 0;
+    private Integer followUpCustomerCount;
 
 }

+ 1 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankRespVO.java

@@ -17,6 +17,7 @@ public class CrmStatisticsRankRespVO {
     @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private String deptName;
 
+    // TODO @芋艿:需要改下,金额是 bigdecimal
     /**
      * 数量是个特别“抽象”的概念,在不同排行下,代表不同含义
      * <p>

+ 12 - 12
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java

@@ -13,32 +13,32 @@ import java.util.List;
 @Mapper
 public interface CrmStatisticsCustomerMapper {
 
-    List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerCreateCountGroupByDate(CrmStatisticsCustomerReqVO reqVO); // 已经 review
+    List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerCreateCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerDealCountGroupByDate(CrmStatisticsCustomerReqVO reqVO); // 已经 review
+    List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerDealCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerCreateCountGroupByUser(CrmStatisticsCustomerReqVO reqVO); // 已经 review
+    List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerCreateCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerDealCountGroupByUser(CrmStatisticsCustomerReqVO crmStatisticsCustomerReqVO); // 已经 review
+    List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerDealCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsCustomerSummaryByUserRespVO> selectContractPriceGroupByUser(CrmStatisticsCustomerReqVO crmStatisticsCustomerReqVO); // 已经 review
+    List<CrmStatisticsCustomerSummaryByUserRespVO> selectContractPriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsCustomerSummaryByUserRespVO> selectReceivablePriceGroupByUser(CrmStatisticsCustomerReqVO crmStatisticsCustomerReqVO);  // 已经 review
+    List<CrmStatisticsCustomerSummaryByUserRespVO> selectReceivablePriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsFollowupSummaryByDateRespVO> selectFollowupRecordCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowUpSummaryByDateRespVO> selectFollowUpRecordCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsFollowupSummaryByDateRespVO> selectFollowupCustomerCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowUpSummaryByDateRespVO> selectFollowUpCustomerCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsFollowupSummaryByUserRespVO> selectFollowupRecordCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowUpSummaryByUserRespVO> selectFollowUpRecordCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsFollowupSummaryByUserRespVO> selectFollowupCustomerCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowUpSummaryByUserRespVO> selectFollowUpCustomerCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
 
     List<CrmStatisticsCustomerContractSummaryRespVO> selectContractSummary(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsFollowupSummaryByTypeRespVO> selectFollowupRecordCountGroupByType(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowUpSummaryByTypeRespVO> selectFollowUpRecordCountGroupByType(CrmStatisticsCustomerReqVO reqVO);
 
     List<CrmStatisticsCustomerDealCycleByDateRespVO> selectCustomerDealCycleGroupByDate(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsCustomerDealCycleByUserRespVO> selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerDealCycleByUserRespVO> selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO); // TODO
 
 }

+ 3 - 5
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java

@@ -27,14 +27,13 @@ public interface CrmStatisticsCustomerService {
      */
     List<CrmStatisticsCustomerSummaryByUserRespVO> getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
 
-
     /**
      * 跟进次数分析(按日期)
      *
      * @param reqVO 请求参数
      * @return 统计数据
      */
-    List<CrmStatisticsFollowupSummaryByDateRespVO> getFollowupSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowUpSummaryByDateRespVO> getFollowUpSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
 
     /**
      * 跟进次数分析(按用户)
@@ -42,7 +41,7 @@ public interface CrmStatisticsCustomerService {
      * @param reqVO 请求参数
      * @return 统计数据
      */
-    List<CrmStatisticsFollowupSummaryByUserRespVO> getFollowupSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowUpSummaryByUserRespVO> getFollowUpSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
 
     /**
      * 客户跟进次数分析(按类型)
@@ -50,8 +49,7 @@ public interface CrmStatisticsCustomerService {
      * @param reqVO 请求参数
      * @return 统计数据
      */
-    List<CrmStatisticsFollowupSummaryByTypeRespVO> getFollowupSummaryByType(CrmStatisticsCustomerReqVO reqVO);
-
+    List<CrmStatisticsFollowUpSummaryByTypeRespVO> getFollowUpSummaryByType(CrmStatisticsCustomerReqVO reqVO);
 
     /**
      * 获取合同摘要信息(客户转化率页面)

+ 146 - 336
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java

@@ -1,19 +1,13 @@
 package cn.iocoder.yudao.module.crm.service.statistics;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.date.DateTime;
-import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.util.ObjUtil;
-import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
 import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
-import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
-import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
@@ -27,10 +21,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Stream;
 
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
-import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.STATISTICS_CUSTOMER_TIMES_NOT_SET;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
 
 /**
  * CRM 客户分析 Service 实现类
@@ -41,13 +33,6 @@ import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.STATISTICS_CU
 @Validated
 public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerService {
 
-    private static final String SQL_DATE_FORMAT_BY_MONTH = "%Y%m";
-    private static final String SQL_DATE_FORMAT_BY_DAY = "%Y%m%d";
-
-    private static final String TIME_FORMAT_BY_MONTH = "yyyyMM";
-    private static final String TIME_FORMAT_BY_DAY = "yyyyMMdd";
-
-
     @Resource
     private CrmStatisticsCustomerMapper customerMapper;
 
@@ -55,283 +40,211 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
     private AdminUserApi adminUserApi;
     @Resource
     private DeptApi deptApi;
-    @Resource
-    private DictDataApi dictDataApi;
 
     @Override
     public List<CrmStatisticsCustomerSummaryByDateRespVO> getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
         // 1. 获得用户编号数组
-        List<Long> userIds = getUserIds(reqVO);
-        if (CollUtil.isEmpty(userIds)) {
+        reqVO.setUserIds(getUserIds(reqVO));
+        if (CollUtil.isEmpty(reqVO.getUserIds())) {
             return Collections.emptyList();
         }
-        reqVO.setUserIds(userIds);
-
-        // 2. 获取分项统计数据
-        initParams(reqVO);
-        List<CrmStatisticsCustomerSummaryByDateRespVO> customerCreateCountVoList = customerMapper.selectCustomerCreateCountGroupByDate(reqVO);
-        List<CrmStatisticsCustomerSummaryByDateRespVO> customerDealCountVoList = customerMapper.selectCustomerDealCountGroupByDate(reqVO);
-
-        // 3. 合并数据
-        List<String> times = generateTimeSeries(reqVO.getTimes()[0], reqVO.getTimes()[1]);
-        Map<String, Integer> customerCreateCountMap = convertMap(customerCreateCountVoList,
-            CrmStatisticsCustomerSummaryByDateRespVO::getTime,
-            CrmStatisticsCustomerSummaryByDateRespVO::getCustomerCreateCount);
-        Map<String, Integer> customerDealCountMap = convertMap(customerDealCountVoList,
-            CrmStatisticsCustomerSummaryByDateRespVO::getTime,
-            CrmStatisticsCustomerSummaryByDateRespVO::getCustomerDealCount);
-        List<CrmStatisticsCustomerSummaryByDateRespVO> respVoList = convertList(times,
-            time -> new CrmStatisticsCustomerSummaryByDateRespVO()
-                .setTime(time)
-                .setCustomerCreateCount(customerCreateCountMap.getOrDefault(time, 0))
-                .setCustomerDealCount(customerDealCountMap.getOrDefault(time, 0)));
-
-        return respVoList;
+
+        // 2. 按天统计,获取分项统计数据
+        List<CrmStatisticsCustomerSummaryByDateRespVO> customerCreateCountList = customerMapper.selectCustomerCreateCountGroupByDate(reqVO);
+        List<CrmStatisticsCustomerSummaryByDateRespVO> customerDealCountList = customerMapper.selectCustomerDealCountGroupByDate(reqVO);
+
+        // 3. 按照日期间隔,合并数据
+        List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
+        return convertList(timeRanges, times -> {
+            Integer customerCreateCount = customerCreateCountList.stream()
+                    .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+                    .mapToInt(CrmStatisticsCustomerSummaryByDateRespVO::getCustomerCreateCount).sum();
+            Integer customerDealCount = customerDealCountList.stream()
+                    .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+                    .mapToInt(CrmStatisticsCustomerSummaryByDateRespVO::getCustomerDealCount).sum();
+            return new CrmStatisticsCustomerSummaryByDateRespVO()
+                    .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
+                    .setCustomerCreateCount(customerCreateCount).setCustomerDealCount(customerDealCount);
+        });
     }
 
     @Override
     public List<CrmStatisticsCustomerSummaryByUserRespVO> getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
         // 1. 获得用户编号数组
-        List<Long> userIds = getUserIds(reqVO);
-        if (CollUtil.isEmpty(userIds)) {
+        reqVO.setUserIds(getUserIds(reqVO));
+        if (CollUtil.isEmpty(reqVO.getUserIds())) {
             return Collections.emptyList();
         }
-        reqVO.setUserIds(userIds);
-
-        // 2. 获取分项统计数据
-        initParams(reqVO);
-        List<CrmStatisticsCustomerSummaryByUserRespVO> customerCreateCount = customerMapper.selectCustomerCreateCountGroupByUser(reqVO);
-        List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
-        List<CrmStatisticsCustomerSummaryByUserRespVO> contractPrice = customerMapper.selectContractPriceGroupByUser(reqVO);
-        List<CrmStatisticsCustomerSummaryByUserRespVO> receivablePrice = customerMapper.selectReceivablePriceGroupByUser(reqVO);
-
-        // 3. 合并统计数据
-        Map<Long, Integer> customerCreateCountMap = convertMap(customerCreateCount,
-            CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId,
-            CrmStatisticsCustomerSummaryByUserRespVO::getCustomerCreateCount);
-        Map<Long, Integer> customerDealCountMap = convertMap(customerDealCount,
-            CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId,
-            CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount);
-        Map<Long, BigDecimal> contractPriceMap = convertMap(contractPrice,
-            CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId,
-            CrmStatisticsCustomerSummaryByUserRespVO::getContractPrice);
-        Map<Long, BigDecimal> receivablePriceMap = convertMap(receivablePrice,
-            CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId,
-            CrmStatisticsCustomerSummaryByUserRespVO::getReceivablePrice);
-        List<CrmStatisticsCustomerSummaryByUserRespVO> respVoList = convertList(userIds, userId -> {
-            CrmStatisticsCustomerSummaryByUserRespVO vo = new CrmStatisticsCustomerSummaryByUserRespVO();
-            // ownerUserId 为基类属性
-            vo.setOwnerUserId(userId);
-            vo.setCustomerCreateCount(customerCreateCountMap.getOrDefault(userId, 0))
-                .setCustomerDealCount(customerDealCountMap.getOrDefault(userId, 0))
-                .setContractPrice(contractPriceMap.getOrDefault(userId, BigDecimal.ZERO))
-                .setReceivablePrice(receivablePriceMap.getOrDefault(userId, BigDecimal.ZERO));
-            return vo;
-        });
-
-        // 4. 拼接用户信息
-        appendUserInfo(respVoList);
 
-        return respVoList;
+        // 2. 按用户统计,获取分项统计数据
+        List<CrmStatisticsCustomerSummaryByUserRespVO> customerCreateCountList = customerMapper.selectCustomerCreateCountGroupByUser(reqVO);
+        List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCountList = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
+        List<CrmStatisticsCustomerSummaryByUserRespVO> contractPriceList = customerMapper.selectContractPriceGroupByUser(reqVO);
+        List<CrmStatisticsCustomerSummaryByUserRespVO> receivablePriceList = customerMapper.selectReceivablePriceGroupByUser(reqVO);
+
+        // 3.1 按照用户,合并统计数据
+        List<CrmStatisticsCustomerSummaryByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
+            Integer customerCreateCount = customerCreateCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+                    .mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerCreateCount).sum();
+            Integer customerDealCount = customerDealCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+                    .mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount).sum();
+            BigDecimal contractPrice = contractPriceList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+                    .reduce(BigDecimal.ZERO, (sum, vo) -> sum.add(vo.getContractPrice()), BigDecimal::add);
+            BigDecimal receivablePrice = receivablePriceList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+                    .reduce(BigDecimal.ZERO, (sum, vo) -> sum.add(vo.getReceivablePrice()), BigDecimal::add);
+            return (CrmStatisticsCustomerSummaryByUserRespVO) new CrmStatisticsCustomerSummaryByUserRespVO()
+                    .setCustomerCreateCount(customerCreateCount).setCustomerDealCount(customerDealCount)
+                    .setContractPrice(contractPrice).setReceivablePrice(receivablePrice).setOwnerUserId(userId);
+        });
+        // 3.2 拼接用户信息
+        appendUserInfo(summaryList);
+        return summaryList;
     }
 
     @Override
-    public List<CrmStatisticsFollowupSummaryByDateRespVO> getFollowupSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
+    public List<CrmStatisticsFollowUpSummaryByDateRespVO> getFollowUpSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
         // 1. 获得用户编号数组
-        List<Long> userIds = getUserIds(reqVO);
-        if (CollUtil.isEmpty(userIds)) {
+        reqVO.setUserIds(getUserIds(reqVO));
+        if (CollUtil.isEmpty(reqVO.getUserIds())) {
             return Collections.emptyList();
         }
-        reqVO.setUserIds(userIds);
-
-        // 2. 获取分项统计数据
-        initParams(reqVO);
-        List<CrmStatisticsFollowupSummaryByDateRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupByDate(reqVO);
-        List<CrmStatisticsFollowupSummaryByDateRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByDate(reqVO);
-
-        // 3. 合并统计数据
-        List<String> times = generateTimeSeries(reqVO.getTimes()[0], reqVO.getTimes()[1]);
-        Map<String, Integer> followupRecordCountMap = convertMap(followupRecordCount,
-            CrmStatisticsFollowupSummaryByDateRespVO::getTime,
-            CrmStatisticsFollowupSummaryByDateRespVO::getFollowupRecordCount);
-        Map<String, Integer> followupCustomerCountMap = convertMap(followupCustomerCount,
-            CrmStatisticsFollowupSummaryByDateRespVO::getTime,
-            CrmStatisticsFollowupSummaryByDateRespVO::getFollowupCustomerCount);
-        List<CrmStatisticsFollowupSummaryByDateRespVO> respVoList = convertList(times, time ->
-            new CrmStatisticsFollowupSummaryByDateRespVO().setTime(time)
-                .setFollowupRecordCount(followupRecordCountMap.getOrDefault(time, 0))
-                .setFollowupCustomerCount(followupCustomerCountMap.getOrDefault(time, 0))
-        );
-
-        return respVoList;
+
+        // 2. 按天统计,获取分项统计数据
+        List<CrmStatisticsFollowUpSummaryByDateRespVO> followUpRecordCountList = customerMapper.selectFollowUpRecordCountGroupByDate(reqVO);
+        List<CrmStatisticsFollowUpSummaryByDateRespVO> followUpCustomerCountList = customerMapper.selectFollowUpCustomerCountGroupByDate(reqVO);
+
+        // 3. 按照时间间隔,合并统计数据
+        List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
+        return convertList(timeRanges, times -> {
+            Integer followUpRecordCount = followUpRecordCountList.stream()
+                    .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+                    .mapToInt(CrmStatisticsFollowUpSummaryByDateRespVO::getFollowUpRecordCount).sum();
+            Integer followUpCustomerCount = followUpCustomerCountList.stream()
+                    .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+                    .mapToInt(CrmStatisticsFollowUpSummaryByDateRespVO::getFollowUpCustomerCount).sum();
+            return new CrmStatisticsFollowUpSummaryByDateRespVO()
+                    .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
+                    .setFollowUpCustomerCount(followUpRecordCount).setFollowUpRecordCount(followUpCustomerCount);
+        });
     }
 
     @Override
-    public List<CrmStatisticsFollowupSummaryByUserRespVO> getFollowupSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
+    public List<CrmStatisticsFollowUpSummaryByUserRespVO> getFollowUpSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
         // 1. 获得用户编号数组
-        List<Long> userIds = getUserIds(reqVO);
-        if (CollUtil.isEmpty(userIds)) {
+        reqVO.setUserIds(getUserIds(reqVO));
+        if (CollUtil.isEmpty(reqVO.getUserIds())) {
             return Collections.emptyList();
         }
-        reqVO.setUserIds(userIds);
-
-        // 2. 获取分项统计数据
-        initParams(reqVO);
-        List<CrmStatisticsFollowupSummaryByUserRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupByUser(reqVO);
-        List<CrmStatisticsFollowupSummaryByUserRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByUser(reqVO);
-
-        // 3. 合并统计数据
-        Map<Long, Integer> followupRecordCountMap = convertMap(followupRecordCount,
-            CrmStatisticsFollowupSummaryByUserRespVO::getOwnerUserId,
-            CrmStatisticsFollowupSummaryByUserRespVO::getFollowupRecordCount);
-        Map<Long, Integer> followupCustomerCountMap = convertMap(followupCustomerCount,
-            CrmStatisticsFollowupSummaryByUserRespVO::getOwnerUserId,
-            CrmStatisticsFollowupSummaryByUserRespVO::getFollowupCustomerCount);
-        List<CrmStatisticsFollowupSummaryByUserRespVO> respVoList = convertList(userIds, userId -> {
-            CrmStatisticsFollowupSummaryByUserRespVO vo = new CrmStatisticsFollowupSummaryByUserRespVO()
-                .setFollowupRecordCount(followupRecordCountMap.getOrDefault(userId, 0))
-                .setFollowupCustomerCount(followupCustomerCountMap.getOrDefault(userId, 0));
-            // ownerUserId 为基类属性
-            vo.setOwnerUserId(userId);
-            return vo;
-        });
 
-        // 4. 拼接用户信息
-        appendUserInfo(respVoList);
-        return respVoList;
+        // 2. 按用户统计,获取分项统计数据
+        List<CrmStatisticsFollowUpSummaryByUserRespVO> followUpRecordCountList = customerMapper.selectFollowUpRecordCountGroupByUser(reqVO);
+        List<CrmStatisticsFollowUpSummaryByUserRespVO> followUpCustomerCountList = customerMapper.selectFollowUpCustomerCountGroupByUser(reqVO);
+
+        // 3.1 按照用户,合并统计数据
+        List<CrmStatisticsFollowUpSummaryByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
+            Integer followUpRecordCount = followUpRecordCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+                    .mapToInt(CrmStatisticsFollowUpSummaryByUserRespVO::getFollowUpRecordCount).sum();
+            Integer followUpCustomerCount = followUpCustomerCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+                    .mapToInt(CrmStatisticsFollowUpSummaryByUserRespVO::getFollowUpCustomerCount).sum();
+            return (CrmStatisticsFollowUpSummaryByUserRespVO) new CrmStatisticsFollowUpSummaryByUserRespVO()
+                    .setFollowUpCustomerCount(followUpRecordCount).setFollowUpRecordCount(followUpCustomerCount).setOwnerUserId(userId);
+        });
+        // 3.2 拼接用户信息
+        appendUserInfo(summaryList);
+        return summaryList;
     }
 
     @Override
-    public List<CrmStatisticsFollowupSummaryByTypeRespVO> getFollowupSummaryByType(CrmStatisticsCustomerReqVO reqVO) {
+    public List<CrmStatisticsFollowUpSummaryByTypeRespVO> getFollowUpSummaryByType(CrmStatisticsCustomerReqVO reqVO) {
         // 1. 获得用户编号数组
-        List<Long> userIds = getUserIds(reqVO);
-        if (CollUtil.isEmpty(userIds)) {
+        reqVO.setUserIds(getUserIds(reqVO));
+        if (CollUtil.isEmpty(reqVO.getUserIds())) {
             return Collections.emptyList();
         }
-        reqVO.setUserIds(userIds);
-
-        // 2. 获得排行数据
-        initParams(reqVO);
-        List<CrmStatisticsFollowupSummaryByTypeRespVO> respVoList = customerMapper.selectFollowupRecordCountGroupByType(reqVO);
-
-        // 3. 获取字典数据
-        List<DictDataRespDTO> followUpTypes = dictDataApi.getDictDataList(CRM_FOLLOW_UP_TYPE);
-        Map<String, String> followUpTypeMap = convertMap(followUpTypes,
-            DictDataRespDTO::getValue, DictDataRespDTO::getLabel);
-        respVoList.forEach(vo -> {
-            vo.setFollowupType(followUpTypeMap.get(vo.getFollowupType()));
-        });
 
-        return respVoList;
+        // 2. 获得跟进数据
+        return customerMapper.selectFollowUpRecordCountGroupByType(reqVO);
     }
 
     @Override
     public List<CrmStatisticsCustomerContractSummaryRespVO> getContractSummary(CrmStatisticsCustomerReqVO reqVO) {
         // 1. 获得用户编号数组
-        List<Long> userIds = getUserIds(reqVO);
-        if (CollUtil.isEmpty(userIds)) {
+        reqVO.setUserIds(getUserIds(reqVO));
+        if (CollUtil.isEmpty(reqVO.getUserIds())) {
             return Collections.emptyList();
         }
-        reqVO.setUserIds(userIds);
-
-        // 2. 获取统计数据
-        initParams(reqVO);
-        List<CrmStatisticsCustomerContractSummaryRespVO> respVoList = customerMapper.selectContractSummary(reqVO);
-
-        // 3. 设置 创建人、负责人、行业、来源
-        // 3.1 获取客户所属行业
-        Map<String, String> industryMap = convertMap(dictDataApi.getDictDataList(CRM_CUSTOMER_INDUSTRY),
-            DictDataRespDTO::getValue, DictDataRespDTO::getLabel);
-        // 3.2 获取客户来源
-        Map<String, String> sourceMap = convertMap(dictDataApi.getDictDataList(CRM_CUSTOMER_SOURCE),
-            DictDataRespDTO::getValue, DictDataRespDTO::getLabel);
-        // 3.3 获取创建人、负责人列表
-        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(respVoList,
-            vo -> Stream.of(NumberUtils.parseLong(vo.getCreatorUserId()), vo.getOwnerUserId())));
-        // 3.4 设置 创建人、负责人、行业、来源
-        respVoList.forEach(vo -> {
-            MapUtils.findAndThen(industryMap, vo.getIndustryId(), vo::setIndustryName);
-            MapUtils.findAndThen(sourceMap, vo.getSource(), vo::setSourceName);
-            MapUtils.findAndThen(userMap, NumberUtils.parseLong(vo.getCreatorUserId()),
-                user -> vo.setCreatorUserName(user.getNickname()));
-            MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname()));
-        });
 
-        return respVoList;
+        // 2. 按用户统计,获取统计数据
+        List<CrmStatisticsCustomerContractSummaryRespVO> summaryList = customerMapper.selectContractSummary(reqVO);
+
+        // 3. 拼接信息
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSetByFlatMap(summaryList, vo -> Stream.of(NumberUtils.parseLong(vo.getCreator()), vo.getOwnerUserId())));
+        summaryList.forEach(vo -> {
+            findAndThen(userMap, NumberUtils.parseLong(vo.getCreator()), user -> vo.setCreatorUserName(user.getNickname()));
+            findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname()));
+        });
+        return summaryList;
     }
 
     @Override
     public List<CrmStatisticsCustomerDealCycleByDateRespVO> getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO) {
         // 1. 获得用户编号数组
-        List<Long> userIds = getUserIds(reqVO);
-        if (CollUtil.isEmpty(userIds)) {
+        reqVO.setUserIds(getUserIds(reqVO));
+        if (CollUtil.isEmpty(reqVO.getUserIds())) {
             return Collections.emptyList();
         }
-        reqVO.setUserIds(userIds);
-
-        // 2. 获取分项统计数据
-        initParams(reqVO);
-        List<CrmStatisticsCustomerDealCycleByDateRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupByDate(reqVO);
-
-        // 3. 合并统计数据
-        List<String> times = generateTimeSeries(reqVO.getTimes()[0], reqVO.getTimes()[1]);
-        Map<String, Double> customerDealCycleMap = convertMap(customerDealCycle,
-            CrmStatisticsCustomerDealCycleByDateRespVO::getTime,
-            CrmStatisticsCustomerDealCycleByDateRespVO::getCustomerDealCycle);
-        List<CrmStatisticsCustomerDealCycleByDateRespVO> respVoList = convertList(times, time ->
-            new CrmStatisticsCustomerDealCycleByDateRespVO().setTime(time)
-                .setCustomerDealCycle(customerDealCycleMap.getOrDefault(time, 0D))
-        );
-
-        return respVoList;
+
+        // 2. 按天统计,获取分项统计数据
+        List<CrmStatisticsCustomerDealCycleByDateRespVO> customerDealCycleList = customerMapper.selectCustomerDealCycleGroupByDate(reqVO);
+
+        // 3. 按照日期间隔,合并统计数据
+        List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
+        return convertList(timeRanges, times -> {
+            Double customerDealCycle = customerDealCycleList.stream()
+                    .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+                    .mapToDouble(CrmStatisticsCustomerDealCycleByDateRespVO::getCustomerDealCycle).sum();
+            return new CrmStatisticsCustomerDealCycleByDateRespVO()
+                    .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
+                    .setCustomerDealCycle(customerDealCycle);
+        });
     }
 
     @Override
     public List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO) {
         // 1. 获得用户编号数组
-        List<Long> userIds = getUserIds(reqVO);
-        if (CollUtil.isEmpty(userIds)) {
+        reqVO.setUserIds(getUserIds(reqVO));
+        if (CollUtil.isEmpty(reqVO.getUserIds())) {
             return Collections.emptyList();
         }
-        reqVO.setUserIds(userIds);
-
-        // 2. 获取分项统计数据
-        initParams(reqVO);
-        List<CrmStatisticsCustomerDealCycleByUserRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupByUser(reqVO);
-        List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
-
-        // 3. 合并统计数据
-        Map<Long, Double> customerDealCycleMap = convertMap(customerDealCycle,
-            CrmStatisticsCustomerDealCycleByUserRespVO::getOwnerUserId,
-            CrmStatisticsCustomerDealCycleByUserRespVO::getCustomerDealCycle);
-        Map<Long, Integer> customerDealCountMap = convertMap(customerDealCount,
-            CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId,
-            CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount);
-        List<CrmStatisticsCustomerDealCycleByUserRespVO> respVoList = convertList(userIds, userId -> {
-            CrmStatisticsCustomerDealCycleByUserRespVO vo = new CrmStatisticsCustomerDealCycleByUserRespVO()
-                .setCustomerDealCycle(customerDealCycleMap.getOrDefault(userId, 0.0))
-                .setCustomerDealCount(customerDealCountMap.getOrDefault(userId, 0));
-            // ownerUserId 为基类属性
-            vo.setOwnerUserId(userId);
-            return vo;
-        });
-
-        // 4. 拼接用户信息
-        appendUserInfo(respVoList);
 
-        return respVoList;
+        // 2. 按用户统计,获取分项统计数据
+        List<CrmStatisticsCustomerDealCycleByUserRespVO> customerDealCycleList = customerMapper.selectCustomerDealCycleGroupByUser(reqVO);
+        List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCountList = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
+
+        // 3.1 按照用户,合并统计数据
+        List<CrmStatisticsCustomerDealCycleByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
+            Double customerDealCycle = customerDealCycleList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+                    .mapToDouble(CrmStatisticsCustomerDealCycleByUserRespVO::getCustomerDealCycle).sum();
+            Integer customerDealCount = customerDealCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+                    .mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount).sum();
+            return (CrmStatisticsCustomerDealCycleByUserRespVO) new CrmStatisticsCustomerDealCycleByUserRespVO()
+                    .setCustomerDealCycle(customerDealCycle).setCustomerDealCount(customerDealCount).setOwnerUserId(userId);
+        });
+        // 3.2 拼接用户信息
+        appendUserInfo(summaryList);
+        return summaryList;
     }
 
     /**
      * 拼接用户信息(昵称)
      *
-     * @param respVoList 统计数据
+     * @param voList 统计数据
      */
-    private <T extends CrmStatisticsCustomerByUserBaseRespVO> void appendUserInfo(List<T> respVoList) {
-        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(respVoList,
-            CrmStatisticsCustomerByUserBaseRespVO::getOwnerUserId));
-        respVoList.forEach(vo -> MapUtils.findAndThen(userMap,
-            vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname())));
+    private <T extends CrmStatisticsCustomerByUserBaseRespVO> void appendUserInfo(List<T> voList) {
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSet(voList, CrmStatisticsCustomerByUserBaseRespVO::getOwnerUserId));
+        voList.forEach(vo -> findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname())));
     }
 
     /**
@@ -347,113 +260,10 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
         }
         // 情况二:选中某个部门
         // 2.1 获得部门列表
-        Long deptId = reqVO.getDeptId();
-        List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId);
-        deptIds.add(deptId);
+        List<Long> deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId);
+        deptIds.add(reqVO.getDeptId());
         // 2.2 获得用户编号
         return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
     }
 
-
-    /**
-     * 判断是否按照 月粒度 统计
-     *
-     * @param startTime 开始时间
-     * @param endTime   结束时间
-     * @return 是, 按月粒度, 否则按天粒度统计。
-     */
-    private boolean queryByMonth(LocalDateTime startTime, LocalDateTime endTime) {
-        return LocalDateTimeUtil.between(startTime, endTime).toDays() > 31;
-    }
-
-    /**
-     * 生成时间序列
-     *
-     * @param startTime 开始时间
-     * @param endTime   结束时间
-     * @return 时间序列
-     */
-    private List<String> generateTimeSeries(LocalDateTime startTime, LocalDateTime endTime) {
-        boolean byMonth = queryByMonth(startTime, endTime);
-        List<String> times = CollUtil.newArrayList();
-        while (!startTime.isAfter(endTime)) {
-            times.add(LocalDateTimeUtil.format(startTime, byMonth ? TIME_FORMAT_BY_MONTH : TIME_FORMAT_BY_DAY));
-            if (byMonth)
-                startTime = startTime.plusMonths(1);
-            else
-                startTime = startTime.plusDays(1);
-        }
-
-        return times;
-    }
-
-    /**
-     * 获取 SQL 查询 GROUP BY 的时间格式
-     *
-     * @param startTime 开始时间
-     * @param endTime   结束时间
-     * @return SQL 查询 GROUP BY 的时间格式
-     */
-    private String getSqlDateFormat(LocalDateTime startTime, LocalDateTime endTime) {
-        return queryByMonth(startTime, endTime) ? SQL_DATE_FORMAT_BY_MONTH : SQL_DATE_FORMAT_BY_DAY;
-    }
-
-    private void initParams(CrmStatisticsCustomerReqVO reqVO) {
-        final Integer intervalType = reqVO.getIntervalType();
-
-        // 1. 自定义时间间隔,必须输入起始日期-结束日期
-        if (DateIntervalEnum.CUSTOMER.getType().equals(intervalType)) {
-            if (ObjUtil.isEmpty(reqVO.getTimes()) || reqVO.getTimes().length != 2) {
-                throw exception(STATISTICS_CUSTOMER_TIMES_NOT_SET);
-            }
-            // 设置 mapper sqlDateFormat 参数
-            reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1]));
-            // 自定义日期无需计算日期参数
-            return;
-        }
-
-        // 2. 根据时间区间类型计算时间段区间日期
-        DateTime beginDate = null;
-        DateTime endDate = null;
-        if (DateIntervalEnum.TODAY.getType().equals(intervalType)) {
-            beginDate = DateUtil.beginOfDay(DateUtil.date());
-            endDate = DateUtil.endOfDay(DateUtil.date());
-        } else if (DateIntervalEnum.YESTERDAY.getType().equals(intervalType)) {
-            beginDate = DateUtil.offsetDay(DateUtil.date(), -1);
-            endDate = DateUtil.offsetDay(DateUtil.date(), -1);
-        } else if (DateIntervalEnum.THIS_WEEK.getType().equals(intervalType)) {
-            beginDate = DateUtil.beginOfWeek(DateUtil.date());
-            endDate = DateUtil.endOfWeek(DateUtil.date());
-        } else if (DateIntervalEnum.LAST_WEEK.getType().equals(intervalType)) {
-            beginDate = DateUtil.beginOfWeek(DateUtil.offsetWeek(DateUtil.date(), -1));
-            endDate = DateUtil.endOfWeek(DateUtil.offsetWeek(DateUtil.date(), -1));
-        } else if (DateIntervalEnum.THIS_MONTH.getType().equals(intervalType)) {
-            beginDate = DateUtil.beginOfMonth(DateUtil.date());
-            endDate = DateUtil.endOfMonth(DateUtil.date());
-        } else if (DateIntervalEnum.LAST_MONTH.getType().equals(intervalType)) {
-            beginDate = DateUtil.beginOfMonth(DateUtil.offsetMonth(DateUtil.date(), -1));
-            endDate = DateUtil.endOfMonth(DateUtil.offsetMonth(DateUtil.date(), -1));
-        } else if (DateIntervalEnum.THIS_QUARTER.getType().equals(intervalType)) {
-            beginDate = DateUtil.beginOfQuarter(DateUtil.date());
-            endDate = DateUtil.endOfQuarter(DateUtil.date());
-        } else if (DateIntervalEnum.LAST_QUARTER.getType().equals(intervalType)) {
-            beginDate = DateUtil.beginOfQuarter(DateUtil.offsetMonth(DateUtil.date(), -3));
-            endDate = DateUtil.endOfQuarter(DateUtil.offsetMonth(DateUtil.date(), -3));
-        } else if (DateIntervalEnum.THIS_YEAR.getType().equals(intervalType)) {
-            beginDate = DateUtil.beginOfYear(DateUtil.date());
-            endDate = DateUtil.endOfYear(DateUtil.date());
-        } else if (DateIntervalEnum.LAST_YEAR.getType().equals(intervalType)) {
-            beginDate = DateUtil.beginOfYear(DateUtil.offsetMonth(DateUtil.date(), -12));
-            endDate = DateUtil.endOfYear(DateUtil.offsetMonth(DateUtil.date(), -12));
-        }
-
-        // 3. 计算开始、结束日期时间,并设置reqVo
-        LocalDateTime[] times = new LocalDateTime[2];
-        times[0] = LocalDateTimeUtil.beginOfDay(LocalDateTimeUtil.of(beginDate));
-        times[1] = LocalDateTimeUtil.endOfDay(LocalDateTimeUtil.of(endDate));
-        // 3.1 设置 mapper 时间区间 参数
-        reqVO.setTimes(times);
-        // 3.2 设置 mapper sqlDateFormat 参数
-        reqVO.setSqlDateFormat(getSqlDateFormat(times[0], times[1]));
-    }
 }

+ 144 - 144
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml

@@ -5,31 +5,31 @@
     <select id="selectCustomerCreateCountGroupByDate"
             resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
         SELECT
-            DATE_FORMAT( create_time, #{sqlDateFormat} ) AS time,
+            DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
             COUNT(*) AS customerCreateCount
-          FROM crm_customer
-         WHERE deleted = 0
-           AND owner_user_id IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-         GROUP BY time
+        FROM crm_customer
+        WHERE deleted = 0
+        AND owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY time
     </select>
 
     <select id="selectCustomerDealCountGroupByDate"
             resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
         SELECT
-            DATE_FORMAT( order_date, #{sqlDateFormat} ) AS time,
+            DATE_FORMAT( order_date, '%Y-%m-%d' ) AS time,
             COUNT( DISTINCT customer_id ) AS customerDealCount
-         FROM crm_contract
+        FROM crm_contract
         WHERE deleted = 0
-          AND audit_status = 20
-          AND owner_user_id IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-          AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        AND audit_status = 20
+        AND owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
         GROUP BY time
     </select>
 
@@ -38,13 +38,13 @@
         SELECT
             owner_user_id,
             COUNT(1) AS customer_create_count
-          FROM crm_customer
-         WHERE deleted = 0
-           AND owner_user_id in
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-          AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        FROM crm_customer
+        WHERE deleted = 0
+        AND owner_user_id in
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
         GROUP BY owner_user_id
     </select>
 
@@ -53,16 +53,16 @@
         SELECT
             customer.owner_user_id,
             COUNT( DISTINCT customer.id ) AS customer_deal_count
-          FROM crm_customer AS customer
-                LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
-         WHERE customer.deleted = 0 AND contract.deleted = 0
-           AND contract.audit_status = 20
-           AND customer.owner_user_id IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-         GROUP BY customer.owner_user_id
+        FROM crm_customer AS customer
+        LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
+        WHERE customer.deleted = 0 AND contract.deleted = 0
+        AND contract.audit_status = 20
+        AND customer.owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY customer.owner_user_id
     </select>
 
     <select id="selectContractPriceGroupByUser"
@@ -70,15 +70,15 @@
         SELECT
             owner_user_id,
             IFNULL(SUM(total_price), 0) AS contract_price
-          FROM crm_contract
-         WHERE deleted = 0
-           AND audit_status = 20
-           AND owner_user_id in
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND order_date BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-         GROUP BY owner_user_id
+        FROM crm_contract
+        WHERE deleted = 0
+        AND audit_status = 20
+        AND owner_user_id in
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND order_date BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY owner_user_id
     </select>
 
     <select id="selectReceivablePriceGroupByUser"
@@ -86,132 +86,132 @@
         SELECT
             owner_user_id,
             IFNULL(SUM(price), 0) AS receivable_price
-          FROM crm_receivable
-         WHERE deleted = 0
-           AND audit_status = 20
-           AND owner_user_id IN
-                 <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                     #{userId}
-                 </foreach>
-           AND return_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-         GROUP BY owner_user_id
+        FROM crm_receivable
+        WHERE deleted = 0
+        AND audit_status = 20
+        AND owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND return_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY owner_user_id
     </select>
 
-    <select id="selectFollowupRecordCountGroupByDate"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowupSummaryByDateRespVO">
+    <select id="selectFollowUpRecordCountGroupByDate"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByDateRespVO">
         SELECT
-            DATE_FORMAT( create_time, #{sqlDateFormat} ) AS time,
-            COUNT(*) AS followup_record_count
-          FROM crm_follow_up_record
-         WHERE creator IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-           AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
-         GROUP BY time
+            DATE_FORMAT( create_time, '%Y-%m-%d' ) AS time,
+            COUNT(*) AS follow_up_record_count
+        FROM crm_follow_up_record
+        WHERE creator IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
+        GROUP BY time
     </select>
 
-    <select id="selectFollowupCustomerCountGroupByDate"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowupSummaryByDateRespVO">
+    <select id="selectFollowUpCustomerCountGroupByDate"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByDateRespVO">
         SELECT
-            DATE_FORMAT( create_time, #{sqlDateFormat} ) AS time,
-            COUNT(DISTINCT biz_id) AS followup_customer_count
-          FROM crm_follow_up_record
-         WHERE creator IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-           AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
-         GROUP BY time
+            DATE_FORMAT( create_time, '%Y-%m-%d' ) AS time,
+            COUNT(DISTINCT biz_id) AS follow_up_customer_count
+        FROM crm_follow_up_record
+        WHERE creator IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
+        GROUP BY time
     </select>
 
-    <select id="selectFollowupRecordCountGroupByUser"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowupSummaryByUserRespVO">
+    <select id="selectFollowUpRecordCountGroupByUser"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByUserRespVO">
         SELECT
             creator as owner_user_id,
-            COUNT(*) AS followup_record_count
-          FROM crm_follow_up_record
-         WHERE creator IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-           AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
-         GROUP BY creator
+            COUNT(*) AS follow_up_record_count
+        FROM crm_follow_up_record
+        WHERE creator IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
+        GROUP BY creator
     </select>
 
-    <select id="selectFollowupCustomerCountGroupByUser"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowupSummaryByUserRespVO">
+    <select id="selectFollowUpCustomerCountGroupByUser"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByUserRespVO">
         SELECT
             creator as owner_user_id,
-            COUNT(DISTINCT biz_id) AS followup_customer_count
-          FROM crm_follow_up_record
-         WHERE creator IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-           AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
-         GROUP BY creator
+            COUNT(DISTINCT biz_id) AS follow_up_customer_count
+        FROM crm_follow_up_record
+        WHERE creator IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
+        GROUP BY creator
     </select>
 
-    <select id="selectFollowupRecordCountGroupByType"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowupSummaryByTypeRespVO">
+    <select id="selectFollowUpRecordCountGroupByType"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByTypeRespVO">
         SELECT
-            type AS followupType,
-            COUNT(*) AS followup_record_count
-          FROM crm_follow_up_record
-         WHERE creator IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-           AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
-         GROUP BY followupType
+            type AS follow_up_type,
+            COUNT(*) AS follow_up_record_count
+        FROM crm_follow_up_record
+        WHERE creator IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
+        GROUP BY follow_up_type
     </select>
 
     <select id="selectContractSummary"
             resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerContractSummaryRespVO">
         SELECT
-            customer.`name` AS customer_name,
-            contract.`name` AS contract_name,
+            customer.name AS customer_name,
+            contract.name AS contract_name,
             contract.total_price,
             IFNULL( receivable.price, 0 ) AS receivable_price,
             customer.industry_id,
             customer.source,
             customer.owner_user_id,
-            customer.creator AS creator_user_id,
+            customer.creator,
             customer.create_time,
             contract.order_date
-          FROM crm_customer AS customer
-                INNER JOIN crm_contract AS contract ON customer.id = contract.customer_id
-                LEFT JOIN crm_receivable AS receivable ON contract.id = receivable.contract_id
-         WHERE customer.deleted = 0 AND contract.deleted = 0 AND receivable.deleted = 0
-           AND contract.audit_status = 20
-           AND customer.owner_user_id IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        FROM crm_customer AS customer
+        INNER JOIN crm_contract AS contract ON customer.id = contract.customer_id
+        LEFT JOIN crm_receivable AS receivable ON contract.id = receivable.contract_id
+        WHERE customer.deleted = 0 AND contract.deleted = 0 AND receivable.deleted = 0
+        AND contract.audit_status = 20
+        AND customer.owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
     </select>
 
     <select id="selectCustomerDealCycleGroupByDate"
             resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByDateRespVO">
         SELECT
-            DATE_FORMAT( contract.order_date, #{sqlDateFormat} ) AS time,
+            DATE_FORMAT( contract.order_date, '%Y-%m-%d' ) AS time,
             IFNULL( TRUNCATE ( AVG( TIMESTAMPDIFF( DAY, customer.create_time, contract.order_date )), 1 ), 0 ) AS customer_deal_cycle
-          FROM crm_customer AS customer
-                LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
-         WHERE customer.deleted = 0 AND contract.deleted = 0
-           AND contract.audit_status = 20
-           AND customer.owner_user_id IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-         GROUP BY time
+        FROM crm_customer AS customer
+        LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
+        WHERE customer.deleted = 0 AND contract.deleted = 0
+        AND contract.audit_status = 20
+        AND customer.owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY time
     </select>
 
     <select id="selectCustomerDealCycleGroupByUser"
@@ -219,16 +219,16 @@
         SELECT
             customer.owner_user_id,
             IFNULL( TRUNCATE ( AVG( TIMESTAMPDIFF( DAY, customer.create_time, contract.order_date )), 1 ), 0 ) AS customer_deal_cycle
-          FROM crm_customer AS customer
-                LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
-         WHERE customer.deleted = 0 AND contract.deleted = 0
-           AND contract.audit_status = 20
-           AND customer.owner_user_id IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
-         GROUP BY customer.owner_user_id
+        FROM crm_customer AS customer
+        LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
+        WHERE customer.deleted = 0 AND contract.deleted = 0
+        AND contract.audit_status = 20
+        AND customer.owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+        AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY customer.owner_user_id
     </select>
 
 </mapper>