Selaa lähdekoodia

fix: [CRM-客户统计]根据Code-Review 修改

dhb52 1 vuosi sitten
vanhempi
commit
a23ea45632
16 muutettua tiedostoa jossa 824 lisäystä ja 282 poistoa
  1. 45 16
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.http
  2. 39 31
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.java
  3. 18 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerByUserBaseRespVO.java
  4. 51 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerContractSummaryRespVO.java
  5. 0 34
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerCountVO.java
  6. 16 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByDateRespVO.java
  7. 16 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByUserRespVO.java
  8. 19 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByDateRespVO.java
  9. 24 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByUserRespVO.java
  10. 19 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowupSummaryByDateRespVO.java
  11. 17 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowupSummaryByTypeRespVO.java
  12. 16 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowupSummaryByUserRespVO.java
  13. 23 8
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java
  14. 37 21
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java
  15. 269 80
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java
  16. 215 92
      yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml

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

@@ -1,39 +1,68 @@
-### 新建客户总量分析(按日)
-GET {{baseUrl}}/crm/statistics-customer/get-total-customer-count?deptId=100&times[0]=2024-12-01 00:00:00&times[1]=2024-12-12 23:59:59
+# == 1. 客户总量分析 ==
+### 1.1 客户总量分析(按日)
+GET {{baseUrl}}/crm/statistics-customer/get-customer-summary-by-date?deptId=100&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
 
-### 新建客户总量分析(按月)
-GET {{baseUrl}}/crm/statistics-customer/get-total-customer-count?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
+### 1.2 客户总量分析(按月)
+GET {{baseUrl}}/crm/statistics-customer/get-customer-summary-by-date?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
 
-### 成交客户总量分析(按日)
-GET {{baseUrl}}/crm/statistics-customer/get-deal-total-customer-count?deptId=100&times[0]=2024-12-01 00:00:00&times[1]=2024-12-12 23:59:59
+### 1.3 客户总量统计(按用户)
+GET {{baseUrl}}/crm/statistics-customer/get-customer-summary-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
 
-### 成交客户总量分析(按月)
-GET {{baseUrl}}/crm/statistics-customer/get-deal-total-customer-count?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
+
+# == 2. 客户跟进次数分析 ==
+### 2.1 客户跟进次数分析(按日)
+GET {{baseUrl}}/crm/statistics-customer/get-followup-summary-by-date?deptId=100&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+### 2.2 客户跟进次数分析(按月)
+GET {{baseUrl}}/crm/statistics-customer/get-followup-summary-by-date?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+### 2.3 客户总量统计(按用户)
+GET {{baseUrl}}/crm/statistics-customer/get-followup-summary-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+
+# == 3. 客户跟进方式分析 ==
+### 3.1 客户跟进方式分析
+GET {{baseUrl}}/crm/statistics-customer/get-followup-summary-by-type?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
+tenant-id: {{adminTenentId}}
 
-### 获取客户跟进次数(按日)
-GET {{baseUrl}}/crm/statistics-customer/get-record-count?deptId=100&times[0]=2024-12-01 00:00:00&times[1]=2024-12-12 23:59:59
+
+# == 4. 客户成交周期 ==
+### 4.1 合同摘要信息(客户转化率页面)
+GET {{baseUrl}}/crm/statistics-customer/get-contract-summary?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
+tenant-id: {{adminTenentId}}
 
-### 获取客户跟进次数(按月)
-GET {{baseUrl}}/crm/statistics-customer/get-record-count?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
+
+# == 5. 客户成交周期 ==
+### 5.1 客户成交周期(按日)
+GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-date?deptId=100&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
+tenant-id: {{adminTenentId}}
 
-### 获取已跟进客户数(按日)
-GET {{baseUrl}}/crm/statistics-customer/get-distinct-record-count?deptId=100&times[0]=2024-12-01 00:00:00&times[1]=2024-12-12 23:59:59
+### 5.2 客户成交周期(按月)
+GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-date?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
 Authorization: Bearer {{token}}
 tenant-id: {{adminTenentId}}
+tenant-id: {{adminTenentId}}
 
-### 获取已跟进客户数(按月)
-GET {{baseUrl}}/crm/statistics-customer/get-distinct-record-count?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
+### 5.3 获取客户成交周期(按用户)
+GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
 Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
 tenant-id: {{adminTenentId}}

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

@@ -1,8 +1,7 @@
 package cn.iocoder.yudao.module.crm.controller.admin.statistics;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO;
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
 import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsCustomerService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -18,8 +17,7 @@ import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
-// TODO @dhb52:数据统计 员工客户分析,改成“客户统计”
-@Tag(name = "管理后台 - CRM 数据统计 员工客户分析")
+@Tag(name = "管理后台 - CRM 客户统计")
 @RestController
 @RequestMapping("/crm/statistics-customer")
 @Validated
@@ -28,50 +26,60 @@ public class CrmStatisticsCustomerController {
     @Resource
     private CrmStatisticsCustomerService customerService;
 
-    // TODO @dhb52:建议 getCustomerCount 和 getDealTotalCustomerCount 搞成一个接口;
-    // 1. 数量接口:【方法:getCustomerSummaryByDate】,VO:CrmStatisticsCustomerSummaryByDateRespVO,然后里面是 time、customerCreateCount customerDealCount
-    // 2. 按人统计:【方法:getCustomerSummaryByUser】,VO:CrmStatisticsCustomerSummaryByOwnerRespVO,然后里面是 ownerUserId、ownerUserName、customerCreateCount customerDealCount、contractPrice、receivablePrice;客户成交率、未回款金额、回款完成率,交给前端计算;
+    @GetMapping("/get-customer-summary-by-date")
+    @Operation(summary = "获取客户总量分析(按日期)")
+    @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
+    public CommonResult<List<CrmStatisticsCustomerSummaryByDateRespVO>> getCustomerSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getCustomerSummaryByDate(reqVO));
+    }
+
+    @GetMapping("/get-customer-summary-by-user")
+    @Operation(summary = "获取客户总量分析(按用户)")
+    @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
+    public CommonResult<List<CrmStatisticsCustomerSummaryByUserRespVO>> getCustomerSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getCustomerSummaryByUser(reqVO));
+    }
 
