crm:code review 销售漏斗

This commit is contained in:
YunaiV 2024-04-14 21:22:02 +08:00
parent 0eff2ae602
commit ed5f3a6bc2
13 changed files with 38 additions and 82 deletions

View File

@ -177,7 +177,7 @@ public class CrmBusinessController {
buildBusinessDetailList(list));
}
private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
public List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
if (CollUtil.isEmpty(list)) {
return Collections.emptyList();
}

View File

@ -1,28 +1,16 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.business.CrmBusinessController;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticBusinessEndStatusRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticFunnelRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsFunnelService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
@ -33,14 +21,9 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - CRM 销售漏斗")
@RestController
@ -48,31 +31,20 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
@Validated
public class CrmStatisticsFunnelController {
// TODO @puhui999crmStatisticsFunnelService 改成 funnelService 更好点哈
@Resource
private CrmStatisticsFunnelService crmStatisticsFunnelService;
@Resource
private CrmBusinessService businessService;
@Resource
private CrmCustomerService customerService;
@Resource
private CrmBusinessStatusService businessStatusTypeService;
@Resource
private CrmBusinessStatusService businessStatusService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@GetMapping("/get-funnel-summary")
@Operation(summary = "获取销售漏斗统计数据", description = "用于【销售漏斗】页面")
@Operation(summary = "获取销售漏斗统计数据", description = "用于【销售漏斗】页面的【销售漏斗分析】")
@PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
public CommonResult<CrmStatisticFunnelRespVO> getFunnelSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
return success(crmStatisticsFunnelService.getFunnelSummary(reqVO));
}
// TODO @puhui这个接口应该是 getBusinessSummaryByEndStatus这样更统一哈
@GetMapping("/get-business-end-status-summary")
@Operation(summary = "获取商机结束状态统计", description = "用于【销售漏斗】页面")
@Operation(summary = "获取商机结束状态统计", description = "用于【销售漏斗】页面的【销售漏斗分析】")
@PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
public CommonResult<List<CrmStatisticBusinessEndStatusRespVO>> getBusinessEndStatusSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
return success(crmStatisticsFunnelService.getBusinessEndStatusSummary(reqVO));
@ -86,7 +58,7 @@ public class CrmStatisticsFunnelController {
}
@GetMapping("/get-business-page-by-date")
@Operation(summary = "获得商机分页(按日期)", description = "用于【销售漏斗】页面")
@Operation(summary = "获得商机分页(按日期)", description = "用于【销售漏斗】页面的【新增商机分析】")
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByDate(@Valid CrmStatisticsFunnelReqVO pageVO) {
PageResult<CrmBusinessDO> pageResult = crmStatisticsFunnelService.getBusinessPageByDate(pageVO);
@ -94,37 +66,7 @@ public class CrmStatisticsFunnelController {
}
private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
if (CollUtil.isEmpty(list)) {
return Collections.emptyList();
}
// 1.1 获取客户列表
Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
convertSet(list, CrmBusinessDO::getCustomerId));
// 1.2 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 1.3 获得商机状态组
Map<Long, CrmBusinessStatusTypeDO> statusTypeMap = businessStatusTypeService.getBusinessStatusTypeMap(
convertSet(list, CrmBusinessDO::getStatusTypeId));
Map<Long, CrmBusinessStatusDO> statusMap = businessStatusService.getBusinessStatusMap(
convertSet(list, CrmBusinessDO::getStatusId));
// 2. 拼接数据
return BeanUtils.toBean(list, CrmBusinessRespVO.class, businessVO -> {
// 2.1 设置客户名称
MapUtils.findAndThen(customerMap, businessVO.getCustomerId(), customer -> businessVO.setCustomerName(customer.getName()));
// 2.2 设置创建人负责人名称
MapUtils.findAndThen(userMap, NumberUtils.parseLong(businessVO.getCreator()),
user -> businessVO.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, businessVO.getOwnerUserId(), user -> {
businessVO.setOwnerUserName(user.getNickname());
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> businessVO.setOwnerUserDeptName(dept.getName()));
});
// 2.3 设置商机状态
MapUtils.findAndThen(statusTypeMap, businessVO.getStatusTypeId(), statusType -> businessVO.setStatusTypeName(statusType.getName()));
MapUtils.findAndThen(statusMap, businessVO.getStatusId(), status -> businessVO.setStatusName(
businessService.getBusinessStatusName(businessVO.getEndStatus(), status)));
});
return SpringUtil.getBean(CrmBusinessController.class).buildBusinessDetailList(list);
}
}

