Commit 4ac28603b8df276a6f402dc170e8a3330888e0cd

Authored by dhb52
1 parent ee2be799

feat: [CRM-客户分析]增加[时间间隔]选项

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.common.enums;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * 时间间隔类型枚举
  11 + *
  12 + * @author dhb52
  13 + */
  14 +@Getter
  15 +@AllArgsConstructor
  16 +public enum DateIntervalEnum implements IntArrayValuable {
  17 +
  18 + TODAY(1, "今天"),
  19 + YESTERDAY(2, "昨天"),
  20 + THIS_WEEK(3, "本周"),
  21 + LAST_WEEK(4, "上周"),
  22 + THIS_MONTH(5, "本月"),
  23 + LAST_MONTH(6, "上月"),
  24 + THIS_QUARTER(7, "本季度"),
  25 + LAST_QUARTER(8, "上季度"),
  26 + THIS_YEAR(9, "本年"),
  27 + LAST_YEAR(10, "去年"),
  28 + CUSTOMER(11, "自定义"),
  29 + ;
  30 +
  31 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getType).toArray();
  32 +
  33 + /**
  34 + * 类型
  35 + */
  36 + private final Integer type;
  37 +
  38 + /**
  39 + * 名称
  40 + */
  41 + private final String name;
  42 +
  43 + @Override
  44 + public int[] array() {
  45 + return ARRAYS;
  46 + }
  47 +
  48 +}