-    @GetMapping("/get-total-customer-count")
-    @Operation(summary = "获得新建客户数量")
+    @GetMapping("/get-followup-summary-by-date")
+    @Operation(summary = "获取客户跟进次数分析(按日期)")
     @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
-    public CommonResult<List<CrmStatisticsCustomerCountVO>> getTotalCustomerCount(@Valid CrmStatisticsCustomerReqVO reqVO) {
-        return success(customerService.getTotalCustomerCount(reqVO));
+    public CommonResult<List<CrmStatisticsFollowupSummaryByDateRespVO>> getFollowupSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getFollowupSummaryByDate(reqVO));
     }
 
-    @GetMapping("/get-deal-total-customer-count")
-    @Operation(summary = "获得成交客户数量")
+    @GetMapping("/get-followup-summary-by-user")
+    @Operation(summary = "获取客户跟进次数分析(按用户)")
     @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
-    public CommonResult<List<CrmStatisticsCustomerCountVO>> getDealTotalCustomerCount(@Valid CrmStatisticsCustomerReqVO reqVO) {
-        return success(customerService.getDealTotalCustomerCount(reqVO));
+    public CommonResult<List<CrmStatisticsFollowupSummaryByUserRespVO>> getFollowupSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getFollowupSummaryByUser(reqVO));
     }
 
-    @GetMapping("/get-record-count")
-    @Operation(summary = "获取客户跟进次数")
+    @GetMapping("/get-followup-summary-by-type")
+    @Operation(summary = "获取客户跟进次数分析(按类型)")
     @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
-    public CommonResult<List<CrmStatisticsCustomerCountVO>> getRecordCount(@Valid CrmStatisticsCustomerReqVO reqVO) {
-        return success(customerService.getRecordCount(reqVO));
+    public CommonResult<List<CrmStatisticsFollowupSummaryByTypeRespVO>> getFollowupSummaryByType(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getFollowupSummaryByType(reqVO));
     }
 
-    @GetMapping("/get-distinct-record-count")
-    @Operation(summary = "获取已跟进客户数")
+    @GetMapping("/get-contract-summary")
+    @Operation(summary = "获取合同摘要信息(客户转化率页面)")
     @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
-    public CommonResult<List<CrmStatisticsCustomerCountVO>> getDistinctRecordCount(@Valid CrmStatisticsCustomerReqVO reqVO) {
-        return success(customerService.getDistinctRecordCount(reqVO));
+    public CommonResult<List<CrmStatisticsCustomerContractSummaryRespVO>> getContractSummary(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getContractSummary(reqVO));
     }
 
-    @GetMapping("/get-record-type-count")
-    @Operation(summary = "获取客户跟进方式统计数")
+    @GetMapping("/get-customer-deal-cycle-by-date")
+    @Operation(summary = "获取客户成交周期(按日期)")
     @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
-    public CommonResult<List<CrmStatisticsCustomerCountVO>> getRecordTypeCount(@Valid CrmStatisticsCustomerReqVO reqVO) {
-        return success(customerService.getRecordTypeCount(reqVO));
+    public CommonResult<List<CrmStatisticsCustomerDealCycleByDateRespVO>> getCustomerDealCycleByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getCustomerDealCycleByDate(reqVO));
     }
 
-    @GetMapping("/get-customer-cycle")
-    @Operation(summary = "获取客户成交周期")
+    @GetMapping("/get-customer-deal-cycle-by-user")
+    @Operation(summary = "获取客户成交周期(按用户)")
     @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
