Commit 67b02c7e5b6a64942829bb525f1f1c9c0d4d11b6

Authored by dhb52
1 parent 2038e520

feat: 客户成交周期分析(按区域、按产品)

yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.http
... ... @@ -53,3 +53,13 @@ tenant-id: {{adminTenentId}}
53 53 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
54 54 Authorization: Bearer {{token}}
55 55 tenant-id: {{adminTenentId}}
  56 +
  57 +### 6.3 获取客户成交周期(按区域)
  58 +GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-area?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
  59 +Authorization: Bearer {{token}}
  60 +tenant-id: {{adminTenentId}}
  61 +
  62 +### 6.4 获取客户成交周期(按产品)
  63 +GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-product?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
  64 +Authorization: Bearer {{token}}
  65 +tenant-id: {{adminTenentId}}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.java
... ... @@ -96,6 +96,18 @@ public class CrmStatisticsCustomerController {
96 96 return success(customerService.getCustomerDealCycleByUser(reqVO));
97 97 }
98 98  
99   - // TODO dhb52:【成交周期分析】里,有按照员工(已实现)、地区(未实现)、产品(未实现),需要在看看哈;可以把 CustomerDealCycle 拆成 3 个 tab,员工客户成交周期分析、地区客户成交周期分析、产品客户成交周期分析;
  99 + @GetMapping("/get-customer-deal-cycle-by-area")
  100 + @Operation(summary = "获取客户成交周期(按用户)")
  101 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  102 + public CommonResult<List<CrmStatisticsCustomerDealCycleByAreaRespVO>> getCustomerDealCycleByArea(@Valid CrmStatisticsCustomerReqVO reqVO) {
  103 + return success(customerService.getCustomerDealCycleByArea(reqVO));
  104 + }
  105 +
  106 + @GetMapping("/get-customer-deal-cycle-by-product")
  107 + @Operation(summary = "获取客户成交周期(按用户)")
  108 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  109 + public CommonResult<List<CrmStatisticsCustomerDealCycleByProductRespVO>> getCustomerDealCycleByProduct(@Valid CrmStatisticsCustomerReqVO reqVO) {
  110 + return success(customerService.getCustomerDealCycleByProduct(reqVO));
  111 + }