View File

@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
import java.math.BigDecimal;
// TODO @puhui999改成 CrmStatisticsBusinessSummaryByEndStatusRespVO按照结束状态
@Schema(description = "管理后台 - CRM 商机结束状态统计 Response VO")
@NoArgsConstructor
@AllArgsConstructor

View File

@ -5,6 +5,7 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// TODO @puhui999改成 CrmStatisticFunnelSummaryRespVO 更有统计的味道
@Schema(description = "管理后台 - CRM 销售漏斗 Response VO")
@NoArgsConstructor
@AllArgsConstructor
@ -17,6 +18,7 @@ public class CrmStatisticFunnelRespVO {
@Schema(description = "商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long businessCount;
// TODO @puhui999这个改成 businessWinCount 可能会更合适点哈
@Schema(description = "赢单数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long winCount;

View File

@ -3,14 +3,12 @@ package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
// TODO @puhui999是不是可以删除哈
@Schema(description = "管理后台 - CRM 商机 Response VO")
@Data
@ExcelIgnoreUnannotated

View File

@ -15,6 +15,7 @@ public class CrmStatisticsBusinessSummaryByDateRespVO {
@Schema(description = "新增商机数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer businessCreateCount;
// TODO @puhui999是不是金额哈不是数量
@Schema(description = "新增商机金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private BigDecimal businessDealCount;

View File

@ -17,6 +17,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@Schema(description = "管理后台 - CRM 销售漏斗 Request VO")
@Data
// TODO @puhui999不用写 EqualsAndHashCodeToString已经全局 lombok
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmStatisticsFunnelReqVO extends PageParam {
@ -42,6 +43,7 @@ public class CrmStatisticsFunnelReqVO extends PageParam {
@InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
private Integer interval;
// TODO @puhui999这个全部前端传递哈参考 CrmStatisticsCustomerReqVO
/**
* 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
* 并作为参数传递给Mapper

View File

@ -73,6 +73,7 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
.betweenIfPresent(CrmBusinessDO::getCreateTime, times));
}
// TODO @puhui999这个可以优化下通过统计 sql不通过内存计算
default List<CrmBusinessDO> selectListByOwnerUserIdsAndEndStatusNotNull(Collection<Long> ownerUserIds, LocalDateTime[] times) {
return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
.in(CrmBusinessDO::getOwnerUserId, ownerUserIds)
@ -80,6 +81,7 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
.isNotNull(CrmBusinessDO::getEndStatus));
}
// TODO @puhui999这个可以优化下通过统计 sql不通过内存计算
default List<CrmBusinessDO> selectListByOwnerUserIdsAndDate(Collection<Long> ownerUserIds, LocalDateTime[] times) {
return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
.in(CrmBusinessDO::getOwnerUserId, ownerUserIds)

View File

@ -19,11 +19,9 @@ import org.apache.ibatis.annotations.Mapper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import javax.management.ObjectName;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* 客户 Mapper
@ -188,6 +186,7 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
return selectCount(query);
}
// TODO @puhui999这个可以优化下通过统计 sql不通过内存计算
default Long selectCountByOwnerUserIds(Collection<Long> ownerUserIds, LocalDateTime[] times){
return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>()
.in(CrmCustomerDO::getOwnerUserId, ownerUserIds)

View File

@ -199,11 +199,12 @@ public interface CrmBusinessService {
*
* @param ownerUserIds 负责人编号
* @param times 时间范围
* @param endStatus 商机结束状态
* @param endStatus 商机结束状态允许为空
* @return 商机数
*/
Long getBusinessCountByOwnerUserIdsAndEndStatus(List<Long> ownerUserIds, LocalDateTime[] times, Integer endStatus);
// TODO @puhui999这个可以优化下通过统计 sql不通过内存计算
/**
* 获得商机列表数据统计
*
@ -213,6 +214,7 @@ public interface CrmBusinessService {
*/
List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndEndStatusNotNull(List<Long> ownerUserIds, LocalDateTime[] times);
// TODO @puhui999这个可以优化下通过统计 sql不通过内存计算
/**
* 获得商机列表数据统计
*
@ -223,10 +225,11 @@ public interface CrmBusinessService {
List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndDate(List<Long> ownerUserIds, LocalDateTime[] times);
/**
* 商机分页数据统计
* 获得商机分页目前用于数据统计
*
* @param ownerUserIds 负责人编号
* @param times 时间范围
* @param pageNo 页码
* @param pageNo 页码 TODO @puhui999直接传递 CrmStatisticsFunnelReqVO 虽然有点耦合但是更清晰一点
* @param pageSize 数量
* @return 商机分页
*/

View File

@ -383,6 +383,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
return businessMapper.selectCountByOwnerUserIdsAndEndStatus(convertSet(ownerUserIds), times, endStatus);
}
// TODO @puhui999这个可以优化下通过统计 sql不通过内存计算
@Override
public List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndEndStatusNotNull(List<Long> ownerUserIds, LocalDateTime[] times) {
if (CollUtil.isEmpty(ownerUserIds)) {
@ -391,6 +392,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
return businessMapper.selectListByOwnerUserIdsAndEndStatusNotNull(convertSet(ownerUserIds), times);
}
// TODO @puhui999这个可以优化下通过统计 sql不通过内存计算
@Override
public List<CrmBusinessDO> getBusinessListByOwnerUserIdsAndDate(List<Long> ownerUserIds, LocalDateTime[] times) {
if (CollUtil.isEmpty(ownerUserIds)) {

View File

@ -51,6 +51,7 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
@Resource
private DeptApi deptApi;
// TODO @puhui999貌似想了下可能还是得按照
@Override
public CrmStatisticFunnelRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO) {
// 1. 获得用户编号数组
@ -76,6 +77,7 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
return Collections.emptyList();
}
// TODO @puhui999这个可以优化下通过统计 sql不通过内存计算
// 2.1 获得用户负责的商机
List<CrmBusinessDO> businessList = businessService.getBusinessListByOwnerUserIdsAndEndStatusNotNull(reqVO.getUserIds(), reqVO.getTimes());
// 2.2 统计各阶段数据
@ -99,6 +101,7 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
}
// 2. 按天统计获取分项统计数据
// TODO @puhui999可以这个统计返回的时候就把数量金额一起统计好
List<CrmStatisticsBusinessSummaryByDateRespVO> businessCreateCountList = funnelMapper.selectBusinessCreateCountGroupByDate(reqVO);
List<CrmBusinessDO> businessList = businessService.getBusinessListByOwnerUserIdsAndDate(reqVO.getUserIds(), reqVO.getTimes());
Map<String, BigDecimal> businessDealCountMap = businessList.stream().collect(Collectors.groupingBy(business ->
@ -128,7 +131,7 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
if (CollUtil.isEmpty(pageVO.getUserIds())) {
return PageResult.empty();
}
// 2. 执行查询
return businessService.getBusinessPageByDate(pageVO.getUserIds(), pageVO.getTimes(), pageVO.getPageNo(), pageVO.getPageSize());
}

View File

@ -4,15 +4,16 @@
<select id="selectBusinessCreateCountGroupByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO">
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time, COUNT(*) AS businessCreateCount
SELECT
DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
COUNT(*) AS businessCreateCount
FROM crm_business
WHERE deleted = 0
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
#{times[1],javaType=java.time.LocalDateTime}
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY time
</select>