-    public CommonResult<List<CrmStatisticsCustomerCountVO>> getCustomerCycle(@Valid CrmStatisticsCustomerReqVO reqVO) {
-        return success(customerService.getCustomerCycle(reqVO));
+    public CommonResult<List<CrmStatisticsCustomerDealCycleByUserRespVO>> getCustomerDealCycleByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
+        return success(customerService.getCustomerDealCycleByUser(reqVO));
     }
 
 }

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

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 用户客户统计响应 Base VO
+ */
+@Data
+public class CrmStatisticsCustomerByUserBaseRespVO {
+
+    @Schema(description = "负责人ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long ownerUserId;
+
+    @Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
+    private String ownerUserName;
+
+}

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

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - CRM 客户转化率分析 VO")
+@Data
+public class CrmStatisticsCustomerContractSummaryRespVO {
+
+    @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
+    private String customerName;
+
+    @Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "演示合同")
+    private String contractName;
+
+    @Schema(description = "合同总金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1200.00")
+    private BigDecimal totalPrice;
+
+    @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1200.00")
+    private BigDecimal receivablePrice;
+
+    @Schema(description = "客户行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "金融")
+    private String customerType;
+
+    @Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "外呼")
+    private String customerSource;
+
+    @Schema(description = "负责人ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long ownerUserId;
+
+    @Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
+    private String ownerUserName;
+
+    @Schema(description = "创建人ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    private Long creatorUserId;
+
+    @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED, example = "源码")
+    private String creatorUserName;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-01 13:24:26")
+    private LocalDateTime createTime;
+
+    @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02 00:00:00")
+    private LocalDate orderDate;
+
+}

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

@@ -1,34 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-
-@Schema(description = "管理后台 - CRM 数据统计 员工客户分析 VO")
-@Data
-public class CrmStatisticsCustomerCountVO {
-
-    /**
-     * 时间轴
-     * <p>
-     * group by DATE_FORMAT(create_date, '%Y%m')
-     */
-    @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
-    private String category;
-
-    /**
-     * 数量是个特别“抽象”的概念,在不同排行下,代表不同含义
-     * <p>
-     * 1. 金额:合同金额排行、回款金额排行
-     * 2. 个数:签约合同排行、产品销量排行、产品销量排行、新增客户数排行、新增联系人排行、跟进次数排行、跟进客户数排行
-     */
-    @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer count = 0;
-
-    /**
-     * 成交周期(天)
-     */
-    @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
-    private Double cycle = 0.0;
-
-}

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

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 客户成交周期分析(按日期) VO")
+@Data
+public class CrmStatisticsCustomerDealCycleByDateRespVO {
+
+    @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
+    private String time;
+
+    @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
+    private Double customerDealCycle = 0.0;
+
+}

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

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 成交周期分析(按用户) VO")
+@Data
+public class CrmStatisticsCustomerDealCycleByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
+
+    @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
+    private Double customerDealCycle = 0.0;
+
+    @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer customerDealCount = 0;
+
+}

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

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 客户总量分析(按日期) VO")
+@Data
+public class CrmStatisticsCustomerSummaryByDateRespVO {
+
+    @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
+    private String time;
+
+    @Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer customerCreateCount = 0;
+
+    @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer customerDealCount = 0;
+
+}

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

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "管理后台 - CRM 客户总量分析(按用户) VO")
+@Data
+public class CrmStatisticsCustomerSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
+
+    @Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer customerCreateCount = 0;
+
+    @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer customerDealCount = 0;
+
+    @Schema(description = "合同总金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+    private BigDecimal contractPrice = BigDecimal.valueOf(0);
+
+    @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+    private BigDecimal receivablePrice = BigDecimal.valueOf(0);
+
+}

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

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 跟进次数分析(按日期) VO")
+@Data
+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;
+
+    @Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer followupCustomerCount = 0;
+
+}

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

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 跟进次数分析(按类型) VO")
+@Data
+public class CrmStatisticsFollowupSummaryByTypeRespVO {
+
+    @Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private String followupType;
+
+    @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer followupRecordCount = 0;
+
+}

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

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 跟进次数分析(按用户) VO")
+@Data
+public class CrmStatisticsFollowupSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
+
+    @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer followupRecordCount = 0;
+
+    @Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer followupCustomerCount = 0;
+
+}

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

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
 
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO;
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
@@ -14,16 +13,32 @@ import java.util.List;
 @Mapper
 public interface CrmStatisticsCustomerMapper {
 
-    List<CrmStatisticsCustomerCountVO> selectCustomerCountGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerCreateCountGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsCustomerCountVO> selectDealCustomerCountGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerDealCountGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsCustomerCountVO> selectRecordCountGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerCreateCountGroupbyUser(CrmStatisticsCustomerReqVO reqVO);
 
-    List<CrmStatisticsCustomerCountVO> selectDistinctRecordCountGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerDealCountGroupbyUser(CrmStatisticsCustomerReqVO crmStatisticsCustomerReqVO);
 
-    List<CrmStatisticsCustomerCountVO> selectRecordCountGroupbyType(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerSummaryByUserRespVO> selectContractPriceGroupbyUser(CrmStatisticsCustomerReqVO crmStatisticsCustomerReqVO);
 
-    List<CrmStatisticsCustomerCountVO> selectCustomerCycleGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerSummaryByUserRespVO> selectReceivablePriceGroupbyUser(CrmStatisticsCustomerReqVO crmStatisticsCustomerReqVO);
+
+    List<CrmStatisticsFollowupSummaryByDateRespVO> selectFollowupRecordCountGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
+
+    List<CrmStatisticsFollowupSummaryByDateRespVO> selectFollowupCustomerCountGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
+
+    List<CrmStatisticsFollowupSummaryByUserRespVO> selectFollowupRecordCountGroupbyUser(CrmStatisticsCustomerReqVO reqVO);
+
+    List<CrmStatisticsFollowupSummaryByUserRespVO> selectFollowupCustomerCountGroupbyUser(CrmStatisticsCustomerReqVO reqVO);
+
+    List<CrmStatisticsCustomerContractSummaryRespVO> selectContractSummary(CrmStatisticsCustomerReqVO reqVO);
+
+    List<CrmStatisticsFollowupSummaryByTypeRespVO> selectFollowupRecordCountGroupbyType(CrmStatisticsCustomerReqVO reqVO);
+
+    List<CrmStatisticsCustomerDealCycleByDateRespVO> selectCustomerDealCycleGroupbyDate(CrmStatisticsCustomerReqVO reqVO);
+
+    List<CrmStatisticsCustomerDealCycleByUserRespVO> selectCustomerDealCycleGroupbyUser(CrmStatisticsCustomerReqVO reqVO);
 
 }

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

@@ -1,64 +1,80 @@
 package cn.iocoder.yudao.module.crm.service.statistics;
 
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO;
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
 
 import java.util.List;
 
 /**
- * CRM 数据统计 员工客户分析 Service 接口
+ * CRM 客户分析 Service 接口
  *
  * @author dhb52
  */
 public interface CrmStatisticsCustomerService {
 
     /**
-     * 获取新建客户数量
+     * 总量分析(按日期)
      *
      * @param reqVO 请求参数
-     * @return 新建客户数量统计
+     * @return 统计数据
      */
-    List<CrmStatisticsCustomerCountVO> getTotalCustomerCount(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerSummaryByDateRespVO> getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
 
     /**
-     * 获取成交客户数量
+     * 总量分析(按用户)
      *
      * @param reqVO 请求参数
-     * @return 成交客户数量统计
+     * @return 统计数据
      */
-    List<CrmStatisticsCustomerCountVO> getDealTotalCustomerCount(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerSummaryByUserRespVO> getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
 
 
     /**
-     * 获取客户跟进次数
+     * 跟进次数分析(按日期)
      *
      * @param reqVO 请求参数
-     * @return 客户跟进次数
+     * @return 统计数据
      */
-    List<CrmStatisticsCustomerCountVO> getRecordCount(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowupSummaryByDateRespVO> getFollowupSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
 
     /**
-     * 获取已跟进客户数
+     * 跟进次数分析(按用户)
      *
      * @param reqVO 请求参数
-     * @return 已跟进客户数
+     * @return 统计数据
      */
-    List<CrmStatisticsCustomerCountVO> getDistinctRecordCount(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowupSummaryByUserRespVO> getFollowupSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
 
     /**
-     * 获取客户跟进方式统计数
+     * 客户跟进次数分析(按类型)
      *
      * @param reqVO 请求参数
-     * @return 客户跟进方式统计数
+     * @return 统计数
      */
-    List<CrmStatisticsCustomerCountVO> getRecordTypeCount(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsFollowupSummaryByTypeRespVO> getFollowupSummaryByType(CrmStatisticsCustomerReqVO reqVO);
+
+
+    /**
+     * 获取合同摘要信息(客户转化率页面)
+     *
+     * @param reqVO 请求参数
+     * @return 合同摘要列表
+     */
+    List<CrmStatisticsCustomerContractSummaryRespVO> getContractSummary(CrmStatisticsCustomerReqVO reqVO);
+
+    /**
+     * 客户成交周期(按日期)
+     *
+     * @param reqVO 请求参数
+     * @return 统计数据
+     */
+    List<CrmStatisticsCustomerDealCycleByDateRespVO> getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO);
 
     /**
-     * 获取客户成交周期
+     * 客户成交周期(按用户)
      *
      * @param reqVO 请求参数
-     * @return 客户成交周期
+     * @return 统计数据
      */
-    List<CrmStatisticsCustomerCountVO> getCustomerCycle(CrmStatisticsCustomerReqVO reqVO);
+    List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO);
 
 }

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

@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.crm.service.statistics;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.util.ObjUtil;
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO;
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+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.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -17,17 +17,14 @@ import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
+import java.util.*;
 
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
 
 /**
- * CRM 数据统计 员工客户分析 Service 实现类
+ * CRM 客户分析 Service 实现类
  *
  * @author dhb52
  */
@@ -35,6 +32,13 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
 @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;
 
@@ -45,130 +49,315 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
     @Resource
     private DictDataApi dictDataApi;
 
+
     @Override
-    public List<CrmStatisticsCustomerCountVO> getTotalCustomerCount(CrmStatisticsCustomerReqVO reqVO) {
-        return getStat(reqVO, customerMapper::selectCustomerCountGroupbyDate);
+    public List<CrmStatisticsCustomerSummaryByDateRespVO> getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
+        // 1. 获得用户编号数组
+        final List<Long> userIds = getUserIds(reqVO);
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyList();
+        }
+        reqVO.setUserIds(userIds);
+
+        // 2. 获取分项统计数据
+        reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1]));
+        final List<CrmStatisticsCustomerSummaryByDateRespVO> customerCreateCount = customerMapper.selectCustomerCreateCountGroupbyDate(reqVO);
+        final List<CrmStatisticsCustomerSummaryByDateRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupbyDate(reqVO);
+
+        // 3. 获取时间序列
+        final List<String> times = generateTimeSeries(reqVO.getTimes()[0], reqVO.getTimes()[1]);
+
+        // 4. 合并统计数据
+        List<CrmStatisticsCustomerSummaryByDateRespVO> result = new ArrayList<>(times.size());
+        final Map<String, Integer> customerCreateCountMap = convertMap(customerCreateCount, CrmStatisticsCustomerSummaryByDateRespVO::getTime, CrmStatisticsCustomerSummaryByDateRespVO::getCustomerCreateCount);
+        final Map<String, Integer> customerDealCountMap = convertMap(customerDealCount, CrmStatisticsCustomerSummaryByDateRespVO::getTime, CrmStatisticsCustomerSummaryByDateRespVO::getCustomerDealCount);
+        times.forEach(time -> result.add(
+            new CrmStatisticsCustomerSummaryByDateRespVO().setTime(time)
+                .setCustomerCreateCount(customerCreateCountMap.getOrDefault(time, 0))
+                .setCustomerDealCount(customerDealCountMap.getOrDefault(time, 0))
+        ));
+
+        return result;
     }
 
     @Override
