userIds;
+
+ @Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
+ private Integer interval;
+
+ @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @Size(min = 2, max = 2, message = "请选择时间范围")
+ private LocalDateTime[] times;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByDateRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByDateRespVO.java
new file mode 100644
index 000000000..7ffcb20ff
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByDateRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 客户总量分析(按日期) VO")
+@Data
+public class CrmStatisticsCustomerSummaryByDateRespVO {
+
+ @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
+ private String time;
+
+ @Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerCreateCount;
+
+ @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerDealCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByUserRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByUserRespVO.java
new file mode 100644
index 000000000..fa8372b8b
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByUserRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "管理后台 - CRM 客户总量分析(按用户) VO")
+@Data
+public class CrmStatisticsCustomerSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
+
+ @Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerCreateCount;
+
+ @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerDealCount;
+
+ @Schema(description = "合同总金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ private BigDecimal contractPrice;
+
+ @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ private BigDecimal receivablePrice;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByDateRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByDateRespVO.java
new file mode 100644
index 000000000..9040c1eab
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByDateRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 跟进次数分析(按日期) VO")
+@Data
+public class CrmStatisticsFollowUpSummaryByDateRespVO {
+
+ @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
+ private String time;
+
+ @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer followUpRecordCount;
+
+ @Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer followUpCustomerCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByTypeRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByTypeRespVO.java
new file mode 100644
index 000000000..d39f1cc0d
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByTypeRespVO.java
@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 跟进次数分析(按类型) VO")
+@Data
+public class CrmStatisticsFollowUpSummaryByTypeRespVO {
+
+ @Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer followUpType;
+
+ @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer followUpRecordCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByUserRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByUserRespVO.java
new file mode 100644
index 000000000..065135626
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByUserRespVO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 跟进次数分析(按用户) VO")
+@Data
+public class CrmStatisticsFollowUpSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
+
+ @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer followUpRecordCount;
+
+ @Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer followUpCustomerCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsPoolSummaryByDateRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsPoolSummaryByDateRespVO.java
new file mode 100644
index 000000000..ce09a9933
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsPoolSummaryByDateRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 公海客户分析(按日期) VO")
+@Data
+public class CrmStatisticsPoolSummaryByDateRespVO {
+
+ @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
+ private String time;
+
+ @Schema(description = "进入公海客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerPutCount;
+
+ @Schema(description = "公海领取客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerTakeCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsPoolSummaryByUserRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsPoolSummaryByUserRespVO.java
new file mode 100644
index 000000000..fb59e8477
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsPoolSummaryByUserRespVO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 公海客户分析(按用户) VO")
+@Data
+public class CrmStatisticsPoolSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
+
+ @Schema(description = "进入公海客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerPutCount;
+
+ @Schema(description = "公海领取客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerTakeCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceReqVO.java
new file mode 100644
index 000000000..d104e375b
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+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 CrmStatisticsPerformanceReqVO {
+
+ @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "部门 id 不能为空")
+ private Long deptId;
+
+ /**
+ * 负责人用户 id, 当用户为空, 则计算部门下用户
+ */
+ @Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
+ private Long userId;
+
+ /**
+ * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
+ *
+ * 后续,可能会支持选择部分用户进行查询
+ */
+ @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
+ private List userIds;
+
+ // TODO @scholar:应该传递的是 int year;年份
+ @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED)
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @NotEmpty(message = "时间范围不能为空")
+ private LocalDateTime[] times;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceRespVO.java
new file mode 100644
index 000000000..f2cb9be60
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceRespVO.java
@@ -0,0 +1,25 @@
+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 CrmStatisticsPerformanceRespVO {
+
+ @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
+ private String time;
+
+ @Schema(description = "当月统计结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private BigDecimal currentMonthCount;
+
+ @Schema(description = "上月统计结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private BigDecimal lastMonthCount;
+
+ @Schema(description = "去年同期统计结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private BigDecimal lastYearCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerAreaRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerAreaRespVO.java
new file mode 100644
index 000000000..3420e7e5b
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerAreaRespVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 客户省份分析 VO")
+@Data
+public class CrmStatisticCustomerAreaRespVO {
+
+ @Schema(description = "省份编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer areaId;
+ @Schema(description = "省份名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "浙江省")
+ private String areaName;
+
+ @Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerCount;
+
+ @Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer dealCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerIndustryRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerIndustryRespVO.java
new file mode 100644
index 000000000..84b8de70f
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerIndustryRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 客户行业分析 VO")
+@Data
+public class CrmStatisticCustomerIndustryRespVO {
+
+ @Schema(description = "客户行业ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer industryId;
+
+ @Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerCount;
+
+ @Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer dealCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerLevelRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerLevelRespVO.java
new file mode 100644
index 000000000..dea4eeb0c
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerLevelRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 客户级别分析 VO")
+@Data
+public class CrmStatisticCustomerLevelRespVO {
+
+ @Schema(description = "客户级别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer level;
+
+ @Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerCount;
+
+ @Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer dealCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerSourceRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerSourceRespVO.java
new file mode 100644
index 000000000..61b9688ff
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerSourceRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 客户来源分析 VO")
+@Data
+public class CrmStatisticCustomerSourceRespVO {
+
+ @Schema(description = "客户来源编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer source;
+
+ @Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer customerCount;
+
+ @Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer dealCount;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticsPortraitReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticsPortraitReqVO.java
new file mode 100644
index 000000000..c284e7457
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticsPortraitReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+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 CrmStatisticsPortraitReqVO {
+
+ @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "部门 id 不能为空")
+ private Long deptId;
+
+ /**
+ * 负责人用户 id, 当用户为空, 则计算部门下用户
+ */
+ @Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
+ private Long userId;
+
+ /**
+ * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
+ * 后续,可能会支持选择部分用户进行查询
+ */
+ @Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
+ private List userIds;
+
+ /**
+ * 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
+ * 并作为参数传递给Mapper
+ */
+ @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] times;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankReqVO.java
similarity index 98%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankReqVO.java
index 49167409d..c4a16ef55 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankReqVO.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotEmpty;
@@ -21,7 +21,7 @@ public class CrmStatisticsRankReqVO {
/**
* userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
- *
+ *
* 后续,可能会支持选择部分用户进行查询
*/
@Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankRespVO.java
similarity index 80%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankRespVO.java
index d5c865fd3..feb2f3f2e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankRespVO.java
@@ -1,12 +1,14 @@
-package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
+import java.math.BigDecimal;
-@Schema(description = "管理后台 - CRM BI 排行榜统计 Response VO")
+
+@Schema(description = "管理后台 - CRM 排行榜统计 Response VO")
@Data
-public class CrmStatisticsRanKRespVO {
+public class CrmStatisticsRankRespVO {
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long ownerUserId;
@@ -22,8 +24,10 @@ public class CrmStatisticsRanKRespVO {
*
* 1. 金额:合同金额排行、回款金额排行
* 2. 个数:签约合同排行、产品销量排行、产品销量排行、新增客户数排行、新增联系人排行、跟进次数排行、跟进客户数排行
+ *
+ * 为什么使用 BigDecimal 的原因:
*/
@Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
- private Integer count;
+ private BigDecimal count;
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
index 4718a8d7a..769c5b485 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
+import java.util.List;
/**
* 商机 Mapper
@@ -57,4 +58,10 @@ public interface CrmBusinessMapper extends BaseMapperX {
return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId);
}
+ default List selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId){
+ return selectList(new LambdaQueryWrapperX()
+ .eq(CrmBusinessDO::getCustomerId, customerId)
+ .eq(CrmBusinessDO::getOwnerUserId, ownerUserId));
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java
index 4a77665ad..75f2a750e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java
@@ -73,4 +73,9 @@ public interface CrmContactMapper extends BaseMapperX {
return selectList(CrmContactDO::getCustomerId, customerId);
}
+ default List selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
+ return selectList(CrmContactDO::getCustomerId, customerId,
+ CrmContactDO::getOwnerUserId, ownerUserId);
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
index e06afb257..14d743291 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
@@ -117,4 +117,10 @@ public interface CrmContractMapper extends BaseMapperX {
return selectCount(query);
}
+ default List selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(CrmContractDO::getCustomerId, customerId)
+ .eq(CrmContractDO::getOwnerUserId, ownerUserId));
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java
index 26f212e5e..07b7b6b1f 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java
@@ -53,9 +53,11 @@ public interface CrmPermissionMapper extends BaseMapperX {
CrmPermissionDO::getUserId, userId);
}
- default CrmPermissionDO selectByBizIdAndUserId(Long bizId, Long userId) {
- return selectOne(CrmPermissionDO::getBizId, bizId,
- CrmPermissionDO::getUserId, userId);
+ default CrmPermissionDO selectByBizAndUserId(Integer bizType, Long bizId, Long userId) {
+ return selectOne(new LambdaQueryWrapperX()
+ .eq(CrmPermissionDO::getBizType, bizType)
+ .eq(CrmPermissionDO::getBizId, bizId)
+ .eq(CrmPermissionDO::getUserId, userId));
}
default int deletePermission(Integer bizType, Long bizId) {
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
index 5a5e9ce2b..0c821c8c2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
@@ -99,4 +99,8 @@ public interface CrmReceivableMapper extends BaseMapperX {
return convertMap(result, obj -> (Long) obj.get("contract_id"), obj -> (BigDecimal) obj.get("total_price"));
}
+ default Long selectCountByContractId(Long contractId) {
+ return selectCount(CrmReceivableDO::getContractId, contractId);
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java
new file mode 100644
index 000000000..458ef79c3
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java
@@ -0,0 +1,194 @@
+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
+ *
+ * @author dhb52
+ */
+@Mapper
+public interface CrmStatisticsCustomerMapper {
+
+ /**
+ * 新建客户数(按日期)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectCustomerCreateCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 成交客户数(按日期)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectCustomerDealCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 新建客户数(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectCustomerCreateCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 成交客户数(按用户)
+ *
+ * @param reqVO 请求参数@param reqVO 请求参数@param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectCustomerDealCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 合同总金额(按用户)
+ * @return 统计数据@return 统计数据@param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectContractPriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 合同回款金额(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectReceivablePriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 跟进次数(按日期)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectFollowUpRecordCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 跟进客户数(按日期)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectFollowUpCustomerCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 跟进次数(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectFollowUpRecordCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 跟进客户数(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectFollowUpCustomerCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+
+
+ /**
+ * 首次合同、回款信息(用于【客户转化率】页面)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectContractSummary(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 跟进次数(按类型)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectFollowUpRecordCountGroupByType(CrmStatisticsCustomerReqVO reqVO);
+
+
+ /**
+ * 进入公海客户数(按日期)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ // TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
+ default List selectPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO) {
+ LocalDateTime currrentDate = LocalDateTimeUtil.beginOfDay(reqVO.getTimes()[0]);
+ LocalDateTime endDate = LocalDateTimeUtil.endOfDay(reqVO.getTimes()[1]);
+ List 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 selectPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO) {
+ return selectPoolCustomerPutCountByDate(reqVO);
+ }
+
+ /**
+ * 进入公海客户数(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ // TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
+ default List 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 selectPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO) {
+ return selectPoolCustomerPutCountByUser(reqVO);
+ }
+
+ /**
+ * 客户成交周期(按日期)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectCustomerDealCycleGroupByDate(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 客户成交周期(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO);
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsPerformanceMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsPerformanceMapper.java
new file mode 100644
index 000000000..6467a098d
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsPerformanceMapper.java
@@ -0,0 +1,41 @@
+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 org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * CRM 员工业绩分析 Mapper
+ *
+ * @author scholar
+ */
+@Mapper
+public interface CrmStatisticsPerformanceMapper {
+
+ /**
+ * 员工签约合同数量
+ *
+ * @param performanceReqVO 参数
+ * @return 员工签约合同数量
+ */
+ List selectContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
+
+ /**
+ * 员工签约合同金额
+ *
+ * @param performanceReqVO 参数
+ * @return 员工签约合同金额
+ */
+ List selectContractPricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
+
+ /**
+ * 员工回款金额
+ *
+ * @param performanceReqVO 参数
+ * @return 员工回款金额
+ */
+ List selectReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsPortraitMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsPortraitMapper.java
new file mode 100644
index 000000000..a7c942752
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsPortraitMapper.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
+
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.*;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * CRM 数据画像 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface CrmStatisticsPortraitMapper {
+
+ List selectSummaryListGroupByAreaId(CrmStatisticsPortraitReqVO reqVO);
+
+ List selectCustomerIndustryListGroupByIndustryId(CrmStatisticsPortraitReqVO reqVO);
+
+ List selectCustomerSourceListGroupBySource(CrmStatisticsPortraitReqVO reqVO);
+
+ List selectCustomerLevelListGroupByLevel(CrmStatisticsPortraitReqVO reqVO);
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankingMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankMapper.java
similarity index 72%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankingMapper.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankMapper.java
index 4b51ab2fe..55062f57a 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankingMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankMapper.java
@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@@ -12,7 +12,7 @@ import java.util.List;
* @author anhaohao
*/
@Mapper
-public interface CrmStatisticsRankingMapper {
+public interface CrmStatisticsRankMapper {
/**
* 查询合同金额排行榜
@@ -20,7 +20,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO 参数
* @return 合同金额排行榜
*/
- List selectContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
+ List selectContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询回款金额排行榜
@@ -28,7 +28,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO 参数
* @return 回款金额排行榜
*/
- List selectReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
+ List selectReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询签约合同数量排行榜
@@ -36,7 +36,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO 参数
* @return 签约合同数量排行榜
*/
- List selectContractCountRank(CrmStatisticsRankReqVO rankReqVO);
+ List selectContractCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询产品销量排行榜
@@ -44,7 +44,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO 参数
* @return 产品销量排行榜
*/
- List selectProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
+ List selectProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询新增客户数排行榜
@@ -52,7 +52,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO 参数
* @return 新增客户数排行榜
*/
- List selectCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
+ List selectCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询联系人数量排行榜
@@ -60,7 +60,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO 参数
* @return 联系人数量排行榜
*/
- List selectContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
+ List selectContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询跟进次数排行榜
@@ -68,7 +68,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO 参数
* @return 跟进次数排行榜
*/
- List selectFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
+ List selectFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询跟进客户数排行榜
@@ -76,6 +76,6 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO 参数
* @return 跟进客户数排行榜
*/
- List selectFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
+ List selectFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/excel/core/AreaExcelColumnSelectFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/excel/core/AreaExcelColumnSelectFunction.java
new file mode 100644
index 000000000..8f0a88905
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/excel/core/AreaExcelColumnSelectFunction.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.crm.framework.excel.core;
+
+import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction;
+import cn.iocoder.yudao.framework.ip.core.Area;
+import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 地区下拉框数据源的 {@link ExcelColumnSelectFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+public class AreaExcelColumnSelectFunction implements ExcelColumnSelectFunction {
+
+ public static final String NAME = "getCrmAreaNameList"; // 防止和别的模块重名
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public List getOptions() {
+ // 获取地区下拉数据
+ // TODO @puhui999:嘿嘿,这里改成省份、城市、区域,三个选项,难度大么?
+ Area area = AreaUtils.getArea(Area.ID_CHINA);
+ return AreaUtils.getAreaNodePathList(area.getChildren());
+ }
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/excel/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/excel/package-info.java
new file mode 100644
index 000000000..c2deef8b8
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/excel/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * crm 模块的 excel 拓展封装
+ */
+package cn.iocoder.yudao.module.crm.framework.excel;
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java
index 975a2eb51..413b652c1 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java
@@ -1 +1,4 @@
+/**
+ * crm 模块的 operatelog 拓展封装
+ */
package cn.iocoder.yudao.module.crm.framework.operatelog;
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java
index 44f408016..97f76dbe1 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java
@@ -1 +1,4 @@
+/**
+ * crm 模块的 permission 拓展封装
+ */
package cn.iocoder.yudao.module.crm.framework.permission;
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
index f71680acd..6f794e0f3 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
@@ -46,8 +46,8 @@ public interface CrmBusinessService {
/**
* 更新商机相关跟进信息
*
- * @param id 编号
- * @param contactNextTime 下次联系时间
+ * @param id 编号
+ * @param contactNextTime 下次联系时间
* @param contactLastContent 最后联系内容
*/
void updateBusinessFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
@@ -55,7 +55,7 @@ public interface CrmBusinessService {
/**
* 更新商机的下次联系时间
*
- * @param ids 编号数组
+ * @param ids 编号数组
* @param contactNextTime 下次联系时间
*/
void updateBusinessContactNextTime(Collection ids, LocalDateTime contactNextTime);
@@ -185,4 +185,13 @@ public interface CrmBusinessService {
return status.getName();
}
+ /**
+ * 获得商机列表
+ *
+ * @param customerId 客户编号
+ * @param ownerUserId 负责人编号
+ * @return 商机列表
+ */
+ List getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
index 47865c64f..142c9def9 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -88,7 +88,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
success = CRM_BUSINESS_CREATE_SUCCESS)
public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
// 1.1 校验产品项的有效性
- List businessProducts = validateBusinessProducts(createReqVO.getProducts());
+ List businessProducts = validateBusinessProducts(createReqVO.getBusinessProducts());
// 1.2 校验关联字段
validateRelationDataExists(createReqVO);
@@ -129,7 +129,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
// 1.1 校验存在
CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
// 1.2 校验产品项的有效性
- List businessProducts = validateBusinessProducts(updateReqVO.getProducts());
+ List businessProducts = validateBusinessProducts(updateReqVO.getBusinessProducts());
// 1.3 校验关联字段
validateRelationDataExists(updateReqVO);
@@ -202,9 +202,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
}
}
- private List validateBusinessProducts(List list) {
+ private List validateBusinessProducts(List list) {
// 1. 校验产品存在
- productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.Product::getProductId));
+ productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.BusinessProduct::getProductId));
// 2. 转化为 CrmBusinessProductDO 列表
return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class,
item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount()))));
@@ -234,7 +234,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
}
// 1.4 校验是不是状态没变更
if ((reqVO.getStatusId() != null && reqVO.getStatusId().equals(business.getStatusId()))
- || (reqVO.getEndStatus() != null && reqVO.getEndStatus().equals(business.getEndStatus()))) {
+ || (reqVO.getEndStatus() != null && reqVO.getEndStatus().equals(business.getEndStatus()))) {
throw exception(BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS);
}
@@ -301,7 +301,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
// 2.1 数据权限转移
permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_BUSINESS.getType(),
- reqVO.getNewOwnerUserId(), reqVO.getId(), CrmPermissionLevelEnum.OWNER.getLevel()));
+ reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
// 2.2 设置新的负责人
businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
@@ -370,4 +370,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
return businessMapper.selectCountByStatusTypeId(statusTypeId);
}
+ @Override
+ public List getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
+ return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
index 72ddd37bd..24273db2f 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
@@ -75,8 +75,8 @@ public interface CrmContactService {
/**
* 更新联系人的下次联系时间
*
- * @param ids 编号数组
- * @param contactNextTime 下次联系时间
+ * @param ids 编号数组
+ * @param contactNextTime 下次联系时间
*/
void updateContactContactNextTime(Collection ids, LocalDateTime contactNextTime);
@@ -160,4 +160,13 @@ public interface CrmContactService {
*/
Long getContactCountByCustomerId(Long customerId);
+ /**
+ * 获得联系人列表
+ *
+ * @param customerId 客户编号
+ * @param ownerUserId 负责人编号
+ * @return 联系人列表
+ */
+ List getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
index bd230f902..d6eb2a83c 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
@@ -298,4 +298,9 @@ public class CrmContactServiceImpl implements CrmContactService {
return contactMapper.selectCount(CrmContactDO::getCustomerId, customerId);
}
+ @Override
+ public List getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
+ return contactMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
index 10be39142..f7a686759 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
@@ -58,8 +58,8 @@ public interface CrmContractService {
/**
* 更新合同相关的更进信息
*
- * @param id 合同编号
- * @param contactNextTime 下次联系时间
+ * @param id 合同编号
+ * @param contactNextTime 下次联系时间
* @param contactLastContent 最后联系内容
*/
void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
@@ -75,7 +75,7 @@ public interface CrmContractService {
/**
* 更新合同流程审批结果
*
- * @param id 合同编号
+ * @param id 合同编号
* @param bpmResult BPM 审批结果
*/
void updateContractAuditStatus(Long id, Integer bpmResult);
@@ -193,4 +193,13 @@ public interface CrmContractService {
*/
Long getRemindContractCount(Long userId);
+ /**
+ * 获得合同列表
+ *
+ * @param customerId 客户编号
+ * @param ownerUserId 负责人编号
+ * @return 合同列表
+ */
+ List getContractListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
index 951828b7f..bcf5d9a9d 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -30,11 +30,13 @@ import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
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.crm.service.product.CrmProductService;
+import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -86,7 +88,9 @@ public class CrmContractServiceImpl implements CrmContractService {
private CrmContactService contactService;
@Resource
private CrmContractConfigService contractConfigService;
-
+ @Resource
+ @Lazy // 延迟加载,避免循环依赖
+ private CrmReceivableService receivableService;
@Resource
private AdminUserApi adminUserApi;
@Resource
@@ -222,15 +226,19 @@ public class CrmContractServiceImpl implements CrmContractService {
success = CRM_CONTRACT_DELETE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void deleteContract(Long id) {
- // TODO @puhui999:如果被 CrmReceivableDO 所使用,则不允许删除
- // 校验存在
+ // 1.1 校验存在
CrmContractDO contract = validateContractExists(id);
- // 删除
+ // 1.2 如果被 CrmReceivableDO 所使用,则不允许删除
+ if (receivableService.getReceivableCountByContractId(contract.getId()) > 0) {
+ throw exception(CONTRACT_DELETE_FAIL);
+ }
+
+ // 2.1 删除合同
contractMapper.deleteById(id);
- // 删除数据权限
+ // 2.2 删除数据权限
crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTRACT.getType(), id);
- // 记录操作日志上下文
+ // 3. 记录操作日志上下文
LogRecordContext.putVariable("contractName", contract.getName());
}
@@ -399,4 +407,9 @@ public class CrmContractServiceImpl implements CrmContractService {
return contractMapper.selectCountByRemind(userId, config);
}
+ @Override
+ public List getContractListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
+ return contractMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
index 68d537582..2ca6c9cc2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -9,7 +9,13 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
@@ -201,7 +207,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
CrmCustomerDO customer = validateCustomerExists(reqVO.getId());
// 1.2 校验拥有客户是否到达上限
validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1);
-
// 2.1 数据权限转移
permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
@@ -209,10 +214,45 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
customerMapper.updateById(new CrmCustomerDO().setId(reqVO.getId())
.setOwnerUserId(reqVO.getNewOwnerUserId()).setOwnerTime(LocalDateTime.now()));
+ // 2.3 同时转移
+ if (CollUtil.isNotEmpty(reqVO.getToBizTypes())) {
+ transfer(reqVO, userId);
+ }
+
// 3. 记录转移日志
LogRecordContext.putVariable("customer", customer);
}
+ /**
+ * 转移客户时,需要额外有【联系人】【商机】【合同】
+ *
+ * @param reqVO 请求
+ * @param userId 用户编号
+ */
+ private void transfer(CrmCustomerTransferReqVO reqVO, Long userId) {
+ if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTACT.getType())) {
+ List contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getId(), userId);
+ contactList.forEach(item -> {
+ contactService.transferContact(new CrmContactTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
+ reqVO.getOldOwnerPermissionLevel()), userId);
+ });
+ }
+ if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_BUSINESS.getType())) {
+ List businessList = businessService.getBusinessListByCustomerIdOwnerUserId(reqVO.getId(), userId);
+ businessList.forEach(item -> {
+ businessService.transferBusiness(new CrmBusinessTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
+ reqVO.getOldOwnerPermissionLevel()), userId);
+ });
+ }
+ if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTRACT.getType())) {
+ List contractList = contractService.getContractListByCustomerIdOwnerUserId(reqVO.getId(), userId);
+ contractList.forEach(item -> {
+ contractService.transferContract(new CrmContractTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
+ reqVO.getOldOwnerPermissionLevel()), userId);
+ });
+ }
+ }
+
@Override
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_LOCK_SUB_TYPE, bizNo = "{{#lockReqVO.id}}",
success = CRM_CUSTOMER_LOCK_SUCCESS)
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java
index caceb055c..c5a6b854c 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.service.permission;
+import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
@@ -19,6 +20,14 @@ import java.util.List;
*/
public interface CrmPermissionService {
+ /**
+ * 创建数据权限
+ *
+ * @param reqVO 创建信息
+ * @param userId 用户编号
+ */
+ void createPermission(CrmPermissionSaveReqVO reqVO, Long userId);
+
/**
* 创建数据权限
*
@@ -111,10 +120,10 @@ public interface CrmPermissionService {
/**
* 校验是否有指定数据的操作权限
*
- * @param bizType 数据类型,关联 {@link CrmBizTypeEnum}
- * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
- * @param userId 用户编号
- * @param level 权限级别
+ * @param bizType 数据类型,关联 {@link CrmBizTypeEnum}
+ * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
+ * @param userId 用户编号
+ * @param level 权限级别
* @return 是否有权限
*/
boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum level);
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
index ba8c88c7c..cb1b6a1c6 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
@@ -4,28 +4,34 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
+import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
+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.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
-import javax.annotation.Resource;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner;
@@ -40,18 +46,119 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
@Resource
private CrmPermissionMapper permissionMapper;
-
+ @Resource
+ @Lazy // 解决依赖循环
+ private CrmContactService contactService;
+ @Resource
+ @Lazy // 解决依赖循环
+ private CrmBusinessService businessService;
+ @Resource
+ @Lazy // 解决依赖循环
+ private CrmContractService contractService;
@Resource
private AdminUserApi adminUserApi;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER)
+ public void createPermission(CrmPermissionSaveReqVO reqVO, Long userId) {
+ // 1. 创建数据权限
+ createPermission0(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class));
+
+ // 2. 处理【同时添加至】的权限
+ if (CollUtil.isEmpty(reqVO.getToBizTypes())) {
+ return;
+ }
+ List createPermissions = new ArrayList<>();
+ buildContactPermissions(reqVO, userId, createPermissions);
+ buildBusinessPermissions(reqVO, userId, createPermissions);
+ buildContractPermissions(reqVO, userId, createPermissions);
+ if (CollUtil.isEmpty(createPermissions)) {
+ return;
+ }
+ createPermissionBatch(createPermissions);
+ }
+
+ /**
+ * 处理同时添加至联系人
+ *
+ * @param reqVO 请求
+ * @param userId 操作人
+ * @param createPermissions 待添加权限列表
+ */
+ private void buildContactPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List createPermissions) {
+ // 1. 校验是否被同时添加
+ Integer type = CrmBizTypeEnum.CRM_CONTACT.getType();
+ if (!reqVO.getToBizTypes().contains(type)) {
+ return;
+ }
+ // 2. 添加数据权限
+ List contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
+ contactList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
+ }
+
+ /**
+ * 处理同时添加至商机
+ *
+ * @param reqVO 请求
+ * @param userId 操作人
+ * @param createPermissions 待添加权限列表
+ */
+ private void buildBusinessPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List createPermissions) {
+ // 1. 校验是否被同时添加
+ Integer type = CrmBizTypeEnum.CRM_BUSINESS.getType();
+ if (!reqVO.getToBizTypes().contains(type)) {
+ return;
+ }
+ // 2. 添加数据权限
+ List businessList = businessService.getBusinessListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
+ businessList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
+ }
+
+ /**
+ * 处理同时添加至合同
+ *
+ * @param reqVO 请求
+ * @param userId 操作人
+ * @param createPermissions 待添加权限列表
+ */
+ private void buildContractPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List createPermissions) {
+ // 1. 校验是否被同时添加
+ Integer type = CrmBizTypeEnum.CRM_CONTRACT.getType();
+ if (!reqVO.getToBizTypes().contains(type)) {
+ return;
+ }
+ // 2. 添加数据权限
+ List contractList = contractService.getContractListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
+ contractList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
+ }
+
+ private void createBizTypePermissions(CrmPermissionSaveReqVO reqVO, Integer type, Long bizId, String name,
+ List createPermissions) {
+ AdminUserRespDTO user = adminUserApi.getUser(reqVO.getUserId()).getCheckedData();
+ // 1. 需要考虑,被添加人,是不是应该有对应的权限了;
+ CrmPermissionDO permission = hasAnyPermission(type, bizId, reqVO.getUserId());
+ if (ObjUtil.isNotNull(permission)) {
+ throw exception(CRM_PERMISSION_CREATE_FAIL_EXISTS, user.getNickname(), CrmBizTypeEnum.getNameByType(type),
+ name, CrmPermissionLevelEnum.getNameByLevel(permission.getLevel()));
+ }
+ // 2. 添加数据权限
+ createPermissions.add(new CrmPermissionCreateReqBO().setBizType(type)
+ .setBizId(bizId).setUserId(reqVO.getUserId()).setLevel(reqVO.getLevel()));
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public Long createPermission(CrmPermissionCreateReqBO createReqBO) {
+ return createPermission0(createReqBO);
+ }
+
+ private Long createPermission0(CrmPermissionCreateReqBO createReqBO) {
validatePermissionNotExists(Collections.singletonList(createReqBO));
// 1. 校验用户是否存在
adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId()));
-
- // 2. 创建
+ // 2. 插入权限
CrmPermissionDO permission = BeanUtils.toBean(createReqBO, CrmPermissionDO.class);
permissionMapper.insert(permission);
return permission.getId();
@@ -170,7 +277,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
throw exception(CRM_PERMISSION_DELETE_FAIL);
}
// 校验操作人是否为负责人
- CrmPermissionDO permission = permissionMapper.selectByBizIdAndUserId(permissions.get(0).getBizId(), userId);
+ CrmPermissionDO permission = permissionMapper.selectByBizAndUserId(permissions.get(0).getBizType(), permissions.get(0).getBizId(), userId);
if (permission == null) {
throw exception(CRM_PERMISSION_DELETE_DENIED);
}
@@ -220,4 +327,9 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), level.getLevel()));
}
+ public CrmPermissionDO hasAnyPermission(Integer bizType, Long bizId, Long userId) {
+ List permissionList = permissionMapper.selectByBizTypeAndBizId(bizType, bizId);
+ return findFirst(permissionList, permission -> ObjUtil.equal(permission.getUserId(), userId));
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
index 21cfe9c69..dd3de19b1 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
@@ -19,9 +19,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
-import javax.annotation.Resource;
-
-import org.springframework.context.annotation.Lazy;
+import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -47,9 +45,6 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
@Resource
private CrmReceivablePlanMapper receivablePlanMapper;
- @Resource
- @Lazy // 延迟加载,避免循环依赖
- private CrmReceivableService receivableService;
@Resource
private CrmContractService contractService;
@Resource
@@ -145,7 +140,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
// 2. 删除
receivablePlanMapper.deleteById(id);
// 3. 删除数据权限
- permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
+ permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), id);
// 4. 记录操作日志上下文
LogRecordContext.putVariable("receivablePlan", receivablePlan);
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
index 74ee2407c..eec4cd550 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
@@ -122,4 +122,12 @@ public interface CrmReceivableService {
*/
Map getReceivablePriceMapByContractId(Collection contractIds);
+ /**
+ * 根据合同编号查询回款数量
+ *
+ * @param contractId 合同编号
+ * @return 回款数量
+ */
+ Long getReceivableCountByContractId(Long contractId);
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
index 565862792..83e68cce2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
@@ -37,10 +37,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.math.BigDecimal;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
@@ -81,7 +78,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
@Resource
private BpmProcessInstanceApi bpmProcessInstanceApi;
- // TODO @puhui999:操作日志没记录上
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}",
@@ -115,6 +111,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
// 5. 记录操作日志上下文
LogRecordContext.putVariable("receivable", receivable);
+ LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
return receivable.getId();
}
@@ -156,7 +153,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
}
}
- // TODO @puhui999:操作日志没记录上
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
@@ -164,11 +160,14 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
@CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) {
Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
- // 1.1 校验可回款金额超过上限
- validateReceivablePriceExceedsLimit(updateReqVO);
updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
- // 1.2 校验存在
+ // 1.1 校验存在
CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());
+ updateReqVO.setOwnerUserId(receivable.getOwnerUserId()).setCustomerId(receivable.getCustomerId())
+ .setContractId(receivable.getContractId()).setPlanId(receivable.getPlanId()); // 设置已存在的值
+ // 1.2 校验可回款金额超过上限
+ validateReceivablePriceExceedsLimit(updateReqVO);
+
// 1.3 只有草稿、审批中,可以编辑;
if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
CrmAuditStatusEnum.PROCESS.getStatus())) {
@@ -180,8 +179,17 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
receivableMapper.updateById(updateObj);
// 3. 记录操作日志上下文
- LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
LogRecordContext.putVariable("receivable", receivable);
+ LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
+ LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
+ }
+
+ private Integer getReceivablePeriod(Long planId) {
+ if (Objects.isNull(planId)) {
+ return null;
+ }
+ CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(planId);
+ return receivablePlan.getPeriod();
}
@Override
@@ -212,15 +220,19 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
throw exception(RECEIVABLE_DELETE_FAIL);
}
- // TODO @puhui999:审批通过时,不允许删除;
+ // 1.3 审批通过时,不允许删除
+ if (ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
+ throw exception(RECEIVABLE_DELETE_FAIL_IS_APPROVE);
+ }
- // 2. 删除
+ // 2.1 删除回款
receivableMapper.deleteById(id);
- // 3. 删除数据权限
+ // 2.2 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id);
- // 4. 记录操作日志上下文
+ // 3. 记录操作日志上下文
LogRecordContext.putVariable("receivable", receivable);
+ LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
}
@Override
@@ -289,4 +301,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
return receivableMapper.selectReceivablePriceMapByContractId(contractIds);
}
+ @Override
+ public Long getReceivableCountByContractId(Long contractId) {
+ return receivableMapper.selectCountByContractId(contractId);
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java
new file mode 100644
index 000000000..0e00e9c22
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java
@@ -0,0 +1,96 @@
+package cn.iocoder.yudao.module.crm.service.statistics;
+
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
+
+import java.util.List;
+
+/**
+ * CRM 客户分析 Service 接口
+ *
+ * @author dhb52
+ */
+public interface CrmStatisticsCustomerService {
+
+ /**
+ * 总量分析(按日期)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 总量分析(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 跟进次数分析(按日期)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getFollowUpSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 跟进次数分析(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getFollowUpSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 客户跟进次数分析(按类型)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getFollowUpSummaryByType(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 获取客户的首次合同、回款信息列表,用于【客户转化率】页面
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getContractSummary(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 公海客户分析(按日期)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getPoolSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 公海客户分析(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getPoolSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 客户成交周期(按日期)
+ *
+ * 成交周期的定义:客户 customer 在创建出来,到合同 contract 第一次成交的时间差
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO);
+
+ /**
+ * 客户成交周期(按用户)
+ *
+ * @param reqVO 请求参数
+ * @return 统计数据
+ */
+ List getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO);
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java
new file mode 100644
index 000000000..3801d90ad
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java
@@ -0,0 +1,323 @@
+package cn.iocoder.yudao.module.crm.service.statistics;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+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.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.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+
+/**
+ * CRM 客户分析 Service 实现类
+ *
+ * @author dhb52
+ */
+@Service
+@Validated
+public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerService {
+
+ @Resource
+ private CrmStatisticsCustomerMapper customerMapper;
+
+ @Resource
+ private AdminUserApi adminUserApi;
+ @Resource
+ private DeptApi deptApi;
+
+ @Override
+ public List getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 按天统计,获取分项统计数据
+ List customerCreateCountList = customerMapper.selectCustomerCreateCountGroupByDate(reqVO);
+ List customerDealCountList = customerMapper.selectCustomerDealCountGroupByDate(reqVO);
+
+ // 3. 按照日期间隔,合并数据
+ List timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
+ return convertList(timeRanges, times -> {
+ Integer customerCreateCount = customerCreateCountList.stream()
+ .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+ .mapToInt(CrmStatisticsCustomerSummaryByDateRespVO::getCustomerCreateCount).sum();
+ Integer customerDealCount = customerDealCountList.stream()
+ .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+ .mapToInt(CrmStatisticsCustomerSummaryByDateRespVO::getCustomerDealCount).sum();
+ return new CrmStatisticsCustomerSummaryByDateRespVO()
+ .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
+ .setCustomerCreateCount(customerCreateCount).setCustomerDealCount(customerDealCount);
+ });
+ }
+
+ @Override
+ public List getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 按用户统计,获取分项统计数据
+ List customerCreateCountList = customerMapper.selectCustomerCreateCountGroupByUser(reqVO);
+ List customerDealCountList = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
+ List contractPriceList = customerMapper.selectContractPriceGroupByUser(reqVO);
+ List receivablePriceList = customerMapper.selectReceivablePriceGroupByUser(reqVO);
+
+ // 3.1 按照用户,合并统计数据
+ List summaryList = convertList(reqVO.getUserIds(), userId -> {
+ Integer customerCreateCount = customerCreateCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerCreateCount).sum();
+ Integer customerDealCount = customerDealCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount).sum();
+ BigDecimal contractPrice = contractPriceList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .reduce(BigDecimal.ZERO, (sum, vo) -> sum.add(vo.getContractPrice()), BigDecimal::add);
+ BigDecimal receivablePrice = receivablePriceList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .reduce(BigDecimal.ZERO, (sum, vo) -> sum.add(vo.getReceivablePrice()), BigDecimal::add);
+ return (CrmStatisticsCustomerSummaryByUserRespVO) new CrmStatisticsCustomerSummaryByUserRespVO()
+ .setCustomerCreateCount(customerCreateCount).setCustomerDealCount(customerDealCount)
+ .setContractPrice(contractPrice).setReceivablePrice(receivablePrice).setOwnerUserId(userId);
+ });
+ // 3.2 拼接用户信息
+ appendUserInfo(summaryList);
+ return summaryList;
+ }
+
+ @Override
+ public List getFollowUpSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 按天统计,获取分项统计数据
+ List followUpRecordCountList = customerMapper.selectFollowUpRecordCountGroupByDate(reqVO);
+ List followUpCustomerCountList = customerMapper.selectFollowUpCustomerCountGroupByDate(reqVO);
+
+ // 3. 按照时间间隔,合并统计数据
+ List timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
+ return convertList(timeRanges, times -> {
+ Integer followUpRecordCount = followUpRecordCountList.stream()
+ .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+ .mapToInt(CrmStatisticsFollowUpSummaryByDateRespVO::getFollowUpRecordCount).sum();
+ Integer followUpCustomerCount = followUpCustomerCountList.stream()
+ .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+ .mapToInt(CrmStatisticsFollowUpSummaryByDateRespVO::getFollowUpCustomerCount).sum();
+ return new CrmStatisticsFollowUpSummaryByDateRespVO()
+ .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
+ .setFollowUpCustomerCount(followUpRecordCount).setFollowUpRecordCount(followUpCustomerCount);
+ });
+ }
+
+ @Override
+ public List getFollowUpSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 按用户统计,获取分项统计数据
+ List followUpRecordCountList = customerMapper.selectFollowUpRecordCountGroupByUser(reqVO);
+ List followUpCustomerCountList = customerMapper.selectFollowUpCustomerCountGroupByUser(reqVO);
+
+ // 3.1 按照用户,合并统计数据
+ List summaryList = convertList(reqVO.getUserIds(), userId -> {
+ Integer followUpRecordCount = followUpRecordCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .mapToInt(CrmStatisticsFollowUpSummaryByUserRespVO::getFollowUpRecordCount).sum();
+ Integer followUpCustomerCount = followUpCustomerCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .mapToInt(CrmStatisticsFollowUpSummaryByUserRespVO::getFollowUpCustomerCount).sum();
+ return (CrmStatisticsFollowUpSummaryByUserRespVO) new CrmStatisticsFollowUpSummaryByUserRespVO()
+ .setFollowUpCustomerCount(followUpRecordCount).setFollowUpRecordCount(followUpCustomerCount).setOwnerUserId(userId);
+ });
+ // 3.2 拼接用户信息
+ appendUserInfo(summaryList);
+ return summaryList;
+ }
+
+ @Override
+ public List getFollowUpSummaryByType(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 获得跟进数据
+ return customerMapper.selectFollowUpRecordCountGroupByType(reqVO);
+ }
+
+ @Override
+ public List getContractSummary(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 按用户统计,获取统计数据
+ List summaryList = customerMapper.selectContractSummary(reqVO);
+
+ // 3. 拼接信息
+ Map userMap = adminUserApi.getUserMap(
+ convertSetByFlatMap(summaryList, vo -> Stream.of(NumberUtils.parseLong(vo.getCreator()), vo.getOwnerUserId())));
+ summaryList.forEach(vo -> {
+ findAndThen(userMap, NumberUtils.parseLong(vo.getCreator()), user -> vo.setCreatorUserName(user.getNickname()));
+ findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname()));
+ });
+ return summaryList;
+ }
+
+ @Override
+ public List getPoolSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 按天统计,获取分项统计数据
+ List customerPutCountList = customerMapper.selectPoolCustomerPutCountByDate(reqVO);
+ List customerTakeCountList = customerMapper.selectPoolCustomerTakeCountByDate(reqVO);
+
+ // 3. 按照日期间隔,合并数据
+ List timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
+ return convertList(timeRanges, times -> {
+ Integer customerPutCount = customerPutCountList.stream()
+ .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+ .mapToInt(CrmStatisticsPoolSummaryByDateRespVO::getCustomerPutCount).sum();
+ Integer customerTakeCount = customerTakeCountList.stream()
+ .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+ .mapToInt(CrmStatisticsPoolSummaryByDateRespVO::getCustomerTakeCount).sum();
+ return new CrmStatisticsPoolSummaryByDateRespVO()
+ .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
+ .setCustomerPutCount(customerPutCount).setCustomerTakeCount(customerTakeCount);
+ });
+ }
+
+ @Override
+ public List getPoolSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 按用户统计,获取分项统计数据
+ List customerPutCountList = customerMapper.selectPoolCustomerPutCountByUser(reqVO);
+ List customerTakeCountList = customerMapper.selectPoolCustomerTakeCountByUser(reqVO);
+
+ // 3.1 按照用户,合并统计数据
+ List summaryList = convertList(reqVO.getUserIds(), userId -> {
+ Integer customerPutCount = customerPutCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .mapToInt(CrmStatisticsPoolSummaryByUserRespVO::getCustomerPutCount).sum();
+ Integer customerTakeCount = customerTakeCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .mapToInt(CrmStatisticsPoolSummaryByUserRespVO::getCustomerTakeCount).sum();
+ return (CrmStatisticsPoolSummaryByUserRespVO) new CrmStatisticsPoolSummaryByUserRespVO()
+ .setCustomerPutCount(customerPutCount).setCustomerTakeCount(customerTakeCount)
+ .setOwnerUserId(userId);
+ });
+ // 3.2 拼接用户信息
+ appendUserInfo(summaryList);
+ return summaryList;
+ }
+
+ @Override
+ public List getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 按天统计,获取分项统计数据
+ List customerDealCycleList = customerMapper.selectCustomerDealCycleGroupByDate(reqVO);
+
+ // 3. 按照日期间隔,合并统计数据
+ List timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
+ return convertList(timeRanges, times -> {
+ Double customerDealCycle = customerDealCycleList.stream()
+ .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
+ .mapToDouble(CrmStatisticsCustomerDealCycleByDateRespVO::getCustomerDealCycle).sum();
+ return new CrmStatisticsCustomerDealCycleByDateRespVO()
+ .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
+ .setCustomerDealCycle(customerDealCycle);
+ });
+ }
+
+ @Override
+ public List getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO) {
+ // 1. 获得用户编号数组
+ reqVO.setUserIds(getUserIds(reqVO));
+ if (CollUtil.isEmpty(reqVO.getUserIds())) {
+ return Collections.emptyList();
+ }
+
+ // 2. 按用户统计,获取分项统计数据
+ List customerDealCycleList = customerMapper.selectCustomerDealCycleGroupByUser(reqVO);
+ List customerDealCountList = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
+
+ // 3.1 按照用户,合并统计数据
+ List summaryList = convertList(reqVO.getUserIds(), userId -> {
+ Double customerDealCycle = customerDealCycleList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .mapToDouble(CrmStatisticsCustomerDealCycleByUserRespVO::getCustomerDealCycle).sum();
+ Integer customerDealCount = customerDealCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
+ .mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount).sum();
+ return (CrmStatisticsCustomerDealCycleByUserRespVO) new CrmStatisticsCustomerDealCycleByUserRespVO()
+ .setCustomerDealCycle(customerDealCycle).setCustomerDealCount(customerDealCount).setOwnerUserId(userId);
+ });
+ // 3.2 拼接用户信息
+ appendUserInfo(summaryList);
+ return summaryList;
+ }
+
+ /**
+ * 拼接用户信息(昵称)
+ *
+ * @param voList 统计数据
+ */
+ private void appendUserInfo(List voList) {
+ Map userMap = adminUserApi.getUserMap(
+ convertSet(voList, CrmStatisticsCustomerByUserBaseRespVO::getOwnerUserId));
+ voList.forEach(vo -> findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname())));
+ }
+
+ /**
+ * 获取用户编号数组。如果用户编号为空, 则获得部门下的用户编号数组,包括子部门的所有用户编号
+ *
+ * @param reqVO 请求参数
+ * @return 用户编号数组
+ */
+ private List getUserIds(CrmStatisticsCustomerReqVO reqVO) {
+ // 情况一:选中某个用户
+ if (ObjUtil.isNotNull(reqVO.getUserId())) {
+ return List.of(reqVO.getUserId());
+ }
+ // 情况二:选中某个部门
+ // 2.1 获得部门列表
+ List deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()).getCheckedData(), DeptRespDTO::getId);
+ deptIds.add(reqVO.getDeptId());
+ // 2.2 获得用户编号
+ return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId);
+ }
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceService.java
new file mode 100644
index 000000000..15423af29
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceService.java
@@ -0,0 +1,42 @@
+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 java.util.List;
+
+/**
+ * CRM 员工绩效统计 Service 接口
+ *
+ * @author scholar
+ */
+public interface CrmStatisticsPerformanceService {
+
+ /**
+ * 员工签约合同数量分析
+ *
+ * @param performanceReqVO 排行参数
+ * @return 员工签约合同数量排行分析
+ */
+ List getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
+
+ /**
+ * 员工签约合同金额分析
+ *
+ * @param performanceReqVO 排行参数
+ * @return 员工签约合同金额分析
+ */
+ List