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

Merge pull request !902 from dhb52/develop
This commit is contained in:
芋道源码 2024-03-09 07:38:44 +00:00 committed by Gitee
commit 85817a2426
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
16 changed files with 913 additions and 306 deletions

View File

@ -1,39 +1,68 @@
### 新建客户总量分析(按日) # == 1. 客户总量分析 ==
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 客户总量分析(按日)
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}} Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}} tenant-id: {{adminTenentId}}
### 新建客户总量分析(按月) ### 1.2 客户总量分析(按月)
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 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}} Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}} tenant-id: {{adminTenentId}}
### 成交客户总量分析(按日) ### 1.3 客户总量统计(按用户)
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 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}} Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}} 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}} Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}} tenant-id: {{adminTenentId}}
### 获取客户跟进次数(按日) ### 2.2 客户跟进次数分析(按月)
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 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}} Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}} tenant-id: {{adminTenentId}}
### 获取客户跟进次数(按月) ### 2.3 客户总量统计(按用户)
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 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}} 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 # == 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}} Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}} 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 # == 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}} Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}} tenant-id: {{adminTenentId}}
tenant-id: {{adminTenentId}}
# == 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}}
### 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}}
### 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}}

View File

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

View File

@ -0,0 +1,20 @@
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;
/**
* 用户客户统计响应 Base VO
*/
@Data
public class CrmStatisticsCustomerByUserBaseRespVO {
@Schema(description = "负责人ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@JsonIgnore
private Long ownerUserId;
@Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
private String ownerUserName;
}

View File

@ -0,0 +1,67 @@
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 org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
@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 = "客户行业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 = "外呼")
private String sourceName;
@Schema(description = "负责人ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@JsonIgnore
private Long ownerUserId;
@Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
private String ownerUserName;
@Schema(description = "创建人ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@JsonIgnore
private String 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")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY, timezone = TIME_ZONE_DEFAULT)
private LocalDate orderDate;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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.ZERO;
@Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
private BigDecimal receivablePrice = BigDecimal.ZERO;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics; 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.*;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List; import java.util.List;
@ -14,16 +13,32 @@ import java.util.List;
@Mapper @Mapper
public interface CrmStatisticsCustomerMapper { 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);
} }

View File

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

View File

@ -3,8 +3,9 @@ package cn.iocoder.yudao.module.crm.service.statistics;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO; import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO; 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.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@ -17,17 +18,19 @@ import jakarta.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
/** /**
* CRM 数据统计 员工客户分析 Service 实现类 * CRM 客户分析 Service 实现类
* *
* @author dhb52 * @author dhb52
*/ */
@ -35,6 +38,13 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
@Validated @Validated
public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerService { 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 @Resource
private CrmStatisticsCustomerMapper customerMapper; private CrmStatisticsCustomerMapper customerMapper;
@ -45,130 +55,354 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
@Resource @Resource
private DictDataApi dictDataApi; private DictDataApi dictDataApi;
@Override
public List<CrmStatisticsCustomerCountVO> getTotalCustomerCount(CrmStatisticsCustomerReqVO reqVO) {
return getStat(reqVO, customerMapper::selectCustomerCountGroupbyDate);
}
@Override @Override
public List<CrmStatisticsCustomerCountVO> getDealTotalCustomerCount(CrmStatisticsCustomerReqVO reqVO) { public List<CrmStatisticsCustomerSummaryByDateRespVO> getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
return getStat(reqVO, customerMapper::selectDealCustomerCountGroupbyDate); // 1. 获得用户编号数组
} final List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
@Override
public List<CrmStatisticsCustomerCountVO> getRecordCount(CrmStatisticsCustomerReqVO reqVO) {
reqVO.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType());
return getStat(reqVO, customerMapper::selectRecordCountGroupbyDate);
}
@Override
public List<CrmStatisticsCustomerCountVO> getDistinctRecordCount(CrmStatisticsCustomerReqVO reqVO) {
reqVO.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType());
return getStat(reqVO, customerMapper::selectDistinctRecordCountGroupbyDate);
}
@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())) {
return Collections.emptyList(); 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> respVoList = 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 -> respVoList.add(
new CrmStatisticsCustomerSummaryByDateRespVO().setTime(time)
.setCustomerCreateCount(customerCreateCountMap.getOrDefault(time, 0))
.setCustomerDealCount(customerDealCountMap.getOrDefault(time, 0))
));
return respVoList;
}
@Override
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> respVoList = new ArrayList<>(userIds.size());
userIds.forEach(userId -> {
final CrmStatisticsCustomerSummaryByUserRespVO vo = new CrmStatisticsCustomerSummaryByUserRespVO();
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));
respVoList.add(vo);
});
// 4. 拼接用户信息
appendUserInfo(respVoList);
return respVoList;
}
@Override
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());
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> respVoList = 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 -> respVoList.add(
new CrmStatisticsFollowupSummaryByDateRespVO().setTime(time)
.setFollowupRecordCount(followupRecordCountMap.getOrDefault(time, 0))
.setFollowupCustomerCount(followupCustomerCountMap.getOrDefault(time, 0))
));
return respVoList;
}
@Override
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());
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> respVoList = new ArrayList<>(userIds.size());
userIds.forEach(userId -> {
final CrmStatisticsFollowupSummaryByUserRespVO vo = new CrmStatisticsFollowupSummaryByUserRespVO()
.setFollowupRecordCount(followupRecordCountMap.getOrDefault(userId, 0))
.setFollowupCustomerCount(followupCustomerCountMap.getOrDefault(userId, 0));
vo.setOwnerUserId(userId);
respVoList.add(vo);
});
// 4. 拼接用户信息
appendUserInfo(respVoList);
return respVoList;
}
@Override
public List<CrmStatisticsFollowupSummaryByTypeRespVO> getFollowupSummaryByType(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
final List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
reqVO.setUserIds(userIds);
// 2. 获得排行数据 // 2. 获得排行数据
reqVO.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()); reqVO.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType());
List<CrmStatisticsCustomerCountVO> stats = customerMapper.selectRecordCountGroupbyType(reqVO); List<CrmStatisticsFollowupSummaryByTypeRespVO> respVoList = customerMapper.selectFollowupRecordCountGroupbyType(reqVO);
// 3. 获取字典数据 // 3. 获取字典数据
List<DictDataRespDTO> followUpTypes = dictDataApi.getDictDataList("crm_follow_up_type"); List<DictDataRespDTO> followUpTypes = dictDataApi.getDictDataList(CRM_FOLLOW_UP_TYPE);
final Map<String, String> followUpTypeMap = convertMap(followUpTypes, DictDataRespDTO::getValue, DictDataRespDTO::getLabel); final Map<String, String> followUpTypeMap = convertMap(followUpTypes,
stats.forEach(stat -> { DictDataRespDTO::getValue, DictDataRespDTO::getLabel);
stat.setCategory(followUpTypeMap.get(stat.getCategory())); respVoList.forEach(vo -> {
vo.setFollowupType(followUpTypeMap.get(vo.getFollowupType()));
}); });
return stats; return respVoList;
} }
@Override @Override
public List<CrmStatisticsCustomerCountVO> getCustomerCycle(CrmStatisticsCustomerReqVO reqVO) { public List<CrmStatisticsCustomerContractSummaryRespVO> getContractSummary(CrmStatisticsCustomerReqVO reqVO) {
return getStat(reqVO, customerMapper::selectCustomerCycleGroupbyDate); // 1. 获得用户编号数组
} final List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
/**
* 获得统计数据
*
* @param reqVO 参数
* @param statFunction 统计方法
* @return 统计数据
*/
private List<CrmStatisticsCustomerCountVO> getStat(CrmStatisticsCustomerReqVO reqVO, Function<CrmStatisticsCustomerReqVO, List<CrmStatisticsCustomerCountVO>> statFunction) {
// 1. 获得用户编号数组: 如果用户编号为空, 则获得部门下的用户编号数组
if (ObjUtil.isNotNull(reqVO.getUserId())) {
reqVO.setUserIds(List.of(reqVO.getUserId()));
} else {
reqVO.setUserIds(getUserIds(reqVO.getDeptId()));
}
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList(); return Collections.emptyList();
} }
reqVO.setUserIds(userIds);
// 2. 生成日期格式 // 2. 获取统计数据
LocalDateTime startTime = reqVO.getTimes()[0]; List<CrmStatisticsCustomerContractSummaryRespVO> respVoList = customerMapper.selectContractSummary(reqVO);
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. 获得排行数据 // 3. 设置 创建人负责人行业来源
List<CrmStatisticsCustomerCountVO> stats = statFunction.apply(reqVO); // 获取客户所属行业
Map<String, String> industryMap = convertMap(dictDataApi.getDictDataList(CRM_CUSTOMER_INDUSTRY),
DictDataRespDTO::getValue, DictDataRespDTO::getLabel);
// 获取客户来源
Map<String, String> sourceMap = convertMap(dictDataApi.getDictDataList(CRM_CUSTOMER_SOURCE),
DictDataRespDTO::getValue, DictDataRespDTO::getLabel);
// 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(respVoList,
vo -> Stream.of(NumberUtils.parseLong(vo.getCreatorUserId()), vo.getOwnerUserId())));
// 4. 生成时间序列 respVoList.forEach(vo -> {
List<CrmStatisticsCustomerCountVO> result = CollUtil.newArrayList(); MapUtils.findAndThen(industryMap, vo.getIndustryId(), vo::setIndustryName);
while (!startTime.isAfter(endTime)) { MapUtils.findAndThen(sourceMap, vo.getSource(), vo::setSourceName);
final String category = LocalDateTimeUtil.format(startTime, byMonth ? "yyyyMM" : "yyyyMMdd"); MapUtils.findAndThen(userMap, NumberUtils.parseLong(vo.getCreatorUserId()),
result.add(new CrmStatisticsCustomerCountVO().setCategory(category)); user -> vo.setCreatorUserName(user.getNickname()));
if (byMonth) MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname()));
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 respVoList;
} }
@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> respVoList = new ArrayList<>(times.size());
final Map<String, Double> customerDealCycleMap = convertMap(customerDealCycle,
CrmStatisticsCustomerDealCycleByDateRespVO::getTime,
CrmStatisticsCustomerDealCycleByDateRespVO::getCustomerDealCycle);
times.forEach(time -> respVoList.add(
new CrmStatisticsCustomerDealCycleByDateRespVO().setTime(time)
.setCustomerDealCycle(customerDealCycleMap.getOrDefault(time, 0D))
));
return respVoList;
}
@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> respVoList = new ArrayList<>(userIds.size());
userIds.forEach(userId -> {
final CrmStatisticsCustomerDealCycleByUserRespVO vo = new CrmStatisticsCustomerDealCycleByUserRespVO()
.setCustomerDealCycle(customerDealCycleMap.getOrDefault(userId, 0.0))
.setCustomerDealCount(customerDealCountMap.getOrDefault(userId, 0));
vo.setOwnerUserId(userId);
respVoList.add(vo);
});
// 4. 拼接用户信息
appendUserInfo(respVoList);
return respVoList;
}
/** /**
* 获得部门下的用户编号数组包括子部门的 * 拼接用户信息昵称
* *
* @param deptId 部门编号 * @param respVoList 统计数据
*/
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())));
}
/**
* 获取用户编号数组如果用户编号为空, 则获得部门下的用户编号数组包括子部门的所有用户编号
*
* @param reqVO 请求参数
* @return 用户编号数组 * @return 用户编号数组
*/ */
public List<Long> getUserIds(Long deptId) { private List<Long> getUserIds(CrmStatisticsCustomerReqVO reqVO) {
if (ObjUtil.isNotNull(reqVO.getUserId())) {
return List.of(reqVO.getUserId());
} else {
// 1. 获得部门列表 // 1. 获得部门列表
final Long deptId = reqVO.getDeptId();
List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId); List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId);
deptIds.add(deptId); deptIds.add(deptId);
// 2. 获得用户编号 // 2. 获得用户编号
return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId); 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;
}
}