-    public List<CrmStatisticsCustomerCountVO> getDealTotalCustomerCount(CrmStatisticsCustomerReqVO reqVO) {
-        return getStat(reqVO, customerMapper::selectDealCustomerCountGroupbyDate);
+    public List<CrmStatisticsCustomerSummaryByUserRespVO> getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
+        // 1. 获得用户编号数组
+        final List<Long> userIds = getUserIds(reqVO);
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyList();
+        }
+        reqVO.setUserIds(userIds);
+
+        // 2. 获取分项统计数据
+        final List<CrmStatisticsCustomerSummaryByUserRespVO> customerCreateCount = customerMapper.selectCustomerCreateCountGroupbyUser(reqVO);
+        final List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupbyUser(reqVO);
+        final List<CrmStatisticsCustomerSummaryByUserRespVO> contractPrice = customerMapper.selectContractPriceGroupbyUser(reqVO);
+        final List<CrmStatisticsCustomerSummaryByUserRespVO> receivablePrice = customerMapper.selectReceivablePriceGroupbyUser(reqVO);
+
+        // 3. 合并统计数据
+        final Map<Long, Integer> customerCreateCountMap = convertMap(customerCreateCount, CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId, CrmStatisticsCustomerSummaryByUserRespVO::getCustomerCreateCount);
+        final Map<Long, Integer> customerDealCountMap = convertMap(customerDealCount, CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId, CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount);
+        final Map<Long, BigDecimal> contractPriceMap = convertMap(contractPrice, CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId, CrmStatisticsCustomerSummaryByUserRespVO::getContractPrice);
+        final Map<Long, BigDecimal> receivablePriceMap = convertMap(receivablePrice, CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId, CrmStatisticsCustomerSummaryByUserRespVO::getReceivablePrice);
+        List<CrmStatisticsCustomerSummaryByUserRespVO> result = new ArrayList<>(userIds.size());
+        userIds.forEach(userId -> {
+            final CrmStatisticsCustomerSummaryByUserRespVO respVO = new CrmStatisticsCustomerSummaryByUserRespVO();
+            respVO.setOwnerUserId(userId);
+            respVO.setCustomerCreateCount(customerCreateCountMap.getOrDefault(userId, 0))
+                .setCustomerDealCount(customerDealCountMap.getOrDefault(userId, 0))
+                .setContractPrice(contractPriceMap.getOrDefault(userId, BigDecimal.valueOf(0)))
+                .setReceivablePrice(receivablePriceMap.getOrDefault(userId, BigDecimal.valueOf(0)));
+            result.add(respVO);
+        });
+
+        // 4. 拼接用户信息
+        appendUserInfo(result);
+
+        return result;
     }
 
     @Override
