Commit 5cf34c2ba74248ab9e5070525de8e7d06f8f111f

Authored by puhui999
1 parent ed5f3a6b

CRM: 完善销售漏斗

Showing 17 changed files with 110 additions and 314 deletions
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsFunnelController.java
... ... @@ -5,8 +5,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.module.crm.controller.admin.business.CrmBusinessController;
7 7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
8   -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticBusinessEndStatusRespVO;
9   -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticFunnelRespVO;
  8 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByEndStatusRespVO;
  9 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticFunnelSummaryRespVO;
10 10 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
11 11 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
12 12 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
... ... @@ -31,37 +31,35 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
31 31 @Validated
32 32 public class CrmStatisticsFunnelController {
33 33  
34   - // TODO @puhui999:crmStatisticsFunnelService 改成 funnelService 更好点哈
35 34 @Resource
36   - private CrmStatisticsFunnelService crmStatisticsFunnelService;
  35 + private CrmStatisticsFunnelService funnelService;
37 36  
38 37 @GetMapping("/get-funnel-summary")
39 38 @Operation(summary = "获取销售漏斗统计数据", description = "用于【销售漏斗】页面的【销售漏斗分析】")
40 39 @PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
41   - public CommonResult<CrmStatisticFunnelRespVO> getFunnelSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
42   - return success(crmStatisticsFunnelService.getFunnelSummary(reqVO));
  40 + public CommonResult<CrmStatisticFunnelSummaryRespVO> getFunnelSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
  41 + return success(funnelService.getFunnelSummary(reqVO));
43 42 }
44 43  
45   - // TODO @puhui:这个接口,应该是 getBusinessSummaryByEndStatus?这样更统一哈;
46   - @GetMapping("/get-business-end-status-summary")
  44 + @GetMapping("/get-business-summary-by-end-status")