View File

@ -2,107 +2,30 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!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"> <mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper">
<select id="selectCustomerCreateCountGroupbyDate"
<select id="selectCustomerCountGroupbyDate" resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
SELECT SELECT
DATE_FORMAT( create_time, #{sqlDateFormat,javaType=java.lang.String} ) AS category, DATE_FORMAT( create_time, #{sqlDateFormat} ) AS time,
count(*) AS count count(*) AS customerCreateCount
FROM FROM crm_customer
crm_customer WHERE deleted = 0
WHERE
deleted = 0
AND owner_user_id IN AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=","> <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId} #{userId}
</foreach> </foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
#{times[1],javaType=java.time.LocalDateTime} #{times[1],javaType=java.time.LocalDateTime}
GROUP BY category GROUP BY time
</select> </select>
<select id="selectDealCustomerCountGroupbyDate" <select id="selectCustomerDealCountGroupbyDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO"> resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
SELECT SELECT
DATE_FORMAT( b.order_date, #{sqlDateFormat,javaType=java.lang.String} ) AS category, DATE_FORMAT( b.order_date, #{sqlDateFormat} ) AS time,
count( DISTINCT a.id ) AS count 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 category
</select>
<select id="selectRecordCountGroupbyDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
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
</select>
<select id="selectDistinctRecordCountGroupbyDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
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
</select>
<select id="selectRecordCountGroupbyType"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
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
</select>
<select id="selectCustomerCycleGroupbyDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerCountVO">
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 FROM crm_customer AS a
LEFT JOIN crm_contract AS b ON b.customer_id = a.id LEFT JOIN crm_contract AS b ON b.customer_id = a.id
WHERE WHERE a.deleted = 0 AND b.deleted = 0
a.deleted = 0 AND b.deleted = 0
AND b.audit_status = 20 AND b.audit_status = 20
AND a.owner_user_id IN AND a.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=","> <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
@ -110,7 +33,209 @@
</foreach> </foreach>
AND b.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND AND b.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
#{times[1],javaType=java.time.LocalDateTime} #{times[1],javaType=java.time.LocalDateTime}
GROUP BY category 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="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( 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="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
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="selectContractSummary"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerContractSummaryRespVO">
SELECT
a.`name` AS customer_name,
b.`name` AS contract_name,
b.total_price,
IFNULL( c.price, 0 ) AS receivable_price,
a.industry_id,
a.source,
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="selectCustomerDealCycleGroupbyDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByDateRespVO">
SELECT
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="selectCustomerDealCycleGroupbyUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByUserRespVO">
SELECT
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> </select>
</mapper> </mapper>