-    public List<CrmStatisticsCustomerCountVO> getRecordCount(CrmStatisticsCustomerReqVO reqVO) {
+    public List<CrmStatisticsFollowupSummaryByDateRespVO> getFollowupSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
+        // 1. 获得用户编号数组
+        final List<Long> userIds = getUserIds(reqVO);
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyList();
+        }
+        reqVO.setUserIds(userIds);
+
+        // 2. 获取分项统计数据
+        reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1]));
         reqVO.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType());
-        return getStat(reqVO, customerMapper::selectRecordCountGroupbyDate);
+        final List<CrmStatisticsFollowupSummaryByDateRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupbyDate(reqVO);
+        final List<CrmStatisticsFollowupSummaryByDateRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupbyDate(reqVO);
+
+        // 3. 获取时间序列
+        final List<String> times = generateTimeSeries(reqVO.getTimes()[0], reqVO.getTimes()[1]);
+
+        // 4. 合并统计数据
+        List<CrmStatisticsFollowupSummaryByDateRespVO> result = new ArrayList<>(times.size());
+        final Map<String, Integer> followupRecordCountMap = convertMap(followupRecordCount, CrmStatisticsFollowupSummaryByDateRespVO::getTime, CrmStatisticsFollowupSummaryByDateRespVO::getFollowupRecordCount);
+        final Map<String, Integer> followupCustomerCountMap = convertMap(followupCustomerCount, CrmStatisticsFollowupSummaryByDateRespVO::getTime, CrmStatisticsFollowupSummaryByDateRespVO::getFollowupCustomerCount);
+        times.forEach(time -> result.add(
+            new CrmStatisticsFollowupSummaryByDateRespVO().setTime(time)
+                .setFollowupRecordCount(followupRecordCountMap.getOrDefault(time, 0))
+                .setFollowupCustomerCount(followupCustomerCountMap.getOrDefault(time, 0))
+        ));
+
+        return result;
     }
 
     @Override
-    public List<CrmStatisticsCustomerCountVO> getDistinctRecordCount(CrmStatisticsCustomerReqVO reqVO) {
+    public List<CrmStatisticsFollowupSummaryByUserRespVO> getFollowupSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
+        // 1. 获得用户编号数组
+        final List<Long> userIds = getUserIds(reqVO);
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyList();
+        }
+        reqVO.setUserIds(userIds);
+
+        // 2. 获取分项统计数据
         reqVO.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType());
-        return getStat(reqVO, customerMapper::selectDistinctRecordCountGroupbyDate);
+        final List<CrmStatisticsFollowupSummaryByUserRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupbyUser(reqVO);
+        final List<CrmStatisticsFollowupSummaryByUserRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupbyUser(reqVO);
+
+        // 3. 合并统计数据
+        final Map<Long, Integer> followupRecordCountMap = convertMap(followupRecordCount, CrmStatisticsFollowupSummaryByUserRespVO::getOwnerUserId, CrmStatisticsFollowupSummaryByUserRespVO::getFollowupRecordCount);
+        final Map<Long, Integer> followupCustomerCountMap = convertMap(followupCustomerCount, CrmStatisticsFollowupSummaryByUserRespVO::getOwnerUserId, CrmStatisticsFollowupSummaryByUserRespVO::getFollowupCustomerCount);
+        List<CrmStatisticsFollowupSummaryByUserRespVO> result = new ArrayList<>(userIds.size());
+        userIds.forEach(userId -> {
+            final CrmStatisticsFollowupSummaryByUserRespVO stat = new CrmStatisticsFollowupSummaryByUserRespVO()
+                .setFollowupRecordCount(followupRecordCountMap.getOrDefault(userId, 0))
+                .setFollowupCustomerCount(followupCustomerCountMap.getOrDefault(userId, 0));
+            stat.setOwnerUserId(userId);
+            result.add(stat);
+        });
+
+        // 4. 拼接用户信息
+        appendUserInfo(result);
+
+        return result;
     }
 
     @Override
