feat(crm): 新增 CRM 统计分析与业绩目标能力

- 新增客户公海、销售漏斗、产品销售、合同汇总等统计分析
- 新增业绩目标设置与业绩达成统计
- 新增客户负责人变更记录能力,并用于公海客户分析
- 完善销售漏斗阶段统计、商机转化率和明细筛选
- 补充产品分类、时间范围、空金额等统计边界处理
- 新增相关前端页面、API 与数据库菜单配置
This commit is contained in:
YunaiV
2026-07-05 22:46:21 +08:00
parent e001bbbdbb
commit 214259ff0e
51 changed files with 2216 additions and 75 deletions
@@ -0,0 +1,149 @@
package cn.iocoder.yudao.module.crm.controller.admin.performance;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
import cn.iocoder.yudao.module.crm.service.performance.CrmPerformanceConfigService;
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.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
@Tag(name = "管理后台 - CRM 业绩目标设置")
@RestController
@RequestMapping("/crm/performance-config")
@Validated
public class CrmPerformanceConfigController {
@Resource
private CrmPerformanceConfigService performanceConfigService;
@Resource
private DeptApi deptApi;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建业绩目标")
@PreAuthorize("@ss.hasPermission('crm:performance-config:create')")
public CommonResult<Long> createPerformanceConfig(@Valid @RequestBody CrmPerformanceConfigSaveReqVO createReqVO) {
return success(performanceConfigService.createPerformanceConfig(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新业绩目标")
@PreAuthorize("@ss.hasPermission('crm:performance-config:update')")
public CommonResult<Boolean> updatePerformanceConfig(@Valid @RequestBody CrmPerformanceConfigSaveReqVO updateReqVO) {
performanceConfigService.updatePerformanceConfig(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除业绩目标")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:performance-config:delete')")
public CommonResult<Boolean> deletePerformanceConfig(@RequestParam("id") Long id) {
performanceConfigService.deletePerformanceConfig(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得业绩目标")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:performance-config:query')")
public CommonResult<CrmPerformanceConfigRespVO> getPerformanceConfig(@RequestParam("id") Long id) {
// 1. 获取业绩目标
CrmPerformanceConfigDO performanceConfig = performanceConfigService.getPerformanceConfig(id);
// 2. 拼接数据
return success(buildPerformanceConfigDetail(performanceConfig));
}
private CrmPerformanceConfigRespVO buildPerformanceConfigDetail(CrmPerformanceConfigDO performanceConfig) {
if (performanceConfig == null) {
return null;
}
// 1. 转换成 VO
CrmPerformanceConfigRespVO respVO = BeanUtils.toBean(performanceConfig, CrmPerformanceConfigRespVO.class);
// 2. 拼接目标对象名称
return buildPerformanceConfigObjectName(respVO);
}
@GetMapping("/page")
@Operation(summary = "获得业绩目标分页")
@PreAuthorize("@ss.hasPermission('crm:performance-config:query')")
public CommonResult<PageResult<CrmPerformanceConfigRespVO>> getPerformanceConfigPage(@Valid CrmPerformanceConfigPageReqVO pageReqVO) {
// 1. 查询业绩目标分页
PageResult<CrmPerformanceConfigDO> pageResult = performanceConfigService.getPerformanceConfigPage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
}
// 2. 拼接数据
return success(BeanUtils.toBean(pageResult, CrmPerformanceConfigRespVO.class)
.setList(buildPerformanceConfigDetailList(pageResult.getList())));
}
private List<CrmPerformanceConfigRespVO> buildPerformanceConfigDetailList(List<CrmPerformanceConfigDO> list) {
if (CollUtil.isEmpty(list)) {
return emptyList();
}
// 1. 转换成 VO
List<CrmPerformanceConfigRespVO> voList = BeanUtils.toBean(list, CrmPerformanceConfigRespVO.class);
// 2. 拼接目标对象名称
return buildPerformanceConfigObjectNameList(voList);
}
// ========== VO 拼装 ==========
private CrmPerformanceConfigRespVO buildPerformanceConfigObjectName(CrmPerformanceConfigRespVO config) {
if (config == null) {
return null;
}
return buildPerformanceConfigObjectNameList(singletonList(config)).get(0);
}
private List<CrmPerformanceConfigRespVO> buildPerformanceConfigObjectNameList(List<CrmPerformanceConfigRespVO> list) {
if (CollUtil.isEmpty(list)) {
return emptyList();
}
// 1.1 获取部门列表
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(list, CrmPerformanceConfigRespVO::getObjectId,
item -> Objects.equals(item.getObjectType(), CrmPerformanceConfigObjectTypeEnum.DEPT.getObjectType())));
// 1.2 获取员工列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(list, CrmPerformanceConfigRespVO::getObjectId,
item -> Objects.equals(item.getObjectType(), CrmPerformanceConfigObjectTypeEnum.USER.getObjectType())));
// 2. 设置目标对象名称
list.forEach(item -> {
if (Objects.equals(item.getObjectType(), CrmPerformanceConfigObjectTypeEnum.DEPT.getObjectType())) {
findAndThen(deptMap, item.getObjectId(), dept -> item.setObjectName(dept.getName()));
} else if (Objects.equals(item.getObjectType(), CrmPerformanceConfigObjectTypeEnum.USER.getObjectType())) {
findAndThen(userMap, item.getObjectId(), user -> item.setObjectName(user.getNickname()));
}
});
return list;
}
}
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - CRM 业绩目标设置分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmPerformanceConfigPageReqVO extends PageParam {
@Schema(description = "目标对象编号", example = "1")
private Long objectId;
@Schema(description = "目标对象类型", example = "2")
@InEnum(value = CrmPerformanceConfigObjectTypeEnum.class, message = "目标对象类型,必须是 {value}")
private Integer objectType;
@Schema(description = "年份", example = "2026")
private Integer year;
@Schema(description = "目标类型", example = "5")
@InEnum(value = CrmPerformanceConfigBizTypeEnum.class, message = "目标类型,必须是 {value}")
private Integer bizType;
}
@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - CRM 业绩目标设置 Response VO")
@Data
public class CrmPerformanceConfigRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "目标对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long objectId;
@Schema(description = "目标对象名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "销售部")
private String objectName;
@Schema(description = "目标对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer objectType;
@Schema(description = "年份", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
private Integer year;
@Schema(description = "一月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal januaryTargetPrice;
@Schema(description = "二月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal februaryTargetPrice;
@Schema(description = "三月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal marchTargetPrice;
@Schema(description = "四月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal aprilTargetPrice;
@Schema(description = "五月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal mayTargetPrice;
@Schema(description = "六月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal juneTargetPrice;
@Schema(description = "七月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal julyTargetPrice;
@Schema(description = "八月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal augustTargetPrice;
@Schema(description = "九月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal septemberTargetPrice;
@Schema(description = "十月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal octoberTargetPrice;
@Schema(description = "十一月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal novemberTargetPrice;
@Schema(description = "十二月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal decemberTargetPrice;
@Schema(description = "目标类型",
requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer bizType;
@Schema(description = "年度目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal yearTargetPrice;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 业绩目标设置新增/修改 Request VO")
@Data
public class CrmPerformanceConfigSaveReqVO {
@Schema(description = "编号", example = "1")
private Long id;
@Schema(description = "目标对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "目标对象编号不能为空")
private Long objectId;
@Schema(description = "目标对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "目标对象类型不能为空")
@InEnum(value = CrmPerformanceConfigObjectTypeEnum.class, message = "目标对象类型,必须是 {value}")
private Integer objectType;
@Schema(description = "年份", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
@NotNull(message = "年份不能为空")
private Integer year;
@Schema(description = "一月目标金额")
private BigDecimal januaryTargetPrice;
@Schema(description = "二月目标金额")
private BigDecimal februaryTargetPrice;
@Schema(description = "三月目标金额")
private BigDecimal marchTargetPrice;
@Schema(description = "四月目标金额")
private BigDecimal aprilTargetPrice;
@Schema(description = "五月目标金额")
private BigDecimal mayTargetPrice;
@Schema(description = "六月目标金额")
private BigDecimal juneTargetPrice;
@Schema(description = "七月目标金额")
private BigDecimal julyTargetPrice;
@Schema(description = "八月目标金额")
private BigDecimal augustTargetPrice;
@Schema(description = "九月目标金额")
private BigDecimal septemberTargetPrice;
@Schema(description = "十月目标金额")
private BigDecimal octoberTargetPrice;
@Schema(description = "十一月目标金额")
private BigDecimal novemberTargetPrice;
@Schema(description = "十二月目标金额")
private BigDecimal decemberTargetPrice;
@Schema(description = "目标类型",
requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "目标类型不能为空")
@InEnum(value = CrmPerformanceConfigBizTypeEnum.class, message = "目标类型,必须是 {value}")
private Integer bizType;
}
@@ -45,6 +45,13 @@ public class CrmStatisticsFunnelController {
return success(funnelService.getBusinessSummaryByEndStatus(reqVO));
}
@GetMapping("/get-business-summary-by-status")
@Operation(summary = "获取商机阶段统计", description = "用于【销售漏斗】页面的【销售漏斗分析】")
@PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
public CommonResult<List<CrmStatisticsBusinessSummaryByStatusRespVO>> getBusinessSummaryByStatus(@Valid CrmStatisticsFunnelReqVO reqVO) {
return success(funnelService.getBusinessSummaryByStatus(reqVO));
}
@GetMapping("/get-business-summary-by-date")
@Operation(summary = "获取新增商机分析(按日期)", description = "用于【销售漏斗】页面")
@PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.statistics;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceSummaryRespVO;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsPerformanceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -49,4 +50,11 @@ public class CrmStatisticsPerformanceController {
return success(performanceService.getReceivablePricePerformance(performanceReqVO));
}
@GetMapping("/get-contract-summary")
@Operation(summary = "合同汇总表")
@PreAuthorize("@ss.hasPermission('crm:statistics-performance:query')")
public CommonResult<List<CrmStatisticsPerformanceSummaryRespVO>> getContractSummary(@Valid CrmStatisticsPerformanceReqVO performanceReqVO) {
return success(performanceService.getContractSummary(performanceReqVO));
}
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetRespVO;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsPerformanceTargetService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - CRM 业绩目标统计")
@RestController
@RequestMapping("/crm/statistics-performance-target")
@Validated
public class CrmStatisticsPerformanceTargetController {
@Resource
private CrmStatisticsPerformanceTargetService performanceTargetService;
@GetMapping("/get-performance-target-summary")
@Operation(summary = "获取业绩目标完成情况")
@PreAuthorize("@ss.hasPermission('crm:statistics-performance-target:query')")
public CommonResult<List<CrmStatisticsPerformanceTargetRespVO>> getPerformanceTargetSummary(@Valid CrmStatisticsPerformanceTargetReqVO reqVO) {
return success(performanceTargetService.getPerformanceTargetSummary(reqVO));
}
}
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsProductService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - CRM 产品统计")
@RestController
@RequestMapping("/crm/statistics-product")
@Validated
public class CrmStatisticsProductController {
@Resource
private CrmStatisticsProductService productService;
@GetMapping("/get-product-sales-list")
@Operation(summary = "获取产品销售情况统计")
@PreAuthorize("@ss.hasPermission('crm:statistics-product:query')")
public CommonResult<List<CrmStatisticsProductSalesRespVO>> getProductSalesList(@Valid CrmStatisticsProductReqVO reqVO) {
return success(productService.getProductSalesList(reqVO));
}
@GetMapping("/get-product-category-summary")
@Operation(summary = "获取产品分类销售分析")
@PreAuthorize("@ss.hasPermission('crm:statistics-product:query')")
public CommonResult<List<CrmStatisticsProductCategoryRespVO>> getProductCategorySummary(@Valid CrmStatisticsProductReqVO reqVO) {
return success(productService.getProductCategorySummary(reqVO));
}
}
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 商机阶段统计 Response VO")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CrmStatisticsBusinessSummaryByStatusRespVO {
@Schema(description = "商机阶段编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long statusId;
@Schema(description = "商机阶段名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "初步接洽")
private String statusName;
@Schema(description = "赢单率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
private Integer statusPercent;
@Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer sort;
@Schema(description = "商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long businessCount;
@Schema(description = "商机总金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private BigDecimal totalPrice;
}
@@ -28,6 +28,9 @@ public class CrmStatisticsFunnelReqVO extends PageParam {
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Long userId;
@Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Long statusTypeId;
/**
* userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
* 后续,可能会支持选择部分用户进行查询
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 员工业绩合同汇总 Response VO")
@Data
public class CrmStatisticsPerformanceSummaryRespVO {
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
private String time;
@Schema(description = "合同数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer contractCount;
@Schema(description = "合同金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000.00")
private BigDecimal contractPrice;
@Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000.00")
private BigDecimal receivablePrice;
@Schema(description = "未回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000.00")
private BigDecimal unreceivedPrice;
}
@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 业绩目标完成实际值 Response VO")
@Data
@Accessors(chain = true)
public class CrmStatisticsPerformanceTargetActualRespVO {
@Schema(description = "月份", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer month;
@Schema(description = "完成金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000.00")
private BigDecimal currentPrice;
}
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - CRM 业绩目标完成情况 Request VO")
@Data
public class CrmStatisticsPerformanceTargetReqVO {
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "部门 id 不能为空")
private Long deptId;
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Long userId;
@Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
private List<Long> userIds;
@Schema(description = "年份", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024")
@NotNull(message = "年份不能为空")
private Integer year;
@Schema(description = "目标类型",
requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "目标类型不能为空")
@InEnum(value = CrmPerformanceConfigBizTypeEnum.class, message = "目标类型,必须是 {value}")
private Integer bizType;
}
@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 业绩目标完成情况 Response VO")
@Data
@Accessors(chain = true)
public class CrmStatisticsPerformanceTargetRespVO {
@Schema(description = "月份", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer month;
@Schema(description = "目标金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000.00")
private BigDecimal targetPrice;
@Schema(description = "完成金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000.00")
private BigDecimal currentPrice;
@Schema(description = "完成率", requiredMode = Schema.RequiredMode.REQUIRED, example = "80.00")
private BigDecimal completionRate;
}
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 产品分类销售分析 Response VO")
@Data
public class CrmStatisticsProductCategoryRespVO {
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Long categoryId;
@Schema(description = "产品分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "软件")
private String categoryName;
@Schema(description = "合同数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer contractCount;
@Schema(description = "销售数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private BigDecimal productCount;
@Schema(description = "销售金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20000.00")
private BigDecimal productTotalPrice;
}
@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 产品分析 Request VO")
@Data
public class CrmStatisticsProductReqVO {
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "部门 id 不能为空")
private Long deptId;
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Long userId;
@Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
private List<Long> userIds;
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@NotEmpty(message = "请选择时间范围")
@Size(min = 2, max = 2, message = "请选择时间范围")
private LocalDateTime[] times;
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "10")
private Long categoryId;
@Schema(description = "产品分类编号集合", hidden = true, example = "10")
private List<Long> categoryIds;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "20")
private Long productId;
}
@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 产品销售情况 Response VO")
@Data
public class CrmStatisticsProductSalesRespVO {
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Long categoryId;
@Schema(description = "产品分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "软件")
private String categoryName;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private Long productId;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "CRM 专业版")
private String productName;
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
private Long contractId;
@Schema(description = "合同编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "HT20240101001")
private String contractNo;
@Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "CRM 采购合同")
private String contractName;
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long ownerUserId;
@Schema(description = "负责人姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String ownerUserName;
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "40")
private Long customerId;
@Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试客户")
private String customerName;
@Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
private BigDecimal productPrice;
@Schema(description = "销售数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private BigDecimal productCount;
@Schema(description = "销售金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "200.00")
private BigDecimal productTotalPrice;
}
@@ -0,0 +1,107 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.performance;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
/**
* CRM 业绩目标 DO
*
* @author 芋道源码
*/
@TableName("crm_performance_config")
@KeySequence("crm_performance_config_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CrmPerformanceConfigDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 目标类型
*
* 枚举 {@link CrmPerformanceConfigBizTypeEnum}
*/
private Integer bizType;
/**
* 目标对象编号
*/
private Long objectId;
/**
* 目标对象类型
*
* 枚举 {@link CrmPerformanceConfigObjectTypeEnum}
*/
private Integer objectType;
/**
* 年份
*/
private Integer year;
/**
* 年度目标金额
*/
private BigDecimal yearTargetPrice;
/**
* 一月目标金额
*/
private BigDecimal januaryTargetPrice;
/**
* 二月目标金额
*/
private BigDecimal februaryTargetPrice;
/**
* 三月目标金额
*/
private BigDecimal marchTargetPrice;
/**
* 四月目标金额
*/
private BigDecimal aprilTargetPrice;
/**
* 五月目标金额
*/
private BigDecimal mayTargetPrice;
/**
* 六月目标金额
*/
private BigDecimal juneTargetPrice;
/**
* 七月目标金额
*/
private BigDecimal julyTargetPrice;
/**
* 八月目标金额
*/
private BigDecimal augustTargetPrice;
/**
* 九月目标金额
*/
private BigDecimal septemberTargetPrice;
/**
* 十月目标金额
*/
private BigDecimal octoberTargetPrice;
/**
* 十一月目标金额
*/
private BigDecimal novemberTargetPrice;
/**
* 十二月目标金额
*/
private BigDecimal decemberTargetPrice;
}
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.permission;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* CRM 负责人变更记录 DO
*
* @author 芋道源码
*/
@TableName("crm_owner_record")
@KeySequence("crm_owner_record_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CrmOwnerRecordDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* CRM 业务类型
*
* 枚举 {@link CrmBizTypeEnum}
*/
private Integer bizType;
/**
* CRM 业务编号
*/
private Long bizId;
/**
* 变更前负责人
*/
private Long preOwnerUserId;
/**
* 变更后负责人
*/
private Long postOwnerUserId;
}
@@ -72,6 +72,7 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
default PageResult<CrmBusinessDO> selectPage(CrmStatisticsFunnelReqVO pageVO) {
return selectPage(pageVO, new LambdaQueryWrapperX<CrmBusinessDO>()
.in(CrmBusinessDO::getOwnerUserId, pageVO.getUserIds())
.eqIfPresent(CrmBusinessDO::getStatusTypeId, pageVO.getStatusTypeId())
.betweenIfPresent(CrmBusinessDO::getCreateTime, pageVO.getTimes()));
}
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.crm.dal.mysql.performance;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigPageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* CRM 业绩目标配置 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface CrmPerformanceConfigMapper extends BaseMapperX<CrmPerformanceConfigDO> {
default PageResult<CrmPerformanceConfigDO> selectPage(CrmPerformanceConfigPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<CrmPerformanceConfigDO>()
.eqIfPresent(CrmPerformanceConfigDO::getObjectId, reqVO.getObjectId())
.eqIfPresent(CrmPerformanceConfigDO::getObjectType, reqVO.getObjectType())
.eqIfPresent(CrmPerformanceConfigDO::getYear, reqVO.getYear())
.eqIfPresent(CrmPerformanceConfigDO::getBizType, reqVO.getBizType())
.orderByDesc(CrmPerformanceConfigDO::getYear)
.orderByAsc(CrmPerformanceConfigDO::getObjectType)
.orderByAsc(CrmPerformanceConfigDO::getObjectId)
.orderByAsc(CrmPerformanceConfigDO::getBizType));
}
default CrmPerformanceConfigDO selectByObjectIdAndObjectTypeAndYearAndBizType(Long objectId, Integer objectType,
Integer year, Integer bizType) {
return selectOne(new LambdaQueryWrapperX<CrmPerformanceConfigDO>()
.eq(CrmPerformanceConfigDO::getObjectId, objectId)
.eq(CrmPerformanceConfigDO::getObjectType, objectType)
.eq(CrmPerformanceConfigDO::getYear, year)
.eq(CrmPerformanceConfigDO::getBizType, bizType));
}
default List<CrmPerformanceConfigDO> selectListByObjectIdsAndObjectTypeAndYearAndBizType(Collection<Long> objectIds,
Integer objectType,
Integer year,
Integer bizType) {
return selectList(new LambdaQueryWrapperX<CrmPerformanceConfigDO>()
.in(CrmPerformanceConfigDO::getObjectId, objectIds)
.eq(CrmPerformanceConfigDO::getObjectType, objectType)
.eq(CrmPerformanceConfigDO::getYear, year)
.eq(CrmPerformanceConfigDO::getBizType, bizType));
}
}
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.crm.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmOwnerRecordDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* CRM 负责人变更记录 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface CrmOwnerRecordMapper extends BaseMapperX<CrmOwnerRecordDO> {
/**
* 进入公海客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 公海领取客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 进入公海客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 公海领取客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO);
}
@@ -1,16 +1,10 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* CRM 客户分析 Mapper
*
@@ -117,65 +111,6 @@ public interface CrmStatisticsCustomerMapper {
List<CrmStatisticsFollowUpSummaryByTypeRespVO> selectFollowUpRecordCountGroupByType(CrmStatisticsCustomerReqVO reqVO);
/**
* 进入公海客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
default List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO) {
LocalDateTime currrentDate = LocalDateTimeUtil.beginOfDay(reqVO.getTimes()[0]);
LocalDateTime endDate = LocalDateTimeUtil.endOfDay(reqVO.getTimes()[1]);
List<CrmStatisticsPoolSummaryByDateRespVO> voList = new ArrayList<>();
while (currrentDate.isBefore(endDate)) {
voList.add(new CrmStatisticsPoolSummaryByDateRespVO()
.setTime(LocalDateTimeUtil.format(currrentDate, "yyyy-MM-dd"))
.setCustomerPutCount(RandomUtil.randomInt(0, 10))
.setCustomerTakeCount(RandomUtil.randomInt(0, 10)));
currrentDate = currrentDate.plusDays(1);
}
return voList;
}
/**
* 公海领取客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
default List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO) {
return selectPoolCustomerPutCountByDate(reqVO);
}
/**
* 进入公海客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
default List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO) {
return convertList(reqVO.getUserIds(), userId ->
(CrmStatisticsPoolSummaryByUserRespVO) new CrmStatisticsPoolSummaryByUserRespVO()
.setCustomerPutCount(RandomUtil.randomInt(0, 10))
.setCustomerTakeCount(RandomUtil.randomInt(0, 10))
.setOwnerUserId(userId));
}
/**
* 公海领取客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
default List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO) {
return selectPoolCustomerPutCountByUser(reqVO);
}
/**
* 客户成交周期(按日期)
*
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessInversionRateSummaryByDateRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByEndStatusRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByStatusRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -23,6 +24,8 @@ public interface CrmStatisticsFunnelMapper {
List<CrmStatisticsBusinessSummaryByEndStatusRespVO> selectBusinessSummaryListGroupByEndStatus(CrmStatisticsFunnelReqVO reqVO);
List<CrmStatisticsBusinessSummaryByStatusRespVO> selectBusinessSummaryListGroupByStatus(CrmStatisticsFunnelReqVO reqVO);
List<CrmStatisticsBusinessSummaryByDateRespVO> selectBusinessSummaryGroupByDate(CrmStatisticsFunnelReqVO reqVO);
List<CrmStatisticsBusinessInversionRateSummaryByDateRespVO> selectBusinessInversionRateSummaryByDate(CrmStatisticsFunnelReqVO reqVO);
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceSummaryRespVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@@ -38,4 +39,12 @@ public interface CrmStatisticsPerformanceMapper {
*/
List<CrmStatisticsPerformanceRespVO> selectReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
* 合同汇总表
*
* @param performanceReqVO 参数
* @return 合同汇总表
*/
List<CrmStatisticsPerformanceSummaryRespVO> selectContractSummary(CrmStatisticsPerformanceReqVO performanceReqVO);
}
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetActualRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetReqVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* CRM 业绩目标完成情况 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface CrmStatisticsPerformanceTargetMapper {
/**
* 按月统计合同金额
*
* @param reqVO 请求参数
* @return 按月统计合同金额
*/
List<CrmStatisticsPerformanceTargetActualRespVO> selectContractActualByMonth(CrmStatisticsPerformanceTargetReqVO reqVO);
/**
* 按月统计回款金额
*
* @param reqVO 请求参数
* @return 按月统计回款金额
*/
List<CrmStatisticsPerformanceTargetActualRespVO> selectReceivableActualByMonth(CrmStatisticsPerformanceTargetReqVO reqVO);
}
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* CRM 产品分析 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface CrmStatisticsProductMapper {
/**
* 获取产品销售情况统计
*
* @param reqVO 请求参数
* @return 产品销售情况
*/
List<CrmStatisticsProductSalesRespVO> selectProductSalesList(CrmStatisticsProductReqVO reqVO);
/**
* 获取产品分类销售分析
*
* @param reqVO 请求参数
* @return 产品分类销售分析
*/
List<CrmStatisticsProductCategoryRespVO> selectProductCategorySummary(CrmStatisticsProductReqVO reqVO);
}
@@ -104,6 +104,9 @@ public interface ErrorCodeConstants {
ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限");
// ========== 数据统计 1_020_014_000 ==========
// ========== 业绩目标配置 1_020_011_000 ==========
ErrorCode PERFORMANCE_CONFIG_NOT_EXISTS = new ErrorCode(1_020_011_000, "业绩目标配置不存在");
ErrorCode PERFORMANCE_CONFIG_EXISTS = new ErrorCode(1_020_011_001, "该对象当前年份的业绩目标配置已存在");
ErrorCode PERFORMANCE_CONFIG_TYPE_INVALID = new ErrorCode(1_020_011_002, "目标对象类型不正确");
}
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.crm.enums.performance;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* CRM 业绩目标业务类型枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum CrmPerformanceConfigBizTypeEnum implements ArrayValuable<Integer> {
CRM_CONTRACT(CrmBizTypeEnum.CRM_CONTRACT.getType(), "销售目标"),
CRM_RECEIVABLE(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), "回款目标");
public static final Integer[] ARRAYS = Arrays.stream(values())
.map(CrmPerformanceConfigBizTypeEnum::getBizType).toArray(Integer[]::new);
/**
* 业务类型
*/
private final Integer bizType;
/**
* 名称
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.crm.enums.performance;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* CRM 业绩目标对象类型枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum CrmPerformanceConfigObjectTypeEnum implements ArrayValuable<Integer> {
DEPT(2, "部门"),
USER(3, "员工");
public static final Integer[] ARRAYS = Arrays.stream(values())
.map(CrmPerformanceConfigObjectTypeEnum::getObjectType).toArray(Integer[]::new);
/**
* 对象类型
*/
private final Integer objectType;
/**
* 名称
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}
@@ -28,7 +28,9 @@ import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.CrmOwnerRecordService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmOwnerRecordCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -66,6 +68,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
@Resource
private CrmCustomerMapper customerMapper;
@Resource
private CrmOwnerRecordService ownerRecordService;
@Resource
private CrmPermissionService permissionService;
@@ -407,6 +411,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
// 2. 领取公海数据
List<CrmCustomerDO> updateCustomers = new ArrayList<>();
List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();
List<CrmOwnerRecordCreateReqBO> ownerRecords = new ArrayList<>();
customers.forEach(customer -> {
// 2.1. 设置负责人
updateCustomers.add(new CrmCustomerDO().setId(customer.getId())
@@ -414,11 +419,16 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
// 2.2. 创建负责人数据权限
createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
// 2.3. 记录负责人从公海变更为指定人员
ownerRecords.add(new CrmOwnerRecordCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setPreOwnerUserId(customer.getOwnerUserId()).setPostOwnerUserId(ownerUserId));
});
// 2.2 更新客户负责人
customerMapper.updateBatch(updateCustomers);
// 2.3 创建负责人数据权限
permissionService.createPermissionBatch(createPermissions);
// 2.4 记录负责人变更历史
ownerRecordService.createOwnerRecordList(ownerRecords);
// TODO @芋艿:要不要处理关联的联系人???
// 3. 记录操作日志
@@ -459,6 +469,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
if (updateOwnerUserIncr == 0) {
throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL);
}
ownerRecordService.createOwnerRecord(new CrmOwnerRecordCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setPreOwnerUserId(customer.getOwnerUserId()).setPostOwnerUserId(null));
// 2. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的
contactService.updateOwnerUserIdByCustomerId(customer.getId(), null);
@@ -0,0 +1,80 @@
package cn.iocoder.yudao.module.crm.service.performance;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* CRM 业绩目标设置 Service 接口
*
* @author 芋道源码
*/
public interface CrmPerformanceConfigService {
/**
* 创建业绩目标
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createPerformanceConfig(@Valid CrmPerformanceConfigSaveReqVO createReqVO);
/**
* 更新业绩目标
*
* @param updateReqVO 更新信息
*/
void updatePerformanceConfig(@Valid CrmPerformanceConfigSaveReqVO updateReqVO);
/**
* 删除业绩目标
*
* @param id 编号
*/
void deletePerformanceConfig(Long id);
/**
* 获得业绩目标
*
* @param id 编号
* @return 业绩目标
*/
CrmPerformanceConfigDO getPerformanceConfig(Long id);
/**
* 获得业绩目标分页
*
* @param pageReqVO 分页查询
* @return 业绩目标分页
*/
PageResult<CrmPerformanceConfigDO> getPerformanceConfigPage(CrmPerformanceConfigPageReqVO pageReqVO);
/**
* 根据对象、年份、目标类型获得业绩目标配置
*
* @param objectId 对象编号
* @param objectType 对象类型
* @param year 年份
* @param bizType 业务类型
* @return 业绩目标配置
*/
CrmPerformanceConfigDO getPerformanceConfig(Long objectId, Integer objectType, Integer year, Integer bizType);
/**
* 根据对象数组、年份、目标类型获得业绩目标配置列表
*
* @param objectIds 对象编号数组
* @param objectType 对象类型
* @param year 年份
* @param bizType 业务类型
* @return 业绩目标配置列表
*/
List<CrmPerformanceConfigDO> getPerformanceConfigList(Collection<Long> objectIds, Integer objectType, Integer year,
Integer bizType);
}
@@ -0,0 +1,170 @@
package cn.iocoder.yudao.module.crm.service.performance;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
import cn.iocoder.yudao.module.crm.dal.mysql.performance.CrmPerformanceConfigMapper;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static cn.hutool.core.collection.CollUtil.isEmpty;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static java.util.Collections.singleton;
/**
* CRM 业绩目标设置 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class CrmPerformanceConfigServiceImpl implements CrmPerformanceConfigService {
@Resource
private CrmPerformanceConfigMapper performanceConfigMapper;
@Resource
private DeptApi deptApi;
@Resource
private AdminUserApi adminUserApi;
@Override
public Long createPerformanceConfig(CrmPerformanceConfigSaveReqVO createReqVO) {
// 1.1 校验目标对象
validatePerformanceConfigObject(createReqVO.getObjectType(), createReqVO.getObjectId());
// 1.2 校验唯一
validatePerformanceConfigNotExists(null, createReqVO);
// 2. 插入
CrmPerformanceConfigDO performanceConfig = BeanUtils.toBean(createReqVO, CrmPerformanceConfigDO.class);
fillTargetPrice(performanceConfig);
performanceConfigMapper.insert(performanceConfig);
return performanceConfig.getId();
}
@Override
public void updatePerformanceConfig(CrmPerformanceConfigSaveReqVO updateReqVO) {
// 1.1 校验存在
validatePerformanceConfigExists(updateReqVO.getId());
// 1.2 校验目标对象
validatePerformanceConfigObject(updateReqVO.getObjectType(), updateReqVO.getObjectId());
// 1.3 校验唯一
validatePerformanceConfigNotExists(updateReqVO.getId(), updateReqVO);
// 2. 更新
CrmPerformanceConfigDO updateObj = BeanUtils.toBean(updateReqVO, CrmPerformanceConfigDO.class);
fillTargetPrice(updateObj);
performanceConfigMapper.updateById(updateObj);
}
@Override
public void deletePerformanceConfig(Long id) {
// 1. 校验存在
validatePerformanceConfigExists(id);
// 2. 删除
performanceConfigMapper.deleteById(id);
}
@Override
public CrmPerformanceConfigDO getPerformanceConfig(Long id) {
return validatePerformanceConfigExists(id);
}
@Override
public PageResult<CrmPerformanceConfigDO> getPerformanceConfigPage(CrmPerformanceConfigPageReqVO pageReqVO) {
return performanceConfigMapper.selectPage(pageReqVO);
}
private CrmPerformanceConfigDO validatePerformanceConfigExists(Long id) {
CrmPerformanceConfigDO performanceConfig = performanceConfigMapper.selectById(id);
if (performanceConfig == null) {
throw exception(PERFORMANCE_CONFIG_NOT_EXISTS);
}
return performanceConfig;
}
private void validatePerformanceConfigNotExists(Long id, CrmPerformanceConfigSaveReqVO reqVO) {
CrmPerformanceConfigDO performanceConfig = performanceConfigMapper.selectByObjectIdAndObjectTypeAndYearAndBizType(
reqVO.getObjectId(), reqVO.getObjectType(), reqVO.getYear(), reqVO.getBizType());
if (performanceConfig == null || Objects.equals(performanceConfig.getId(), id)) {
return;
}
throw exception(PERFORMANCE_CONFIG_EXISTS);
}
private void validatePerformanceConfigObject(Integer objectType, Long objectId) {
// 情况一:部门
if (Objects.equals(objectType, CrmPerformanceConfigObjectTypeEnum.DEPT.getObjectType())) {
deptApi.validateDeptList(singleton(objectId));
return;
}
// 情况二:员工
if (Objects.equals(objectType, CrmPerformanceConfigObjectTypeEnum.USER.getObjectType())) {
adminUserApi.validateUserList(singleton(objectId));
return;
}
throw exception(PERFORMANCE_CONFIG_TYPE_INVALID);
}
@Override
public CrmPerformanceConfigDO getPerformanceConfig(Long objectId, Integer objectType, Integer year, Integer bizType) {
return performanceConfigMapper.selectByObjectIdAndObjectTypeAndYearAndBizType(objectId, objectType, year, bizType);
}
@Override
public List<CrmPerformanceConfigDO> getPerformanceConfigList(Collection<Long> objectIds, Integer objectType,
Integer year, Integer bizType) {
if (isEmpty(objectIds)) {
return Collections.emptyList();
}
return performanceConfigMapper.selectListByObjectIdsAndObjectTypeAndYearAndBizType(objectIds, objectType,
year, bizType);
}
private void fillTargetPrice(CrmPerformanceConfigDO performanceConfig) {
// 1. 空值设置为 0
performanceConfig.setJanuaryTargetPrice(defaultIfNull(performanceConfig.getJanuaryTargetPrice()));
performanceConfig.setFebruaryTargetPrice(defaultIfNull(performanceConfig.getFebruaryTargetPrice()));
performanceConfig.setMarchTargetPrice(defaultIfNull(performanceConfig.getMarchTargetPrice()));
performanceConfig.setAprilTargetPrice(defaultIfNull(performanceConfig.getAprilTargetPrice()));
performanceConfig.setMayTargetPrice(defaultIfNull(performanceConfig.getMayTargetPrice()));
performanceConfig.setJuneTargetPrice(defaultIfNull(performanceConfig.getJuneTargetPrice()));
performanceConfig.setJulyTargetPrice(defaultIfNull(performanceConfig.getJulyTargetPrice()));
performanceConfig.setAugustTargetPrice(defaultIfNull(performanceConfig.getAugustTargetPrice()));
performanceConfig.setSeptemberTargetPrice(defaultIfNull(performanceConfig.getSeptemberTargetPrice()));
performanceConfig.setOctoberTargetPrice(defaultIfNull(performanceConfig.getOctoberTargetPrice()));
performanceConfig.setNovemberTargetPrice(defaultIfNull(performanceConfig.getNovemberTargetPrice()));
performanceConfig.setDecemberTargetPrice(defaultIfNull(performanceConfig.getDecemberTargetPrice()));
// 2. 计算年度目标
performanceConfig.setYearTargetPrice(performanceConfig.getJanuaryTargetPrice()
.add(performanceConfig.getFebruaryTargetPrice())
.add(performanceConfig.getMarchTargetPrice())
.add(performanceConfig.getAprilTargetPrice())
.add(performanceConfig.getMayTargetPrice())
.add(performanceConfig.getJuneTargetPrice())
.add(performanceConfig.getJulyTargetPrice())
.add(performanceConfig.getAugustTargetPrice())
.add(performanceConfig.getSeptemberTargetPrice())
.add(performanceConfig.getOctoberTargetPrice())
.add(performanceConfig.getNovemberTargetPrice())
.add(performanceConfig.getDecemberTargetPrice()));
}
private BigDecimal defaultIfNull(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
}
}
@@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.crm.service.permission;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmOwnerRecordCreateReqBO;
import jakarta.validation.Valid;
import java.util.List;
/**
* CRM 负责人变更记录 Service 接口
*
* @author 芋道源码
*/
public interface CrmOwnerRecordService {
/**
* 创建负责人变更记录
*
* @param createReqBO 创建信息
* @return 编号
*/
Long createOwnerRecord(@Valid CrmOwnerRecordCreateReqBO createReqBO);
/**
* 批量创建负责人变更记录
*
* @param createReqBOs 创建信息
*/
void createOwnerRecordList(@Valid List<CrmOwnerRecordCreateReqBO> createReqBOs);
/**
* 进入公海客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByDateRespVO> getPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 公海领取客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByDateRespVO> getPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 进入公海客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByUserRespVO> getPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 公海领取客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByUserRespVO> getPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO);
}
@@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.crm.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmOwnerRecordDO;
import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmOwnerRecordMapper;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmOwnerRecordCreateReqBO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
/**
* CRM 负责人变更记录 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class CrmOwnerRecordServiceImpl implements CrmOwnerRecordService {
@Resource
private CrmOwnerRecordMapper ownerRecordMapper;
@Override
public Long createOwnerRecord(CrmOwnerRecordCreateReqBO createReqBO) {
CrmOwnerRecordDO ownerRecord = BeanUtils.toBean(createReqBO, CrmOwnerRecordDO.class);
ownerRecordMapper.insert(ownerRecord);
return ownerRecord.getId();
}
@Override
public void createOwnerRecordList(List<CrmOwnerRecordCreateReqBO> createReqBOs) {
if (CollUtil.isEmpty(createReqBOs)) {
return;
}
List<CrmOwnerRecordDO> list = BeanUtils.toBean(createReqBOs, CrmOwnerRecordDO.class);
ownerRecordMapper.insertBatch(list);
}
@Override
public List<CrmStatisticsPoolSummaryByDateRespVO> getPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO) {
return ownerRecordMapper.selectPoolCustomerPutCountByDate(reqVO);
}
@Override
public List<CrmStatisticsPoolSummaryByDateRespVO> getPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO) {
return ownerRecordMapper.selectPoolCustomerTakeCountByDate(reqVO);
}
@Override
public List<CrmStatisticsPoolSummaryByUserRespVO> getPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO) {
return ownerRecordMapper.selectPoolCustomerPutCountByUser(reqVO);
}
@Override
public List<CrmStatisticsPoolSummaryByUserRespVO> getPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO) {
return ownerRecordMapper.selectPoolCustomerTakeCountByUser(reqVO);
}
}
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.crm.service.permission.bo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* CRM 负责人变更记录 Create Req BO
*
* @author 芋道源码
*/
@Data
public class CrmOwnerRecordCreateReqBO {
/**
* CRM 业务类型
*/
@NotNull(message = "CRM 业务类型不能为空")
@InEnum(CrmBizTypeEnum.class)
private Integer bizType;
/**
* CRM 业务编号
*/
@NotNull(message = "CRM 业务编号不能为空")
private Long bizId;
/**
* 变更前负责人
*/
private Long preOwnerUserId;
/**
* 变更后负责人
*/
private Long postOwnerUserId;
}
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper;
import cn.iocoder.yudao.module.crm.service.permission.CrmOwnerRecordService;
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;
@@ -41,6 +42,9 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
@Resource
private CrmStatisticsCustomerMapper customerMapper;
@Resource
private CrmOwnerRecordService ownerRecordService;
@Resource
private AdminUserApi adminUserApi;
@Resource
@@ -201,8 +205,8 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
}
// 2. 按天统计,获取分项统计数据
List<CrmStatisticsPoolSummaryByDateRespVO> customerPutCountList = customerMapper.selectPoolCustomerPutCountByDate(reqVO);
List<CrmStatisticsPoolSummaryByDateRespVO> customerTakeCountList = customerMapper.selectPoolCustomerTakeCountByDate(reqVO);
List<CrmStatisticsPoolSummaryByDateRespVO> customerPutCountList = ownerRecordService.getPoolCustomerPutCountByDate(reqVO);
List<CrmStatisticsPoolSummaryByDateRespVO> customerTakeCountList = ownerRecordService.getPoolCustomerTakeCountByDate(reqVO);
// 3. 按照日期间隔,合并数据
List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
@@ -228,8 +232,8 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
}
// 2. 按用户统计,获取分项统计数据
List<CrmStatisticsPoolSummaryByUserRespVO> customerPutCountList = customerMapper.selectPoolCustomerPutCountByUser(reqVO);
List<CrmStatisticsPoolSummaryByUserRespVO> customerTakeCountList = customerMapper.selectPoolCustomerTakeCountByUser(reqVO);
List<CrmStatisticsPoolSummaryByUserRespVO> customerPutCountList = ownerRecordService.getPoolCustomerPutCountByUser(reqVO);
List<CrmStatisticsPoolSummaryByUserRespVO> customerTakeCountList = ownerRecordService.getPoolCustomerTakeCountByUser(reqVO);
// 3.1 按照用户,合并统计数据
List<CrmStatisticsPoolSummaryByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
@@ -331,7 +335,6 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
reqVO.setUserIds(userIds);
// 2. 获取客户产品统计数据
// TODO @dhb52:未读取产品名
return customerMapper.selectCustomerDealCycleGroupByProductId(reqVO);
}
@@ -29,6 +29,14 @@ public interface CrmStatisticsFunnelService {
*/
List<CrmStatisticsBusinessSummaryByEndStatusRespVO> getBusinessSummaryByEndStatus(CrmStatisticsFunnelReqVO reqVO);
/**
* 获得商机阶段统计
*
* @param reqVO 请求
* @return 商机阶段统计
*/
List<CrmStatisticsBusinessSummaryByStatusRespVO> getBusinessSummaryByStatus(CrmStatisticsFunnelReqVO reqVO);
/**
* 获取新增商机分析(按日期)
*
@@ -70,6 +70,18 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic
return funnelMapper.selectBusinessSummaryListGroupByEndStatus(reqVO);
}
@Override
public List<CrmStatisticsBusinessSummaryByStatusRespVO> getBusinessSummaryByStatus(CrmStatisticsFunnelReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 获得统计数据
return funnelMapper.selectBusinessSummaryListGroupByStatus(reqVO);
}
@Override
public List<CrmStatisticsBusinessSummaryByDateRespVO> getBusinessSummaryByDate(CrmStatisticsFunnelReqVO reqVO) {
// 1. 获得用户编号数组
@@ -4,11 +4,12 @@ package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceSummaryRespVO;
import java.util.List;
/**
* CRM 员工绩效统计 Service 接口
* CRM 员工业绩统计 Service 接口
*
* @author scholar
*/
@@ -38,5 +39,12 @@ public interface CrmStatisticsPerformanceService {
*/
List<CrmStatisticsPerformanceRespVO> getReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
* 员工合同汇总表
*
* @param performanceReqVO 排行参数
* @return 员工合同汇总表
*/
List<CrmStatisticsPerformanceSummaryRespVO> getContractSummary(CrmStatisticsPerformanceReqVO performanceReqVO);
}
@@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceSummaryRespVO;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPerformanceMapper;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@@ -56,6 +57,29 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform
return getPerformance(performanceReqVO, performanceMapper::selectReceivablePricePerformance);
}
@Override
public List<CrmStatisticsPerformanceSummaryRespVO> getContractSummary(CrmStatisticsPerformanceReqVO performanceReqVO) {
List<Long> userIds = getUserIds(performanceReqVO);
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
performanceReqVO.setUserIds(userIds);
int year = performanceReqVO.getTimes()[0].getYear();
List<CrmStatisticsPerformanceSummaryRespVO> summaryList = performanceMapper.selectContractSummary(performanceReqVO);
Map<String, CrmStatisticsPerformanceSummaryRespVO> summaryMap = convertMap(summaryList,
CrmStatisticsPerformanceSummaryRespVO::getTime);
List<CrmStatisticsPerformanceSummaryRespVO> result = new ArrayList<>();
for (int month = 1; month <= 12; month++) {
String currentMonth = String.format("%d%02d", year, month);
CrmStatisticsPerformanceSummaryRespVO summary = summaryMap.get(currentMonth);
result.add(summary != null ? summary : new CrmStatisticsPerformanceSummaryRespVO().setTime(currentMonth)
.setContractCount(0).setContractPrice(BigDecimal.ZERO)
.setReceivablePrice(BigDecimal.ZERO).setUnreceivedPrice(BigDecimal.ZERO));
}
return result;
}
/**
* 获得员工业绩数据
*
@@ -117,4 +141,4 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform
return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
}
}
}
@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetRespVO;
import java.util.List;
/**
* CRM 业绩目标统计 Service 接口
*
* @author 芋道源码
*/
public interface CrmStatisticsPerformanceTargetService {
/**
* 获取业绩目标完成情况
*
* @param reqVO 请求参数
* @return 业绩目标完成情况
*/
List<CrmStatisticsPerformanceTargetRespVO> getPerformanceTargetSummary(CrmStatisticsPerformanceTargetReqVO reqVO);
}
@@ -0,0 +1,190 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetActualRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetRespVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPerformanceTargetMapper;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
import cn.iocoder.yudao.module.crm.service.performance.CrmPerformanceConfigService;
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 jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* CRM 业绩目标统计 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class CrmStatisticsPerformanceTargetServiceImpl implements CrmStatisticsPerformanceTargetService {
@Resource
private CrmStatisticsPerformanceTargetMapper performanceTargetMapper;
@Resource
private CrmPerformanceConfigService performanceConfigService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Override
public List<CrmStatisticsPerformanceTargetRespVO> getPerformanceTargetSummary(CrmStatisticsPerformanceTargetReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2.1 获得目标配置
List<CrmPerformanceConfigDO> performanceConfigs = getPerformanceConfigs(reqVO);
// 2.2 获得实际完成金额
List<CrmStatisticsPerformanceTargetActualRespVO> actualList = ObjUtil.equal(reqVO.getBizType(),
CrmPerformanceConfigBizTypeEnum.CRM_RECEIVABLE.getBizType())
? performanceTargetMapper.selectReceivableActualByMonth(reqVO)
: performanceTargetMapper.selectContractActualByMonth(reqVO);
Map<Integer, BigDecimal> actualMap = convertMap(actualList,
CrmStatisticsPerformanceTargetActualRespVO::getMonth,
CrmStatisticsPerformanceTargetActualRespVO::getCurrentPrice);
// 3. 组装数据返回
List<CrmStatisticsPerformanceTargetRespVO> result = new ArrayList<>();
for (int month = 1; month <= 12; month++) {
BigDecimal targetPrice = getMonthTargetPrice(performanceConfigs, month);
BigDecimal currentPrice = actualMap.getOrDefault(month, BigDecimal.ZERO);
result.add(new CrmStatisticsPerformanceTargetRespVO().setMonth(month).setTargetPrice(targetPrice)
.setCurrentPrice(currentPrice)
.setCompletionRate(calculateCompletionRate(currentPrice, targetPrice)));
}
return result;
}
private List<CrmPerformanceConfigDO> getPerformanceConfigs(CrmStatisticsPerformanceTargetReqVO reqVO) {
// 情况一:选中某个用户
if (ObjUtil.isNotNull(reqVO.getUserId())) {
CrmPerformanceConfigDO performanceConfig = performanceConfigService.getPerformanceConfig(reqVO.getUserId(),
CrmPerformanceConfigObjectTypeEnum.USER.getObjectType(), reqVO.getYear(), reqVO.getBizType());
return performanceConfig == null ? Collections.emptyList() : ListUtil.of(performanceConfig);
}
// 情况二:选中某个部门
return performanceConfigService.getPerformanceConfigList(getDeptIds(reqVO.getDeptId()),
CrmPerformanceConfigObjectTypeEnum.DEPT.getObjectType(), reqVO.getYear(), reqVO.getBizType());
}
private BigDecimal getMonthTargetPrice(List<CrmPerformanceConfigDO> performanceConfigs, int month) {
return performanceConfigs.stream()
.map(performanceConfig -> getMonthTargetPrice(performanceConfig, month))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
@SuppressWarnings("EnhancedSwitchMigration")
private BigDecimal getMonthTargetPrice(CrmPerformanceConfigDO performanceConfig, int month) {
// 1. 无目标配置,返回 0
if (performanceConfig == null) {
return BigDecimal.ZERO;
}
// 2. 获得月份目标
BigDecimal targetPrice;
switch (month) {
case 1:
targetPrice = performanceConfig.getJanuaryTargetPrice();
break;
case 2:
targetPrice = performanceConfig.getFebruaryTargetPrice();
break;
case 3:
targetPrice = performanceConfig.getMarchTargetPrice();
break;
case 4:
targetPrice = performanceConfig.getAprilTargetPrice();
break;
case 5:
targetPrice = performanceConfig.getMayTargetPrice();
break;
case 6:
targetPrice = performanceConfig.getJuneTargetPrice();
break;
case 7:
targetPrice = performanceConfig.getJulyTargetPrice();
break;
case 8:
targetPrice = performanceConfig.getAugustTargetPrice();
break;
case 9:
targetPrice = performanceConfig.getSeptemberTargetPrice();
break;
case 10:
targetPrice = performanceConfig.getOctoberTargetPrice();
break;
case 11:
targetPrice = performanceConfig.getNovemberTargetPrice();
break;
case 12:
targetPrice = performanceConfig.getDecemberTargetPrice();
break;
default:
targetPrice = BigDecimal.ZERO;
}
return targetPrice != null ? targetPrice : BigDecimal.ZERO;
}
private BigDecimal calculateCompletionRate(BigDecimal currentPrice, BigDecimal targetPrice) {
if (targetPrice == null || targetPrice.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
return currentPrice.multiply(BigDecimal.valueOf(100)).divide(targetPrice, 2, RoundingMode.HALF_UP);
}
/**
* 获取用户编号数组
*
* 如果用户编号为空,则获得部门下的用户编号数组,包括子部门的所有用户编号
*
* @param reqVO 请求参数
* @return 用户编号数组
*/
private List<Long> getUserIds(CrmStatisticsPerformanceTargetReqVO reqVO) {
// 情况一:选中某个用户
if (ObjUtil.isNotNull(reqVO.getUserId())) {
return ListUtil.of(reqVO.getUserId());
}
// 情况二:选中某个部门
// 2.1 获得部门列表
List<Long> deptIds = getDeptIds(reqVO.getDeptId());
// 2.2 获得用户编号
return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
}
/**
* 获得部门编号数组,包括子部门
*
* @param deptId 部门编号
* @return 部门编号数组
*/
private List<Long> getDeptIds(Long deptId) {
List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId);
deptIds.add(deptId);
return deptIds;
}
}
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO;
import java.util.List;
/**
* CRM 产品分析 Service 接口
*
* @author 芋道源码
*/
public interface CrmStatisticsProductService {
/**
* 获取产品销售情况统计
*
* @param reqVO 请求参数
* @return 产品销售情况
*/
List<CrmStatisticsProductSalesRespVO> getProductSalesList(CrmStatisticsProductReqVO reqVO);
/**
* 获取产品分类销售分析
*
* @param reqVO 请求参数
* @return 产品分类销售分析
*/
List<CrmStatisticsProductCategoryRespVO> getProductCategorySummary(CrmStatisticsProductReqVO reqVO);
}
@@ -0,0 +1,104 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsProductMapper;
import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService;
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 jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* CRM 产品分析 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class CrmStatisticsProductServiceImpl implements CrmStatisticsProductService {
@Resource
private CrmStatisticsProductMapper productMapper;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Resource
private CrmProductCategoryService productCategoryService;
@Override
public List<CrmStatisticsProductSalesRespVO> getProductSalesList(CrmStatisticsProductReqVO reqVO) {
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
setCategoryIds(reqVO);
return productMapper.selectProductSalesList(reqVO);
}
@Override
public List<CrmStatisticsProductCategoryRespVO> getProductCategorySummary(CrmStatisticsProductReqVO reqVO) {
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
setCategoryIds(reqVO);
return productMapper.selectProductCategorySummary(reqVO);
}
/**
* 设置产品分类编号数组,选择父分类时,包含子分类
*
* @param reqVO 请求参数
*/
private void setCategoryIds(CrmStatisticsProductReqVO reqVO) {
if (ObjUtil.isNull(reqVO.getCategoryId())) {
return;
}
List<Long> categoryIds = ListUtil.of(reqVO.getCategoryId());
List<CrmProductCategoryDO> categoryList = productCategoryService.getProductCategoryList(
new CrmProductCategoryListReqVO());
categoryIds.addAll(convertList(categoryList, CrmProductCategoryDO::getId,
category -> Objects.equals(category.getParentId(), reqVO.getCategoryId())));
reqVO.setCategoryIds(categoryIds);
}
/**
* 获取用户编号数组
*
* 如果用户编号为空,则获得部门下的用户编号数组,包括子部门的所有用户编号
*
* @param reqVO 请求参数
* @return 用户编号数组
*/
private List<Long> getUserIds(CrmStatisticsProductReqVO reqVO) {
// 情况一:选中某个用户
if (ObjUtil.isNotNull(reqVO.getUserId())) {
return ListUtil.of(reqVO.getUserId());
}
// 情况二:选中某个部门
// 2.1 获得部门列表
List<Long> deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId);
deptIds.add(reqVO.getDeptId());
// 2.2 获得用户编号
return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
}
}
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmOwnerRecordMapper">
<select id="selectPoolCustomerPutCountByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO">
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
COUNT(*) AS customer_put_count
FROM crm_owner_record
WHERE deleted = 0
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
AND pre_owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND post_owner_user_id IS NULL
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY time
</select>
<select id="selectPoolCustomerTakeCountByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO">
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
COUNT(*) AS customer_take_count
FROM crm_owner_record
WHERE deleted = 0
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
AND pre_owner_user_id IS NULL
AND post_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}
GROUP BY time
</select>
<select id="selectPoolCustomerPutCountByUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO">
SELECT pre_owner_user_id AS owner_user_id,
COUNT(*) AS customer_put_count
FROM crm_owner_record
WHERE deleted = 0
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
AND pre_owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND post_owner_user_id IS NULL
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY pre_owner_user_id
</select>
<select id="selectPoolCustomerTakeCountByUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO">
SELECT post_owner_user_id AS owner_user_id,
COUNT(*) AS customer_take_count
FROM crm_owner_record
WHERE deleted = 0
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
AND pre_owner_user_id IS NULL
AND post_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}
GROUP BY post_owner_user_id
</select>
</mapper>
@@ -25,6 +25,9 @@
<if test="status != null">
AND end_status = #{status}
</if>
<if test="reqVO.statusTypeId != null">
AND status_type_id = #{reqVO.statusTypeId}
</if>
AND owner_user_id IN
<foreach collection="reqVO.userIds" item="userId" open="(" close=")" separator=",">
#{userId}
@@ -38,9 +41,12 @@
SELECT
end_status AS endStatus,
COUNT(*) AS businessCount,
SUM(total_price) AS totalPrice
IFNULL(SUM(total_price), 0) AS totalPrice
FROM crm_business
WHERE deleted = 0 AND end_status IS NOT NULL
<if test="statusTypeId != null">
AND status_type_id = #{statusTypeId}
</if>
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
@@ -50,14 +56,44 @@
GROUP BY end_status
</select>
<select id="selectBusinessSummaryListGroupByStatus"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByStatusRespVO">
SELECT
status.id AS statusId,
status.name AS statusName,
status.percent AS statusPercent,
status.sort AS sort,
COUNT(business.id) AS businessCount,
IFNULL(SUM(business.total_price), 0) AS totalPrice
FROM crm_business_status status
LEFT JOIN crm_business business ON business.status_id = status.id
AND business.deleted = 0
AND business.end_status IS NULL
AND business.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND business.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
#{times[1],javaType=java.time.LocalDateTime}
WHERE status.deleted = 0
<if test="statusTypeId != null">
AND status.type_id = #{statusTypeId}
</if>
GROUP BY status.id, status.name, status.percent, status.sort
ORDER BY status.sort ASC
</select>
<select id="selectBusinessSummaryGroupByDate"
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,
SUM(total_price) AS totalPrice
IFNULL(SUM(total_price), 0) AS totalPrice
FROM crm_business
WHERE deleted = 0
<if test="statusTypeId != null">
AND status_type_id = #{statusTypeId}
</if>
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
@@ -75,6 +111,9 @@
SUM(IF(end_status = 1, 1, 0)) AS businessWinCount
FROM crm_business
WHERE deleted = 0
<if test="statusTypeId != null">
AND status_type_id = #{statusTypeId}
</if>
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
@@ -53,4 +53,31 @@
GROUP BY time
</select>
<select id="selectContractSummary"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceSummaryRespVO">
SELECT DATE_FORMAT(contract.order_date, '%Y%m') AS time,
COUNT(DISTINCT contract.id) AS contract_count,
IFNULL(SUM(contract.total_price), 0) AS contract_price,
IFNULL(SUM(receivable.receivable_price), 0) AS receivable_price,
IFNULL(SUM(contract.total_price), 0) - IFNULL(SUM(receivable.receivable_price), 0) AS unreceived_price
FROM crm_contract AS contract
LEFT JOIN (
SELECT contract_id,
IFNULL(SUM(price), 0) AS receivable_price
FROM crm_receivable
WHERE deleted = 0
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
GROUP BY contract_id
) AS receivable ON contract.id = receivable.contract_id
WHERE contract.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND contract.owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND contract.order_date between #{times[0],javaType=java.time.LocalDateTime} and
#{times[1],javaType=java.time.LocalDateTime}
GROUP BY time
</select>
</mapper>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPerformanceTargetMapper">
<resultMap id="CrmStatisticsPerformanceTargetActualResultMap"
type="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetActualRespVO">
<result property="month" column="month"/>
<result property="currentPrice" column="current_price"/>
</resultMap>
<select id="selectContractActualByMonth"
resultMap="CrmStatisticsPerformanceTargetActualResultMap">
SELECT MONTH(order_date) AS month,
IFNULL(SUM(total_price), 0) AS current_price
FROM crm_contract
WHERE deleted = 0
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND YEAR(order_date) = #{year}
GROUP BY MONTH(order_date)
</select>
<select id="selectReceivableActualByMonth"
resultMap="CrmStatisticsPerformanceTargetActualResultMap">
SELECT MONTH(return_time) AS month,
IFNULL(SUM(price), 0) AS current_price
FROM crm_receivable
WHERE deleted = 0
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND YEAR(return_time) = #{year}
GROUP BY MONTH(return_time)
</select>
</mapper>
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsProductMapper">
<sql id="ProductStatisticsWhere">
contract.deleted = 0
AND contract_product.deleted = 0
AND product.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND contract.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND contract.order_date BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
<if test="categoryIds != null and categoryIds.size() > 0">
AND product.category_id IN
<foreach collection="categoryIds" item="categoryId" open="(" close=")" separator=",">
#{categoryId}
</foreach>
</if>
<if test="productId != null">
AND product.id = #{productId}
</if>
</sql>
<select id="selectProductSalesList"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO">
SELECT category.id AS category_id,
IFNULL(category.name, '未分类') AS category_name,
product.id AS product_id,
product.name AS product_name,
contract.id AS contract_id,
contract.no AS contract_no,
contract.name AS contract_name,
contract.owner_user_id,
owner.nickname AS owner_user_name,
customer.id AS customer_id,
customer.name AS customer_name,
contract_product.product_price,
contract_product.count AS product_count,
contract_product.total_price AS product_total_price
FROM crm_contract_product AS contract_product
INNER JOIN crm_contract AS contract ON contract_product.contract_id = contract.id
INNER JOIN crm_product AS product ON contract_product.product_id = product.id
LEFT JOIN crm_product_category AS category ON product.category_id = category.id AND category.deleted = 0
LEFT JOIN crm_customer AS customer ON contract.customer_id = customer.id AND customer.deleted = 0
LEFT JOIN system_users AS owner ON contract.owner_user_id = owner.id AND owner.deleted = 0
WHERE <include refid="ProductStatisticsWhere"/>
ORDER BY category.id ASC, product.id ASC, contract.order_date DESC
</select>
<select id="selectProductCategorySummary"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO">
SELECT category.id AS category_id,
IFNULL(category.name, '未分类') AS category_name,
COUNT(DISTINCT contract.id) AS contract_count,
IFNULL(SUM(contract_product.count), 0) AS product_count,
IFNULL(SUM(contract_product.total_price), 0) AS product_total_price
FROM crm_contract_product AS contract_product
INNER JOIN crm_contract AS contract ON contract_product.contract_id = contract.id
INNER JOIN crm_product AS product ON contract_product.product_id = product.id
LEFT JOIN crm_product_category AS category ON product.category_id = category.id AND category.deleted = 0
WHERE <include refid="ProductStatisticsWhere"/>
GROUP BY category.id, category.name
ORDER BY product_total_price DESC, product_count DESC
</select>
</mapper>
@@ -167,6 +167,15 @@
<if test="sourceDocCode != null and sourceDocCode != ''">
AND t.sourceDocCode LIKE CONCAT('%', #{sourceDocCode}, '%')
</if>
<if test="sourceDocType != null">
AND t.sourceDocType = #{sourceDocType}
</if>
<if test="sourceDocId != null">
AND t.sourceDocId = #{sourceDocId}
</if>
<if test="sourceLineId != null">
AND t.sourceLineId = #{sourceLineId}
</if>
<if test="qcType != null">
AND t.qcType = #{qcType}
</if>