100 112  
101 113 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByAreaRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonIgnore;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - CRM 客户成交周期分析(按区域) VO")
  8 +@Data
  9 +public class CrmStatisticsCustomerDealCycleByAreaRespVO {
  10 +
  11 + @Schema(description = "省份编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  12 + @JsonIgnore
  13 + private Integer areaId;
  14 +
  15 + @Schema(description = "省份名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "浙江省")
  16 + private String areaName;
  17 +
  18 + @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
  19 + private Double customerDealCycle;
  20 +
  21 + @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  22 + private Integer customerDealCount;
  23 +
  24 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByProductRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 客户成交周期分析(按产品) VO")
  7 +@Data
  8 +public class CrmStatisticsCustomerDealCycleByProductRespVO {
  9 +
  10 + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "演示产品")
  11 + private String productName;
  12 +
  13 + @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
  14 + private Double customerDealCycle;
  15 +
  16 + @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Integer customerDealCount;
  18 +
  19 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java
... ... @@ -53,6 +53,7 @@ public interface CrmStatisticsCustomerMapper {
53 53  
54 54 /**
55 55 * 合同总金额(按用户)
  56 + *
56 57 * @return 统计数据@return 统计数据@param reqVO 请求参数
57 58 * @return 统计数据
58 59 */
... ... @@ -191,4 +192,20 @@ public interface CrmStatisticsCustomerMapper {
191 192 */
192 193 List<CrmStatisticsCustomerDealCycleByUserRespVO> selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO);
193 194  
  195 + /**
  196 + * 客户成交周期(按区域)
  197 + *
  198 + * @param reqVO 请求参数
  199 + * @return 统计数据
  200 + */
  201 + List<CrmStatisticsCustomerDealCycleByAreaRespVO> selectCustomerDealCycleGroupByAreaId(CrmStatisticsCustomerReqVO reqVO);
  202 +
  203 + /**
  204 + * 客户成交周期(按产品)
  205 + *
  206 + * @param reqVO 请求参数
  207 + * @return 统计数据
  208 + */
  209 + List<CrmStatisticsCustomerDealCycleByProductRespVO> selectCustomerDealCycleGroupByProductId(CrmStatisticsCustomerReqVO reqVO);
  210 +
194 211 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java
... ... @@ -77,7 +77,7 @@ public interface CrmStatisticsCustomerService {
77 77  
78 78 /**
79 79 * 客户成交周期(按日期)
80   - *
  80 + * <p>
81 81 * 成交周期的定义:客户 customer 在创建出来,到合同 contract 第一次成交的时间差
82 82 *
83 83 * @param reqVO 请求参数
... ... @@ -93,4 +93,20 @@ public interface CrmStatisticsCustomerService {
93 93 */
94 94 List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO);
95 95  
  96 + /**
  97 + * 客户成交周期(按区域)
  98 + *
  99 + * @param reqVO 请求参数
  100 + * @return 统计数据
  101 + */
  102 + List<CrmStatisticsCustomerDealCycleByAreaRespVO> getCustomerDealCycleByArea(CrmStatisticsCustomerReqVO reqVO);
  103 +
  104 + /**
  105 + * 客户成交周期(按产品)
  106 + *
  107 + * @param reqVO 请求参数
  108 + * @return 统计数据
  109 + */
  110 + List<CrmStatisticsCustomerDealCycleByProductRespVO> getCustomerDealCycleByProduct(CrmStatisticsCustomerReqVO reqVO);
  111 +
96 112 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java
... ... @@ -4,6 +4,9 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
5 5 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
6 6 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  7 +import cn.iocoder.yudao.framework.ip.core.Area;
  8 +import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
  9 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
7 10 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
8 11 import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper;
9 12 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
... ... @@ -19,6 +22,7 @@ import java.time.LocalDateTime;
19 22 import java.util.Collections;
20 23 import java.util.List;
21 24 import java.util.Map;
  25 +import java.util.function.Function;
22 26 import java.util.stream.Stream;
23 27  
24 28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
... ... @@ -290,6 +294,51 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
290 294 return summaryList;
291 295 }
292 296  
  297 + @Override
  298 + public List<CrmStatisticsCustomerDealCycleByAreaRespVO> getCustomerDealCycleByArea(CrmStatisticsCustomerReqVO reqVO) {
  299 + // 1. 获得用户编号数组
  300 + List<Long> userIds = getUserIds(reqVO);
  301 + if (CollUtil.isEmpty(userIds)) {
  302 + return Collections.emptyList();
  303 + }
  304 + reqVO.setUserIds(userIds);
  305 +
  306 + // 2. 获取客户地区统计数据
  307 + List<CrmStatisticsCustomerDealCycleByAreaRespVO> dealCycleByAreaList = customerMapper.selectCustomerDealCycleGroupByAreaId(reqVO);
  308 + if (CollUtil.isEmpty(dealCycleByAreaList)) {
  309 + return Collections.emptyList();
  310 + }
  311 +
  312 + // 3. 拼接数据
  313 + Map<Integer, Area> areaMap = convertMap(AreaUtils.getByType(AreaTypeEnum.PROVINCE, Function.identity()),
  314 + Area::getId);
  315 + return convertList(dealCycleByAreaList, vo -> {
  316 + if (vo.getAreaId() != null) {
  317 + Integer parentId = AreaUtils.getParentIdByType(vo.getAreaId(), AreaTypeEnum.PROVINCE);
  318 + findAndThen(areaMap, parentId, area -> vo.setAreaId(parentId).setAreaName(area.getName()));
  319 + }
  320 + return vo;
  321 + });
  322 + }
  323 +
  324 + @Override
  325 + public List<CrmStatisticsCustomerDealCycleByProductRespVO> getCustomerDealCycleByProduct(CrmStatisticsCustomerReqVO reqVO) {
  326 + // 1. 获得用户编号数组
  327 + List<Long> userIds = getUserIds(reqVO);
  328 + if (CollUtil.isEmpty(userIds)) {
  329 + return Collections.emptyList();
  330 + }
  331 + reqVO.setUserIds(userIds);
  332 +
  333 + // 2. 获取客户产品统计数据
  334 + List<CrmStatisticsCustomerDealCycleByProductRespVO> dealCycleByProductList = customerMapper.selectCustomerDealCycleGroupByProductId(reqVO);
  335 + if (CollUtil.isEmpty(dealCycleByProductList)) {
  336 + return Collections.emptyList();
  337 + }
  338 +
  339 + return dealCycleByProductList;
  340 + }
  341 +
293 342 /**
294 343 * 拼接用户信息(昵称)
295 344 *
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml
... ... @@ -19,17 +19,18 @@
19 19 <!-- TODO 芋艿:应该不用过滤时间 -->
20 20 <select id="selectCustomerDealCountGroupByDate"
21 21 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
22   - SELECT DATE_FORMAT(customer.create_time, '%Y-%m-%d') AS time,
23   - COUNT(DISTINCT customer.id) AS customer_deal_count
  22 + SELECT DATE_FORMAT(customer.create_time, '%Y-%m-%d') AS time,
  23 + COUNT(DISTINCT customer.id) AS customer_deal_count
24 24 FROM crm_customer AS customer
25 25 LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
26   - WHERE customer.deleted = 0 AND contract.deleted = 0
  26 + WHERE customer.deleted = 0
  27 + AND contract.deleted = 0
27 28 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
28 29 AND customer.owner_user_id IN
29 30 <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
30 31 #{userId}
31 32 </foreach>
32   - AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  33 + AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
33 34 GROUP BY time
34 35 </select>
35 36  
... ... @@ -53,13 +54,14 @@
53 54 COUNT(DISTINCT customer.id) AS customer_deal_count
54 55 FROM crm_customer AS customer
55 56 LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
56   - WHERE customer.deleted = 0 AND contract.deleted = 0
  57 + WHERE customer.deleted = 0
  58 + AND contract.deleted = 0
57 59 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
58 60 AND customer.owner_user_id IN
59 61 <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
60 62 #{userId}
61 63 </foreach>
62   - AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  64 + AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
63 65 GROUP BY customer.owner_user_id
64 66 </select>
65 67  
... ... @@ -221,4 +223,42 @@
221 223 GROUP BY customer.owner_user_id
222 224 </select>
223 225  
  226 + <select id="selectCustomerDealCycleGroupByAreaId"
  227 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByAreaRespVO">
  228 + SELECT customer.area_id AS area_id,
  229 + IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle,
  230 + COUNT(DISTINCT customer.id) AS customer_deal_count
  231 + FROM crm_customer AS customer
  232 + LEFT JOIN crm_contract AS contract ON customer.id = contract.customer_id
  233 + WHERE customer.deleted = 0
  234 + AND contract.deleted = 0
  235 + AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  236 + AND customer.owner_user_id IN
  237 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  238 + #{userId}
  239 + </foreach>
  240 + AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  241 + GROUP BY
  242 + customer.area_id
  243 + </select>
  244 +
  245 + <select id="selectCustomerDealCycleGroupByProductId"
  246 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByProductRespVO">
  247 + SELECT (SELECT name FROM crm_product WHERE id = product.id) AS product_name,
  248 + IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle,
  249 + COUNT(DISTINCT customer.id) AS customer_deal_count
  250 + FROM crm_customer AS customer
  251 + LEFT JOIN crm_contract AS contract ON customer.id = contract.customer_id
  252 + LEFT JOIN crm_contract_product AS product ON product.contract_id = contract.id
  253 + WHERE customer.deleted = 0
  254 + AND contract.deleted = 0
  255 + AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  256 + AND customer.owner_user_id IN
  257 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  258 + #{userId}
  259 + </foreach>
  260 + AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  261 + GROUP BY product.id
  262 + </select>
  263 +
224 264 </mapper>
... ...