-    public List<CrmStatisticsCustomerCountVO> getRecordTypeCount(CrmStatisticsCustomerReqVO reqVO) {
-        // 1. 获得用户编号数组: 如果用户编号为空, 则获得部门下的用户编号数组
-        if (ObjUtil.isNotNull(reqVO.getUserId())) {
-            reqVO.setUserIds(List.of(reqVO.getUserId()));
-        } else {
-            reqVO.setUserIds(getUserIds(reqVO.getDeptId()));
-        }
-        if (CollUtil.isEmpty(reqVO.getUserIds())) {
+    public List<CrmStatisticsFollowupSummaryByTypeRespVO> getFollowupSummaryByType(CrmStatisticsCustomerReqVO reqVO) {
+        // 1. 获得用户编号数组
+        final List<Long> userIds = getUserIds(reqVO);
+        if (CollUtil.isEmpty(userIds)) {
             return Collections.emptyList();
         }
+        reqVO.setUserIds(userIds);
 
         // 2. 获得排行数据
         reqVO.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType());
-        List<CrmStatisticsCustomerCountVO> stats = customerMapper.selectRecordCountGroupbyType(reqVO);
+        List<CrmStatisticsFollowupSummaryByTypeRespVO> stats = customerMapper.selectFollowupRecordCountGroupbyType(reqVO);
 
         // 3. 获取字典数据
         List<DictDataRespDTO> followUpTypes = dictDataApi.getDictDataList("crm_follow_up_type");
         final Map<String, String> followUpTypeMap = convertMap(followUpTypes, DictDataRespDTO::getValue, DictDataRespDTO::getLabel);
         stats.forEach(stat -> {
-            stat.setCategory(followUpTypeMap.get(stat.getCategory()));
+            stat.setFollowupType(followUpTypeMap.get(stat.getFollowupType()));
         });
 
         return stats;
     }
 
     @Override
-    public List<CrmStatisticsCustomerCountVO> getCustomerCycle(CrmStatisticsCustomerReqVO reqVO) {
-        return getStat(reqVO, customerMapper::selectCustomerCycleGroupbyDate);
+    public List<CrmStatisticsCustomerContractSummaryRespVO> getContractSummary(CrmStatisticsCustomerReqVO reqVO) {
+        // 1. 获得用户编号数组
+        final List<Long> userIds = getUserIds(reqVO);
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyList();
+        }
+        reqVO.setUserIds(userIds);
+
+        List<CrmStatisticsCustomerContractSummaryRespVO> contractSummary = customerMapper.selectContractSummary(reqVO);
+
+        // 2. 拼接用户信息
+        final Set<Long> userIdSet = new HashSet<>();
+        userIdSet.addAll(userIds);
+        userIdSet.addAll(convertSet(contractSummary, CrmStatisticsCustomerContractSummaryRespVO::getCreatorUserId));
+        final Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIdSet);
+        contractSummary.forEach(contract -> contract.setCreatorUserName(userMap.get(contract.getCreatorUserId()).getNickname())
+            .setOwnerUserName(userMap.get(contract.getOwnerUserId()).getNickname()));
+
+        return contractSummary;
+    }
+
+    @Override
+    public List<CrmStatisticsCustomerDealCycleByDateRespVO> getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO) {
+        // 1. 获得用户编号数组
+        final List<Long> userIds = getUserIds(reqVO);
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyList();
+        }
+        reqVO.setUserIds(userIds);
+
+        // 2. 获取分项统计数据
+        reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1]));
+        reqVO.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType());
+        final List<CrmStatisticsCustomerDealCycleByDateRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupbyDate(reqVO);
+
+        // 3. 获取时间序列
+        final List<String> times = generateTimeSeries(reqVO.getTimes()[0], reqVO.getTimes()[1]);
+
+        // 4. 合并统计数据
+        List<CrmStatisticsCustomerDealCycleByDateRespVO> result = new ArrayList<>(times.size());
+        final Map<String, Double> customerDealCycleMap = convertMap(customerDealCycle, CrmStatisticsCustomerDealCycleByDateRespVO::getTime, CrmStatisticsCustomerDealCycleByDateRespVO::getCustomerDealCycle);
+        times.forEach(time -> result.add(
+            new CrmStatisticsCustomerDealCycleByDateRespVO().setTime(time)
+                .setCustomerDealCycle(customerDealCycleMap.getOrDefault(time, Double.valueOf(0)))
+        ));
+
+        return result;
+    }
+
+    @Override
+    public List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO) {
+        // 1. 获得用户编号数组
+        final List<Long> userIds = getUserIds(reqVO);
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyList();
+        }
+        reqVO.setUserIds(userIds);
+
+        // 2. 获取分项统计数据
+        reqVO.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType());
+        final List<CrmStatisticsCustomerDealCycleByUserRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupbyUser(reqVO);
+        final List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupbyUser(reqVO);
+
+        // 3. 合并统计数据
+        final Map<Long, Double> customerDealCycleMap = convertMap(customerDealCycle, CrmStatisticsCustomerDealCycleByUserRespVO::getOwnerUserId, CrmStatisticsCustomerDealCycleByUserRespVO::getCustomerDealCycle);
+        final Map<Long, Integer> customerDealCountMap = convertMap(customerDealCount, CrmStatisticsCustomerSummaryByUserRespVO::getOwnerUserId, CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount);
+        List<CrmStatisticsCustomerDealCycleByUserRespVO> result = new ArrayList<>(userIds.size());
+        userIds.forEach(userId -> {
+            final CrmStatisticsCustomerDealCycleByUserRespVO stat = new CrmStatisticsCustomerDealCycleByUserRespVO()
+                .setCustomerDealCycle(customerDealCycleMap.getOrDefault(userId, 0.0))
+                .setCustomerDealCount(customerDealCountMap.getOrDefault(userId, 0));
+            stat.setOwnerUserId(userId);
+            result.add(stat);
+        });
+
+        // 4. 拼接用户信息
+        appendUserInfo(result);
+
+        return result;
+    }
+
+    /**
+     * 拼接用户信息(昵称)
+     *
+     * @param stats 统计数据
+     */
+    private <T extends CrmStatisticsCustomerByUserBaseRespVO> void appendUserInfo(List<T> stats) {
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(stats, CrmStatisticsCustomerByUserBaseRespVO::getOwnerUserId));
+        stats.forEach(stat -> MapUtils.findAndThen(userMap, stat.getOwnerUserId(), user -> {
+            stat.setOwnerUserName(user.getNickname());
+        }));
     }
 
     /**
-     * 获得统计数据
+     * 获取用户编号数组。如果用户编号为空, 则获得部门下的用户编号数组,包括子部门的所有用户编号
      *
-     * @param reqVO        参数
-     * @param statFunction 统计方法
-     * @return 统计数据
+     * @param reqVO 请求参数
+     * @return 用户编号数组
      */
