From 4ac28603b8df276a6f402dc170e8a3330888e0cd Mon Sep 17 00:00:00 2001 From: dhb52 Date: Sun, 10 Mar 2024 10:30:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20[CRM-=E5=AE=A2=E6=88=B7=E5=88=86?= =?UTF-8?q?=E6=9E=90]=E5=A2=9E=E5=8A=A0[=E6=97=B6=E9=97=B4=E9=97=B4?= =?UTF-8?q?=E9=9A=94]=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/enums/DateIntervalEnum.java | 48 ++++++++++++ .../module/crm/enums/ErrorCodeConstants.java | 3 + .../customer/CrmStatisticsCustomerReqVO.java | 19 +++-- .../CrmStatisticsCustomerServiceImpl.java | 75 ++++++++++++++++++- 4 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java new file mode 100644 index 000000000..9a614ddc9 --- /dev/null +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.framework.common.enums; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 时间间隔类型枚举 + * + * @author dhb52 + */ +@Getter +@AllArgsConstructor +public enum DateIntervalEnum implements IntArrayValuable { + + TODAY(1, "今天"), + YESTERDAY(2, "昨天"), + THIS_WEEK(3, "本周"), + LAST_WEEK(4, "上周"), + THIS_MONTH(5, "本月"), + LAST_MONTH(6, "上月"), + THIS_QUARTER(7, "本季度"), + LAST_QUARTER(8, "上季度"), + THIS_YEAR(9, "本年"), + LAST_YEAR(10, "去年"), + CUSTOMER(11, "自定义"), + ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + + /** + * 名称 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java index 4a0976bb1..d49f6068c 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java +++ b/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 { ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在"); ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限"); + // ========== 数据统计 1_020_014_000 ========== + ErrorCode STATISTICS_CUSTOMER_TIMES_NOT_SET = new ErrorCode(1_020_014_000, "自定义时间间隔,必须输入时间区间"); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerReqVO.java index 13b3adda7..38f15cf03 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerReqVO.java @@ -1,7 +1,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer; +import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; @@ -27,22 +28,26 @@ public class CrmStatisticsCustomerReqVO { /** * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来 - *

* 后续,可能会支持选择部分用户进行查询 */ @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2") private List userIds; - @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}") + private Integer intervalType; + + /** + * 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间 + * 并作为参数传递给Mapper + */ + @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @NotEmpty(message = "时间范围不能为空") private LocalDateTime[] times; - // TODO @dhb52:这个时间间隔,建议前端传递;例如说:字段叫 interval,枚举有天、周、月、季度、年。因为一般分析类的系统,都是交给用户选择筛选时间间隔,而我们这里是默认根据日期选项,默认对应的 interval 而已 - // 然后实现上,可以在 common 包的 enums 加个 DateIntervalEnum,里面一个是 interval 字段,枚举过去,然后有个 pattern 字段,用于格式化时间格式; - // 这样的话,可以通过 interval 获取到 pattern,然后前端就可以根据 pattern 格式化时间,计算还是交给数据库 /** * group by DATE_FORMAT(field, #{dateFormat}) + * 非前端传递, 由Service计算后传递给Mapper的参数 */ @Schema(description = "Group By 日期格式", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "%Y%m") private String sqlDateFormat; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java index a4c3f4ddf..f951e25f5 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java @@ -1,8 +1,11 @@ package cn.iocoder.yudao.module.crm.service.statistics; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*; @@ -24,8 +27,10 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.STATISTICS_CUSTOMER_TIMES_NOT_SET; /** * CRM 客户分析 Service 实现类 @@ -63,7 +68,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe reqVO.setUserIds(userIds); // 2. 获取分项统计数据 - reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1])); + initParams(reqVO); List customerCreateCountVoList = customerMapper.selectCustomerCreateCountGroupByDate(reqVO); List customerDealCountVoList = customerMapper.selectCustomerDealCountGroupByDate(reqVO); @@ -94,6 +99,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe reqVO.setUserIds(userIds); // 2. 获取分项统计数据 + initParams(reqVO); List customerCreateCount = customerMapper.selectCustomerCreateCountGroupByUser(reqVO); List customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO); List contractPrice = customerMapper.selectContractPriceGroupByUser(reqVO); @@ -139,7 +145,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe reqVO.setUserIds(userIds); // 2. 获取分项统计数据 - reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1])); + initParams(reqVO); List followupRecordCount = customerMapper.selectFollowupRecordCountGroupByDate(reqVO); List followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByDate(reqVO); @@ -170,6 +176,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe reqVO.setUserIds(userIds); // 2. 获取分项统计数据 + initParams(reqVO); List followupRecordCount = customerMapper.selectFollowupRecordCountGroupByUser(reqVO); List followupCustomerCount = customerMapper.selectFollowupCustomerCountGroupByUser(reqVO); @@ -204,6 +211,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe reqVO.setUserIds(userIds); // 2. 获得排行数据 + initParams(reqVO); List respVoList = customerMapper.selectFollowupRecordCountGroupByType(reqVO); // 3. 获取字典数据 @@ -227,6 +235,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe reqVO.setUserIds(userIds); // 2. 获取统计数据 + initParams(reqVO); List respVoList = customerMapper.selectContractSummary(reqVO); // 3. 设置 创建人、负责人、行业、来源 @@ -261,7 +270,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe reqVO.setUserIds(userIds); // 2. 获取分项统计数据 - reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1])); + initParams(reqVO); List customerDealCycle = customerMapper.selectCustomerDealCycleGroupByDate(reqVO); // 3. 合并统计数据 @@ -287,6 +296,7 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe reqVO.setUserIds(userIds); // 2. 获取分项统计数据 + initParams(reqVO); List customerDealCycle = customerMapper.selectCustomerDealCycleGroupByUser(reqVO); List customerDealCount = customerMapper.selectCustomerDealCountGroupByUser(reqVO); @@ -363,7 +373,6 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe * @param endTime 结束时间 * @return 时间序列 */ - // TODO @dhb52:可以抽象到 DateUtils 里,开始时间、结束时间,事件间隔,然后返回这个哈; private List generateTimeSeries(LocalDateTime startTime, LocalDateTime endTime) { boolean byMonth = queryByMonth(startTime, endTime); List times = CollUtil.newArrayList(); @@ -389,4 +398,62 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe return queryByMonth(startTime, endTime) ? SQL_DATE_FORMAT_BY_MONTH : SQL_DATE_FORMAT_BY_DAY; } + private void initParams(CrmStatisticsCustomerReqVO reqVO) { + final Integer intervalType = reqVO.getIntervalType(); + + // 1. 自定义时间间隔,必须输入起始日期-结束日期 + if (DateIntervalEnum.CUSTOMER.getType().equals(intervalType)) { + if (ObjUtil.isEmpty(reqVO.getTimes()) || reqVO.getTimes().length != 2) { + throw exception(STATISTICS_CUSTOMER_TIMES_NOT_SET); + } + // 设置 mapper sqlDateFormat 参数 + reqVO.setSqlDateFormat(getSqlDateFormat(reqVO.getTimes()[0], reqVO.getTimes()[1])); + // 自定义日期无需计算日期参数 + return; + } + + // 2. 根据时间区间类型计算时间段区间日期 + DateTime beginDate = null; + DateTime endDate = null; + if (DateIntervalEnum.TODAY.getType().equals(intervalType)) { + beginDate = DateUtil.beginOfDay(DateUtil.date()); + endDate = DateUtil.endOfDay(DateUtil.date()); + } else if (DateIntervalEnum.YESTERDAY.getType().equals(intervalType)) { + beginDate = DateUtil.offsetDay(DateUtil.date(), -1); + endDate = DateUtil.offsetDay(DateUtil.date(), -1); + } else if (DateIntervalEnum.THIS_WEEK.getType().equals(intervalType)) { + beginDate = DateUtil.beginOfWeek(DateUtil.date()); + endDate = DateUtil.endOfWeek(DateUtil.date()); + } else if (DateIntervalEnum.LAST_WEEK.getType().equals(intervalType)) { + beginDate = DateUtil.beginOfWeek(DateUtil.offsetWeek(DateUtil.date(), -1)); + endDate = DateUtil.endOfWeek(DateUtil.offsetWeek(DateUtil.date(), -1)); + } else if (DateIntervalEnum.THIS_MONTH.getType().equals(intervalType)) { + beginDate = DateUtil.beginOfMonth(DateUtil.date()); + endDate = DateUtil.endOfMonth(DateUtil.date()); + } else if (DateIntervalEnum.LAST_MONTH.getType().equals(intervalType)) { + beginDate = DateUtil.beginOfMonth(DateUtil.offsetMonth(DateUtil.date(), -1)); + endDate = DateUtil.endOfMonth(DateUtil.offsetMonth(DateUtil.date(), -1)); + } else if (DateIntervalEnum.THIS_QUARTER.getType().equals(intervalType)) { + beginDate = DateUtil.beginOfQuarter(DateUtil.date()); + endDate = DateUtil.endOfQuarter(DateUtil.date()); + } else if (DateIntervalEnum.LAST_QUARTER.getType().equals(intervalType)) { + beginDate = DateUtil.beginOfQuarter(DateUtil.offsetMonth(DateUtil.date(), -3)); + endDate = DateUtil.endOfQuarter(DateUtil.offsetMonth(DateUtil.date(), -3)); + } else if (DateIntervalEnum.THIS_YEAR.getType().equals(intervalType)) { + beginDate = DateUtil.beginOfYear(DateUtil.date()); + endDate = DateUtil.endOfYear(DateUtil.date()); + } else if (DateIntervalEnum.LAST_YEAR.getType().equals(intervalType)) { + beginDate = DateUtil.beginOfYear(DateUtil.offsetMonth(DateUtil.date(), -12)); + endDate = DateUtil.endOfYear(DateUtil.offsetMonth(DateUtil.date(), -12)); + } + + // 3. 计算开始、结束日期时间,并设置reqVo + LocalDateTime[] times = new LocalDateTime[2]; + times[0] = LocalDateTimeUtil.beginOfDay(LocalDateTimeUtil.of(beginDate)); + times[1] = LocalDateTimeUtil.endOfDay(LocalDateTimeUtil.of(endDate)); + // 3.1 设置 mapper 时间区间 参数 + reqVO.setTimes(times); + // 3.2 设置 mapper sqlDateFormat 参数 + reqVO.setSqlDateFormat(getSqlDateFormat(times[0], times[1])); + } }