Commit 0d1e2c604de3f6eda7e9db50a5fad95fa452a29f
Committed by
Gitee
!944 feat: 客户成交周期分析(按区域、按产品)
Merge pull request !944 from dhb52/develop
Showing
8 changed files
with
195 additions
and
8 deletions
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×[0]=2023-01-01 00:00:00×[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×[0]=2023-01-01 00:00:00×[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×[0]=2023-01-01 00:00:00×[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> | ... | ... |