-    private List<CrmStatisticsCustomerCountVO> getStat(CrmStatisticsCustomerReqVO reqVO, Function<CrmStatisticsCustomerReqVO, List<CrmStatisticsCustomerCountVO>> statFunction) {
-        // 1. 获得用户编号数组: 如果用户编号为空, 则获得部门下的用户编号数组
+    private List<Long> getUserIds(CrmStatisticsCustomerReqVO reqVO) {
         if (ObjUtil.isNotNull(reqVO.getUserId())) {
-            reqVO.setUserIds(List.of(reqVO.getUserId()));
+            return List.of(reqVO.getUserId());
         } else {
-            reqVO.setUserIds(getUserIds(reqVO.getDeptId()));
-        }
-        if (CollUtil.isEmpty(reqVO.getUserIds())) {
-            return Collections.emptyList();
+            // 1. 获得部门列表
+            final Long deptId = reqVO.getDeptId();
+            List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId);
+            deptIds.add(deptId);
+            // 2. 获得用户编号
+            return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
         }
+    }
 
-        // 2. 生成日期格式
-        LocalDateTime startTime = reqVO.getTimes()[0];
-        final LocalDateTime endTime = reqVO.getTimes()[1];
-        final long days = LocalDateTimeUtil.between(startTime, endTime).toDays();
-        boolean byMonth = days > 31;
-        if (byMonth) {
-            // 按月
-            reqVO.setSqlDateFormat("%Y%m");
-        } else {
-            // 按日
-            reqVO.setSqlDateFormat("%Y%m%d");
-        }
 
-        // 3. 获得排行数据
-        List<CrmStatisticsCustomerCountVO> stats = statFunction.apply(reqVO);
+    /**
+     * 判断是否按照 月粒度 统计
+     *
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @return 是, 按月粒度, 否则按天粒度统计。
+     */
+    private boolean queryByMonth(LocalDateTime startTime, LocalDateTime endTime) {
+        return LocalDateTimeUtil.between(startTime, endTime).toDays() > 31;
+    }
 
-        // 4. 生成时间序列
-        List<CrmStatisticsCustomerCountVO> result = CollUtil.newArrayList();
+    /**
+     * 生成时间序列
+     *
+     * @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)) {
-            final String category = LocalDateTimeUtil.format(startTime, byMonth ? "yyyyMM" : "yyyyMMdd");
-            result.add(new CrmStatisticsCustomerCountVO().setCategory(category));
+            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);
         }
 
-        // 5. 使用时间序列填充结果
-        final Map<String, CrmStatisticsCustomerCountVO> statMap = convertMap(stats,
-            CrmStatisticsCustomerCountVO::getCategory,
-            Function.identity());
-        result.forEach(r -> {
-            if (statMap.containsKey(r.getCategory())) {
-                r.setCount(statMap.get(r.getCategory()).getCount())
-                    .setCycle(statMap.get(r.getCategory()).getCycle());
-            }
-        });
-
-        return result;
+        return times;
     }
 
-
     /**
-     * 获得部门下的用户编号数组,包括子部门的
+     * 获取 SQL 查询 GROUP BY 的时间格式
      *
-     * @param deptId 部门编号
-     * @return 用户编号数组
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @return SQL 查询 GROUP BY 的时间格式
      */
-    public List<Long> getUserIds(Long deptId) {
-        // 1. 获得部门列表
-        List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId);
-        deptIds.add(deptId);
-        // 2. 获得用户编号
-        return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
+    private String getSqlDateFormat(LocalDateTime startTime, LocalDateTime endTime) {
+        return queryByMonth(startTime, endTime) ? SQL_DATE_FORMAT_BY_MONTH : SQL_DATE_FORMAT_BY_DAY;
     }
+
 }

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

@@ -2,115 +2,238 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper">
 