0 \ No newline at end of file 49 \ No newline at end of file
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -103,4 +103,7 @@ public interface ErrorCodeConstants { @@ -103,4 +103,7 @@ public interface ErrorCodeConstants {
103 ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在"); 103 ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
104 ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限"); 104 ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限");
105 105
  106 + // ========== 数据统计 1_020_014_000 ==========
  107 + ErrorCode STATISTICS_CUSTOMER_TIMES_NOT_SET = new ErrorCode(1_020_014_000, "自定义时间间隔,必须输入时间区间");
  108 +
106 } 109 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerReqVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer; 1 package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
3 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
4 -import jakarta.validation.constraints.NotEmpty;  
5 import jakarta.validation.constraints.NotNull; 6 import jakarta.validation.constraints.NotNull;
6 import lombok.Data; 7 import lombok.Data;
7 import org.springframework.format.annotation.DateTimeFormat; 8 import org.springframework.format.annotation.DateTimeFormat;
@@ -27,22 +28,26 @@ public class CrmStatisticsCustomerReqVO { @@ -27,22 +28,26 @@ public class CrmStatisticsCustomerReqVO {
27 28
28 /** 29 /**
29 * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来 30 * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
30 - * <p>  
31 * 后续,可能会支持选择部分用户进行查询 31 * 后续,可能会支持选择部分用户进行查询
32 */ 32 */
33 @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2") 33 @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
34 private List<Long> userIds; 34 private List<Long> userIds;
35 35
36 - @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED) 36 + @Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + @InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
  38 + private Integer intervalType;
  39 +
  40 + /**
  41 + * 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
  42 + * 并作为参数传递给Mapper
  43 + */
  44 + @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
37 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 45 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
38 - @NotEmpty(message = "时间范围不能为空")  
39 private LocalDateTime[] times; 46 private LocalDateTime[] times;
40 47
41 - // TODO @dhb52:这个时间间隔,建议前端传递;例如说:字段叫 interval,枚举有天、周、月、季度、年。因为一般分析类的系统,都是交给用户选择筛选时间间隔,而我们这里是默认根据日期选项,默认对应的 interval 而已  
42 - // 然后实现上,可以在 common 包的 enums 加个 DateIntervalEnum,里面一个是 interval 字段,枚举过去,然后有个 pattern 字段,用于格式化时间格式;  
43 - // 这样的话,可以通过 interval 获取到 pattern,然后前端就可以根据 pattern 格式化时间,计算还是交给数据库  
44 /** 48 /**
45 * group by DATE_FORMAT(field, #{dateFormat}) 49 * group by DATE_FORMAT(field, #{dateFormat})
  50 + * 非前端传递, 由Service计算后传递给Mapper的参数
46 */ 51 */
47 @Schema(description = "Group By 日期格式", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "%Y%m") 52 @Schema(description = "Group By 日期格式", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "%Y%m")
48 private String sqlDateFormat; 53 private String sqlDateFormat;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java
1 package cn.iocoder.yudao.module.crm.service.statistics; 1 package cn.iocoder.yudao.module.crm.service.statistics;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.date.DateTime;
  5 +import cn.hutool.core.date.DateUtil;
4 import cn.hutool.core.date.LocalDateTimeUtil; 6 import cn.hutool.core.date.LocalDateTimeUtil;
5 import cn.hutool.core.util.ObjUtil; 7 import cn.hutool.core.util.ObjUtil;
  8 +import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 9 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 10 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*; 11 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
@@ -24,8 +27,10 @@ import java.util.List; @@ -24,8 +27,10 @@ import java.util.List;
24 import java.util.Map; 27 import java.util.Map;
25 import java.util.stream.Stream; 28 import java.util.stream.Stream;
26 29
  30 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
27 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 31 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
28 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*; 32 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
  33 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.STATISTICS_CUSTOMER_TIMES_NOT_SET;
29 34
30 /** 35 /**
31 * CRM 客户分析 Service 实现类 36 * CRM 客户分析 Service 实现类
@@ -63,7 +68,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -63,7 +68,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
63 reqVO.setUserIds(userIds); 68 reqVO.setUserIds(userIds);
64 69
65 // 2. 获取分项统计数据 70 // 2. 获取分项统计数据
66 - reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1])); 71 + initParams(reqVO);
67 List<CrmStatisticsCustomerSummaryByDateRespVO> customerCreateCountVoList = customerMapper.selectCustomerCreateCountGroupByDate(reqVO); 72 List<CrmStatisticsCustomerSummaryByDateRespVO> customerCreateCountVoList = customerMapper.selectCustomerCreateCountGroupByDate(reqVO);
68 List<CrmStatisticsCustomerSummaryByDateRespVO> customerDealCountVoList = customerMapper.selectCustomerDealCountGroupByDate(reqVO); 73 List<CrmStatisticsCustomerSummaryByDateRespVO> customerDealCountVoList = customerMapper.selectCustomerDealCountGroupByDate(reqVO);
69 74
@@ -94,6 +99,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -94,6 +99,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
94 reqVO.setUserIds(userIds); 99 reqVO.setUserIds(userIds);
95 100
96 // 2. 获取分项统计数据 101 // 2. 获取分项统计数据
  102 + initParams(reqVO);
97 List<CrmStatisticsCustomerSummaryByUserRespVO> customerCreateCount = customerMapper.selectCustomerCreateCountGroupByUser(reqVO); 103 List<CrmStatisticsCustomerSummaryByUserRespVO> customerCreateCount = customerMapper.selectCustomerCreateCountGroupByUser(reqVO);
98 List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO); 104 List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
99 List<CrmStatisticsCustomerSummaryByUserRespVO> contractPrice = customerMapper.selectContractPriceGroupByUser(reqVO); 105 List<CrmStatisticsCustomerSummaryByUserRespVO> contractPrice = customerMapper.selectContractPriceGroupByUser(reqVO);
@@ -139,7 +145,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -139,7 +145,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
139 reqVO.setUserIds(userIds); 145 reqVO.setUserIds(userIds);
140 146
141 // 2. 获取分项统计数据 147 // 2. 获取分项统计数据
142 - reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1])); 148 + initParams(reqVO);
143 List<CrmStatisticsFollowupSummaryByDateRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupByDate(reqVO); 149 List<CrmStatisticsFollowupSummaryByDateRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupByDate(reqVO);
144 List<CrmStatisticsFollowupSummaryByDateRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByDate(reqVO); 150 List<CrmStatisticsFollowupSummaryByDateRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByDate(reqVO);
145 151
@@ -170,6 +176,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -170,6 +176,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
170 reqVO.setUserIds(userIds); 176 reqVO.setUserIds(userIds);
171 177
172 // 2. 获取分项统计数据 178 // 2. 获取分项统计数据
  179 + initParams(reqVO);
173 List<CrmStatisticsFollowupSummaryByUserRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupByUser(reqVO); 180 List<CrmStatisticsFollowupSummaryByUserRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupByUser(reqVO);
174 List<CrmStatisticsFollowupSummaryByUserRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByUser(reqVO); 181 List<CrmStatisticsFollowupSummaryByUserRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByUser(reqVO);
175 182
@@ -204,6 +211,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -204,6 +211,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
204 reqVO.setUserIds(userIds); 211 reqVO.setUserIds(userIds);
205 212
206 // 2. 获得排行数据 213 // 2. 获得排行数据
  214 + initParams(reqVO);
207 List<CrmStatisticsFollowupSummaryByTypeRespVO> respVoList = customerMapper.selectFollowupRecordCountGroupByType(reqVO); 215 List<CrmStatisticsFollowupSummaryByTypeRespVO> respVoList = customerMapper.selectFollowupRecordCountGroupByType(reqVO);
208 216
209 // 3. 获取字典数据 217 // 3. 获取字典数据
@@ -227,6 +235,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -227,6 +235,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
227 reqVO.setUserIds(userIds); 235 reqVO.setUserIds(userIds);
228 236
229 // 2. 获取统计数据 237 // 2. 获取统计数据
  238 + initParams(reqVO);
230 List<CrmStatisticsCustomerContractSummaryRespVO> respVoList = customerMapper.selectContractSummary(reqVO); 239 List<CrmStatisticsCustomerContractSummaryRespVO> respVoList = customerMapper.selectContractSummary(reqVO);
231 240
232 // 3. 设置 创建人、负责人、行业、来源 241 // 3. 设置 创建人、负责人、行业、来源
@@ -261,7 +270,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -261,7 +270,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
261 reqVO.setUserIds(userIds); 270 reqVO.setUserIds(userIds);
262 271
263 // 2. 获取分项统计数据 272 // 2. 获取分项统计数据
264 - reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1])); 273 + initParams(reqVO);
265 List<CrmStatisticsCustomerDealCycleByDateRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupByDate(reqVO); 274 List<CrmStatisticsCustomerDealCycleByDateRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupByDate(reqVO);
266 275
267 // 3. 合并统计数据 276 // 3. 合并统计数据
@@ -287,6 +296,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -287,6 +296,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
287 reqVO.setUserIds(userIds); 296 reqVO.setUserIds(userIds);
288 297
289 // 2. 获取分项统计数据 298 // 2. 获取分项统计数据
  299 + initParams(reqVO);
290 List<CrmStatisticsCustomerDealCycleByUserRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupByUser(reqVO); 300 List<CrmStatisticsCustomerDealCycleByUserRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupByUser(reqVO);
291 List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO); 301 List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
292 302
@@ -363,7 +373,6 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -363,7 +373,6 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
363 * @param endTime 结束时间 373 * @param endTime 结束时间
364 * @return 时间序列 374 * @return 时间序列
365 */ 375 */
366 - // TODO @dhb52:可以抽象到 DateUtils 里,开始时间、结束时间,事件间隔,然后返回这个哈;  
367 private List<String> generateTimeSeries(LocalDateTime startTime, LocalDateTime endTime) { 376 private List<String> generateTimeSeries(LocalDateTime startTime, LocalDateTime endTime) {
368 boolean byMonth = queryByMonth(startTime, endTime); 377 boolean byMonth = queryByMonth(startTime, endTime);
369 List<String> times = CollUtil.newArrayList(); 378 List<String> times = CollUtil.newArrayList();
@@ -389,4 +398,62 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe @@ -389,4 +398,62 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
389 return queryByMonth(startTime, endTime) ? SQL_DATE_FORMAT_BY_MONTH : SQL_DATE_FORMAT_BY_DAY; 398 return queryByMonth(startTime, endTime) ? SQL_DATE_FORMAT_BY_MONTH : SQL_DATE_FORMAT_BY_DAY;
390 } 399 }
391 400
  401 + private void initParams(CrmStatisticsCustomerReqVO reqVO) {
  402 + final Integer intervalType = reqVO.getIntervalType();
  403 +
  404 + // 1. 自定义时间间隔,必须输入起始日期-结束日期
  405 + if (DateIntervalEnum.CUSTOMER.getType().equals(intervalType)) {
  406 + if (ObjUtil.isEmpty(reqVO.getTimes()) || reqVO.getTimes().length != 2) {
  407 + throw exception(STATISTICS_CUSTOMER_TIMES_NOT_SET);
  408 + }
  409 + // 设置 mapper sqlDateFormat 参数
  410 + reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1]));
  411 + // 自定义日期无需计算日期参数
  412 + return;
  413 + }
  414 +
  415 + // 2. 根据时间区间类型计算时间段区间日期
  416 + DateTime beginDate = null;
  417 + DateTime endDate = null;
  418 + if (DateIntervalEnum.TODAY.getType().equals(intervalType)) {
  419 + beginDate = DateUtil.beginOfDay(DateUtil.date());
  420 + endDate = DateUtil.endOfDay(DateUtil.date());
  421 + } else if (DateIntervalEnum.YESTERDAY.getType().equals(intervalType)) {
  422 + beginDate = DateUtil.offsetDay(DateUtil.date(), -1);
  423 + endDate = DateUtil.offsetDay(DateUtil.date(), -1);
  424 + } else if (DateIntervalEnum.THIS_WEEK.getType().equals(intervalType)) {
  425 + beginDate = DateUtil.beginOfWeek(DateUtil.date());
  426 + endDate = DateUtil.endOfWeek(DateUtil.date());
  427 + } else if (DateIntervalEnum.LAST_WEEK.getType().equals(intervalType)) {
  428 + beginDate = DateUtil.beginOfWeek(DateUtil.offsetWeek(DateUtil.date(), -1));
  429 + endDate = DateUtil.endOfWeek(DateUtil.offsetWeek(DateUtil.date(), -1));
  430 + } else if (DateIntervalEnum.THIS_MONTH.getType().equals(intervalType)) {
  431 + beginDate = DateUtil.beginOfMonth(DateUtil.date());
  432 + endDate = DateUtil.endOfMonth(DateUtil.date());
  433 + } else if (DateIntervalEnum.LAST_MONTH.getType().equals(intervalType)) {
  434 + beginDate = DateUtil.beginOfMonth(DateUtil.offsetMonth(DateUtil.date(), -1));
  435 + endDate = DateUtil.endOfMonth(DateUtil.offsetMonth(DateUtil.date(), -1));
  436 + } else if (DateIntervalEnum.THIS_QUARTER.getType().equals(intervalType)) {
  437 + beginDate = DateUtil.beginOfQuarter(DateUtil.date());
  438 + endDate = DateUtil.endOfQuarter(DateUtil.date());
  439 + } else if (DateIntervalEnum.LAST_QUARTER.getType().equals(intervalType)) {
  440 + beginDate = DateUtil.beginOfQuarter(DateUtil.offsetMonth(DateUtil.date(), -3));
  441 + endDate = DateUtil.endOfQuarter(DateUtil.offsetMonth(DateUtil.date(), -3));
  442 + } else if (DateIntervalEnum.THIS_YEAR.getType().equals(intervalType)) {
  443 + beginDate = DateUtil.beginOfYear(DateUtil.date());
  444 + endDate = DateUtil.endOfYear(DateUtil.date());
  445 + } else if (DateIntervalEnum.LAST_YEAR.getType().equals(intervalType)) {
  446 + beginDate = DateUtil.beginOfYear(DateUtil.offsetMonth(DateUtil.date(), -12));
  447 + endDate = DateUtil.endOfYear(DateUtil.offsetMonth(DateUtil.date(), -12));
  448 + }
  449 +
  450 + // 3. 计算开始、结束日期时间,并设置reqVo
  451 + LocalDateTime[] times = new LocalDateTime[2];
  452 + times[0] = LocalDateTimeUtil.beginOfDay(LocalDateTimeUtil.of(beginDate));
  453 + times[1] = LocalDateTimeUtil.endOfDay(LocalDateTimeUtil.of(endDate));
  454 + // 3.1 设置 mapper 时间区间 参数
  455 + reqVO.setTimes(times);
  456 + // 3.2 设置 mapper sqlDateFormat 参数
  457 + reqVO.setSqlDateFormat(getSqlDateFormat(times[0], times[1]));
  458 + }
392 } 459 }