Commit 0eff2ae6023371b15978ba007df515445d279c6a

Authored by 芋道源码
Committed by Gitee
2 parents 5159da30 20b4bfd1

!946 CRM: 完善销售漏斗分析

Merge pull request !946 from puhui999/develop
Showing 18 changed files with 746 additions and 14 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -97,6 +97,10 @@ public class CollectionUtils { @@ -97,6 +97,10 @@ public class CollectionUtils {
97 .collect(Collectors.toList()); 97 .collect(Collectors.toList());
98 } 98 }
99 99
  100 + public static <T> Set<T> convertSet(Collection<T> from) {
  101 + return convertSet(from, v -> v);
  102 + }
  103 +
100 public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) { 104 public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
101 if (CollUtil.isEmpty(from)) { 105 if (CollUtil.isEmpty(from)) {
102 return new HashSet<>(); 106 return new HashSet<>();
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsFunnelController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
  10 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticBusinessEndStatusRespVO;
  11 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticFunnelRespVO;
  12 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
  13 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
  14 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  15 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
  16 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
  17 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
  18 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
  19 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
  20 +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  21 +import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsFunnelService;
  22 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  23 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  24 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  25 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  26 +import io.swagger.v3.oas.annotations.Operation;
  27 +import io.swagger.v3.oas.annotations.tags.Tag;
  28 +import jakarta.annotation.Resource;
  29 +import jakarta.validation.Valid;
  30 +import org.springframework.security.access.prepost.PreAuthorize;
  31 +import org.springframework.validation.annotation.Validated;
  32 +import org.springframework.web.bind.annotation.GetMapping;
  33 +import org.springframework.web.bind.annotation.RequestMapping;
  34 +import org.springframework.web.bind.annotation.RestController;
  35 +
  36 +import java.util.Collections;
  37 +import java.util.List;
  38 +import java.util.Map;
  39 +import java.util.stream.Stream;
  40 +
  41 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  42 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  44 +
  45 +@Tag(name = "管理后台 - CRM 销售漏斗")
  46 +@RestController
  47 +@RequestMapping("/crm/statistics-funnel")
  48 +@Validated
  49 +public class CrmStatisticsFunnelController {
  50 +
  51 + @Resource
  52 + private CrmStatisticsFunnelService crmStatisticsFunnelService;
  53 + @Resource
  54 + private CrmBusinessService businessService;
  55 + @Resource
  56 + private CrmCustomerService customerService;
  57 + @Resource
  58 + private CrmBusinessStatusService businessStatusTypeService;
  59 + @Resource
  60 + private CrmBusinessStatusService businessStatusService;
  61 +
  62 + @Resource
  63 + private AdminUserApi adminUserApi;
  64 + @Resource
  65 + private DeptApi deptApi;
  66 +
  67 + @GetMapping("/get-funnel-summary")
  68 + @Operation(summary = "获取销售漏斗统计数据", description = "用于【销售漏斗】页面")
  69 + @PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
  70 + public CommonResult<CrmStatisticFunnelRespVO> getFunnelSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
  71 + return success(crmStatisticsFunnelService.getFunnelSummary(reqVO));
  72 + }
  73 +
  74 + @GetMapping("/get-business-end-status-summary")
  75 + @Operation(summary = "获取商机结束状态统计", description = "用于【销售漏斗】页面")
  76 + @PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
  77 + public CommonResult<List<CrmStatisticBusinessEndStatusRespVO>> getBusinessEndStatusSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
  78 + return success(crmStatisticsFunnelService.getBusinessEndStatusSummary(reqVO));
  79 + }
  80 +
  81 + @GetMapping("/get-business-summary-by-date")
  82 + @Operation(summary = "获取新增商机分析(按日期)", description = "用于【销售漏斗】页面")
  83 + @PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
  84 + public CommonResult<List<CrmStatisticsBusinessSummaryByDateRespVO>> getBusinessSummaryByDate(@Valid CrmStatisticsFunnelReqVO reqVO) {
  85 + return success(crmStatisticsFunnelService.getBusinessSummaryByDate(reqVO));
  86 + }
  87 +
  88 + @GetMapping("/get-business-page-by-date")
  89 + @Operation(summary = "获得商机分页(按日期)", description = "用于【销售漏斗】页面")
  90 + @PreAuthorize("@ss.hasPermission('crm:business:query')")
  91 + public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByDate(@Valid CrmStatisticsFunnelReqVO pageVO) {
  92 + PageResult<CrmBusinessDO> pageResult = crmStatisticsFunnelService.getBusinessPageByDate(pageVO);
  93 + return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
  94 + }
  95 +
  96 + private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
  97 + if (CollUtil.isEmpty(list)) {
  98 + return Collections.emptyList();
  99 + }
  100 + // 1.1 获取客户列表
  101 + Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
  102 + convertSet(list, CrmBusinessDO::getCustomerId));
  103 + // 1.2 获取创建人、负责人列表
  104 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
  105 + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
  106 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  107 + // 1.3 获得商机状态组
  108 + Map<Long, CrmBusinessStatusTypeDO> statusTypeMap = businessStatusTypeService.getBusinessStatusTypeMap(
  109 + convertSet(list, CrmBusinessDO::getStatusTypeId));
  110 + Map<Long, CrmBusinessStatusDO> statusMap = businessStatusService.getBusinessStatusMap(
  111 + convertSet(list, CrmBusinessDO::getStatusId));
  112 + // 2. 拼接数据
  113 + return BeanUtils.toBean(list, CrmBusinessRespVO.class, businessVO -> {
  114 + // 2.1 设置客户名称
  115 + MapUtils.findAndThen(customerMap, businessVO.getCustomerId(), customer -> businessVO.setCustomerName(customer.getName()));
  116 + // 2.2 设置创建人、负责人名称
  117 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(businessVO.getCreator()),
  118 + user -> businessVO.setCreatorName(user.getNickname()));
  119 + MapUtils.findAndThen(userMap, businessVO.getOwnerUserId(), user -> {
  120 + businessVO.setOwnerUserName(user.getNickname());
  121 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> businessVO.setOwnerUserDeptName(dept.getName()));
  122 + });
  123 + // 2.3 设置商机状态
  124 + MapUtils.findAndThen(statusTypeMap, businessVO.getStatusTypeId(), statusType -> businessVO.setStatusTypeName(statusType.getName()));
  125 + MapUtils.findAndThen(statusMap, businessVO.getStatusId(), status -> businessVO.setStatusName(
  126 + businessService.getBusinessStatusName(businessVO.getEndStatus(), status)));
  127 + });
  128 + }
  129 +
  130 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticBusinessEndStatusRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +import java.math.BigDecimal;
  9 +
  10 +@Schema(description = "管理后台 - CRM 商机结束状态统计 Response VO")
  11 +@NoArgsConstructor
  12 +@AllArgsConstructor
  13 +@Data
  14 +public class CrmStatisticBusinessEndStatusRespVO {
  15 +
  16 + @Schema(description = "结束状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Integer endStatus;
  18 +
  19 + @Schema(description = "商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  20 + private Long businessCount;
  21 +
  22 + @Schema(description = "商机总金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  23 + private BigDecimal totalPrice;
  24 +
  25 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticFunnelRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +@Schema(description = "管理后台 - CRM 销售漏斗 Response VO")
  9 +@NoArgsConstructor
  10 +@AllArgsConstructor
  11 +@Data
  12 +public class CrmStatisticFunnelRespVO {
  13 +
  14 + @Schema(description = "客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  15 + private Long customerCount;
  16 +
  17 + @Schema(description = "商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + private Long businessCount;
  19 +
  20 + @Schema(description = "赢单数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  21 + private Long winCount;
  22 +
  23 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsBusinessRespVO.java 0 → 100644
  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.AllArgsConstructor;
  7 +import lombok.Data;
  8 +import lombok.NoArgsConstructor;
  9 +
  10 +import java.math.BigDecimal;
  11 +import java.time.LocalDateTime;
  12 +import java.util.List;
  13 +
  14 +@Schema(description = "管理后台 - CRM 商机 Response VO")
  15 +@Data
  16 +@ExcelIgnoreUnannotated
  17 +public class CrmStatisticsBusinessRespVO {
  18 +
  19 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
  20 + @ExcelProperty("编号")
  21 + private Long id;
  22 +
  23 + @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  24 + @ExcelProperty("商机名称")
  25 + private String name;
  26 +
  27 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
  28 + private Long customerId;
  29 + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  30 + @ExcelProperty("客户名称")
  31 + private String customerName;
  32 +
  33 + @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example ="true")
  34 + @ExcelProperty("跟进状态")
  35 + private Boolean followUpStatus;
  36 +
  37 + @Schema(description = "最后跟进时间")
  38 + @ExcelProperty("最后跟进时间")
  39 + private LocalDateTime contactLastTime;
  40 +
  41 + @Schema(description = "下次联系时间")
  42 + @ExcelProperty("下次联系时间")
  43 + private LocalDateTime contactNextTime;
  44 +
  45 + @Schema(description = "负责人的用户编号", example = "25682")
  46 + @ExcelProperty("负责人的用户编号")
  47 + private Long ownerUserId;
  48 + @Schema(description = "负责人名字", example = "25682")
  49 + @ExcelProperty("负责人名字")
  50 + private String ownerUserName;
  51 + @Schema(description = "负责人部门")
  52 + @ExcelProperty("负责人部门")
  53 + private String ownerUserDeptName;
  54 +
  55 + @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
  56 + private Long statusTypeId;
  57 + @Schema(description = "商机状组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中")
  58 + @ExcelProperty("商机状态组")
  59 + private String statusTypeName;
  60 +
  61 + @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
  62 + private Long statusId;
  63 + @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中")
  64 + @ExcelProperty("商机状态")
  65 + private String statusName;
  66 +
  67 + @Schema
  68 + @ExcelProperty("结束状态")
  69 + private Integer endStatus;
  70 +
  71 + @ExcelProperty("结束时的备注")
  72 + private String endRemark;
  73 +
  74 + @Schema(description = "预计成交日期")
  75 + @ExcelProperty("预计成交日期")
  76 + private LocalDateTime dealTime;
  77 +
  78 + @Schema(description = "产品总金额", example = "12025")
  79 + @ExcelProperty("产品总金额")
  80 + private BigDecimal totalProductPrice;
  81 +
  82 + @Schema(description = "整单折扣")
  83 + @ExcelProperty("整单折扣")
  84 + private BigDecimal discountPercent;
  85 +
  86 + @Schema(description = "商机总金额", example = "12371")
  87 + @ExcelProperty("商机总金额")
  88 + private BigDecimal totalPrice;
  89 +
  90 + @Schema(description = "备注", example = "随便")
  91 + @ExcelProperty("备注")
  92 + private String remark;
  93 +
  94 + @Schema(description = "创建人", example = "1024")
  95 + @ExcelProperty("创建人")
  96 + private String creator;
  97 + @Schema(description = "创建人名字", example = "芋道源码")
  98 + @ExcelProperty("创建人名字")
  99 + private String creatorName;
  100 +
  101 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  102 + @ExcelProperty("创建时间")
  103 + private LocalDateTime createTime;
  104 +
  105 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  106 + @ExcelProperty("更新时间")
  107 + private LocalDateTime updateTime;
  108 +
  109 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsBusinessSummaryByDateRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
  7 +
  8 +@Schema(description = "管理后台 - CRM 新增商机分析(按日期) VO")
  9 +@Data
  10 +public class CrmStatisticsBusinessSummaryByDateRespVO {
  11 +
  12 + @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
  13 + private String time;
  14 +
  15 + @Schema(description = "新增商机数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  16 + private Integer businessCreateCount;
  17 +
  18 + @Schema(description = "新增商机金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + private BigDecimal businessDealCount;
  20 +
  21 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsFunnelReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import jakarta.validation.constraints.NotNull;
  8 +import lombok.Data;
  9 +import lombok.EqualsAndHashCode;
  10 +import lombok.ToString;
  11 +import org.springframework.format.annotation.DateTimeFormat;
  12 +
  13 +import java.time.LocalDateTime;
  14 +import java.util.List;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  17 +
  18 +@Schema(description = "管理后台 - CRM 销售漏斗 Request VO")
  19 +@Data
  20 +@EqualsAndHashCode(callSuper = true)
  21 +@ToString(callSuper = true)
  22 +public class CrmStatisticsFunnelReqVO extends PageParam {
  23 +
  24 + @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + @NotNull(message = "部门 id 不能为空")
  26 + private Long deptId;
  27 +
  28 + /**
  29 + * 负责人用户 id, 当用户为空, 则计算部门下用户
  30 + */
  31 + @Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
  32 + private Long userId;
  33 +
  34 + /**
  35 + * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
  36 + * 后续,可能会支持选择部分用户进行查询
  37 + */
  38 + @Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
  39 + private List<Long> userIds;
  40 +
  41 + @Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  42 + @InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
  43 + private Integer interval;
  44 +
  45 + /**
  46 + * 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
  47 + * 并作为参数传递给Mapper
  48 + */
  49 + @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
  50 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  51 + private LocalDateTime[] times;
  52 +
  53 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
1 package cn.iocoder.yudao.module.crm.dal.mysql.business; 1 package cn.iocoder.yudao.module.crm.dal.mysql.business;
2 2
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; 7 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; 8 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
8 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; 9 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
9 -import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;  
10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; 11 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
12 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 12 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
13 import org.apache.ibatis.annotations.Mapper; 13 import org.apache.ibatis.annotations.Mapper;
14 14
  15 +import java.time.LocalDateTime;
15 import java.util.Collection; 16 import java.util.Collection;
16 import java.util.List; 17 import java.util.List;
17 18
@@ -59,10 +60,36 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; { @@ -59,10 +60,36 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
59 return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId); 60 return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId);
60 } 61 }
61 62
62 - default List<CrmBusinessDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId){ 63 + default List<CrmBusinessDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
63 return selectList(new LambdaQueryWrapperX<CrmBusinessDO>() 64 return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
64 .eq(CrmBusinessDO::getCustomerId, customerId) 65 .eq(CrmBusinessDO::getCustomerId, customerId)
65 .eq(CrmBusinessDO::getOwnerUserId, ownerUserId)); 66 .eq(CrmBusinessDO::getOwnerUserId, ownerUserId));
66 } 67 }
67 68
  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 + default List<CrmBusinessDO> selectListByOwnerUserIdsAndEndStatusNotNull(Collection<Long> ownerUserIds, LocalDateTime[] times) {
  77 + return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
  78 + .in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
  79 + .betweenIfPresent(CrmBusinessDO::getCreateTime, times)
  80 + .isNotNull(CrmBusinessDO::getEndStatus));
  81 + }
  82 +
  83 + default List<CrmBusinessDO> selectListByOwnerUserIdsAndDate(Collection<Long> ownerUserIds, LocalDateTime[] times) {
  84 + return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
  85 + .in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
  86 + .betweenIfPresent(CrmBusinessDO::getCreateTime, times));
  87 + }
  88 +
  89 + default PageResult<CrmBusinessDO> selectPage(Collection<Long> ownerUserIds, LocalDateTime[] times, Integer pageNo, Integer pageSize) {
  90 + return selectPage(new PageParam().setPageNo(pageNo).setPageSize(pageSize), new LambdaQueryWrapperX<CrmBusinessDO>()
  91 + .in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
  92 + .betweenIfPresent(CrmBusinessDO::getCreateTime, times));
  93 + }
  94 +
68 } 95 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
@@ -19,9 +19,11 @@ import org.apache.ibatis.annotations.Mapper; @@ -19,9 +19,11 @@ import org.apache.ibatis.annotations.Mapper;
19 import org.springframework.lang.Nullable; 19 import org.springframework.lang.Nullable;
20 import org.springframework.util.Assert; 20 import org.springframework.util.Assert;
21 21
  22 +import javax.management.ObjectName;
22 import java.time.LocalDateTime; 23 import java.time.LocalDateTime;
23 import java.util.Collection; 24 import java.util.Collection;
24 import java.util.List; 25 import java.util.List;
  26 +import java.util.Set;
25 27
26 /** 28 /**
27 * 客户 Mapper 29 * 客户 Mapper
@@ -186,4 +188,10 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; { @@ -186,4 +188,10 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; {
186 return selectCount(query); 188 return selectCount(query);
187 } 189 }
188 190
  191 + default Long selectCountByOwnerUserIds(Collection<Long> ownerUserIds, LocalDateTime[] times){
  192 + return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>()
  193 + .in(CrmCustomerDO::getOwnerUserId, ownerUserIds)
  194 + .betweenIfPresent(CrmCustomerDO::getCreateTime, times));
  195 + }
  196 +
189 } 197 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsFunnelMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
  2 +
  3 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * CRM 销售漏斗 Mapper
  12 + *
  13 + * @author HUIHUI
  14 + */
  15 +@Mapper
  16 +public interface CrmStatisticsFunnelMapper {
  17 +
  18 + List<CrmStatisticsBusinessSummaryByDateRespVO> selectBusinessCreateCountGroupByDate(CrmStatisticsFunnelReqVO reqVO);
  19 +
  20 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
@@ -194,4 +194,42 @@ public interface CrmBusinessService { @@ -194,4 +194,42 @@ public interface CrmBusinessService {
194 */ 194 */
195 List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId); 195 List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
196 196
  197 + /**
  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 + /**
  208 + * 获得商机列表【数据统计】
  209 + *
  210 + * @param ownerUserIds 负责人编号
  211 + * @param times 时间范围
  212 + * @return 商机列表
  213 + */
  214 + List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndEndStatusNotNull(List<Long> ownerUserIds, LocalDateTime[] times);
  215 +
  216 + /**
  217 + * 获得商机列表【数据统计】
  218 + *
  219 + * @param ownerUserIds 负责人编号
  220 + * @param times 时间范围
  221 + * @return 商机列表
  222 + */
  223 + List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndDate(List<Long> ownerUserIds, LocalDateTime[] times);
  224 +
  225 + /**
  226 + * 商机分页【数据统计】
  227 + * @param ownerUserIds 负责人编号
  228 + * @param times 时间范围
  229 + * @param pageNo 页码
  230 + * @param pageSize 数量
  231 + * @return 商机分页
  232 + */
  233 + PageResult<CrmBusinessDO> getBusinessPageByDate(List<Long> ownerUserIds, LocalDateTime[] times, Integer pageNo, Integer pageSize);
  234 +
197 } 235 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -375,4 +375,34 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -375,4 +375,34 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
375 return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId); 375 return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
376 } 376 }
377 377
  378 + @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 + @Override
  387 + public List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndEndStatusNotNull(List<Long> ownerUserIds, LocalDateTime[] times) {
  388 + if (CollUtil.isEmpty(ownerUserIds)) {
  389 + return Collections.emptyList();
  390 + }
  391 + return businessMapper.selectListByOwnerUserIdsAndEndStatusNotNull(convertSet(ownerUserIds), times);
  392 + }
  393 +
  394 + @Override
  395 + public List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndDate(List<Long> ownerUserIds, LocalDateTime[] times) {
  396 + if (CollUtil.isEmpty(ownerUserIds)) {
  397 + return Collections.emptyList();
  398 + }
  399 +
  400 + return businessMapper.selectListByOwnerUserIdsAndDate(convertSet(ownerUserIds), times);
  401 + }
  402 +
  403 + @Override
  404 + public PageResult<CrmBusinessDO> getBusinessPageByDate(List<Long> ownerUserIds, LocalDateTime[] times, Integer pageNo, Integer pageSize) {
  405 + return businessMapper.selectPage(ownerUserIds, times, pageNo, pageSize);
  406 + }
  407 +
378 } 408 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
@@ -39,7 +39,7 @@ public interface CrmCustomerService { @@ -39,7 +39,7 @@ public interface CrmCustomerService {
39 /** 39 /**
40 * 更新客户的跟进状态 40 * 更新客户的跟进状态
41 * 41 *
42 - * @param id 编号 42 + * @param id 编号
43 * @param dealStatus 跟进状态 43 * @param dealStatus 跟进状态
44 */ 44 */
45 void updateCustomerDealStatus(Long id, Boolean dealStatus); 45 void updateCustomerDealStatus(Long id, Boolean dealStatus);
@@ -47,8 +47,8 @@ public interface CrmCustomerService { @@ -47,8 +47,8 @@ public interface CrmCustomerService {
47 /** 47 /**
48 * 更新客户相关的跟进信息 48 * 更新客户相关的跟进信息
49 * 49 *
50 - * @param id 编号  
51 - * @param contactNextTime 下次联系时间 50 + * @param id 编号
  51 + * @param contactNextTime 下次联系时间
52 * @param contactLastContent 最后联系内容 52 * @param contactLastContent 最后联系内容
53 */ 53 */
54 void updateCustomerFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent); 54 void updateCustomerFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
@@ -99,8 +99,8 @@ public interface CrmCustomerService { @@ -99,8 +99,8 @@ public interface CrmCustomerService {
99 /** 99 /**
100 * 获得放入公海提醒的客户分页 100 * 获得放入公海提醒的客户分页
101 * 101 *
102 - * @param pageVO 分页查询  
103 - * @param userId 用户编号 102 + * @param pageVO 分页查询
  103 + * @param userId 用户编号
104 * @return 客户分页 104 * @return 客户分页
105 */ 105 */
106 PageResult<CrmCustomerDO> getPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, Long userId); 106 PageResult<CrmCustomerDO> getPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, Long userId);
@@ -108,7 +108,7 @@ public interface CrmCustomerService { @@ -108,7 +108,7 @@ public interface CrmCustomerService {
108 /** 108 /**
109 * 获得待进入公海的客户数量 109 * 获得待进入公海的客户数量
110 * 110 *
111 - * @param userId 用户编号 111 + * @param userId 用户编号
112 * @return 提醒数量 112 * @return 提醒数量
113 */ 113 */
114 Long getPutPoolRemindCustomerCount(Long userId); 114 Long getPutPoolRemindCustomerCount(Long userId);
@@ -195,4 +195,13 @@ public interface CrmCustomerService { @@ -195,4 +195,13 @@ public interface CrmCustomerService {
195 */ 195 */
196 int autoPutCustomerPool(); 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 +
198 } 207 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -47,6 +47,7 @@ import java.time.LocalDateTime; @@ -47,6 +47,7 @@ import java.time.LocalDateTime;
47 import java.util.*; 47 import java.util.*;
48 48
49 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 49 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  50 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
50 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; 51 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
51 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; 52 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
52 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; 53 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
@@ -650,6 +651,14 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @@ -650,6 +651,14 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
650 } 651 }
651 } 652 }
652 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 +
653 /** 662 /**
654 * 获得自身的代理对象,解决 AOP 生效问题 663 * 获得自身的代理对象,解决 AOP 生效问题
655 * 664 *
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.statistics;
  2 +
  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;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
  8 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * CRM 销售漏斗分析 Service
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +public interface CrmStatisticsFunnelService {
  18 +
  19 + /**
  20 + * 获得销售漏斗数据
  21 + *
  22 + * @param reqVO 请求
  23 + * @return 销售漏斗数据
  24 + */
  25 + CrmStatisticFunnelRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO);
  26 +
  27 + /**
  28 + * 获得商机结束状态统计
  29 + *
  30 + * @param reqVO 请求
  31 + * @return 商机结束状态统计
  32 + */
  33 + List<CrmStatisticBusinessEndStatusRespVO> getBusinessEndStatusSummary(CrmStatisticsFunnelReqVO reqVO);
  34 +
  35 + /**
  36 + * 获取新增商机分析(按日期)
  37 + *
  38 + * @param reqVO 请求
  39 + * @return 新增商机分析
  40 + */
  41 + List<CrmStatisticsBusinessSummaryByDateRespVO> getBusinessSummaryByDate(CrmStatisticsFunnelReqVO reqVO);
  42 +
  43 + /**
  44 + * 获得商机分页(按日期)
  45 + *
  46 + * @param pageVO 请求
  47 + * @return 商机分页
  48 + */
  49 + PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO);
  50 +
  51 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.statistics;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  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;
  9 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
  10 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
  11 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  12 +import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsFunnelMapper;
  13 +import cn.iocoder.yudao.module.crm.enums.business.CrmBusinessEndStatusEnum;
  14 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
  15 +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  16 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  17 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  18 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  19 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  20 +import jakarta.annotation.Resource;
  21 +import org.springframework.stereotype.Service;
  22 +
  23 +import java.math.BigDecimal;
  24 +import java.time.LocalDateTime;
  25 +import java.time.format.DateTimeFormatter;
  26 +import java.util.Collections;
  27 +import java.util.List;
  28 +import java.util.Map;
  29 +import java.util.stream.Collectors;
  30 +
  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;
  33 +
  34 +/**
  35 + * CRM 销售漏斗分析 Service 实现类
  36 + *
  37 + * @author HUIHUI
  38 + */
  39 +@Service
  40 +public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelService {
  41 +
  42 + @Resource
  43 + private CrmStatisticsFunnelMapper funnelMapper;
  44 +
  45 + @Resource
  46 + private AdminUserApi adminUserApi;
  47 + @Resource
  48 + private CrmCustomerService customerService;
  49 + @Resource
  50 + private CrmBusinessService businessService;
  51 + @Resource
  52 + private DeptApi deptApi;
  53 +
  54 + @Override
  55 + public CrmStatisticFunnelRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO) {
  56 + // 1. 获得用户编号数组
  57 + List<Long> userIds = getUserIds(reqVO);
  58 + if (CollUtil.isEmpty(userIds)) {
  59 + return null;
  60 + }
  61 + reqVO.setUserIds(userIds);
  62 +
  63 + // 2. 获得漏斗数据
  64 + return new CrmStatisticFunnelRespVO(
  65 + customerService.getCustomerCountByOwnerUserIds(userIds, reqVO.getTimes()),
  66 + businessService.getBusinessCountByOwnerUserIdsAndEndStatus(userIds, reqVO.getTimes(), null),
  67 + businessService.getBusinessCountByOwnerUserIdsAndEndStatus(userIds, reqVO.getTimes(), CrmBusinessEndStatusEnum.WIN.getStatus())
  68 + );
  69 + }
  70 +
  71 + @Override
  72 + public List<CrmStatisticBusinessEndStatusRespVO> getBusinessEndStatusSummary(CrmStatisticsFunnelReqVO reqVO) {
  73 + // 1. 获得用户编号数组
  74 + reqVO.setUserIds(getUserIds(reqVO));
  75 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  76 + return Collections.emptyList();
  77 + }
  78 +
  79 + // 2.1 获得用户负责的商机
  80 + List<CrmBusinessDO> businessList = businessService.getBusinessListByOwnerUserIdsAndEndStatusNotNull(reqVO.getUserIds(), reqVO.getTimes());
  81 + // 2.2 统计各阶段数据
  82 + Map<Integer, List<CrmBusinessDO>> businessMap = convertMultiMap(businessList, CrmBusinessDO::getEndStatus);
  83 + return convertList(CrmBusinessEndStatusEnum.values(), endStatusEnum -> {
  84 + List<CrmBusinessDO> list = businessMap.get(endStatusEnum.getStatus());
  85 + if (CollUtil.isEmpty(list)) {
  86 + return new CrmStatisticBusinessEndStatusRespVO(endStatusEnum.getStatus(), 0L, BigDecimal.ZERO);
  87 + }
  88 + return new CrmStatisticBusinessEndStatusRespVO(endStatusEnum.getStatus(), (long) list.size(),
  89 + getSumValue(list, CrmBusinessDO::getTotalPrice, BigDecimal::add));
  90 + });
  91 + }
  92 +
  93 + @Override
  94 + public List<CrmStatisticsBusinessSummaryByDateRespVO> getBusinessSummaryByDate(CrmStatisticsFunnelReqVO reqVO) {
  95 + // 1. 获得用户编号数组
  96 + reqVO.setUserIds(getUserIds(reqVO));
  97 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  98 + return Collections.emptyList();
  99 + }
  100 +
  101 + // 2. 按天统计,获取分项统计数据
  102 + List<CrmStatisticsBusinessSummaryByDateRespVO> businessCreateCountList = funnelMapper.selectBusinessCreateCountGroupByDate(reqVO);
  103 + List<CrmBusinessDO> businessList = businessService.getBusinessListByOwnerUserIdsAndDate(reqVO.getUserIds(), reqVO.getTimes());
  104 + Map<String, BigDecimal> businessDealCountMap = businessList.stream().collect(Collectors.groupingBy(business ->
  105 + business.getCreateTime().format(DateTimeFormatter.ofPattern(FORMAT_YEAR_MONTH_DAY)),
  106 + Collectors.reducing(BigDecimal.ZERO, CrmBusinessDO::getTotalPrice, BigDecimal::add)));
  107 +
  108 + // 3. 按照日期间隔,合并数据
  109 + List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
  110 + return convertList(timeRanges, times -> {
  111 + Integer businessCreateCount = businessCreateCountList.stream()
  112 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
  113 + .mapToInt(CrmStatisticsBusinessSummaryByDateRespVO::getBusinessCreateCount).sum();
  114 + BigDecimal businessDealCount = businessDealCountMap.entrySet().stream()
  115 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getKey()))
  116 + .map(Map.Entry::getValue)
  117 + .reduce(BigDecimal.ZERO, BigDecimal::add);
  118 + return new CrmStatisticsBusinessSummaryByDateRespVO()
  119 + .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
  120 + .setBusinessCreateCount(businessCreateCount).setBusinessDealCount(businessDealCount);
  121 + });
  122 + }
  123 +
  124 + @Override
  125 + public PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO) {
  126 + // 1. 获得用户编号数组
  127 + pageVO.setUserIds(getUserIds(pageVO));
  128 + if (CollUtil.isEmpty(pageVO.getUserIds())) {
  129 + return PageResult.empty();
  130 + }
  131 +
  132 + return businessService.getBusinessPageByDate(pageVO.getUserIds(), pageVO.getTimes(), pageVO.getPageNo(), pageVO.getPageSize());
  133 + }
  134 +
  135 + /**
  136 + * 获取用户编号数组。如果用户编号为空, 则获得部门下的用户编号数组,包括子部门的所有用户编号
  137 + *
  138 + * @param reqVO 请求参数
  139 + * @return 用户编号数组
  140 + */
  141 + private List<Long> getUserIds(CrmStatisticsFunnelReqVO reqVO) {
  142 + // 情况一:选中某个用户
  143 + if (ObjUtil.isNotNull(reqVO.getUserId())) {
  144 + return List.of(reqVO.getUserId());
  145 + }
  146 + // 情况二:选中某个部门
  147 + // 2.1 获得部门列表
  148 + List<Long> deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId);
  149 + deptIds.add(reqVO.getDeptId());
  150 + // 2.2 获得用户编号
  151 + return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
  152 + }
  153 +
  154 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPortraitServiceImpl.java