+    <select id="selectCustomerCreateCountGroupbyDate"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
+        SELECT
+        DATE_FORMAT( create_time, #{sqlDateFormat} ) 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
+    </select>
 
-    <select id="selectCustomerCountGroupbyDate"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
+    <select id="selectCustomerDealCountGroupbyDate"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
         SELECT
-            DATE_FORMAT( create_time, #{sqlDateFormat,javaType=java.lang.String} ) AS category,
-            count(*) AS 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}
-        GROUP BY category
+        DATE_FORMAT( b.order_date, #{sqlDateFormat} ) AS time,
+        count( DISTINCT a.id ) AS customerDealCount
+        FROM crm_customer AS a
+        LEFT JOIN crm_contract AS b ON b.customer_id = a.id
+        WHERE a.deleted = 0 AND b.deleted = 0
+        AND b.audit_status = 20
+        AND a.owner_user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+        AND b.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
+        #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY time
+    </select>
+
+    <select id="selectCustomerCreateCountGroupbyUser"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
+        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}
+        GROUP BY owner_user_id
+    </select>
+
+    <select id="selectCustomerDealCountGroupbyUser"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
+        SELECT a.owner_user_id, count( DISTINCT a.id ) AS customer_deal_count
+        FROM crm_customer AS a
+        LEFT JOIN crm_contract AS b ON b.customer_id = a.id
+        WHERE a.deleted = 0 AND b.deleted = 0
+        AND b.audit_status = 20
+        AND a.owner_user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+        AND b.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
+        #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY a.owner_user_id
+    </select>
+
+    <select id="selectContractPriceGroupbyUser"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
+        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
+    </select>
+
+
+    <select id="selectReceivablePriceGroupbyUser"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
+        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
     </select>
 
-    <select id="selectDealCustomerCountGroupbyDate"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
+    <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 = #{bizType}
+        GROUP BY time
+    </select>
+
+    <select id="selectFollowupCustomerCountGroupbyDate"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowupSummaryByDateRespVO">
         SELECT
-            DATE_FORMAT( b.order_date, #{sqlDateFormat,javaType=java.lang.String} ) AS category,
-            count( DISTINCT a.id ) AS count
-        FROM
-            crm_customer AS a
-                LEFT JOIN crm_contract AS b ON b.customer_id = a.id
-        WHERE
-            a.deleted = 0 AND b.deleted = 0
-          AND b.audit_status = 20
-          AND a.owner_user_id IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-          AND b.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
-                #{times[1],javaType=java.time.LocalDateTime}
-        GROUP BY category
+        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 = #{bizType}
+        GROUP BY time
     </select>
 
-    <select id="selectRecordCountGroupbyDate"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
+    <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 = #{bizType}
+        GROUP BY creator
+    </select>
+
+    <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 = #{bizType}
+        GROUP BY creator
+    </select>
+
+    <select id="selectFollowupRecordCountGroupbyType"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowupSummaryByTypeRespVO">
         SELECT
-            DATE_FORMAT( create_time, #{sqlDateFormat,javaType=java.lang.String} ) AS category,
-            count(*) AS 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 = #{bizType,javaType=java.lang.Integer}
-        GROUP BY category
+        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 = #{bizType}
+        GROUP BY followupType
     </select>
 
-    <select id="selectDistinctRecordCountGroupbyDate"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
+    <select id="selectContractSummary"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerContractSummaryRespVO">
         SELECT
-            DATE_FORMAT( create_time, #{sqlDateFormat,javaType=java.lang.String} ) AS category,
-            count(DISTINCT biz_id) AS 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 = #{bizType,javaType=java.lang.Integer}
-        GROUP BY category
+        a.`name` AS customer_name,
+        b.`name` AS contract_name,
+        b.total_price AS contract_price,
+        IFNULL( c.price, 0 ) AS receivable_price,
+        a.owner_user_id,
+        a.creator AS creator_user_id,
+        a.create_time,
+        b.order_date
+        FROM
+        crm_customer AS a
+        INNER JOIN crm_contract AS b ON a.id = b.customer_id
+        LEFT JOIN crm_receivable AS c ON b.id = c.contract_id
+        WHERE a.deleted = 0 AND b.deleted = 0 AND c.deleted = 0
+        AND b.audit_status = 20
+        AND a.owner_user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+        AND b.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
+        #{times[1],javaType=java.time.LocalDateTime}
     </select>
 
-    <select id="selectRecordCountGroupbyType"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
+    <select id="selectCustomerDealCycleGroupbyDate"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByDateRespVO">
         SELECT
-            type AS category,
-            count(*) AS 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 = #{bizType,javaType=java.lang.Integer}
-        GROUP BY category
+        DATE_FORMAT( b.order_date, #{sqlDateFormat} ) AS time,
+        IFNULL( TRUNCATE ( AVG( TIMESTAMPDIFF( DAY, a.create_time, b.order_date )), 1 ), 0 ) AS customer_deal_cycle
+        FROM crm_customer AS a
+        LEFT JOIN crm_contract AS b ON b.customer_id = a.id
+        WHERE a.deleted = 0 AND b.deleted = 0
+        AND b.audit_status = 20
+        AND a.owner_user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+        AND b.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
+        #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY time
     </select>
 
-    <select id="selectCustomerCycleGroupbyDate"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
+    <select id="selectCustomerDealCycleGroupbyUser"
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByUserRespVO">
         SELECT
-            DATE_FORMAT( b.order_date, #{sqlDateFormat,javaType=java.lang.String} ) AS category,
-            IFNULL( TRUNCATE ( AVG( TIMESTAMPDIFF( DAY, a.create_time, b.order_date )), 1 ), 0 ) AS cycle
-          FROM crm_customer AS a
-            LEFT JOIN crm_contract AS b ON b.customer_id = a.id
-         WHERE
-            a.deleted = 0 AND b.deleted = 0
-           AND b.audit_status = 20
-           AND a.owner_user_id IN
-                <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
-                    #{userId}
-                </foreach>
-           AND b.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
-                #{times[1],javaType=java.time.LocalDateTime}
-         GROUP BY category
+        a.owner_user_id,
+        IFNULL( TRUNCATE ( AVG( TIMESTAMPDIFF( DAY, a.create_time, b.order_date )), 1 ), 0 ) AS customer_deal_cycle
+        FROM crm_customer AS a
+        LEFT JOIN crm_contract AS b ON b.customer_id = a.id
+        WHERE a.deleted = 0 AND b.deleted = 0
+        AND b.audit_status = 20
+        AND a.owner_user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+        AND b.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
+        #{times[1],javaType=java.time.LocalDateTime}
+        GROUP BY a.owner_user_id
     </select>
 
 </mapper>