47 45 @Operation(summary = "获取商机结束状态统计", description = "用于【销售漏斗】页面的【销售漏斗分析】")
48 46 @PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
49   - public CommonResult<List<CrmStatisticBusinessEndStatusRespVO>> getBusinessEndStatusSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
50   - return success(crmStatisticsFunnelService.getBusinessEndStatusSummary(reqVO));
  47 + public CommonResult<List<CrmStatisticsBusinessSummaryByEndStatusRespVO>> getBusinessSummaryByEndStatus(@Valid CrmStatisticsFunnelReqVO reqVO) {
  48 + return success(funnelService.getBusinessSummaryByEndStatus(reqVO));
51 49 }
52 50  
53 51 @GetMapping("/get-business-summary-by-date")
54 52 @Operation(summary = "获取新增商机分析(按日期)", description = "用于【销售漏斗】页面")
55 53 @PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
56 54 public CommonResult<List<CrmStatisticsBusinessSummaryByDateRespVO>> getBusinessSummaryByDate(@Valid CrmStatisticsFunnelReqVO reqVO) {
57   - return success(crmStatisticsFunnelService.getBusinessSummaryByDate(reqVO));
  55 + return success(funnelService.getBusinessSummaryByDate(reqVO));
58 56 }
59 57  
60 58 @GetMapping("/get-business-page-by-date")
61 59 @Operation(summary = "获得商机分页(按日期)", description = "用于【销售漏斗】页面的【新增商机分析】")
62 60 @PreAuthorize("@ss.hasPermission('crm:business:query')")
63 61 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByDate(@Valid CrmStatisticsFunnelReqVO pageVO) {
64   - PageResult<CrmBusinessDO> pageResult = crmStatisticsFunnelService.getBusinessPageByDate(pageVO);
  62 + PageResult<CrmBusinessDO> pageResult = funnelService.getBusinessPageByDate(pageVO);
65 63 return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
66 64 }
67 65  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticFunnelRespVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticFunnelSummaryRespVO.java
... ... @@ -5,12 +5,11 @@ import lombok.AllArgsConstructor;
5 5 import lombok.Data;
6 6 import lombok.NoArgsConstructor;
7 7  
8   -// TODO @puhui999:改成 CrmStatisticFunnelSummaryRespVO 更有统计的味道
9 8 @Schema(description = "管理后台 - CRM 销售漏斗 Response VO")
10 9 @NoArgsConstructor
11 10 @AllArgsConstructor
12 11 @Data
13   -public class CrmStatisticFunnelRespVO {
  12 +public class CrmStatisticFunnelSummaryRespVO {
14 13  
15 14 @Schema(description = "客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
16 15 private Long customerCount;
... ... @@ -18,8 +17,7 @@ public class CrmStatisticFunnelRespVO {
18 17 @Schema(description = "商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
19 18 private Long businessCount;
20 19  
21   - // TODO @puhui999:这个改成 businessWinCount 可能会更合适点哈;
22 20 @Schema(description = "赢单数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
23   - private Long winCount;
  21 + private Long businessWinCount;
24 22  
25 23 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsBusinessRespVO.java deleted
1   -package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
2   -
3   -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4   -import com.alibaba.excel.annotation.ExcelProperty;
5   -import io.swagger.v3.oas.annotations.media.Schema;
6   -import lombok.Data;
7   -
8   -import java.math.BigDecimal;
9   -import java.time.LocalDateTime;
10   -
11   -// TODO @puhui999:是不是可以删除哈?
12   -@Schema(description = "管理后台 - CRM 商机 Response VO")
13   -@Data
14   -@ExcelIgnoreUnannotated
15   -public class CrmStatisticsBusinessRespVO {
16   -
17   - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
18   - @ExcelProperty("编号")
19   - private Long id;
20   -
21   - @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
22   - @ExcelProperty("商机名称")
23   - private String name;
24   -
25   - @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
26   - private Long customerId;
27   - @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
28   - @ExcelProperty("客户名称")
29   - private String customerName;
30   -
31   - @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example ="true")
32   - @ExcelProperty("跟进状态")
33   - private Boolean followUpStatus;
34   -
35   - @Schema(description = "最后跟进时间")
36   - @ExcelProperty("最后跟进时间")
37   - private LocalDateTime contactLastTime;
38   -
39   - @Schema(description = "下次联系时间")
40   - @ExcelProperty("下次联系时间")
41   - private LocalDateTime contactNextTime;
42   -
43   - @Schema(description = "负责人的用户编号", example = "25682")
44   - @ExcelProperty("负责人的用户编号")
45   - private Long ownerUserId;
46   - @Schema(description = "负责人名字", example = "25682")
47   - @ExcelProperty("负责人名字")
48   - private String ownerUserName;
49   - @Schema(description = "负责人部门")
50   - @ExcelProperty("负责人部门")
51   - private String ownerUserDeptName;
52   -
53   - @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
54   - private Long statusTypeId;
55   - @Schema(description = "商机状组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中")
56   - @ExcelProperty("商机状态组")
57   - private String statusTypeName;
58   -
59   - @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
60   - private Long statusId;
61   - @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中")
62   - @ExcelProperty("商机状态")
63   - private String statusName;
64   -
65   - @Schema
66   - @ExcelProperty("结束状态")
67   - private Integer endStatus;
68   -
69   - @ExcelProperty("结束时的备注")
70   - private String endRemark;
71   -
72   - @Schema(description = "预计成交日期")
73   - @ExcelProperty("预计成交日期")
74   - private LocalDateTime dealTime;
75   -
76   - @Schema(description = "产品总金额", example = "12025")
77   - @ExcelProperty("产品总金额")
78   - private BigDecimal totalProductPrice;
79   -
80   - @Schema(description = "整单折扣")
81   - @ExcelProperty("整单折扣")
82   - private BigDecimal discountPercent;
83   -
84   - @Schema(description = "商机总金额", example = "12371")
85   - @ExcelProperty("商机总金额")
86   - private BigDecimal totalPrice;
87   -
88   - @Schema(description = "备注", example = "随便")
89   - @ExcelProperty("备注")
90   - private String remark;
91   -
92   - @Schema(description = "创建人", example = "1024")
93   - @ExcelProperty("创建人")
94   - private String creator;
95   - @Schema(description = "创建人名字", example = "芋道源码")
96   - @ExcelProperty("创建人名字")
97   - private String creatorName;
98   -
99   - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
100   - @ExcelProperty("创建时间")
101   - private LocalDateTime createTime;
102   -
103   - @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
104   - @ExcelProperty("更新时间")
105   - private LocalDateTime updateTime;
106   -
107   -}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsBusinessSummaryByDateRespVO.java
... ... @@ -15,8 +15,7 @@ public class CrmStatisticsBusinessSummaryByDateRespVO {
15 15 @Schema(description = "新增商机数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
16 16 private Integer businessCreateCount;
17 17  
18   - // TODO @puhui999:是不是金额哈,不是数量
19 18 @Schema(description = "新增商机金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
20   - private BigDecimal businessDealCount;
  19 + private BigDecimal totalPrice;
21 20  
22 21 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticBusinessEndStatusRespVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsBusinessSummaryByEndStatusRespVO.java
... ... @@ -7,12 +7,11 @@ import lombok.NoArgsConstructor;
7 7  
8 8 import java.math.BigDecimal;
9 9  
10   -// TODO @puhui999:改成 CrmStatisticsBusinessSummaryByEndStatusRespVO,按照结束状态
11 10 @Schema(description = "管理后台 - CRM 商机结束状态统计 Response VO")
12 11 @NoArgsConstructor
13 12 @AllArgsConstructor
14 13 @Data
15   -public class CrmStatisticBusinessEndStatusRespVO {
  14 +public class CrmStatisticsBusinessSummaryByEndStatusRespVO {
16 15  
17 16 @Schema(description = "结束状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
18 17 private Integer endStatus;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsFunnelReqVO.java
... ... @@ -5,9 +5,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 5 import cn.iocoder.yudao.framework.common.validation.InEnum;
6 6 import io.swagger.v3.oas.annotations.media.Schema;
7 7 import jakarta.validation.constraints.NotNull;
  8 +import jakarta.validation.constraints.Size;
8 9 import lombok.Data;
9   -import lombok.EqualsAndHashCode;
10   -import lombok.ToString;
11 10 import org.springframework.format.annotation.DateTimeFormat;
12 11  
13 12 import java.time.LocalDateTime;
... ... @@ -17,9 +16,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
17 16  
18 17 @Schema(description = "管理后台 - CRM 销售漏斗 Request VO")
19 18 @Data
20   -// TODO @puhui999:不用写 EqualsAndHashCode、ToString,已经全局 lombok 啦
21   -@EqualsAndHashCode(callSuper = true)
22   -@ToString(callSuper = true)
23 19 public class CrmStatisticsFunnelReqVO extends PageParam {
24 20  
25 21 @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
... ... @@ -43,13 +39,9 @@ public class CrmStatisticsFunnelReqVO extends PageParam {
43 39 @InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
44 40 private Integer interval;
45 41  
46   - // TODO @puhui999:这个全部前端传递哈;参考 CrmStatisticsCustomerReqVO
47   - /**
48   - * 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
49   - * 并作为参数传递给Mapper
50   - */
51 42 @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
52 43 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  44 + @Size(min = 2, max = 2, message = "请选择时间范围")
53 45 private LocalDateTime[] times;
54 46  
55 47 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticsPortraitReqVO.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotNull;
  5 +import jakarta.validation.constraints.Size;
5 6 import lombok.Data;
6 7 import org.springframework.format.annotation.DateTimeFormat;
7 8  
... ... @@ -31,12 +32,9 @@ public class CrmStatisticsPortraitReqVO {
31 32 @Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
32 33 private List<Long> userIds;
33 34  
34   - /**
35   - * 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
36   - * 并作为参数传递给Mapper
37   - */
38 35 @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
39 36 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  37 + @Size(min = 2, max = 2, message = "请选择时间范围")
40 38 private LocalDateTime[] times;
41 39  
42 40 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
1 1 package cn.iocoder.yudao.module.crm.dal.mysql.business;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
8 7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
  8 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
9 9 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
10 10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 11 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
12 12 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
13 13 import org.apache.ibatis.annotations.Mapper;
14 14  
15   -import java.time.LocalDateTime;
16 15 import java.util.Collection;
17 16 import java.util.List;
18 17  
... ... @@ -66,32 +65,10 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
66 65 .eq(CrmBusinessDO::getOwnerUserId, ownerUserId));
67 66 }
68 67  
69   - default Long selectCountByOwnerUserIdsAndEndStatus(Collection<Long> ownerUserIds, LocalDateTime[] times, Integer endStatus) {
70   - return selectCount(new LambdaQueryWrapperX<CrmBusinessDO>()
71   - .in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
72   - .eqIfPresent(CrmBusinessDO::getEndStatus, endStatus)
73   - .betweenIfPresent(CrmBusinessDO::getCreateTime, times));
74   - }
75   -
76   - // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
77   - default List<CrmBusinessDO> selectListByOwnerUserIdsAndEndStatusNotNull(Collection<Long> ownerUserIds, LocalDateTime[] times) {
78   - return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
79   - .in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
80   - .betweenIfPresent(CrmBusinessDO::getCreateTime, times)
81   - .isNotNull(CrmBusinessDO::getEndStatus));
82   - }
83   -
84   - // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
85   - default List<CrmBusinessDO> selectListByOwnerUserIdsAndDate(Collection<Long> ownerUserIds, LocalDateTime[] times) {
86   - return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
87   - .in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
88   - .betweenIfPresent(CrmBusinessDO::getCreateTime, times));
89   - }
90   -
91   - default PageResult<CrmBusinessDO> selectPage(Collection<Long> ownerUserIds, LocalDateTime[] times, Integer pageNo, Integer pageSize) {
92   - return selectPage(new PageParam().setPageNo(pageNo).setPageSize(pageSize), new LambdaQueryWrapperX<CrmBusinessDO>()
93   - .in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
94   - .betweenIfPresent(CrmBusinessDO::getCreateTime, times));
  68 + default PageResult<CrmBusinessDO> selectPage(CrmStatisticsFunnelReqVO pageVO) {
  69 + return selectPage(pageVO, new LambdaQueryWrapperX<CrmBusinessDO>()
  70 + .in(CrmBusinessDO::getOwnerUserId, pageVO.getUserIds())
  71 + .betweenIfPresent(CrmBusinessDO::getCreateTime, pageVO.getTimes()));
95 72 }
96 73  
97 74 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
... ... @@ -186,11 +186,4 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; {
186 186 return selectCount(query);
187 187 }
188 188  
189   - // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
190   - default Long selectCountByOwnerUserIds(Collection<Long> ownerUserIds, LocalDateTime[] times){
191   - return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>()
192   - .in(CrmCustomerDO::getOwnerUserId, ownerUserIds)
193   - .betweenIfPresent(CrmCustomerDO::getCreateTime, times));
194   - }
195   -
196 189 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsFunnelMapper.java
1 1 package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
2 2  
3   -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO;
4 3 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByEndStatusRespVO;
5 5 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
6 6 import org.apache.ibatis.annotations.Mapper;
  7 +import org.apache.ibatis.annotations.Param;
7 8  
8 9 import java.util.List;
9 10  
... ... @@ -15,6 +16,12 @@ import java.util.List;
15 16 @Mapper
16 17 public interface CrmStatisticsFunnelMapper {
17 18  
18   - List<CrmStatisticsBusinessSummaryByDateRespVO> selectBusinessCreateCountGroupByDate(CrmStatisticsFunnelReqVO reqVO);
  19 + Long selectCustomerCountByDate(CrmStatisticsFunnelReqVO reqVO);
  20 +
  21 + Long selectBusinessCountByDateAndEndStatus(@Param("reqVO") CrmStatisticsFunnelReqVO reqVO, @Param("status") Integer status);
  22 +
  23 + List<CrmStatisticsBusinessSummaryByEndStatusRespVO> selectBusinessSummaryListGroupByEndStatus(CrmStatisticsFunnelReqVO reqVO);
  24 +
  25 + List<CrmStatisticsBusinessSummaryByDateRespVO> selectBusinessSummaryGroupByDate(CrmStatisticsFunnelReqVO reqVO);
19 26  
20 27 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
5 5 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
6 6 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
7 7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO;
  8 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
8 9 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
9 10 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
10 11 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
... ... @@ -195,44 +196,11 @@ public interface CrmBusinessService {
195 196 List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
196 197  
197 198 /**
198   - * 获得商机数
199   - *
200   - * @param ownerUserIds 负责人编号
201   - * @param times 时间范围
202   - * @param endStatus 商机结束状态,允许为空
203   - * @return 商机数
204   - */
205   - Long getBusinessCountByOwnerUserIdsAndEndStatus(List<Long> ownerUserIds, LocalDateTime[] times, Integer endStatus);
206   -
207   - // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
208   - /**
209   - * 获得商机列表【数据统计】
210   - *
211   - * @param ownerUserIds 负责人编号
212   - * @param times 时间范围
213   - * @return 商机列表
214   - */
215   - List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndEndStatusNotNull(List<Long> ownerUserIds, LocalDateTime[] times);
216   -
217   - // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
218   - /**
219   - * 获得商机列表【数据统计】
220   - *
221   - * @param ownerUserIds 负责人编号
222   - * @param times 时间范围
223   - * @return 商机列表
224   - */
225   - List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndDate(List<Long> ownerUserIds, LocalDateTime[] times);
226   -
227   - /**
228 199 * 获得商机分页,目前用于【数据统计】
229 200 *
230   - * @param ownerUserIds 负责人编号
231   - * @param times 时间范围
232   - * @param pageNo 页码 TODO @puhui999:直接传递 CrmStatisticsFunnelReqVO 吧,虽然有点耦合,但是更清晰一点;
233   - * @param pageSize 数量
  201 + * @param pageVO 请求
234 202 * @return 商机分页
235 203 */
236   - PageResult<CrmBusinessDO> getBusinessPageByDate(List<Long> ownerUserIds, LocalDateTime[] times, Integer pageNo, Integer pageSize);
  204 + PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO);
237 205  
238 206 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
... ... @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
10 10 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
11 11 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO;
12 12 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
  13 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
13 14 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
14 15 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
15 16 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
... ... @@ -376,35 +377,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
376 377 }
377 378  
378 379 @Override
379   - public Long getBusinessCountByOwnerUserIdsAndEndStatus(List<Long> ownerUserIds, LocalDateTime[] times, Integer endStatus) {
380   - if (CollUtil.isEmpty(ownerUserIds)) {
381   - return 0L;
382   - }
383   - return businessMapper.selectCountByOwnerUserIdsAndEndStatus(convertSet(ownerUserIds), times, endStatus);
384   - }
385   -
386   - // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
387   - @Override
388   - public List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndEndStatusNotNull(List<Long> ownerUserIds, LocalDateTime[] times) {
389   - if (CollUtil.isEmpty(ownerUserIds)) {
390   - return Collections.emptyList();
391   - }
392   - return businessMapper.selectListByOwnerUserIdsAndEndStatusNotNull(convertSet(ownerUserIds), times);
393   - }
394   -
395   - // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
396   - @Override
397   - public List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndDate(List<Long> ownerUserIds, LocalDateTime[] times) {
398   - if (CollUtil.isEmpty(ownerUserIds)) {
399   - return Collections.emptyList();
400   - }
401   -
402   - return businessMapper.selectListByOwnerUserIdsAndDate(convertSet(ownerUserIds), times);
403   - }
404   -
405   - @Override
406   - public PageResult<CrmBusinessDO> getBusinessPageByDate(List<Long> ownerUserIds, LocalDateTime[] times, Integer pageNo, Integer pageSize) {
407   - return businessMapper.selectPage(ownerUserIds, times, pageNo, pageSize);
  380 + public PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO) {
  381 + return businessMapper.selectPage(pageVO);
408 382 }
409 383  
410 384 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
... ... @@ -195,13 +195,4 @@ public interface CrmCustomerService {
195 195 */
196 196 int autoPutCustomerPool();
197 197  
198   - /**
199   - * 获得客户数
200   - *
201   - * @param ownerUserIds 负责人编号
202   - * @param times 时间范围
203   - * @return 客户数
204   - */
205   - Long getCustomerCountByOwnerUserIds(List<Long> ownerUserIds, LocalDateTime[] times);
206   -
207 198 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
... ... @@ -651,14 +651,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
651 651 }
652 652 }
653 653  
654   - @Override
655   - public Long getCustomerCountByOwnerUserIds(List<Long> ownerUserIds, LocalDateTime[] times) {
656   - if (CollUtil.isEmpty(ownerUserIds)) {
657   - return 0L;
658   - }
659   - return customerMapper.selectCountByOwnerUserIds(convertSet(ownerUserIds), times);
660   - }
661   -
662 654 /**
663 655 * 获得自身的代理对象,解决 AOP 生效问题
664 656 *
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelService.java
1 1 package cn.iocoder.yudao.module.crm.service.statistics;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticBusinessEndStatusRespVO;
5   -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticFunnelRespVO;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByEndStatusRespVO;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticFunnelSummaryRespVO;
6 6 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
7 7 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
8 8 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
... ... @@ -22,7 +22,7 @@ public interface CrmStatisticsFunnelService {
22 22 * @param reqVO 请求
23 23 * @return 销售漏斗数据
24 24 */
25   - CrmStatisticFunnelRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO);
  25 + CrmStatisticFunnelSummaryRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO);
26 26  
27 27 /**
28 28 * 获得商机结束状态统计
... ... @@ -30,7 +30,7 @@ public interface CrmStatisticsFunnelService {
30 30 * @param reqVO 请求
31 31 * @return 商机结束状态统计
32 32 */
33   - List<CrmStatisticBusinessEndStatusRespVO> getBusinessEndStatusSummary(CrmStatisticsFunnelReqVO reqVO);
  33 + List<CrmStatisticsBusinessSummaryByEndStatusRespVO> getBusinessSummaryByEndStatus(CrmStatisticsFunnelReqVO reqVO);
34 34  
35 35 /**
36 36 * 获取新增商机分析(按日期)
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelServiceImpl.java
... ... @@ -4,15 +4,14 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
7   -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticBusinessEndStatusRespVO;
8   -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticFunnelRespVO;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticFunnelSummaryRespVO;
9 8 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
  9 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByEndStatusRespVO;
10 10 import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
11 11 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
12 12 import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsFunnelMapper;
13 13 import cn.iocoder.yudao.module.crm.enums.business.CrmBusinessEndStatusEnum;
14 14 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
15   -import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
16 15 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
17 16 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
18 17 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
... ... @@ -22,14 +21,10 @@ import org.springframework.stereotype.Service;
22 21  
23 22 import java.math.BigDecimal;
24 23 import java.time.LocalDateTime;
25   -import java.time.format.DateTimeFormatter;
26 24 import java.util.Collections;
27 25 import java.util.List;
28   -import java.util.Map;
29   -import java.util.stream.Collectors;
30 26  
31   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
32   -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
  27 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
33 28  
34 29 /**
35 30 * CRM 销售漏斗分析 Service 实现类
... ... @@ -45,15 +40,12 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
45 40 @Resource
46 41 private AdminUserApi adminUserApi;
47 42 @Resource
48   - private CrmCustomerService customerService;
49   - @Resource
50 43 private CrmBusinessService businessService;
51 44 @Resource
52 45 private DeptApi deptApi;
53 46  
54   - // TODO @puhui999:貌似想了下,可能还是得按照;;;
55 47 @Override
56   - public CrmStatisticFunnelRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO) {
  48 + public CrmStatisticFunnelSummaryRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO) {
57 49 // 1. 获得用户编号数组
58 50 List<Long> userIds = getUserIds(reqVO);
59 51 if (CollUtil.isEmpty(userIds)) {
... ... @@ -62,34 +54,22 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
62 54 reqVO.setUserIds(userIds);
63 55  
64 56 // 2. 获得漏斗数据
65   - return new CrmStatisticFunnelRespVO(
66   - customerService.getCustomerCountByOwnerUserIds(userIds, reqVO.getTimes()),
67   - businessService.getBusinessCountByOwnerUserIdsAndEndStatus(userIds, reqVO.getTimes(), null),
68   - businessService.getBusinessCountByOwnerUserIdsAndEndStatus(userIds, reqVO.getTimes(), CrmBusinessEndStatusEnum.WIN.getStatus())
69   - );
  57 + Long customerCount = funnelMapper.selectCustomerCountByDate(reqVO);
  58 + Long businessCount = funnelMapper.selectBusinessCountByDateAndEndStatus(reqVO, null);
  59 + Long businessWinCount = funnelMapper.selectBusinessCountByDateAndEndStatus(reqVO, CrmBusinessEndStatusEnum.WIN.getStatus());
  60 + return new CrmStatisticFunnelSummaryRespVO(customerCount, businessCount, businessWinCount);
70 61 }
71 62  
72 63 @Override
73   - public List<CrmStatisticBusinessEndStatusRespVO> getBusinessEndStatusSummary(CrmStatisticsFunnelReqVO reqVO) {
  64 + public List<CrmStatisticsBusinessSummaryByEndStatusRespVO> getBusinessSummaryByEndStatus(CrmStatisticsFunnelReqVO reqVO) {
74 65 // 1. 获得用户编号数组
75 66 reqVO.setUserIds(getUserIds(reqVO));
76 67 if (CollUtil.isEmpty(reqVO.getUserIds())) {
77 68 return Collections.emptyList();
78 69 }
79 70  
80   - // TODO @puhui999:这个可以优化下,通过统计 sql,不通过内存计算;
81   - // 2.1 获得用户负责的商机
82   - List<CrmBusinessDO> businessList = businessService.getBusinessListByOwnerUserIdsAndEndStatusNotNull(reqVO.getUserIds(), reqVO.getTimes());
83   - // 2.2 统计各阶段数据
84   - Map<Integer, List<CrmBusinessDO>> businessMap = convertMultiMap(businessList, CrmBusinessDO::getEndStatus);
85   - return convertList(CrmBusinessEndStatusEnum.values(), endStatusEnum -> {
86   - List<CrmBusinessDO> list = businessMap.get(endStatusEnum.getStatus());
87   - if (CollUtil.isEmpty(list)) {
88   - return new CrmStatisticBusinessEndStatusRespVO(endStatusEnum.getStatus(), 0L, BigDecimal.ZERO);
89   - }
90   - return new CrmStatisticBusinessEndStatusRespVO(endStatusEnum.getStatus(), (long) list.size(),
91   - getSumValue(list, CrmBusinessDO::getTotalPrice, BigDecimal::add));
92   - });
  71 + // 2. 获得统计数据
  72 + return funnelMapper.selectBusinessSummaryListGroupByEndStatus(reqVO);
93 73 }
94 74  
95 75 @Override
... ... @@ -101,26 +81,20 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
101 81 }
102 82  
103 83 // 2. 按天统计,获取分项统计数据
104   - // TODO @puhui999:可以这个统计,返回的时候,就把数量、金额一起统计好;
105   - List<CrmStatisticsBusinessSummaryByDateRespVO> businessCreateCountList = funnelMapper.selectBusinessCreateCountGroupByDate(reqVO);
106   - List<CrmBusinessDO> businessList = businessService.getBusinessListByOwnerUserIdsAndDate(reqVO.getUserIds(), reqVO.getTimes());
107   - Map<String, BigDecimal> businessDealCountMap = businessList.stream().collect(Collectors.groupingBy(business ->
108   - business.getCreateTime().format(DateTimeFormatter.ofPattern(FORMAT_YEAR_MONTH_DAY)),
109   - Collectors.reducing(BigDecimal.ZERO, CrmBusinessDO::getTotalPrice, BigDecimal::add)));
110   -
  84 + List<CrmStatisticsBusinessSummaryByDateRespVO> businessSummaryList = funnelMapper.selectBusinessSummaryGroupByDate(reqVO);
111 85 // 3. 按照日期间隔,合并数据
112 86 List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
113 87 return convertList(timeRanges, times -> {
114   - Integer businessCreateCount = businessCreateCountList.stream()
  88 + Integer businessCreateCount = businessSummaryList.stream()
115 89 .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
116 90 .mapToInt(CrmStatisticsBusinessSummaryByDateRespVO::getBusinessCreateCount).sum();
117   - BigDecimal businessDealCount = businessDealCountMap.entrySet().stream()
118   - .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getKey()))
119   - .map(Map.Entry::getValue)
  91 + BigDecimal businessDealCount = businessSummaryList.stream()
  92 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
  93 + .map(CrmStatisticsBusinessSummaryByDateRespVO::getTotalPrice)
120 94 .reduce(BigDecimal.ZERO, BigDecimal::add);
121 95 return new CrmStatisticsBusinessSummaryByDateRespVO()
122 96 .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
123   - .setBusinessCreateCount(businessCreateCount).setBusinessDealCount(businessDealCount);
  97 + .setBusinessCreateCount(businessCreateCount).setTotalPrice(businessDealCount);
124 98 });
125 99 }
126 100  
... ... @@ -132,7 +106,7 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
132 106 return PageResult.empty();
133 107 }
134 108 // 2. 执行查询
135   - return businessService.getBusinessPageByDate(pageVO.getUserIds(), pageVO.getTimes(), pageVO.getPageNo(), pageVO.getPageSize());
  109 + return businessService.getBusinessPageByDate(pageVO);
136 110 }
137 111  
138 112 /**
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsFunnelMapper.xml
... ... @@ -2,18 +2,61 @@
2 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3 3 <mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsFunnelMapper">
4 4  
5   - <select id="selectBusinessCreateCountGroupByDate"
  5 + <select id="selectCustomerCountByDate" resultType="java.lang.Long">
  6 + SELECT COUNT(*)
  7 + FROM crm_customer
  8 + WHERE deleted = 0
  9 + AND owner_user_id IN
  10 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  11 + #{userId}
  12 + </foreach>
  13 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
  14 + #{times[1],javaType=java.time.LocalDateTime}
  15 + </select>
  16 +
  17 + <select id="selectBusinessCountByDateAndEndStatus" resultType="java.lang.Long">
  18 + SELECT COUNT(*)
  19 + FROM crm_business
  20 + WHERE deleted = 0
  21 + <if test="status != null">
  22 + AND end_status = #{status}
  23 + </if>
  24 + AND owner_user_id IN
  25 + <foreach collection="reqVO.userIds" item="userId" open="(" close=")" separator=",">
  26 + #{userId}
  27 + </foreach>
  28 + AND create_time BETWEEN #{reqVO.times[0],javaType=java.time.LocalDateTime} AND
  29 + #{reqVO.times[1],javaType=java.time.LocalDateTime}
  30 + </select>
  31 +
  32 + <select id="selectBusinessSummaryListGroupByEndStatus"
  33 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByEndStatusRespVO">
  34 + SELECT end_status AS endStatus,COUNT(*) AS businessCount, SUM(total_price) AS totalPrice
  35 + FROM crm_business
  36 + WHERE deleted = 0 AND end_status IS NOT NULL
  37 + AND owner_user_id IN
  38 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  39 + #{userId}
  40 + </foreach>
  41 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
  42 + #{times[1],javaType=java.time.LocalDateTime}
  43 + GROUP BY end_status
  44 + </select>
  45 +
  46 + <select id="selectBusinessSummaryGroupByDate"
6 47 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO">
7 48 SELECT
8   - DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
9   - COUNT(*) AS businessCreateCount
  49 + DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
  50 + COUNT(*) AS businessCreateCount,
  51 + SUM(total_price) AS totalPrice
10 52 FROM crm_business
11 53 WHERE deleted = 0
12 54 AND owner_user_id IN
13   - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
14   - #{userId}
15   - </foreach>
16   - AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  55 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  56 + #{userId}
  57 + </foreach>
  58 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
  59 + #{times[1],javaType=java.time.LocalDateTime}
17 60 GROUP BY time
18 61 </select>
19 62  
... ...