@@ -20,7 +20,6 @@ import java.util.Map; @@ -20,7 +20,6 @@ import java.util.Map;
20 20
21 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 21 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
22 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 22 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
23 -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;  
24 23
25 /** 24 /**
26 * CRM 客户画像 Service 实现类 25 * CRM 客户画像 Service 实现类
@@ -55,15 +54,18 @@ public class CrmStatisticsPortraitServiceImpl implements CrmStatisticsPortraitSe @@ -55,15 +54,18 @@ public class CrmStatisticsPortraitServiceImpl implements CrmStatisticsPortraitSe
55 54
56 // 3. 拼接数据 55 // 3. 拼接数据
57 List<Area> areaList = AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area); 56 List<Area> areaList = AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area);
58 - areaList.add(new Area().setId(null).setName("未知")); // TODO @puhui999:是不是 65 find 的逻辑改下;不用 findAndThen,直接从 areaMap 拿;拿到就设置,不拿到就设置 null 和 未知;这样,58 本行可以删除掉完事了;这样代码更简单和一致  
59 Map<Integer, Area> areaMap = convertMap(areaList, Area::getId); 57 Map<Integer, Area> areaMap = convertMap(areaList, Area::getId);
60 return convertList(list, item -> { 58 return convertList(list, item -> {
61 Integer parentId = AreaUtils.getParentIdByType(item.getAreaId(), AreaTypeEnum.PROVINCE); 59 Integer parentId = AreaUtils.getParentIdByType(item.getAreaId(), AreaTypeEnum.PROVINCE);
62 - if (parentId == null) { // 找不到,归到未知  
63 - return item.setAreaId(null).setAreaName("未知"); 60 + if (parentId != null) {
  61 + Area area = areaMap.get(parentId);
  62 + if (area != null) {
  63 + item.setAreaId(parentId).setAreaName(area.getName());
  64 + return item;
  65 + }
64 } 66 }
65 - findAndThen(areaMap, parentId, area -> item.setAreaId(parentId).setAreaName(area.getName()));  
66 - return item; 67 + // 找不到,归到未知
  68 + return item.setAreaId(null).setAreaName("未知");
67 }); 69 });
68 } 70 }
69 71
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsFunnelMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3 +<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsFunnelMapper">
  4 +
  5 + <select id="selectBusinessCreateCountGroupByDate"
  6 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO">
  7 + SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time, COUNT(*) AS businessCreateCount
  8 + FROM crm_business
  9 + WHERE deleted = 0
  10 + AND owner_user_id IN
  11 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  12 + #{userId}
  13 + </foreach>
  14 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
  15 + #{times[1],javaType=java.time.LocalDateTime}
  16 + GROUP BY time
  17 + </select>
  18 +
  19 +</mapper>