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 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 103 ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
104 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 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 5 import io.swagger.v3.oas.annotations.media.Schema;
4   -import jakarta.validation.constraints.NotEmpty;
5 6 import jakarta.validation.constraints.NotNull;
6 7 import lombok.Data;
7 8 import org.springframework.format.annotation.DateTimeFormat;
... ... @@ -27,22 +28,26 @@ public class CrmStatisticsCustomerReqVO {
27 28  
28 29 /**
29 30 * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
30   - * <p>
31 31 * 后续,可能会支持选择部分用户进行查询
32 32 */
33 33 @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
34 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 45 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
38   - @NotEmpty(message = "时间范围不能为空")
39 46 private LocalDateTime[] times;
40 47  
41   - // TODO @dhb52:这个时间间隔,建议前端传递;例如说:字段叫 interval,枚举有天、周、月、季度、年。因为一般分析类的系统,都是交给用户选择筛选时间间隔,而我们这里是默认根据日期选项,默认对应的 interval 而已
42   - // 然后实现上,可以在 common 包的 enums 加个 DateIntervalEnum,里面一个是 interval 字段,枚举过去,然后有个 pattern 字段,用于格式化时间格式;
43   - // 这样的话,可以通过 interval 获取到 pattern,然后前端就可以根据 pattern 格式化时间,计算还是交给数据库
44 48 /**
45 49 * group by DATE_FORMAT(field, #{dateFormat})
  50 + * 非前端传递, 由Service计算后传递给Mapper的参数
46 51 */
47 52 @Schema(description = "Group By 日期格式", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "%Y%m")
48 53 private String sqlDateFormat;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java
1 1 package cn.iocoder.yudao.module.crm.service.statistics;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.date.DateTime;
  5 +import cn.hutool.core.date.DateUtil;
4 6 import cn.hutool.core.date.LocalDateTimeUtil;
5 7 import cn.hutool.core.util.ObjUtil;
  8 +import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
6 9 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 10 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 11 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
... ... @@ -24,8 +27,10 @@ import java.util.List;
24 27 import java.util.Map;
25 28 import java.util.stream.Stream;
26 29  
  30 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
27 31 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
28 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 36 * CRM 客户分析 Service 实现类
... ... @@ -63,7 +68,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
63 68 reqVO.setUserIds(userIds);
64 69  
65 70 // 2. 获取分项统计数据
66   - reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1]));
  71 + initParams(reqVO);
67 72 List<CrmStatisticsCustomerSummaryByDateRespVO> customerCreateCountVoList = customerMapper.selectCustomerCreateCountGroupByDate(reqVO);
68 73 List<CrmStatisticsCustomerSummaryByDateRespVO> customerDealCountVoList = customerMapper.selectCustomerDealCountGroupByDate(reqVO);
69 74  
... ... @@ -94,6 +99,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
94 99 reqVO.setUserIds(userIds);
95 100  
96 101 // 2. 获取分项统计数据
  102 + initParams(reqVO);
97 103 List<CrmStatisticsCustomerSummaryByUserRespVO> customerCreateCount = customerMapper.selectCustomerCreateCountGroupByUser(reqVO);
98 104 List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
99 105 List<CrmStatisticsCustomerSummaryByUserRespVO> contractPrice = customerMapper.selectContractPriceGroupByUser(reqVO);
... ... @@ -139,7 +145,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
139 145 reqVO.setUserIds(userIds);
140 146  
141 147 // 2. 获取分项统计数据
142   - reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1]));
  148 + initParams(reqVO);
143 149 List<CrmStatisticsFollowupSummaryByDateRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupByDate(reqVO);
144 150 List<CrmStatisticsFollowupSummaryByDateRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByDate(reqVO);
145 151  
... ... @@ -170,6 +176,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
170 176 reqVO.setUserIds(userIds);
171 177  
172 178 // 2. 获取分项统计数据
  179 + initParams(reqVO);
173 180 List<CrmStatisticsFollowupSummaryByUserRespVO> followupRecordCount = customerMapper.selectFollowupRecordCountGroupByUser(reqVO);
174 181 List<CrmStatisticsFollowupSummaryByUserRespVO> followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByUser(reqVO);
175 182  
... ... @@ -204,6 +211,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
204 211 reqVO.setUserIds(userIds);
205 212  
206 213 // 2. 获得排行数据
  214 + initParams(reqVO);
207 215 List<CrmStatisticsFollowupSummaryByTypeRespVO> respVoList = customerMapper.selectFollowupRecordCountGroupByType(reqVO);
208 216  
209 217 // 3. 获取字典数据
... ... @@ -227,6 +235,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
227 235 reqVO.setUserIds(userIds);
228 236  
229 237 // 2. 获取统计数据
  238 + initParams(reqVO);
230 239 List<CrmStatisticsCustomerContractSummaryRespVO> respVoList = customerMapper.selectContractSummary(reqVO);
231 240  
232 241 // 3. 设置 创建人、负责人、行业、来源
... ... @@ -261,7 +270,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
261 270 reqVO.setUserIds(userIds);
262 271  
263 272 // 2. 获取分项统计数据
264   - reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1]));
  273 + initParams(reqVO);
265 274 List<CrmStatisticsCustomerDealCycleByDateRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupByDate(reqVO);
266 275  
267 276 // 3. 合并统计数据
... ... @@ -287,6 +296,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
287 296 reqVO.setUserIds(userIds);
288 297  
289 298 // 2. 获取分项统计数据
  299 + initParams(reqVO);
290 300 List<CrmStatisticsCustomerDealCycleByUserRespVO> customerDealCycle = customerMapper.selectCustomerDealCycleGroupByUser(reqVO);
291 301 List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
292 302  
... ... @@ -363,7 +373,6 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
363 373 * @param endTime 结束时间
364 374 * @return 时间序列
365 375 */
366   - // TODO @dhb52:可以抽象到 DateUtils 里,开始时间、结束时间,事件间隔,然后返回这个哈;
367 376 private List<String> generateTimeSeries(LocalDateTime startTime, LocalDateTime endTime) {
368 377 boolean byMonth = queryByMonth(startTime, endTime);
369 378 List<String> times = CollUtil.newArrayList();
... ... @@ -389,4 +398,62 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
389 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 }
... ...