Commit 9cba258d8fccdf7436d9e4241390c125f6cd672c

Authored by YunaiV
2 parents 1e348dbf 2038e520

Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk21

Showing 109 changed files with 3196 additions and 408 deletions

Too many changes.

To preserve performance only 100 of 109 files are displayed.

@@ -16,12 +16,12 @@ @@ -16,12 +16,12 @@
16 <module>yudao-module-system</module> 16 <module>yudao-module-system</module>
17 <module>yudao-module-infra</module> 17 <module>yudao-module-infra</module>
18 <!-- <module>yudao-module-member</module>--> 18 <!-- <module>yudao-module-member</module>-->
19 -<!-- <module>yudao-module-bpm</module>--> 19 + <module>yudao-module-bpm</module>
20 <!-- <module>yudao-module-report</module>--> 20 <!-- <module>yudao-module-report</module>-->
21 <!-- <module>yudao-module-mp</module>--> 21 <!-- <module>yudao-module-mp</module>-->
22 <!-- <module>yudao-module-pay</module>--> 22 <!-- <module>yudao-module-pay</module>-->
23 <!-- <module>yudao-module-mall</module>--> 23 <!-- <module>yudao-module-mall</module>-->
24 -<!-- <module>yudao-module-crm</module>--> 24 + <module>yudao-module-crm</module>
25 <!-- <module>yudao-module-erp</module>--> 25 <!-- <module>yudao-module-erp</module>-->
26 <!-- 示例项目 --> 26 <!-- 示例项目 -->
27 <!-- <module>yudao-example</module>--> 27 <!-- <module>yudao-example</module>-->
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.common.enums;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * 时间间隔的枚举
  12 + *
  13 + * @author dhb52
  14 + */
  15 +@Getter
  16 +@AllArgsConstructor
  17 +public enum DateIntervalEnum implements IntArrayValuable {
  18 +
  19 + DAY(1, "天"),
  20 + WEEK(2, "周"),
  21 + MONTH(3, "月"),
  22 + QUARTER(4, "季度"),
  23 + YEAR(5, "年")
  24 + ;
  25 +
  26 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getInterval).toArray();
  27 +
  28 + /**
  29 + * 类型
  30 + */
  31 + private final Integer interval;
  32 + /**
  33 + * 名称
  34 + */
  35 + private final String name;
  36 +
  37 + @Override
  38 + public int[] array() {
  39 + return ARRAYS;
  40 + }
  41 +
  42 + public static DateIntervalEnum valueOf(Integer interval) {
  43 + return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values());
  44 + }
  45 +
  46 +}
0 \ No newline at end of file 47 \ No newline at end of file
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java
@@ -7,6 +7,7 @@ import com.google.common.cache.LoadingCache; @@ -7,6 +7,7 @@ import com.google.common.cache.LoadingCache;
7 7
8 import java.time.Duration; 8 import java.time.Duration;
9 import java.util.concurrent.Executor; 9 import java.util.concurrent.Executor;
  10 +import java.util.concurrent.ExecutorService;
10 import java.util.concurrent.Executors; 11 import java.util.concurrent.Executors;
11 12
12 /** 13 /**
@@ -17,8 +18,11 @@ import java.util.concurrent.Executors; @@ -17,8 +18,11 @@ import java.util.concurrent.Executors;
17 public class CacheUtils { 18 public class CacheUtils {
18 19
19 public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) { 20 public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
20 - Executor executor = Executors.newCachedThreadPool( // TODO 芋艿:可能要思考下,未来要不要做成可配置  
21 - TtlExecutors.getDefaultDisableInheritableThreadFactory()); // TTL 保证 ThreadLocal 可以透传 21 + // 1. 使用 TTL 包装 ExecutorService,实现 ThreadLocal 的透传
  22 + // https://github.com/YunaiV/ruoyi-vue-pro/issues/432
  23 + ExecutorService executorService = Executors.newCachedThreadPool(); // TODO 芋艿:可能要思考下,未来要不要做成可配置
  24 + Executor executor = TtlExecutors.getTtlExecutorService(executorService);
  25 + // 2. 创建 Guava LoadingCache
22 return CacheBuilder.newBuilder() 26 return CacheBuilder.newBuilder()
23 // 只阻塞当前数据加载线程,其他线程返回旧值 27 // 只阻塞当前数据加载线程,其他线程返回旧值
24 .refreshAfterWrite(duration) 28 .refreshAfterWrite(duration)
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -78,7 +78,7 @@ public class CollectionUtils { @@ -78,7 +78,7 @@ public class CollectionUtils {
78 if (CollUtil.isEmpty(from)) { 78 if (CollUtil.isEmpty(from)) {
79 return new ArrayList<>(); 79 return new ArrayList<>();
80 } 80 }
81 - return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toList()); 81 + return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
82 } 82 }
83 83
84 public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from, 84 public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
@@ -87,7 +87,7 @@ public class CollectionUtils { @@ -87,7 +87,7 @@ public class CollectionUtils {
87 if (CollUtil.isEmpty(from)) { 87 if (CollUtil.isEmpty(from)) {
88 return new ArrayList<>(); 88 return new ArrayList<>();
89 } 89 }
90 - return from.stream().map(mapper).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList()); 90 + return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
91 } 91 }
92 92
93 public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) { 93 public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
@@ -123,7 +123,7 @@ public class CollectionUtils { @@ -123,7 +123,7 @@ public class CollectionUtils {
123 if (CollUtil.isEmpty(from)) { 123 if (CollUtil.isEmpty(from)) {
124 return new HashSet<>(); 124 return new HashSet<>();
125 } 125 }
126 - return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet()); 126 + return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
127 } 127 }
128 128
129 public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from, 129 public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
@@ -132,7 +132,7 @@ public class CollectionUtils { @@ -132,7 +132,7 @@ public class CollectionUtils {
132 if (CollUtil.isEmpty(from)) { 132 if (CollUtil.isEmpty(from)) {
133 return new HashSet<>(); 133 return new HashSet<>();
134 } 134 }
135 - return from.stream().map(mapper).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet()); 135 + return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
136 } 136 }
137 137
138 public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) { 138 public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
@@ -315,4 +315,4 @@ public class CollectionUtils { @@ -315,4 +315,4 @@ public class CollectionUtils {
315 return list.stream().flatMap(Collection::stream).collect(Collectors.toList()); 315 return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
316 } 316 }
317 317
318 -} 318 -}
  319 +}
319 \ No newline at end of file 320 \ No newline at end of file
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/MapUtils.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.CollectionUtil; 4 import cn.hutool.core.collection.CollectionUtil;
  5 +import cn.hutool.core.util.ObjUtil;
5 import cn.iocoder.yudao.framework.common.core.KeyValue; 6 import cn.iocoder.yudao.framework.common.core.KeyValue;
6 import com.google.common.collect.Maps; 7 import com.google.common.collect.Maps;
7 import com.google.common.collect.Multimap; 8 import com.google.common.collect.Multimap;
@@ -40,6 +41,7 @@ public class MapUtils { @@ -40,6 +41,7 @@ public class MapUtils {
40 41
41 /** 42 /**
42 * 从哈希表查找到 key 对应的 value,然后进一步处理 43 * 从哈希表查找到 key 对应的 value,然后进一步处理
  44 + * key 为 null 时, 不处理
43 * 注意,如果查找到的 value 为 null 时,不进行处理 45 * 注意,如果查找到的 value 为 null 时,不进行处理
44 * 46 *
45 * @param map 哈希表 47 * @param map 哈希表
@@ -47,7 +49,7 @@ public class MapUtils { @@ -47,7 +49,7 @@ public class MapUtils {
47 * @param consumer 进一步处理的逻辑 49 * @param consumer 进一步处理的逻辑
48 */ 50 */
49 public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) { 51 public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
50 - if (CollUtil.isEmpty(map)) { 52 + if (ObjUtil.isNull(key) || CollUtil.isEmpty(map)) {
51 return; 53 return;
52 } 54 }
53 V value = map.get(key); 55 V value = map.get(key);
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
@@ -27,8 +27,6 @@ public class DateUtils { @@ -27,8 +27,6 @@ public class DateUtils {
27 27
28 public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss"; 28 public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
29 29
30 - public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";  
31 -  
32 /** 30 /**
33 * 将 LocalDateTime 转换成 Date 31 * 将 LocalDateTime 转换成 Date
34 * 32 *
@@ -67,19 +65,11 @@ public class DateUtils { @@ -67,19 +65,11 @@ public class DateUtils {
67 return new Date(System.currentTimeMillis() + duration.toMillis()); 65 return new Date(System.currentTimeMillis() + duration.toMillis());
68 } 66 }
69 67
70 - public static boolean isExpired(Date time) {  
71 - return System.currentTimeMillis() > time.getTime();  
72 - }  
73 -  
74 public static boolean isExpired(LocalDateTime time) { 68 public static boolean isExpired(LocalDateTime time) {
75 LocalDateTime now = LocalDateTime.now(); 69 LocalDateTime now = LocalDateTime.now();
76 return now.isAfter(time); 70 return now.isAfter(time);
77 } 71 }
78 72
79 - public static long diff(Date endTime, Date startTime) {  
80 - return endTime.getTime() - startTime.getTime();  
81 - }  
82 -  
83 /** 73 /**
84 * 创建指定时间 74 * 创建指定时间
85 * 75 *
@@ -137,37 +127,6 @@ public class DateUtils { @@ -137,37 +127,6 @@ public class DateUtils {
137 } 127 }
138 128
139 /** 129 /**
140 - * 计算当期时间相差的日期  
141 - *  
142 - * @param field 日历字段.<br/>eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,<br/>Calendar.HOUR_OF_DAY等.  
143 - * @param amount 相差的数值  
144 - * @return 计算后的日志  
145 - */  
146 - public static Date addDate(int field, int amount) {  
147 - return addDate(null, field, amount);  
148 - }  
149 -  
150 - /**  
151 - * 计算当期时间相差的日期  
152 - *  
153 - * @param date 设置时间  
154 - * @param field 日历字段 例如说,{@link Calendar#DAY_OF_MONTH} 等  
155 - * @param amount 相差的数值  
156 - * @return 计算后的日志  
157 - */  
158 - public static Date addDate(Date date, int field, int amount) {  
159 - if (amount == 0) {  
160 - return date;  
161 - }  
162 - Calendar c = Calendar.getInstance();  
163 - if (date != null) {  
164 - c.setTime(date);  
165 - }  
166 - c.add(field, amount);  
167 - return c.getTime();  
168 - }  
169 -  
170 - /**  
171 * 是否今天 130 * 是否今天
172 * 131 *
173 * @param date 日期 132 * @param date 日期
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
1 package cn.iocoder.yudao.framework.common.util.date; 1 package cn.iocoder.yudao.framework.common.util.date;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.date.DatePattern;
3 import cn.hutool.core.date.LocalDateTimeUtil; 5 import cn.hutool.core.date.LocalDateTimeUtil;
  6 +import cn.hutool.core.lang.Assert;
  7 +import cn.hutool.core.util.StrUtil;
  8 +import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
4 9
5 -import java.time.Duration;  
6 -import java.time.LocalDate;  
7 -import java.time.LocalDateTime;  
8 -import java.time.LocalTime; 10 +import java.time.*;
  11 +import java.time.format.DateTimeParseException;
9 import java.time.temporal.ChronoUnit; 12 import java.time.temporal.ChronoUnit;
10 import java.time.temporal.TemporalAdjusters; 13 import java.time.temporal.TemporalAdjusters;
  14 +import java.util.ArrayList;
  15 +import java.util.List;
11 16
12 /** 17 /**
13 * 时间工具类,用于 {@link java.time.LocalDateTime} 18 * 时间工具类,用于 {@link java.time.LocalDateTime}
@@ -21,6 +26,22 @@ public class LocalDateTimeUtils { @@ -21,6 +26,22 @@ public class LocalDateTimeUtils {
21 */ 26 */
22 public static LocalDateTime EMPTY = buildTime(1970, 1, 1); 27 public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
23 28
  29 + /**
  30 + * 解析时间
  31 + *
  32 + * 相比 {@link LocalDateTimeUtil#parse(CharSequence)} 方法来说,会尽量去解析,直到成功
  33 + *
  34 + * @param time 时间
  35 + * @return 时间字符串
  36 + */
  37 + public static LocalDateTime parse(String time) {
  38 + try {
  39 + return LocalDateTimeUtil.parse(time, DatePattern.NORM_DATE_PATTERN);
  40 + } catch (DateTimeParseException e) {
  41 + return LocalDateTimeUtil.parse(time);
  42 + }
  43 + }
  44 +
24 public static LocalDateTime addTime(Duration duration) { 45 public static LocalDateTime addTime(Duration duration) {
25 return LocalDateTime.now().plus(duration); 46 return LocalDateTime.now().plus(duration);
26 } 47 }
@@ -55,6 +76,21 @@ public class LocalDateTimeUtils { @@ -55,6 +76,21 @@ public class LocalDateTimeUtils {
55 } 76 }
56 77
57 /** 78 /**
  79 + * 判指定断时间,是否在该时间范围内
  80 + *
  81 + * @param startTime 开始时间
  82 + * @param endTime 结束时间
  83 + * @param time 指定时间
  84 + * @return 是否
  85 + */
  86 + public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, String time) {
  87 + if (startTime == null || endTime == null || time == null) {
  88 + return false;
  89 + }
  90 + return LocalDateTimeUtil.isIn(parse(time), startTime, endTime);
  91 + }
  92 +
  93 + /**
58 * 判断当前时间是否在该时间范围内 94 * 判断当前时间是否在该时间范围内
59 * 95 *
60 * @param startTime 开始时间 96 * @param startTime 开始时间
@@ -123,6 +159,16 @@ public class LocalDateTimeUtils { @@ -123,6 +159,16 @@ public class LocalDateTimeUtils {
123 } 159 }
124 160
125 /** 161 /**
  162 + * 获得指定日期所在季度
  163 + *
  164 + * @param date 日期
  165 + * @return 所在季度
  166 + */
  167 + public static int getQuarterOfYear(LocalDateTime date) {
  168 + return (date.getMonthValue() - 1) / 3 + 1;
  169 + }
  170 +
  171 + /**
126 * 获取指定日期到现在过了几天,如果指定日期在当前日期之后,获取结果为负 172 * 获取指定日期到现在过了几天,如果指定日期在当前日期之后,获取结果为负
127 * 173 *
128 * @param dateTime 日期 174 * @param dateTime 日期
@@ -168,4 +214,96 @@ public class LocalDateTimeUtils { @@ -168,4 +214,96 @@ public class LocalDateTimeUtils {
168 return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN); 214 return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);
169 } 215 }
170 216
  217 + public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime,
  218 + LocalDateTime endTime,
  219 + Integer interval) {
  220 + // 1.1 找到枚举
  221 + DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
  222 + Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
  223 + // 1.2 将时间对齐
  224 + startTime = LocalDateTimeUtil.beginOfDay(startTime);
  225 + endTime = LocalDateTimeUtil.endOfDay(endTime);
  226 +
  227 + // 2. 循环,生成时间范围
  228 + List<LocalDateTime[]> timeRanges = new ArrayList<>();
  229 + switch (intervalEnum) {
  230 + case DateIntervalEnum.DAY:
  231 + while (startTime.isBefore(endTime)) {
  232 + timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});
  233 + startTime = startTime.plusDays(1);
  234 + }
  235 + break;
  236 + case DateIntervalEnum.WEEK:
  237 + while (startTime.isBefore(endTime)) {
  238 + LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1);
  239 + timeRanges.add(new LocalDateTime[]{startTime, endOfWeek});
  240 + startTime = endOfWeek.plusNanos(1);
  241 + }
  242 + break;
  243 + case DateIntervalEnum.MONTH:
  244 + while (startTime.isBefore(endTime)) {
  245 + LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1);
  246 + timeRanges.add(new LocalDateTime[]{startTime, endOfMonth});
  247 + startTime = endOfMonth.plusNanos(1);
  248 + }
  249 + break;
  250 + case DateIntervalEnum.QUARTER:
  251 + while (startTime.isBefore(endTime)) {
  252 + int quarterOfYear = getQuarterOfYear(startTime);
  253 + LocalDateTime quarterEnd = quarterOfYear == 4
  254 + ? startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1)
  255 + : startTime.withMonth(quarterOfYear * 3 + 1).withDayOfMonth(1).minusNanos(1);
  256 + timeRanges.add(new LocalDateTime[]{startTime, quarterEnd});
  257 + startTime = quarterEnd.plusNanos(1);
  258 + }
  259 + break;
  260 + case DateIntervalEnum.YEAR:
  261 + while (startTime.isBefore(endTime)) {
  262 + LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1);
  263 + timeRanges.add(new LocalDateTime[]{startTime, endOfYear});
  264 + startTime = endOfYear.plusNanos(1);
  265 + }
  266 + break;
  267 + default:
  268 + throw new IllegalArgumentException("Invalid interval: " + interval);
  269 + }
  270 + // 3. 兜底,最后一个时间,需要保持在 endTime 之前
  271 + LocalDateTime[] lastTimeRange = CollUtil.getLast(timeRanges);
  272 + if (lastTimeRange != null) {
  273 + lastTimeRange[1] = endTime;
  274 + }
  275 + return timeRanges;
  276 + }
  277 +
  278 + /**
  279 + * 格式化时间范围
  280 + *
  281 + * @param startTime 开始时间
  282 + * @param endTime 结束时间
  283 + * @param interval 时间间隔
  284 + * @return 时间范围
  285 + */
  286 + public static String formatDateRange(LocalDateTime startTime, LocalDateTime endTime, Integer interval) {
  287 + // 1. 找到枚举
  288 + DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
  289 + Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
  290 +
  291 + // 2. 循环,生成时间范围
  292 + switch (intervalEnum) {
  293 + case DateIntervalEnum.DAY:
  294 + return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);
  295 + case DateIntervalEnum.WEEK:
  296 + return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)
  297 + + StrUtil.format("(第 {} 周)", LocalDateTimeUtil.weekOfYear(startTime));
  298 + case DateIntervalEnum.MONTH:
  299 + return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN);
  300 + case DateIntervalEnum.QUARTER:
  301 + return StrUtil.format("{}-Q{}", startTime.getYear(), getQuarterOfYear(startTime));
  302 + case DateIntervalEnum.YEAR:
  303 + return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN);
  304 + default:
  305 + throw new IllegalArgumentException("Invalid interval: " + interval);
  306 + }
  307 + }
  308 +
171 } 309 }
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/core/DictFrameworkUtils.java
@@ -11,6 +11,7 @@ import lombok.SneakyThrows; @@ -11,6 +11,7 @@ import lombok.SneakyThrows;
11 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
12 12
13 import java.time.Duration; 13 import java.time.Duration;
  14 +import java.util.List;
14 15
15 /** 16 /**
16 * 字典工具类 17 * 字典工具类
@@ -24,6 +25,7 @@ public class DictFrameworkUtils { @@ -24,6 +25,7 @@ public class DictFrameworkUtils {
24 25
25 private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO(); 26 private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();
26 27
  28 + // TODO @puhui999:GET_DICT_DATA_CACHE、GET_DICT_DATA_LIST_CACHE、PARSE_DICT_DATA_CACHE 这 3 个缓存是有点重叠,可以思考下,有没可能减少 1 个。微信讨论好私聊,再具体改哈
27 /** 29 /**
28 * 针对 {@link #getDictDataLabel(String, String)} 的缓存 30 * 针对 {@link #getDictDataLabel(String, String)} 的缓存
29 */ 31 */
@@ -39,6 +41,20 @@ public class DictFrameworkUtils { @@ -39,6 +41,20 @@ public class DictFrameworkUtils {
39 }); 41 });
40 42
41 /** 43 /**
  44 + * 针对 {@link #getDictDataLabelList(String)} 的缓存
  45 + */
  46 + private static final LoadingCache<String, List<String>> GET_DICT_DATA_LIST_CACHE = CacheUtils.buildAsyncReloadingCache(
  47 + Duration.ofMinutes(1L), // 过期时间 1 分钟
  48 + new CacheLoader<String, List<String>>() {
  49 +
  50 + @Override
  51 + public List<String> load(String dictType) {
  52 + return dictDataApi.getDictDataLabelList(dictType);
  53 + }
  54 +
  55 + });
  56 +
  57 + /**
42 * 针对 {@link #parseDictDataValue(String, String)} 的缓存 58 * 针对 {@link #parseDictDataValue(String, String)} 的缓存
43 */ 59 */
44 private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache( 60 private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
@@ -68,6 +84,11 @@ public class DictFrameworkUtils { @@ -68,6 +84,11 @@ public class DictFrameworkUtils {
68 } 84 }
69 85
70 @SneakyThrows 86 @SneakyThrows
  87 + public static List<String> getDictDataLabelList(String dictType) {
  88 + return GET_DICT_DATA_LIST_CACHE.get(dictType);
  89 + }
  90 +
  91 + @SneakyThrows
71 public static String parseDictDataValue(String dictType, String label) { 92 public static String parseDictDataValue(String dictType, String label) {
72 return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue(); 93 return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue();
73 } 94 }
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/annotations/ExcelColumnSelect.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.excel.core.annotations;
  2 +
  3 +import java.lang.annotation.*;
  4 +
  5 +/**
  6 + * 给 Excel 列添加下拉选择数据
  7 + *
  8 + * 其中 {@link #dictType()} 和 {@link #functionName()} 二选一
  9 + *
  10 + * @author HUIHUI
  11 + */
  12 +@Target({ElementType.FIELD})
  13 +@Retention(RetentionPolicy.RUNTIME)
  14 +@Inherited
  15 +public @interface ExcelColumnSelect {
  16 +
  17 + /**
  18 + * @return 字典类型
  19 + */
  20 + String dictType() default "";
  21 +
  22 + /**
  23 + * @return 获取下拉数据源的方法名称
  24 + */
  25 + String functionName() default "";
  26 +
  27 +}
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/enums/ExcelColumn.java deleted
1 -package cn.iocoder.yudao.framework.excel.core.enums;  
2 -  
3 -import lombok.AllArgsConstructor;  
4 -import lombok.Getter;  
5 -  
6 -// TODO @puhui999:列表有办法通过 field name 么?主要考虑一个点,可能导入模版的顺序可能会变  
7 -/**  
8 - * Excel 列名枚举  
9 - * 默认枚举 26 列列名如果有需求更多的列名请自行补充  
10 - *  
11 - * @author HUIHUI  
12 - */  
13 -@Getter  
14 -@AllArgsConstructor  
15 -public enum ExcelColumn {  
16 -  
17 - A(0), B(1), C(2), D(3), E(4), F(5), G(6), H(7), I(8),  
18 - J(9), K(10), L(11), M(12), N(13), O(14), P(15), Q(16),  
19 - R(17), S(18), T(19), U(20), V(21), W(22), X(23), Y(24),  
20 - Z(25);  
21 -  
22 - /**  
23 - * 列索引  
24 - */  
25 - private final int colNum;  
26 -  
27 -}  
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/function/ExcelColumnSelectFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.excel.core.function;
  2 +
  3 +import java.util.List;
  4 +
  5 +/**
  6 + * Excel 列下拉数据源获取接口
  7 + *
  8 + * 为什么不直接解析字典还搞个接口?考虑到有的下拉数据不是从字典中获取的所有需要做一个兼容
  9 +
  10 + * @author HUIHUI
  11 + */
  12 +public interface ExcelColumnSelectFunction {
  13 +
  14 + /**
  15 + * 获得方法名称
  16 + *
  17 + * @return 方法名称
  18 + */
  19 + String getName();
  20 +
  21 + /**
  22 + * 获得列下拉数据源
  23 + *
  24 + * @return 下拉数据源
  25 + */
  26 + List<String> getOptions();
  27 +
  28 +}
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java
@@ -2,25 +2,38 @@ package cn.iocoder.yudao.framework.excel.core.handler; @@ -2,25 +2,38 @@ package cn.iocoder.yudao.framework.excel.core.handler;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.map.MapUtil;
  6 +import cn.hutool.core.util.ObjectUtil;
  7 +import cn.hutool.core.util.StrUtil;
  8 +import cn.hutool.extra.spring.SpringUtil;
  9 +import cn.hutool.poi.excel.ExcelUtil;
5 import cn.iocoder.yudao.framework.common.core.KeyValue; 10 import cn.iocoder.yudao.framework.common.core.KeyValue;
6 -import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn; 11 +import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
  12 +import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
  13 +import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction;
  14 +import com.alibaba.excel.annotation.ExcelProperty;
7 import com.alibaba.excel.write.handler.SheetWriteHandler; 15 import com.alibaba.excel.write.handler.SheetWriteHandler;
8 import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; 16 import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
9 import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; 17 import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
  18 +import lombok.extern.slf4j.Slf4j;
10 import org.apache.poi.hssf.usermodel.HSSFDataValidation; 19 import org.apache.poi.hssf.usermodel.HSSFDataValidation;
11 import org.apache.poi.ss.usermodel.*; 20 import org.apache.poi.ss.usermodel.*;
12 import org.apache.poi.ss.util.CellRangeAddressList; 21 import org.apache.poi.ss.util.CellRangeAddressList;
13 22
  23 +import java.lang.reflect.Field;
14 import java.util.Comparator; 24 import java.util.Comparator;
  25 +import java.util.HashMap;
15 import java.util.List; 26 import java.util.List;
16 import java.util.Map; 27 import java.util.Map;
17 -import java.util.stream.Collectors; 28 +
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
18 30
19 /** 31 /**
20 * 基于固定 sheet 实现下拉框 32 * 基于固定 sheet 实现下拉框
21 * 33 *
22 * @author HUIHUI 34 * @author HUIHUI
23 */ 35 */
  36 +@Slf4j
24 public class SelectSheetWriteHandler implements SheetWriteHandler { 37 public class SelectSheetWriteHandler implements SheetWriteHandler {
25 38
26 /** 39 /**
@@ -36,21 +49,56 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { @@ -36,21 +49,56 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
36 49
37 private static final String DICT_SHEET_NAME = "字典sheet"; 50 private static final String DICT_SHEET_NAME = "字典sheet";
38 51
39 - // TODO @puhui999:Map<ExcelColumn, List<String>> 可以么?之前用 keyvalue 的原因,返回给前端,无法用 linkedhashmap,默认 key 会乱序  
40 - private final List<KeyValue<ExcelColumn, List<String>>> selectMap; 52 + /**
  53 + * key: 列 value: 下拉数据源
  54 + */
  55 + private final Map<Integer, List<String>> selectMap = new HashMap<>();
41 56
42 - public SelectSheetWriteHandler(List<KeyValue<ExcelColumn, List<String>>> selectMap) {  
43 - if (CollUtil.isEmpty(selectMap)) {  
44 - this.selectMap = null; 57 + public SelectSheetWriteHandler(Class<?> head) {
  58 + // 加载下拉数据获取接口
  59 + Map<String, ExcelColumnSelectFunction> beansMap = SpringUtil.getBeanFactory().getBeansOfType(ExcelColumnSelectFunction.class);
  60 + if (MapUtil.isEmpty(beansMap)) {
  61 + return;
  62 + }
  63 +
  64 + // 解析下拉数据
  65 + int colIndex = 0;
  66 + for (Field field : head.getDeclaredFields()) {
  67 + if (field.isAnnotationPresent(ExcelColumnSelect.class)) {
  68 + ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
  69 + if (excelProperty != null && excelProperty.index() != -1) {
  70 + colIndex = excelProperty.index();
  71 + }
  72 + getSelectDataList(colIndex, field);
  73 + }
  74 + colIndex++;
  75 + }
  76 + }
  77 +
  78 + /**
  79 + * 获得下拉数据,并添加到 {@link #selectMap} 中
  80 + *
  81 + * @param colIndex 列索引
  82 + * @param field 字段
  83 + */
  84 + private void getSelectDataList(int colIndex, Field field) {
  85 + ExcelColumnSelect columnSelect = field.getAnnotation(ExcelColumnSelect.class);
  86 + String dictType = columnSelect.dictType();
  87 + String functionName = columnSelect.functionName();
  88 + Assert.isTrue(ObjectUtil.isNotEmpty(dictType) || ObjectUtil.isNotEmpty(functionName),
  89 + "Field({}) 的 @ExcelColumnSelect 注解,dictType 和 functionName 不能同时为空", field.getName());
  90 +
  91 + // 情况一:使用 dictType 获得下拉数据
  92 + if (StrUtil.isNotEmpty(dictType)) { // 情况一: 字典数据 (默认)
  93 + selectMap.put(colIndex, DictFrameworkUtils.getDictDataLabelList(dictType));
45 return; 94 return;
46 } 95 }
47 - // 校验一下 key 是否唯一  
48 - Map<String, Long> nameCounts = selectMap.stream()  
49 - .collect(Collectors.groupingBy(item -> item.getKey().name(), Collectors.counting()));  
50 - Assert.isFalse(nameCounts.entrySet().stream().allMatch(entry -> entry.getValue() > 1), "下拉数据 key 重复请排查!!!");  
51 96
52 - selectMap.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错  
53 - this.selectMap = selectMap; 97 + // 情况二:使用 functionName 获得下拉数据
  98 + Map<String, ExcelColumnSelectFunction> functionMap = SpringUtil.getApplicationContext().getBeansOfType(ExcelColumnSelectFunction.class);
  99 + ExcelColumnSelectFunction function = CollUtil.findOne(functionMap.values(), item -> item.getName().equals(functionName));
  100 + Assert.notNull(function, "未找到对应的 function({})", functionName);
  101 + selectMap.put(colIndex, function.getOptions());
54 } 102 }
55 103
56 @Override 104 @Override
@@ -62,18 +110,20 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { @@ -62,18 +110,20 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
62 // 1. 获取相应操作对象 110 // 1. 获取相应操作对象
63 DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper(); // 需要设置下拉框的 sheet 页的数据验证助手 111 DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper(); // 需要设置下拉框的 sheet 页的数据验证助手
64 Workbook workbook = writeWorkbookHolder.getWorkbook(); // 获得工作簿 112 Workbook workbook = writeWorkbookHolder.getWorkbook(); // 获得工作簿
  113 + List<KeyValue<Integer, List<String>>> keyValues = convertList(selectMap.entrySet(), entry -> new KeyValue<>(entry.getKey(), entry.getValue()));
  114 + keyValues.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错
65 115
66 // 2. 创建数据字典的 sheet 页 116 // 2. 创建数据字典的 sheet 页
67 Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME); 117 Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME);
68 - for (KeyValue<ExcelColumn, List<String>> keyValue : selectMap) { 118 + for (KeyValue<Integer, List<String>> keyValue : keyValues) {
69 int rowLength = keyValue.getValue().size(); 119 int rowLength = keyValue.getValue().size();
70 - // 2.1 设置字典 sheet 页的值 每一列一个字典项 120 + // 2.1 设置字典 sheet 页的值,每一列一个字典项
71 for (int i = 0; i < rowLength; i++) { 121 for (int i = 0; i < rowLength; i++) {
72 Row row = dictSheet.getRow(i); 122 Row row = dictSheet.getRow(i);
73 if (row == null) { 123 if (row == null) {
74 row = dictSheet.createRow(i); 124 row = dictSheet.createRow(i);
75 } 125 }
76 - row.createCell(keyValue.getKey().getColNum()).setCellValue(keyValue.getValue().get(i)); 126 + row.createCell(keyValue.getKey()).setCellValue(keyValue.getValue().get(i));
77 } 127 }
78 // 2.2 设置单元格下拉选择 128 // 2.2 设置单元格下拉选择
79 setColumnSelect(writeSheetHolder, workbook, helper, keyValue); 129 setColumnSelect(writeSheetHolder, workbook, helper, keyValue);
@@ -84,10 +134,10 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { @@ -84,10 +134,10 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
84 * 设置单元格下拉选择 134 * 设置单元格下拉选择
85 */ 135 */
86 private static void setColumnSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper, 136 private static void setColumnSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper,
87 - KeyValue<ExcelColumn, List<String>> keyValue) { 137 + KeyValue<Integer, List<String>> keyValue) {
88 // 1.1 创建可被其他单元格引用的名称 138 // 1.1 创建可被其他单元格引用的名称
89 Name name = workbook.createName(); 139 Name name = workbook.createName();
90 - String excelColumn = keyValue.getKey().name(); 140 + String excelColumn = ExcelUtil.indexToColName(keyValue.getKey());
91 // 1.2 下拉框数据来源 eg:字典sheet!$B1:$B2 141 // 1.2 下拉框数据来源 eg:字典sheet!$B1:$B2
92 String refers = DICT_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + keyValue.getValue().size(); 142 String refers = DICT_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + keyValue.getValue().size();
93 name.setNameName("dict" + keyValue.getKey()); // 设置名称的名字 143 name.setNameName("dict" + keyValue.getKey()); // 设置名称的名字
@@ -97,7 +147,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { @@ -97,7 +147,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
97 DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); // 设置引用约束 147 DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); // 设置引用约束
98 // 设置下拉单元格的首行、末行、首列、末列 148 // 设置下拉单元格的首行、末行、首列、末列
99 CellRangeAddressList rangeAddressList = new CellRangeAddressList(FIRST_ROW, LAST_ROW, 149 CellRangeAddressList rangeAddressList = new CellRangeAddressList(FIRST_ROW, LAST_ROW,
100 - keyValue.getKey().getColNum(), keyValue.getKey().getColNum()); 150 + keyValue.getKey(), keyValue.getKey());
101 DataValidation validation = helper.createValidation(constraint, rangeAddressList); 151 DataValidation validation = helper.createValidation(constraint, rangeAddressList);
102 if (validation instanceof HSSFDataValidation) { 152 if (validation instanceof HSSFDataValidation) {
103 validation.setSuppressDropDownArrow(false); 153 validation.setSuppressDropDownArrow(false);
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
1 package cn.iocoder.yudao.framework.excel.core.util; 1 package cn.iocoder.yudao.framework.excel.core.util;
2 2
3 -import cn.iocoder.yudao.framework.common.core.KeyValue;  
4 -import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;  
5 import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler; 3 import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
6 import com.alibaba.excel.EasyExcel; 4 import com.alibaba.excel.EasyExcel;
7 import com.alibaba.excel.converters.longconverter.LongStringConverter; 5 import com.alibaba.excel.converters.longconverter.LongStringConverter;
@@ -34,27 +32,11 @@ public class ExcelUtils { @@ -34,27 +32,11 @@ public class ExcelUtils {
34 */ 32 */
35 public static <T> void write(HttpServletResponse response, String filename, String sheetName, 33 public static <T> void write(HttpServletResponse response, String filename, String sheetName,
36 Class<T> head, List<T> data) throws IOException { 34 Class<T> head, List<T> data) throws IOException {
37 - write(response, filename, sheetName, head, data, null);  
38 - }  
39 -  
40 - /**  
41 - * 将列表以 Excel 响应给前端  
42 - *  
43 - * @param response 响应  
44 - * @param filename 文件名  
45 - * @param sheetName Excel sheet 名  
46 - * @param head Excel head 头  
47 - * @param data 数据列表哦  
48 - * @param selectMap 下拉选择数据 Map<下拉所对应的列表名,下拉数据>  
49 - * @throws IOException 写入失败的情况  
50 - */  
51 - public static <T> void write(HttpServletResponse response, String filename, String sheetName,  
52 - Class<T> head, List<T> data, List<KeyValue<ExcelColumn, List<String>>> selectMap) throws IOException {  
53 // 输出 Excel 35 // 输出 Excel
54 EasyExcel.write(response.getOutputStream(), head) 36 EasyExcel.write(response.getOutputStream(), head)
55 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 37 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
56 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度 38 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
57 - .registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框 39 + .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
58 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 40 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
59 .sheet(sheetName).doWrite(data); 41 .sheet(sheetName).doWrite(data);
60 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 42 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/core/TimeoutRedisCacheManager.java
@@ -40,12 +40,15 @@ public class TimeoutRedisCacheManager extends RedisCacheManager { @@ -40,12 +40,15 @@ public class TimeoutRedisCacheManager extends RedisCacheManager {
40 // 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间 40 // 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间
41 if (cacheConfig != null) { 41 if (cacheConfig != null) {
42 // 移除 # 后面的 : 以及后面的内容,避免影响解析 42 // 移除 # 后面的 : 以及后面的内容,避免影响解析
43 - names[1] = StrUtil.subBefore(names[1], StrUtil.COLON, false); 43 + String ttlStr = StrUtil.subBefore(names[1], StrUtil.COLON, false); // 获得 ttlStr 时间部分
  44 + names[1] = StrUtil.subAfter(names[1], ttlStr, false); // 移除掉 ttlStr 时间部分
44 // 解析时间 45 // 解析时间
45 - Duration duration = parseDuration(names[1]); 46 + Duration duration = parseDuration(ttlStr);
46 cacheConfig = cacheConfig.entryTtl(duration); 47 cacheConfig = cacheConfig.entryTtl(duration);
47 } 48 }
48 - return super.createRedisCache(name, cacheConfig); 49 +
  50 + // 创建 RedisCache 对象,需要忽略掉 ttlStr
  51 + return super.createRedisCache(names[0] + names[1], cacheConfig);
49 } 52 }
50 53
51 /** 54 /**
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogV2Configuration.java renamed to yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogConfiguration.java
@@ -16,7 +16,7 @@ import org.springframework.context.annotation.Primary; @@ -16,7 +16,7 @@ import org.springframework.context.annotation.Primary;
16 @EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦 16 @EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
17 @AutoConfiguration 17 @AutoConfiguration
18 @Slf4j 18 @Slf4j
19 -public class YudaoOperateLogV2Configuration { 19 +public class YudaoOperateLogConfiguration {
20 20
21 @Bean 21 @Bean
22 @Primary 22 @Primary
yudao-framework/yudao-spring-boot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 cn.iocoder.yudao.framework.security.config.YudaoSecurityAutoConfiguration 1 cn.iocoder.yudao.framework.security.config.YudaoSecurityAutoConfiguration
2 cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter 2 cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter
3 -cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogV2Configuration  
4 \ No newline at end of file 3 \ No newline at end of file
  4 +cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogConfiguration
5 \ No newline at end of file 5 \ No newline at end of file
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -15,6 +15,7 @@ public interface ErrorCodeConstants { @@ -15,6 +15,7 @@ public interface ErrorCodeConstants {
15 ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态"); 15 ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态");
16 ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态"); 16 ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态");
17 ErrorCode CONTRACT_NO_EXISTS = new ErrorCode(1_020_000_004, "生成合同序列号重复,请重试"); 17 ErrorCode CONTRACT_NO_EXISTS = new ErrorCode(1_020_000_004, "生成合同序列号重复,请重试");
  18 + ErrorCode CONTRACT_DELETE_FAIL = new ErrorCode(1_020_000_005, "删除合同失败,原因:有被回款所使用");
18 19
19 // ========== 线索管理 1-020-001-000 ========== 20 // ========== 线索管理 1-020-001-000 ==========
20 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); 21 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
@@ -40,6 +41,7 @@ public interface ErrorCodeConstants { @@ -40,6 +41,7 @@ public interface ErrorCodeConstants {
40 ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试"); 41 ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试");
41 ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态"); 42 ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态");
42 ErrorCode RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_004_007, "创建回款失败,原因:回款金额超出合同金额,目前剩余可退:{} 元"); 43 ErrorCode RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_004_007, "创建回款失败,原因:回款金额超出合同金额,目前剩余可退:{} 元");
  44 + ErrorCode RECEIVABLE_DELETE_FAIL_IS_APPROVE = new ErrorCode(1_020_004_008, "删除回款失败,原因:回款审批已通过");
43 45
44 // ========== 回款计划 1-020-005-000 ========== 46 // ========== 回款计划 1-020-005-000 ==========
45 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在"); 47 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
@@ -72,6 +74,7 @@ public interface ErrorCodeConstants { @@ -72,6 +74,7 @@ public interface ErrorCodeConstants {
72 ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限"); 74 ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限");
73 ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人"); 75 ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人");
74 ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限"); 76 ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限");
  77 + ErrorCode CRM_PERMISSION_CREATE_FAIL_EXISTS = new ErrorCode(1_020_007_009, "同时添加数据权限失败,原因:用户【{}】已有模块【{}】数据【{}】的【{}】权限");
75 78
76 // ========== 产品 1_020_008_000 ========== 79 // ========== 产品 1_020_008_000 ==========
77 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在"); 80 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
@@ -100,4 +103,6 @@ public interface ErrorCodeConstants { @@ -100,4 +103,6 @@ public interface ErrorCodeConstants {
100 ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在"); 103 ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
101 ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限"); 104 ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限");
102 105
  106 + // ========== 数据统计 1_020_014_000 ==========
  107 +
103 } 108 }
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
@@ -142,11 +142,11 @@ public interface LogRecordConstants { @@ -142,11 +142,11 @@ public interface LogRecordConstants {
142 142
143 String CRM_RECEIVABLE_TYPE = "CRM 回款"; 143 String CRM_RECEIVABLE_TYPE = "CRM 回款";
144 String CRM_RECEIVABLE_CREATE_SUB_TYPE = "创建回款"; 144 String CRM_RECEIVABLE_CREATE_SUB_TYPE = "创建回款";
145 - String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; 145 + String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款";
146 String CRM_RECEIVABLE_UPDATE_SUB_TYPE = "更新回款"; 146 String CRM_RECEIVABLE_UPDATE_SUB_TYPE = "更新回款";
147 - String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}"; 147 + String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款: {_DIFF{#updateReqVO}}";
148 String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款"; 148 String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款";
149 - String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; 149 + String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款";
150 String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批"; 150 String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批";
151 String CRM_RECEIVABLE_SUBMIT_SUCCESS = "提交编号为【{{#receivableNo}}】的回款审批成功"; 151 String CRM_RECEIVABLE_SUBMIT_SUCCESS = "提交编号为【{{#receivableNo}}】的回款审批成功";
152 152
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java
1 package cn.iocoder.yudao.module.crm.enums.permission; 1 package cn.iocoder.yudao.module.crm.enums.permission;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
4 import cn.iocoder.yudao.framework.common.core.IntArrayValuable; 5 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
5 import lombok.AllArgsConstructor; 6 import lombok.AllArgsConstructor;
@@ -50,4 +51,10 @@ public enum CrmPermissionLevelEnum implements IntArrayValuable { @@ -50,4 +51,10 @@ public enum CrmPermissionLevelEnum implements IntArrayValuable {
50 return ObjUtil.equal(WRITE.level, level); 51 return ObjUtil.equal(WRITE.level, level);
51 } 52 }
52 53
  54 + public static String getNameByLevel(Integer level) {
  55 + CrmPermissionLevelEnum typeEnum = CollUtil.findOne(CollUtil.newArrayList(CrmPermissionLevelEnum.values()),
  56 + item -> ObjUtil.equal(item.level, level));
  57 + return typeEnum == null ? null : typeEnum.getName();
  58 + }
  59 +
53 } 60 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
@@ -66,13 +66,13 @@ public class CrmBusinessSaveReqVO { @@ -66,13 +66,13 @@ public class CrmBusinessSaveReqVO {
66 private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段 66 private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段
67 67
68 @Schema(description = "产品列表") 68 @Schema(description = "产品列表")
69 - private List<Product> products; 69 + private List<BusinessProduct> businessProducts;
70 70
71 @Schema(description = "产品列表") 71 @Schema(description = "产品列表")
72 @Data 72 @Data
73 @NoArgsConstructor 73 @NoArgsConstructor
74 @AllArgsConstructor 74 @AllArgsConstructor
75 - public static class Product { 75 + public static class BusinessProduct {
76 76
77 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") 77 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
78 @NotNull(message = "产品编号不能为空") 78 @NotNull(message = "产品编号不能为空")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java
@@ -3,10 +3,14 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; @@ -3,10 +3,14 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
4 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
5 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
  6 +import lombok.AllArgsConstructor;
6 import lombok.Data; 7 import lombok.Data;
  8 +import lombok.NoArgsConstructor;
7 9
8 @Schema(description = "管理后台 - 商机转移 Request VO") 10 @Schema(description = "管理后台 - 商机转移 Request VO")
9 @Data 11 @Data
  12 +@NoArgsConstructor
  13 +@AllArgsConstructor
10 public class CrmBusinessTransferReqVO { 14 public class CrmBusinessTransferReqVO {
11 15
12 @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") 16 @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactTransferReqVO.java
@@ -2,12 +2,16 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; @@ -2,12 +2,16 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
2 2
3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
4 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.AllArgsConstructor;
5 import lombok.Data; 6 import lombok.Data;
6 7
7 import jakarta.validation.constraints.NotNull; 8 import jakarta.validation.constraints.NotNull;
  9 +import lombok.NoArgsConstructor;
8 10
9 @Schema(description = "管理后台 - CRM 联系人转移 Request VO") 11 @Schema(description = "管理后台 - CRM 联系人转移 Request VO")
10 @Data 12 @Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
11 public class CrmContactTransferReqVO { 15 public class CrmContactTransferReqVO {
12 16
13 @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") 17 @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractTransferReqVO.java
@@ -3,12 +3,16 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract; @@ -3,12 +3,16 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 4 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
5 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.AllArgsConstructor;
6 import lombok.Data; 7 import lombok.Data;
7 8
8 import jakarta.validation.constraints.NotNull; 9 import jakarta.validation.constraints.NotNull;
  10 +import lombok.NoArgsConstructor;
9 11
10 @Schema(description = "管理后台 - CRM 合同转移 Request VO") 12 @Schema(description = "管理后台 - CRM 合同转移 Request VO")
11 @Data 13 @Data
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
12 public class CrmContractTransferReqVO { 16 public class CrmContractTransferReqVO {
13 17
14 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") 18 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -11,9 +11,7 @@ import cn.iocoder.yudao.framework.common.util.collection.MapUtils; @@ -11,9 +11,7 @@ import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
11 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; 11 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
12 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 12 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
13 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 13 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
14 -import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;  
15 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 14 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
16 -import cn.iocoder.yudao.framework.ip.core.Area;  
17 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; 15 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
18 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; 16 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
19 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 17 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@@ -38,7 +36,6 @@ import org.springframework.web.bind.annotation.*; @@ -38,7 +36,6 @@ import org.springframework.web.bind.annotation.*;
38 36
39 import java.io.IOException; 37 import java.io.IOException;
40 import java.time.LocalDateTime; 38 import java.time.LocalDateTime;
41 -import java.util.ArrayList;  
42 import java.util.Arrays; 39 import java.util.Arrays;
43 import java.util.List; 40 import java.util.List;
44 import java.util.Map; 41 import java.util.Map;
@@ -49,7 +46,6 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -49,7 +46,6 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
49 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; 46 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
50 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 47 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
51 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 48 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
52 -import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;  
53 import static java.util.Collections.singletonList; 49 import static java.util.Collections.singletonList;
54 50
55 @Tag(name = "管理后台 - CRM 客户") 51 @Tag(name = "管理后台 - CRM 客户")
@@ -67,8 +63,6 @@ public class CrmCustomerController { @@ -67,8 +63,6 @@ public class CrmCustomerController {
67 private DeptApi deptApi; 63 private DeptApi deptApi;
68 @Resource 64 @Resource
69 private AdminUserApi adminUserApi; 65 private AdminUserApi adminUserApi;
70 - @Resource  
71 - private DictDataApi dictDataApi;  
72 66
73 @PostMapping("/create") 67 @PostMapping("/create")
74 @Operation(summary = "创建客户") 68 @Operation(summary = "创建客户")
@@ -142,7 +136,7 @@ public class CrmCustomerController { @@ -142,7 +136,7 @@ public class CrmCustomerController {
142 return java.util.Collections.emptyList(); 136 return java.util.Collections.emptyList();
143 } 137 }
144 // 1.1 获取创建人、负责人列表 138 // 1.1 获取创建人、负责人列表
145 - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list, 139 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(list,
146 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); 140 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
147 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); 141 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
148 // 1.2 获取距离进入公海的时间 142 // 1.2 获取距离进入公海的时间
@@ -265,25 +259,7 @@ public class CrmCustomerController { @@ -265,25 +259,7 @@ public class CrmCustomerController {
265 .areaId(null).detailAddress("").remark("").build() 259 .areaId(null).detailAddress("").remark("").build()
266 ); 260 );
267 // 输出 261 // 输出
268 - ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list, builderSelectMap());  
269 - }  
270 -  
271 - private List<KeyValue<ExcelColumn, List<String>>> builderSelectMap() {  
272 - List<KeyValue<ExcelColumn, List<String>>> selectMap = new ArrayList<>();  
273 - // 获取地区下拉数据  
274 - // TODO @puhui999:嘿嘿,这里改成省份、城市、区域,三个选项,难度大么?  
275 - Area area = AreaUtils.getArea(Area.ID_CHINA);  
276 - selectMap.add(new KeyValue<>(ExcelColumn.G, AreaUtils.getAreaNodePathList(area.getChildren())));  
277 - // 获取客户所属行业  
278 - List<String> customerIndustries = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_INDUSTRY);  
279 - selectMap.add(new KeyValue<>(ExcelColumn.I, customerIndustries));  
280 - // 获取客户等级  
281 - List<String> customerLevels = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_LEVEL);  
282 - selectMap.add(new KeyValue<>(ExcelColumn.J, customerLevels));  
283 - // 获取客户来源  
284 - List<String> customerSources = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_SOURCE);  
285 - selectMap.add(new KeyValue<>(ExcelColumn.K, customerSources));  
286 - return selectMap; 262 + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list);
287 } 263 }
288 264
289 @PostMapping("/import") 265 @PostMapping("/import")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; 1 package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
4 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
  7 +import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunction;
6 import com.alibaba.excel.annotation.ExcelProperty; 8 import com.alibaba.excel.annotation.ExcelProperty;
7 import lombok.AllArgsConstructor; 9 import lombok.AllArgsConstructor;
8 import lombok.Builder; 10 import lombok.Builder;
@@ -41,6 +43,7 @@ public class CrmCustomerImportExcelVO { @@ -41,6 +43,7 @@ public class CrmCustomerImportExcelVO {
41 private String email; 43 private String email;
42 44
43 @ExcelProperty(value = "地区", converter = AreaConvert.class) 45 @ExcelProperty(value = "地区", converter = AreaConvert.class)
  46 + @ExcelColumnSelect(functionName = AreaExcelColumnSelectFunction.NAME)
44 private Integer areaId; 47 private Integer areaId;
45 48
46 @ExcelProperty("详细地址") 49 @ExcelProperty("详细地址")
@@ -48,14 +51,17 @@ public class CrmCustomerImportExcelVO { @@ -48,14 +51,17 @@ public class CrmCustomerImportExcelVO {
48 51
49 @ExcelProperty(value = "所属行业", converter = DictConvert.class) 52 @ExcelProperty(value = "所属行业", converter = DictConvert.class)
50 @DictFormat(CRM_CUSTOMER_INDUSTRY) 53 @DictFormat(CRM_CUSTOMER_INDUSTRY)
  54 + @ExcelColumnSelect(dictType = CRM_CUSTOMER_INDUSTRY)
51 private Integer industryId; 55 private Integer industryId;
52 56
53 @ExcelProperty(value = "客户等级", converter = DictConvert.class) 57 @ExcelProperty(value = "客户等级", converter = DictConvert.class)
54 @DictFormat(CRM_CUSTOMER_LEVEL) 58 @DictFormat(CRM_CUSTOMER_LEVEL)
  59 + @ExcelColumnSelect(dictType = CRM_CUSTOMER_LEVEL)
55 private Integer level; 60 private Integer level;
56 61
57 @ExcelProperty(value = "客户来源", converter = DictConvert.class) 62 @ExcelProperty(value = "客户来源", converter = DictConvert.class)
58 @DictFormat(CRM_CUSTOMER_SOURCE) 63 @DictFormat(CRM_CUSTOMER_SOURCE)
  64 + @ExcelColumnSelect(dictType = CRM_CUSTOMER_SOURCE)
59 private Integer source; 65 private Integer source;
60 66
61 @ExcelProperty("备注") 67 @ExcelProperty("备注")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerTransferReqVO.java
@@ -5,6 +5,8 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -5,6 +5,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
5 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
6 import lombok.Data; 6 import lombok.Data;
7 7
  8 +import java.util.List;
  9 +
8 @Schema(description = "管理后台 - CRM 客户转移 Request VO") 10 @Schema(description = "管理后台 - CRM 客户转移 Request VO")
9 @Data 11 @Data
10 public class CrmCustomerTransferReqVO { 12 public class CrmCustomerTransferReqVO {
@@ -28,4 +30,10 @@ public class CrmCustomerTransferReqVO { @@ -28,4 +30,10 @@ public class CrmCustomerTransferReqVO {
28 @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 30 @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
29 private Integer oldOwnerPermissionLevel; 31 private Integer oldOwnerPermissionLevel;
30 32
  33 + /**
  34 + * 转移客户时,需要额外有【联系人】【商机】【合同】的 checkbox 选择。选中时,也一起转移
  35 + */
  36 + @Schema(description = "同时转移", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  37 + private List<Integer> toBizTypes;
  38 +
31 } 39 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java
1 package cn.iocoder.yudao.module.crm.controller.admin.permission; 1 package cn.iocoder.yudao.module.crm.controller.admin.permission;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 7 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 -import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO;  
9 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO; 9 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO;
  10 +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
10 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; 11 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
  12 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  13 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
  14 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
11 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
  16 +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
12 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 17 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
13 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; 18 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
  19 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
  20 +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
  21 +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
14 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; 22 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
15 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; 23 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
16 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 24 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -27,13 +35,11 @@ import io.swagger.v3.oas.annotations.tags.Tag; @@ -27,13 +35,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
27 import jakarta.annotation.Resource; 35 import jakarta.annotation.Resource;
28 import jakarta.validation.Valid; 36 import jakarta.validation.Valid;
29 import org.springframework.security.access.prepost.PreAuthorize; 37 import org.springframework.security.access.prepost.PreAuthorize;
  38 +import org.springframework.transaction.annotation.Transactional;
30 import org.springframework.validation.annotation.Validated; 39 import org.springframework.validation.annotation.Validated;
31 import org.springframework.web.bind.annotation.*; 40 import org.springframework.web.bind.annotation.*;
32 41
33 -import java.util.Collection;  
34 -import java.util.Collections;  
35 -import java.util.List;  
36 -import java.util.Map; 42 +import java.util.*;
37 import java.util.stream.Stream; 43 import java.util.stream.Stream;
38 44
39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 45 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -50,7 +56,6 @@ public class CrmPermissionController { @@ -50,7 +56,6 @@ public class CrmPermissionController {
50 56
51 @Resource 57 @Resource
52 private CrmPermissionService permissionService; 58 private CrmPermissionService permissionService;
53 -  
54 @Resource 59 @Resource
55 private AdminUserApi adminUserApi; 60 private AdminUserApi adminUserApi;
56 @Resource 61 @Resource
@@ -61,9 +66,8 @@ public class CrmPermissionController { @@ -61,9 +66,8 @@ public class CrmPermissionController {
61 @PostMapping("/create") 66 @PostMapping("/create")
62 @Operation(summary = "创建数据权限") 67 @Operation(summary = "创建数据权限")
63 @PreAuthorize("@ss.hasPermission('crm:permission:create')") 68 @PreAuthorize("@ss.hasPermission('crm:permission:create')")
64 - @CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER)  
65 - public CommonResult<Boolean> addPermission(@Valid @RequestBody CrmPermissionCreateReqVO reqVO) {  
66 - permissionService.createPermission(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class)); 69 + public CommonResult<Boolean> create(@Valid @RequestBody CrmPermissionSaveReqVO reqVO) {
  70 + permissionService.createPermission(reqVO, getLoginUserId());
67 return success(true); 71 return success(true);
68 } 72 }
69 73
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionCreateReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.permission.vo;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.Data;  
5 -import lombok.EqualsAndHashCode;  
6 -import lombok.ToString;  
7 -  
8 -@Schema(description = "管理后台 - CRM 数据权限创建 Request VO")  
9 -@Data  
10 -@EqualsAndHashCode(callSuper = true)  
11 -@ToString(callSuper = true)  
12 -public class CrmPermissionCreateReqVO extends CrmPermissionBaseVO {  
13 -  
14 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.permission.vo; 1 package cn.iocoder.yudao.module.crm.controller.admin.permission.vo;
2 2
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
  5 +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
3 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
  7 +import jakarta.validation.constraints.NotNull;
4 import lombok.Data; 8 import lombok.Data;
5 9
6 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
@@ -8,11 +12,29 @@ import java.util.Set; @@ -8,11 +12,29 @@ import java.util.Set;
8 12
9 @Schema(description = "管理后台 - CRM 数据权限 Response VO") 13 @Schema(description = "管理后台 - CRM 数据权限 Response VO")
10 @Data 14 @Data
11 -public class CrmPermissionRespVO extends CrmPermissionBaseVO { 15 +public class CrmPermissionRespVO {
12 16
13 @Schema(description = "数据权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") 17 @Schema(description = "数据权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
14 private Long id; 18 private Long id;
15 19
  20 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
  21 + @NotNull(message = "用户编号不能为空")
  22 + private Long userId;
  23 +
  24 + @Schema(description = "CRM 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  25 + @InEnum(CrmBizTypeEnum.class)
  26 + @NotNull(message = "CRM 类型不能为空")
  27 + private Integer bizType;
  28 +
  29 + @Schema(description = "CRM 类型数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  30 + @NotNull(message = "CRM 类型数据编号不能为空")
  31 + private Long bizId;
  32 +
  33 + @Schema(description = "权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  34 + @InEnum(CrmPermissionLevelEnum.class)
  35 + @NotNull(message = "权限级别不能为空")
  36 + private Integer level;
  37 +
16 @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") 38 @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
17 private String nickname; 39 private String nickname;
18 40
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionBaseVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionSaveReqVO.java
@@ -8,14 +8,11 @@ import lombok.Data; @@ -8,14 +8,11 @@ import lombok.Data;
8 8
9 import jakarta.validation.constraints.NotNull; 9 import jakarta.validation.constraints.NotNull;
10 10
11 -/**  
12 - * 数据权限 Base VO,提供给添加、修改、详细的子 VO 使用  
13 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成  
14 - *  
15 - * @author HUIHUI  
16 - */ 11 +import java.util.List;
  12 +
  13 +@Schema(description = "管理后台 - CRM 数据权限创建/更新 Request VO")
17 @Data 14 @Data
18 -public class CrmPermissionBaseVO { 15 +public class CrmPermissionSaveReqVO {
19 16
20 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") 17 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
21 @NotNull(message = "用户编号不能为空") 18 @NotNull(message = "用户编号不能为空")
@@ -35,4 +32,11 @@ public class CrmPermissionBaseVO { @@ -35,4 +32,11 @@ public class CrmPermissionBaseVO {
35 @NotNull(message = "权限级别不能为空") 32 @NotNull(message = "权限级别不能为空")
36 private Integer level; 33 private Integer level;
37 34
  35 + /**
  36 + * 添加客户团队成员时,需要额外有【联系人】【商机】【合同】的 checkbox 选择。
  37 + * 选中时,同时添加对应的权限
  38 + */
  39 + @Schema(description = "同时添加", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
  40 + private List<Integer> toBizTypes;
  41 +
38 } 42 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
2 2
3 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; 3 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
  4 +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 import com.alibaba.excel.annotation.ExcelProperty; 5 import com.alibaba.excel.annotation.ExcelProperty;
5 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 7 import lombok.Data;
@@ -8,53 +9,69 @@ import lombok.Data; @@ -8,53 +9,69 @@ import lombok.Data;
8 import java.math.BigDecimal; 9 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
10 11
11 -// TODO @puhui999:缺导出  
12 @Schema(description = "管理后台 - CRM 回款计划 Response VO") 12 @Schema(description = "管理后台 - CRM 回款计划 Response VO")
13 @Data 13 @Data
  14 +@ExcelIgnoreUnannotated
14 public class CrmReceivablePlanRespVO { 15 public class CrmReceivablePlanRespVO {
15 16
16 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 17 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  18 + @ExcelProperty("编号")
17 private Long id; 19 private Long id;
18 20
19 @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 21 @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  22 + @ExcelProperty("期数")
20 private Integer period; 23 private Integer period;
21 24
22 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 25 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  26 + @ExcelProperty("客户编号")
23 private Long customerId; 27 private Long customerId;
24 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") 28 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
  29 + @ExcelProperty("客户名字")
25 private String customerName; 30 private String customerName;
26 31
27 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 32 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  33 + @ExcelProperty("合同编号")
28 private Long contractId; 34 private Long contractId;
29 @Schema(description = "合同编号", example = "Q110") 35 @Schema(description = "合同编号", example = "Q110")
  36 + @ExcelProperty("合同编号")
30 private String contractNo; 37 private String contractNo;
31 38
32 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 39 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  40 + @ExcelProperty("负责人编号")
33 private Long ownerUserId; 41 private Long ownerUserId;
34 @Schema(description = "负责人", example = "test") 42 @Schema(description = "负责人", example = "test")
  43 + @ExcelProperty("负责人")
35 private String ownerUserName; 44 private String ownerUserName;
36 45
37 @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") 46 @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  47 + @ExcelProperty("计划回款日期")
38 private LocalDateTime returnTime; 48 private LocalDateTime returnTime;
39 49
40 @Schema(description = "计划回款方式", example = "1") 50 @Schema(description = "计划回款方式", example = "1")
  51 + @ExcelProperty("计划回款方式")
41 private Integer returnType; 52 private Integer returnType;
42 53
43 @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") 54 @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  55 + @ExcelProperty("计划回款金额")
44 private BigDecimal price; 56 private BigDecimal price;
45 57
46 @Schema(description = "回款编号", example = "19852") 58 @Schema(description = "回款编号", example = "19852")
  59 + @ExcelProperty("回款编号")
47 private Long receivableId; 60 private Long receivableId;
48 @Schema(description = "回款信息") 61 @Schema(description = "回款信息")
  62 + @ExcelProperty("回款信息")
49 private CrmReceivableRespVO receivable; 63 private CrmReceivableRespVO receivable;
50 64
51 @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 65 @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  66 + @ExcelProperty("提前几天提醒")
52 private Integer remindDays; 67 private Integer remindDays;
53 68
54 @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") 69 @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  70 + @ExcelProperty("提醒日期")
55 private LocalDateTime remindTime; 71 private LocalDateTime remindTime;
56 72
57 @Schema(description = "备注", example = "备注") 73 @Schema(description = "备注", example = "备注")
  74 + @ExcelProperty("备注")
58 private String remark; 75 private String remark;
59 76
60 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 77 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
2 2
3 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO; 3 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
  4 +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 import com.alibaba.excel.annotation.ExcelProperty; 5 import com.alibaba.excel.annotation.ExcelProperty;
5 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 7 import lombok.Data;
@@ -8,37 +9,47 @@ import lombok.Data; @@ -8,37 +9,47 @@ import lombok.Data;
8 import java.math.BigDecimal; 9 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
10 11
11 -// TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能  
12 @Schema(description = "管理后台 - CRM 回款 Response VO") 12 @Schema(description = "管理后台 - CRM 回款 Response VO")
13 @Data 13 @Data
  14 +@ExcelIgnoreUnannotated
14 public class CrmReceivableRespVO { 15 public class CrmReceivableRespVO {
15 16
16 @Schema(description = "编号", example = "25787") 17 @Schema(description = "编号", example = "25787")
  18 + @ExcelProperty("编号")
17 private Long id; 19 private Long id;
18 20
19 @Schema(description = "回款编号", example = "31177") 21 @Schema(description = "回款编号", example = "31177")
  22 + @ExcelProperty("回款编号")
20 private String no; 23 private String no;
21 24
22 @Schema(description = "回款计划编号", example = "1024") 25 @Schema(description = "回款计划编号", example = "1024")
  26 + @ExcelProperty("回款计划编号")
23 private Long planId; 27 private Long planId;
24 28
25 @Schema(description = "回款方式", example = "2") 29 @Schema(description = "回款方式", example = "2")
  30 + @ExcelProperty("回款方式")
26 private Integer returnType; 31 private Integer returnType;
27 32
28 @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") 33 @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  34 + @ExcelProperty("回款金额")
29 private BigDecimal price; 35 private BigDecimal price;
30 36
31 @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") 37 @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  38 + @ExcelProperty("计划回款日期")
32 private LocalDateTime returnTime; 39 private LocalDateTime returnTime;
33 40
34 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 41 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  42 + @ExcelProperty("客户编号")
35 private Long customerId; 43 private Long customerId;
36 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") 44 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
  45 + @ExcelProperty("客户名字")
37 private String customerName; 46 private String customerName;
38 47
39 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 48 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  49 + @ExcelProperty("合同编号")
40 private Long contractId; 50 private Long contractId;
41 @Schema(description = "合同信息") 51 @Schema(description = "合同信息")
  52 + @ExcelProperty("合同信息")
42 private CrmContractRespVO contract; 53 private CrmContractRespVO contract;
43 54
44 @Schema(description = "负责人的用户编号", example = "25682") 55 @Schema(description = "负责人的用户编号", example = "25682")
@@ -56,20 +67,26 @@ public class CrmReceivableRespVO { @@ -56,20 +67,26 @@ public class CrmReceivableRespVO {
56 private String processInstanceId; 67 private String processInstanceId;
57 68
58 @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") 69 @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  70 + @ExcelProperty("审批状态")
59 private Integer auditStatus; 71 private Integer auditStatus;
60 72
61 - @Schema(description = "备注", example = "备注") 73 + @Schema(description = "工作流编号", example = "备注")
  74 + @ExcelProperty("工作流编号")
62 private String remark; 75 private String remark;
63 76
64 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 77 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  78 + @ExcelProperty("创建时间")
65 private LocalDateTime createTime; 79 private LocalDateTime createTime;
66 80
67 @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) 81 @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  82 + @ExcelProperty("更新时间")
68 private LocalDateTime updateTime; 83 private LocalDateTime updateTime;
69 84
70 @Schema(description = "创建人", example = "25682") 85 @Schema(description = "创建人", example = "25682")
  86 + @ExcelProperty("创建人")
71 private String creator; 87 private String creator;
72 @Schema(description = "创建人名字", example = "test") 88 @Schema(description = "创建人名字", example = "test")
  89 + @ExcelProperty("创建人名字")
73 private String creatorName; 90 private String creatorName;
74 91
75 } 92 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.http 0 → 100644
  1 +# == 1. 客户总量分析 ==
  2 +### 1.1 客户总量分析(按日期)
  3 +GET {{baseUrl}}/crm/statistics-customer/get-customer-summary-by-date?deptId=100&interval=2&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
  4 +Authorization: Bearer {{token}}
  5 +tenant-id: {{adminTenentId}}
  6 +
  7 +### 1.2 客户总量统计(按用户)
  8 +GET {{baseUrl}}/crm/statistics-customer/get-customer-summary-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
  9 +Authorization: Bearer {{token}}
  10 +tenant-id: {{adminTenentId}}
  11 +
  12 +# == 2. 客户跟进次数分析 ==
  13 +### 2.1 客户跟进次数分析(按日期)
  14 +GET {{baseUrl}}/crm/statistics-customer/get-follow-up-summary-by-date?deptId=100&interval=2&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
  15 +Authorization: Bearer {{token}}
  16 +tenant-id: {{adminTenentId}}
  17 +
  18 +### 2.2 客户总量统计(按用户)
  19 +GET {{baseUrl}}/crm/statistics-customer/get-follow-up-summary-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
  20 +Authorization: Bearer {{token}}
  21 +tenant-id: {{adminTenentId}}
  22 +
  23 +# == 3. 客户跟进方式分析 ==
  24 +### 3.1 客户跟进方式分析
  25 +GET {{baseUrl}}/crm/statistics-customer/get-follow-up-summary-by-type?deptId=100&interval=2&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
  26 +Authorization: Bearer {{token}}
  27 +tenant-id: {{adminTenentId}}
  28 +
  29 +# == 4. 客户成交周期 ==
  30 +### 4.1 合同摘要信息(客户转化率页面)
  31 +GET {{baseUrl}}/crm/statistics-customer/get-contract-summary?deptId=100&interval=2&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
  32 +Authorization: Bearer {{token}}
  33 +tenant-id: {{adminTenentId}}
  34 +
  35 +# == 5. 客户成交周期 ==
  36 +### 5.1 获取客户公海分析(按日期)
  37 +GET {{baseUrl}}/crm/statistics-customer/get-pool-summary-by-date?deptId=100&interval=2&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
  38 +Authorization: Bearer {{token}}
  39 +tenant-id: {{adminTenentId}}
  40 +
  41 +### 5.2 获取客户公海分析(按用户)
  42 +GET {{baseUrl}}/crm/statistics-customer/get-pool-summary-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
  43 +Authorization: Bearer {{token}}
  44 +tenant-id: {{adminTenentId}}
  45 +
  46 +# == 6. 客户成交周期 ==
  47 +### 6.1 客户成交周期(按日期)
  48 +GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-date?deptId=100&interval=2&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
  49 +Authorization: Bearer {{token}}
  50 +tenant-id: {{adminTenentId}}
  51 +
  52 +### 6.2 获取客户成交周期(按用户)
  53 +GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
  54 +Authorization: Bearer {{token}}
  55 +tenant-id: {{adminTenentId}}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
  5 +import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsCustomerService;
  6 +import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import jakarta.annotation.Resource;
  9 +import jakarta.validation.Valid;
  10 +import org.springframework.security.access.prepost.PreAuthorize;
  11 +import org.springframework.validation.annotation.Validated;
  12 +import org.springframework.web.bind.annotation.GetMapping;
  13 +import org.springframework.web.bind.annotation.RequestMapping;
  14 +import org.springframework.web.bind.annotation.RestController;
  15 +
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "管理后台 - CRM 客户统计")
  21 +@RestController
  22 +@RequestMapping("/crm/statistics-customer")
  23 +@Validated
  24 +public class CrmStatisticsCustomerController {
  25 +
  26 + @Resource
  27 + private CrmStatisticsCustomerService customerService;
  28 +
  29 + @GetMapping("/get-customer-summary-by-date")
  30 + @Operation(summary = "获取客户总量分析(按日期)")
  31 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  32 + public CommonResult<List<CrmStatisticsCustomerSummaryByDateRespVO>> getCustomerSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
  33 + return success(customerService.getCustomerSummaryByDate(reqVO));
  34 + }
  35 +
  36 + @GetMapping("/get-customer-summary-by-user")
  37 + @Operation(summary = "获取客户总量分析(按用户)")
  38 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  39 + public CommonResult<List<CrmStatisticsCustomerSummaryByUserRespVO>> getCustomerSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
  40 + return success(customerService.getCustomerSummaryByUser(reqVO));
  41 + }
  42 +
  43 + @GetMapping("/get-follow-up-summary-by-date")
  44 + @Operation(summary = "获取客户跟进次数分析(按日期)")
  45 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  46 + public CommonResult<List<CrmStatisticsFollowUpSummaryByDateRespVO>> getFollowupSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
  47 + return success(customerService.getFollowUpSummaryByDate(reqVO));
  48 + }
  49 +
  50 + @GetMapping("/get-follow-up-summary-by-user")
  51 + @Operation(summary = "获取客户跟进次数分析(按用户)")
  52 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  53 + public CommonResult<List<CrmStatisticsFollowUpSummaryByUserRespVO>> getFollowUpSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
  54 + return success(customerService.getFollowUpSummaryByUser(reqVO));
  55 + }
  56 +
  57 + @GetMapping("/get-follow-up-summary-by-type")
  58 + @Operation(summary = "获取客户跟进次数分析(按类型)")
  59 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  60 + public CommonResult<List<CrmStatisticsFollowUpSummaryByTypeRespVO>> getFollowUpSummaryByType(@Valid CrmStatisticsCustomerReqVO reqVO) {
  61 + return success(customerService.getFollowUpSummaryByType(reqVO));
  62 + }
  63 +
  64 + @GetMapping("/get-contract-summary")
  65 + @Operation(summary = "获取客户的首次合同、回款信息列表", description = "用于【客户转化率】页面")
  66 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  67 + public CommonResult<List<CrmStatisticsCustomerContractSummaryRespVO>> getContractSummary(@Valid CrmStatisticsCustomerReqVO reqVO) {
  68 + return success(customerService.getContractSummary(reqVO));
  69 + }
  70 +
  71 + @GetMapping("/get-pool-summary-by-date")
  72 + @Operation(summary = "获取公海客户分析(按日期)")
  73 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  74 + public CommonResult<List<CrmStatisticsPoolSummaryByDateRespVO>> getPoolSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
  75 + return success(customerService.getPoolSummaryByDate(reqVO));
  76 + }
  77 +
  78 + @GetMapping("/get-pool-summary-by-user")
  79 + @Operation(summary = "获取公海客户分析(按用户)")
  80 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  81 + public CommonResult<List<CrmStatisticsPoolSummaryByUserRespVO>> getPoolSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
  82 + return success(customerService.getPoolSummaryByUser(reqVO));
  83 + }
  84 +
  85 + @GetMapping("/get-customer-deal-cycle-by-date")
  86 + @Operation(summary = "获取客户成交周期(按日期)")
  87 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  88 + public CommonResult<List<CrmStatisticsCustomerDealCycleByDateRespVO>> getCustomerDealCycleByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
  89 + return success(customerService.getCustomerDealCycleByDate(reqVO));
  90 + }
  91 +
  92 + @GetMapping("/get-customer-deal-cycle-by-user")
  93 + @Operation(summary = "获取客户成交周期(按用户)")
  94 + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
  95 + public CommonResult<List<CrmStatisticsCustomerDealCycleByUserRespVO>> getCustomerDealCycleByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
  96 + return success(customerService.getCustomerDealCycleByUser(reqVO));
  97 + }
  98 +
  99 + // TODO dhb52:【成交周期分析】里,有按照员工(已实现)、地区(未实现)、产品(未实现),需要在看看哈;可以把 CustomerDealCycle 拆成 3 个 tab,员工客户成交周期分析、地区客户成交周期分析、产品客户成交周期分析;
  100 +
  101 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsPerformanceController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
  6 +import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsPerformanceService;
  7 +import io.swagger.v3.oas.annotations.Operation;
  8 +import io.swagger.v3.oas.annotations.tags.Tag;
  9 +import org.springframework.security.access.prepost.PreAuthorize;
  10 +import org.springframework.validation.annotation.Validated;
  11 +import org.springframework.web.bind.annotation.GetMapping;
  12 +import org.springframework.web.bind.annotation.RequestMapping;
  13 +import org.springframework.web.bind.annotation.RestController;
  14 +
  15 +import jakarta.annotation.Resource;
  16 +import jakarta.validation.Valid;
  17 +import java.util.List;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +
  21 +
  22 +@Tag(name = "管理后台 - CRM 员工业绩统计")
  23 +@RestController
  24 +@RequestMapping("/crm/statistics-performance")
  25 +@Validated
  26 +public class CrmStatisticsPerformanceController {
  27 +
  28 + @Resource
  29 + private CrmStatisticsPerformanceService performanceService;
  30 +
  31 + @GetMapping("/get-contract-count-performance")
  32 + @Operation(summary = "合同数量统计", description = "用于【合同数量分析】页面")
  33 + @PreAuthorize("@ss.hasPermission('crm:statistics-performance:query')")
  34 + public CommonResult<List<CrmStatisticsPerformanceRespVO>> getContractCountPerformance(@Valid CrmStatisticsPerformanceReqVO performanceReqVO) {
  35 + return success(performanceService.getContractCountPerformance(performanceReqVO));
  36 + }
  37 +
  38 + @GetMapping("/get-contract-price-performance")
  39 + @Operation(summary = "合同金额统计")
  40 + @PreAuthorize("@ss.hasPermission('crm:statistics-performance:query')")
  41 + public CommonResult<List<CrmStatisticsPerformanceRespVO>> getContractPriceStaffPerformance(@Valid CrmStatisticsPerformanceReqVO performanceReqVO) {
  42 + return success(performanceService.getContractPricePerformance(performanceReqVO));
  43 + }
  44 +
  45 + @GetMapping("/get-receivable-price-performance")
  46 + @Operation(summary = "回款金额统计")
  47 + @PreAuthorize("@ss.hasPermission('crm:statistics-performance:query')")
  48 + public CommonResult<List<CrmStatisticsPerformanceRespVO>> getReceivablePriceStaffPerformance(@Valid CrmStatisticsPerformanceReqVO performanceReqVO) {
  49 + return success(performanceService.getReceivablePricePerformance(performanceReqVO));
  50 + }
  51 +
  52 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsPortraitController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticsPortraitReqVO;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerAreaRespVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerIndustryRespVO;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerLevelRespVO;
  8 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerSourceRespVO;
  9 +import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsPortraitService;
  10 +import io.swagger.v3.oas.annotations.Operation;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import jakarta.validation.Valid;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.GetMapping;
  17 +import org.springframework.web.bind.annotation.RequestMapping;
  18 +import org.springframework.web.bind.annotation.RestController;
  19 +
  20 +import java.util.List;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  23 +
  24 +@Tag(name = "管理后台 - CRM 客户画像")
  25 +@RestController
  26 +@RequestMapping("/crm/statistics-portrait")
  27 +@Validated
  28 +public class CrmStatisticsPortraitController {
  29 +
  30 + @Resource
  31 + private CrmStatisticsPortraitService statisticsPortraitService;
  32 +
  33 + @GetMapping("/get-customer-area-summary")
  34 + @Operation(summary = "获取客户地区统计数据", description = "用于【城市分布分析】页面")
  35 + @PreAuthorize("@ss.hasPermission('crm:statistics-portrait:query')")
  36 + public CommonResult<List<CrmStatisticCustomerAreaRespVO>> getCustomerAreaSummary(@Valid CrmStatisticsPortraitReqVO reqVO) {
  37 + return success(statisticsPortraitService.getCustomerSummaryByArea(reqVO));
  38 + }
  39 +
  40 + @GetMapping("/get-customer-industry-summary")
  41 + @Operation(summary = "获取客户行业统计数据", description = "用于【客户行业分析】页面")
  42 + @PreAuthorize("@ss.hasPermission('crm:statistics-portrait:query')")
  43 + public CommonResult<List<CrmStatisticCustomerIndustryRespVO>> getCustomerIndustrySummary(@Valid CrmStatisticsPortraitReqVO reqVO) {
  44 + return success(statisticsPortraitService.getCustomerSummaryByIndustry(reqVO));
  45 + }
  46 +
  47 + @GetMapping("/get-customer-level-summary")
  48 + @Operation(summary = "获取客户级别统计数据", description = "用于【客户级别分析】页面")
  49 + @PreAuthorize("@ss.hasPermission('crm:statistics-portrait:query')")
  50 + public CommonResult<List<CrmStatisticCustomerLevelRespVO>> getCustomerLevelSummary(@Valid CrmStatisticsPortraitReqVO reqVO) {
  51 + return success(statisticsPortraitService.getCustomerSummaryByLevel(reqVO));
  52 + }
  53 +
  54 + @GetMapping("/get-customer-source-summary")
  55 + @Operation(summary = "获取客户来源统计数据", description = "用于【客户来源分析】页面")
  56 + @PreAuthorize("@ss.hasPermission('crm:statistics-portrait:query')")
  57 + public CommonResult<List<CrmStatisticCustomerSourceRespVO>> getCustomerSourceSummary(@Valid CrmStatisticsPortraitReqVO reqVO) {
  58 + return success(statisticsPortraitService.getCustomerSummaryBySource(reqVO));
  59 + }
  60 +
  61 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.java
1 package cn.iocoder.yudao.module.crm.controller.admin.statistics; 1 package cn.iocoder.yudao.module.crm.controller.admin.statistics;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;  
5 -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;  
6 -import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsRankingService; 4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
  6 +import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsRankService;
7 import io.swagger.v3.oas.annotations.Operation; 7 import io.swagger.v3.oas.annotations.Operation;
8 import io.swagger.v3.oas.annotations.tags.Tag; 8 import io.swagger.v3.oas.annotations.tags.Tag;
9 import jakarta.annotation.Resource; 9 import jakarta.annotation.Resource;
@@ -18,7 +18,6 @@ import java.util.List; @@ -18,7 +18,6 @@ import java.util.List;
18 18
19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20 20
21 -  
22 @Tag(name = "管理后台 - CRM 排行榜统计") 21 @Tag(name = "管理后台 - CRM 排行榜统计")
23 @RestController 22 @RestController
24 @RequestMapping("/crm/statistics-rank") 23 @RequestMapping("/crm/statistics-rank")
@@ -26,62 +25,62 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -26,62 +25,62 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
26 public class CrmStatisticsRankController { 25 public class CrmStatisticsRankController {
27 26
28 @Resource 27 @Resource
29 - private CrmStatisticsRankingService rankingService; 28 + private CrmStatisticsRankService rankService;
30 29
31 @GetMapping("/get-contract-price-rank") 30 @GetMapping("/get-contract-price-rank")
32 @Operation(summary = "获得合同金额排行榜") 31 @Operation(summary = "获得合同金额排行榜")
33 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") 32 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
34 - public CommonResult<List<CrmStatisticsRanKRespVO>> getContractPriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {  
35 - return success(rankingService.getContractPriceRank(rankingReqVO)); 33 + public CommonResult<List<CrmStatisticsRankRespVO>> getContractPriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
  34 + return success(rankService.getContractPriceRank(rankingReqVO));
36 } 35 }
37 36
38 @GetMapping("/get-receivable-price-rank") 37 @GetMapping("/get-receivable-price-rank")
39 @Operation(summary = "获得回款金额排行榜") 38 @Operation(summary = "获得回款金额排行榜")
40 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") 39 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
41 - public CommonResult<List<CrmStatisticsRanKRespVO>> getReceivablePriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {  
42 - return success(rankingService.getReceivablePriceRank(rankingReqVO)); 40 + public CommonResult<List<CrmStatisticsRankRespVO>> getReceivablePriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
  41 + return success(rankService.getReceivablePriceRank(rankingReqVO));
43 } 42 }
44 43
45 @GetMapping("/get-contract-count-rank") 44 @GetMapping("/get-contract-count-rank")
46 @Operation(summary = "获得签约合同数量排行榜") 45 @Operation(summary = "获得签约合同数量排行榜")
47 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") 46 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
48 - public CommonResult<List<CrmStatisticsRanKRespVO>> getContractCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {  
49 - return success(rankingService.getContractCountRank(rankingReqVO)); 47 + public CommonResult<List<CrmStatisticsRankRespVO>> getContractCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
  48 + return success(rankService.getContractCountRank(rankingReqVO));
50 } 49 }
51 50
52 @GetMapping("/get-product-sales-rank") 51 @GetMapping("/get-product-sales-rank")
53 @Operation(summary = "获得产品销量排行榜") 52 @Operation(summary = "获得产品销量排行榜")
54 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") 53 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
55 - public CommonResult<List<CrmStatisticsRanKRespVO>> getProductSalesRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {  
56 - return success(rankingService.getProductSalesRank(rankingReqVO)); 54 + public CommonResult<List<CrmStatisticsRankRespVO>> getProductSalesRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
  55 + return success(rankService.getProductSalesRank(rankingReqVO));
57 } 56 }
58 57
59 @GetMapping("/get-customer-count-rank") 58 @GetMapping("/get-customer-count-rank")
60 @Operation(summary = "获得新增客户数排行榜") 59 @Operation(summary = "获得新增客户数排行榜")
61 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") 60 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
62 - public CommonResult<List<CrmStatisticsRanKRespVO>> getCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {  
63 - return success(rankingService.getCustomerCountRank(rankingReqVO)); 61 + public CommonResult<List<CrmStatisticsRankRespVO>> getCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
  62 + return success(rankService.getCustomerCountRank(rankingReqVO));
64 } 63 }
65 64
66 @GetMapping("/get-contacts-count-rank") 65 @GetMapping("/get-contacts-count-rank")
67 @Operation(summary = "获得新增联系人数排行榜") 66 @Operation(summary = "获得新增联系人数排行榜")
68 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") 67 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
69 - public CommonResult<List<CrmStatisticsRanKRespVO>> getContactsCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {  
70 - return success(rankingService.getContactsCountRank(rankingReqVO)); 68 + public CommonResult<List<CrmStatisticsRankRespVO>> getContactsCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
  69 + return success(rankService.getContactsCountRank(rankingReqVO));
71 } 70 }
72 71
73 @GetMapping("/get-follow-count-rank") 72 @GetMapping("/get-follow-count-rank")
74 @Operation(summary = "获得跟进次数排行榜") 73 @Operation(summary = "获得跟进次数排行榜")
75 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") 74 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
76 - public CommonResult<List<CrmStatisticsRanKRespVO>> getFollowCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {  
77 - return success(rankingService.getFollowCountRank(rankingReqVO)); 75 + public CommonResult<List<CrmStatisticsRankRespVO>> getFollowCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
  76 + return success(rankService.getFollowCountRank(rankingReqVO));
78 } 77 }
79 78
80 @GetMapping("/get-follow-customer-count-rank") 79 @GetMapping("/get-follow-customer-count-rank")
81 @Operation(summary = "获得跟进客户数排行榜") 80 @Operation(summary = "获得跟进客户数排行榜")
82 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") 81 @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
83 - public CommonResult<List<CrmStatisticsRanKRespVO>> getFollowCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {  
84 - return success(rankingService.getFollowCustomerCountRank(rankingReqVO)); 82 + public CommonResult<List<CrmStatisticsRankRespVO>> getFollowCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
  83 + return success(rankService.getFollowCustomerCountRank(rankingReqVO));
85 } 84 }
86 85
87 } 86 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerByUserBaseRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 用户客户统计响应 Base Response VO
  8 + *
  9 + * 目的:可以统一拼接子 VO 的 ownerUserId、ownerUserName 属性
  10 + */
  11 +@Data
  12 +public class CrmStatisticsCustomerByUserBaseRespVO {
  13 +
  14 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  15 + private Long ownerUserId;
  16 +
  17 + @Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
  18 + private String ownerUserName;
  19 +
  20 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerContractSummaryRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +import java.math.BigDecimal;
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - CRM 客户转化率分析 VO")
  11 +@Data
  12 +public class CrmStatisticsCustomerContractSummaryRespVO {
  13 +
  14 + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
  15 + private String customerName;
  16 +
  17 + @Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "演示合同")
  18 + private String contractName;
  19 +
  20 + @Schema(description = "合同总金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1200.00")
  21 + private BigDecimal totalPrice;
  22 +
  23 + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1200.00")
  24 + private BigDecimal receivablePrice;
  25 +
  26 + @Schema(description = "客户行业编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  27 + private Integer industryId;
  28 +
  29 + @Schema(description = "客户来源编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  30 + private Integer source;
  31 +
  32 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  33 + private Long ownerUserId;
  34 + @Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
  35 + private String ownerUserName;
  36 +
  37 + @Schema(description = "创建人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  38 + private String creator;
  39 + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED, example = "源码")
  40 + private String creatorUserName;
  41 +
  42 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-01 13:24:26")
  43 + private LocalDateTime createTime;
  44 +
  45 + @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02 00:00:00")
  46 + private LocalDateTime orderDate;
  47 +
  48 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByDateRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 客户成交周期分析(按日期) VO")
  7 +@Data
  8 +public class CrmStatisticsCustomerDealCycleByDateRespVO {
  9 +
  10 + @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
  11 + private String time;
  12 +
  13 + @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
  14 + private Double customerDealCycle;
  15 +
  16 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByUserRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 成交周期分析(按用户) VO")
  7 +@Data
  8 +public class CrmStatisticsCustomerDealCycleByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
  9 +
  10 + @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
  11 + private Double customerDealCycle;
  12 +
  13 + @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer customerDealCount;
  15 +
  16 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import jakarta.validation.constraints.Size;
  8 +import lombok.Data;
  9 +import org.springframework.format.annotation.DateTimeFormat;
  10 +
  11 +import java.time.LocalDateTime;
  12 +import java.util.List;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  15 +
  16 +@Schema(description = "管理后台 - CRM 数据统计的员工客户分析 Request VO")
  17 +@Data
  18 +public class CrmStatisticsCustomerReqVO {
  19 +
  20 + @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  21 + @NotNull(message = "部门 id 不能为空")
  22 + private Long deptId;
  23 +
  24 + /**
  25 + * 负责人用户 id, 当用户为空, 则计算部门下用户
  26 + */
  27 + @Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
  28 + private Long userId;
  29 +
  30 + /**
  31 + * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
  32 + * 后续,可能会支持选择部分用户进行查询
  33 + */
  34 + @Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
  35 + private List<Long> userIds;
  36 +
  37 + @Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  38 + @InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
  39 + private Integer interval;
  40 +
  41 + @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
  42 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  43 + @Size(min = 2, max = 2, message = "请选择时间范围")
  44 + private LocalDateTime[] times;
  45 +
  46 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByDateRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 客户总量分析(按日期) VO")
  7 +@Data
  8 +public class CrmStatisticsCustomerSummaryByDateRespVO {
  9 +
  10 + @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
  11 + private String time;
  12 +
  13 + @Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer customerCreateCount;
  15 +
  16 + @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Integer customerDealCount;
  18 +
  19 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerSummaryByUserRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
  7 +
  8 +@Schema(description = "管理后台 - CRM 客户总量分析(按用户) VO")
  9 +@Data
  10 +public class CrmStatisticsCustomerSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
  11 +
  12 + @Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  13 + private Integer customerCreateCount;
  14 +
  15 + @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  16 + private Integer customerDealCount;
  17 +
  18 + @Schema(description = "合同总金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
  19 + private BigDecimal contractPrice;
  20 +
  21 + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
  22 + private BigDecimal receivablePrice;
  23 +
  24 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByDateRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 跟进次数分析(按日期) VO")
  7 +@Data
  8 +public class CrmStatisticsFollowUpSummaryByDateRespVO {
  9 +
  10 + @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
  11 + private String time;
  12 +
  13 + @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer followUpRecordCount;
  15 +
  16 + @Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Integer followUpCustomerCount;
  18 +
  19 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByTypeRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - CRM 跟进次数分析(按类型) VO")
  8 +@Data
  9 +public class CrmStatisticsFollowUpSummaryByTypeRespVO {
  10 +
  11 + @Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  12 + private Integer followUpType;
  13 +
  14 + @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  15 + private Integer followUpRecordCount;
  16 +
  17 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsFollowUpSummaryByUserRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 跟进次数分析(按用户) VO")
  7 +@Data
  8 +public class CrmStatisticsFollowUpSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
  9 +
  10 + @Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  11 + private Integer followUpRecordCount;
  12 +
  13 + @Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer followUpCustomerCount;
  15 +
  16 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsPoolSummaryByDateRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 公海客户分析(按日期) VO")
  7 +@Data
  8 +public class CrmStatisticsPoolSummaryByDateRespVO {
  9 +
  10 + @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
  11 + private String time;
  12 +
  13 + @Schema(description = "进入公海客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer customerPutCount;
  15 +
  16 + @Schema(description = "公海领取客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Integer customerTakeCount;
  18 +
  19 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsPoolSummaryByUserRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 公海客户分析(按用户) VO")
  7 +@Data
  8 +public class CrmStatisticsPoolSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
  9 +
  10 + @Schema(description = "进入公海客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  11 + private Integer customerPutCount;
  12 +
  13 + @Schema(description = "公海领取客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer customerTakeCount;
  15 +
  16 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +
  7 +import jakarta.validation.constraints.NotEmpty;
  8 +import jakarta.validation.constraints.NotNull;
  9 +import java.time.LocalDateTime;
  10 +import java.util.List;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +@Schema(description = "管理后台 - CRM 员工业绩统计 Request VO")
  15 +@Data
  16 +public class CrmStatisticsPerformanceReqVO {
  17 +
  18 + @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + @NotNull(message = "部门 id 不能为空")
  20 + private Long deptId;
  21 +
  22 + /**
  23 + * 负责人用户 id, 当用户为空, 则计算部门下用户
  24 + */
  25 + @Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
  26 + private Long userId;
  27 +
  28 + /**
  29 + * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
  30 + * <p>
  31 + * 后续,可能会支持选择部分用户进行查询
  32 + */
  33 + @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
  34 + private List<Long> userIds;
  35 +
  36 + // TODO @scholar:应该传递的是 int year;年份
  37 + @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED)
  38 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  39 + @NotEmpty(message = "时间范围不能为空")
  40 + private LocalDateTime[] times;
  41 +
  42 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import java.math.BigDecimal;
  6 +
  7 +
  8 +@Schema(description = "管理后台 - CRM 员工业绩统计 Response VO")
  9 +@Data
  10 +public class CrmStatisticsPerformanceRespVO {
  11 +
  12 + @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
  13 + private String time;
  14 +
  15 + @Schema(description = "当月统计结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  16 + private BigDecimal currentMonthCount;
  17 +
  18 + @Schema(description = "上月统计结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  19 + private BigDecimal lastMonthCount;
  20 +
  21 + @Schema(description = "去年同期统计结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  22 + private BigDecimal lastYearCount;
  23 +
  24 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerAreaRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 客户省份分析 VO")
  7 +@Data
  8 +public class CrmStatisticCustomerAreaRespVO {
  9 +
  10 + @Schema(description = "省份编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  11 + private Integer areaId;
  12 + @Schema(description = "省份名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "浙江省")
  13 + private String areaName;
  14 +
  15 + @Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  16 + private Integer customerCount;
  17 +
  18 + @Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + private Integer dealCount;
  20 +
  21 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerIndustryRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 客户行业分析 VO")
  7 +@Data
  8 +public class CrmStatisticCustomerIndustryRespVO {
  9 +
  10 + @Schema(description = "客户行业ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  11 + private Integer industryId;
  12 +
  13 + @Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer customerCount;
  15 +
  16 + @Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Integer dealCount;
  18 +
  19 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerLevelRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 客户级别分析 VO")
  7 +@Data
  8 +public class CrmStatisticCustomerLevelRespVO {
  9 +
  10 + @Schema(description = "客户级别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  11 + private Integer level;
  12 +
  13 + @Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer customerCount;
  15 +
  16 + @Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Integer dealCount;
  18 +
  19 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticCustomerSourceRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 客户来源分析 VO")
  7 +@Data
  8 +public class CrmStatisticCustomerSourceRespVO {
  9 +
  10 + @Schema(description = "客户来源编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  11 + private Integer source;
  12 +
  13 + @Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer customerCount;
  15 +
  16 + @Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Integer dealCount;
  18 +
  19 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticsPortraitReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import java.time.LocalDateTime;
  9 +import java.util.List;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - CRM 客户画像 Request VO")
  14 +@Data
  15 +public class CrmStatisticsPortraitReqVO {
  16 +
  17 + @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + @NotNull(message = "部门 id 不能为空")
  19 + private Long deptId;
  20 +
  21 + /**
  22 + * 负责人用户 id, 当用户为空, 则计算部门下用户
  23 + */
  24 + @Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
  25 + private Long userId;
  26 +
  27 + /**
  28 + * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
  29 + * 后续,可能会支持选择部分用户进行查询
  30 + */
  31 + @Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
  32 + private List<Long> userIds;
  33 +
  34 + /**
  35 + * 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
  36 + * 并作为参数传递给Mapper
  37 + */
  38 + @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
  39 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  40 + private LocalDateTime[] times;
  41 +
  42 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
@@ -21,7 +21,7 @@ public class CrmStatisticsRankReqVO { @@ -21,7 +21,7 @@ public class CrmStatisticsRankReqVO {
21 21
22 /** 22 /**
23 * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来 23 * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
24 - * 24 + * <p>
25 * 后续,可能会支持选择部分用户进行查询 25 * 后续,可能会支持选择部分用户进行查询
26 */ 26 */
27 @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2") 27 @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/rank/CrmStatisticsRankRespVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
  6 +import java.math.BigDecimal;
6 7
7 -@Schema(description = "管理后台 - CRM BI 排行榜统计 Response VO") 8 +
  9 +@Schema(description = "管理后台 - CRM 排行榜统计 Response VO")
8 @Data 10 @Data
9 -public class CrmStatisticsRanKRespVO { 11 +public class CrmStatisticsRankRespVO {
10 12
11 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 13 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
12 private Long ownerUserId; 14 private Long ownerUserId;
@@ -22,8 +24,10 @@ public class CrmStatisticsRanKRespVO { @@ -22,8 +24,10 @@ public class CrmStatisticsRanKRespVO {
22 * 24 *
23 * 1. 金额:合同金额排行、回款金额排行 25 * 1. 金额:合同金额排行、回款金额排行
24 * 2. 个数:签约合同排行、产品销量排行、产品销量排行、新增客户数排行、新增联系人排行、跟进次数排行、跟进客户数排行 26 * 2. 个数:签约合同排行、产品销量排行、产品销量排行、新增客户数排行、新增联系人排行、跟进次数排行、跟进客户数排行
  27 + *
  28 + * 为什么使用 BigDecimal 的原因:
25 */ 29 */
26 @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 30 @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
27 - private Integer count; 31 + private BigDecimal count;
28 32
29 } 33 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
@@ -6,12 +6,14 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -6,12 +6,14 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; 7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
8 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; 8 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  9 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
10 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; 11 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
11 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 12 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
12 import org.apache.ibatis.annotations.Mapper; 13 import org.apache.ibatis.annotations.Mapper;
13 14
14 import java.util.Collection; 15 import java.util.Collection;
  16 +import java.util.List;
15 17
16 /** 18 /**
17 * 商机 Mapper 19 * 商机 Mapper
@@ -57,4 +59,10 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; { @@ -57,4 +59,10 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
57 return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId); 59 return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId);
58 } 60 }
59 61
  62 + default List<CrmBusinessDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId){
  63 + return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
  64 + .eq(CrmBusinessDO::getCustomerId, customerId)
  65 + .eq(CrmBusinessDO::getOwnerUserId, ownerUserId));
  66 + }
  67 +
60 } 68 }
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&lt;CrmContactDO&gt; { @@ -73,4 +73,9 @@ public interface CrmContactMapper extends BaseMapperX&lt;CrmContactDO&gt; {
73 return selectList(CrmContactDO::getCustomerId, customerId); 73 return selectList(CrmContactDO::getCustomerId, customerId);
74 } 74 }
75 75
  76 + default List<CrmContactDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
  77 + return selectList(CrmContactDO::getCustomerId, customerId,
  78 + CrmContactDO::getOwnerUserId, ownerUserId);
  79 + }
  80 +
76 } 81 }
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&lt;CrmContractDO&gt; { @@ -117,4 +117,10 @@ public interface CrmContractMapper extends BaseMapperX&lt;CrmContractDO&gt; {
117 return selectCount(query); 117 return selectCount(query);
118 } 118 }
119 119
  120 + default List<CrmContractDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
  121 + return selectList(new LambdaQueryWrapperX<CrmContractDO>()
  122 + .eq(CrmContractDO::getCustomerId, customerId)
  123 + .eq(CrmContractDO::getOwnerUserId, ownerUserId));
  124 + }
  125 +
120 } 126 }
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&lt;CrmPermissionDO&gt; { @@ -53,9 +53,11 @@ public interface CrmPermissionMapper extends BaseMapperX&lt;CrmPermissionDO&gt; {
53 CrmPermissionDO::getUserId, userId); 53 CrmPermissionDO::getUserId, userId);
54 } 54 }
55 55
56 - default CrmPermissionDO selectByBizIdAndUserId(Long bizId, Long userId) {  
57 - return selectOne(CrmPermissionDO::getBizId, bizId,  
58 - CrmPermissionDO::getUserId, userId); 56 + default CrmPermissionDO selectByBizAndUserId(Integer bizType, Long bizId, Long userId) {
  57 + return selectOne(new LambdaQueryWrapperX<CrmPermissionDO>()
  58 + .eq(CrmPermissionDO::getBizType, bizType)
  59 + .eq(CrmPermissionDO::getBizId, bizId)
  60 + .eq(CrmPermissionDO::getUserId, userId));
59 } 61 }
60 62
61 default int deletePermission(Integer bizType, Long bizId) { 63 default int deletePermission(Integer bizType, Long bizId) {
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&lt;CrmReceivableDO&gt; { @@ -99,4 +99,8 @@ public interface CrmReceivableMapper extends BaseMapperX&lt;CrmReceivableDO&gt; {
99 return convertMap(result, obj -> (Long) obj.get("contract_id"), obj -> (BigDecimal) obj.get("total_price")); 99 return convertMap(result, obj -> (Long) obj.get("contract_id"), obj -> (BigDecimal) obj.get("total_price"));
100 } 100 }
101 101
  102 + default Long selectCountByContractId(Long contractId) {
  103 + return selectCount(CrmReceivableDO::getContractId, contractId);
  104 + }
  105 +
102 } 106 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.util.RandomUtil;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +import java.time.LocalDateTime;
  9 +import java.util.ArrayList;
  10 +import java.util.List;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  13 +
  14 +/**
  15 + * CRM 客户分析 Mapper
  16 + *
  17 + * @author dhb52
  18 + */
  19 +@Mapper
  20 +public interface CrmStatisticsCustomerMapper {
  21 +
  22 + /**
  23 + * 新建客户数(按日期)
  24 + *
  25 + * @param reqVO 请求参数
  26 + * @return 统计数据
  27 + */
  28 + List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerCreateCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
  29 +
  30 + /**
  31 + * 成交客户数(按日期)
  32 + *
  33 + * @param reqVO 请求参数
  34 + * @return 统计数据
  35 + */
  36 + List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerDealCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
  37 +
  38 + /**
  39 + * 新建客户数(按用户)
  40 + *
  41 + * @param reqVO 请求参数
  42 + * @return 统计数据
  43 + */
  44 + List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerCreateCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
  45 +
  46 + /**
  47 + * 成交客户数(按用户)
  48 + *
  49 + * @param reqVO 请求参数@param reqVO 请求参数@param reqVO 请求参数
  50 + * @return 统计数据
  51 + */
  52 + List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerDealCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
  53 +
  54 + /**
  55 + * 合同总金额(按用户)
  56 + * @return 统计数据@return 统计数据@param reqVO 请求参数
  57 + * @return 统计数据
  58 + */
  59 + List<CrmStatisticsCustomerSummaryByUserRespVO> selectContractPriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
  60 +
  61 + /**
  62 + * 合同回款金额(按用户)
  63 + *
  64 + * @param reqVO 请求参数
  65 + * @return 统计数据
  66 + */
  67 + List<CrmStatisticsCustomerSummaryByUserRespVO> selectReceivablePriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
  68 +
  69 + /**
  70 + * 跟进次数(按日期)
  71 + *
  72 + * @param reqVO 请求参数
  73 + * @return 统计数据
  74 + */
  75 + List<CrmStatisticsFollowUpSummaryByDateRespVO> selectFollowUpRecordCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
  76 +
  77 + /**
  78 + * 跟进客户数(按日期)
  79 + *
  80 + * @param reqVO 请求参数
  81 + * @return 统计数据
  82 + */
  83 + List<CrmStatisticsFollowUpSummaryByDateRespVO> selectFollowUpCustomerCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
  84 +
  85 + /**
  86 + * 跟进次数(按用户)
  87 + *
  88 + * @param reqVO 请求参数
  89 + * @return 统计数据
  90 + */
  91 + List<CrmStatisticsFollowUpSummaryByUserRespVO> selectFollowUpRecordCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
  92 +
  93 + /**
  94 + * 跟进客户数(按用户)
  95 + *
  96 + * @param reqVO 请求参数
  97 + * @return 统计数据
  98 + */
  99 + List<CrmStatisticsFollowUpSummaryByUserRespVO> selectFollowUpCustomerCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
  100 +
  101 +
  102 + /**
  103 + * 首次合同、回款信息(用于【客户转化率】页面)
  104 + *
  105 + * @param reqVO 请求参数
  106 + * @return 统计数据
  107 + */
  108 + List<CrmStatisticsCustomerContractSummaryRespVO> selectContractSummary(CrmStatisticsCustomerReqVO reqVO);
  109 +
  110 + /**
  111 + * 跟进次数(按类型)
  112 + *
  113 + * @param reqVO 请求参数
  114 + * @return 统计数据
  115 + */
  116 + List<CrmStatisticsFollowUpSummaryByTypeRespVO> selectFollowUpRecordCountGroupByType(CrmStatisticsCustomerReqVO reqVO);
  117 +
  118 +
  119 + /**
  120 + * 进入公海客户数(按日期)
  121 + *
  122 + * @param reqVO 请求参数
  123 + * @return 统计数据
  124 + */
  125 + // TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
  126 + default List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO) {
  127 + LocalDateTime currrentDate = LocalDateTimeUtil.beginOfDay(reqVO.getTimes()[0]);
  128 + LocalDateTime endDate = LocalDateTimeUtil.endOfDay(reqVO.getTimes()[1]);
  129 + List<CrmStatisticsPoolSummaryByDateRespVO> voList = new ArrayList<>();
  130 + while (currrentDate.isBefore(endDate)) {
  131 + voList.add(new CrmStatisticsPoolSummaryByDateRespVO()
  132 + .setTime(LocalDateTimeUtil.format(currrentDate, "yyyy-MM-dd"))
  133 + .setCustomerPutCount(RandomUtil.randomInt(0, 10))
  134 + .setCustomerTakeCount(RandomUtil.randomInt(0, 10)));
  135 + currrentDate = currrentDate.plusDays(1);
  136 + }
  137 +
  138 + return voList;
  139 + }
  140 +
  141 + /**
  142 + * 公海领取客户数(按日期)
  143 + *
  144 + * @param reqVO 请求参数
  145 + * @return 统计数据
  146 + */
  147 + // TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
  148 + default List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO) {
  149 + return selectPoolCustomerPutCountByDate(reqVO);
  150 + }
  151 +
  152 + /**
  153 + * 进入公海客户数(按用户)
  154 + *
  155 + * @param reqVO 请求参数
  156 + * @return 统计数据
  157 + */
  158 + // TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
  159 + default List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO) {
  160 + return convertList(reqVO.getUserIds(), userId ->
  161 + (CrmStatisticsPoolSummaryByUserRespVO) new CrmStatisticsPoolSummaryByUserRespVO()
  162 + .setCustomerPutCount(RandomUtil.randomInt(0, 10))
  163 + .setCustomerTakeCount(RandomUtil.randomInt(0, 10))
  164 + .setOwnerUserId(userId));
  165 + }
  166 +
  167 + /**
  168 + * 公海领取客户数(按用户)
  169 + *
  170 + * @param reqVO 请求参数
  171 + * @return 统计数据
  172 + */
  173 + // TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
  174 + default List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO) {
  175 + return selectPoolCustomerPutCountByUser(reqVO);
  176 + }
  177 +
  178 + /**
  179 + * 客户成交周期(按日期)
  180 + *
  181 + * @param reqVO 请求参数
  182 + * @return 统计数据
  183 + */
  184 + List<CrmStatisticsCustomerDealCycleByDateRespVO> selectCustomerDealCycleGroupByDate(CrmStatisticsCustomerReqVO reqVO);
  185 +
  186 + /**
  187 + * 客户成交周期(按用户)
  188 + *
  189 + * @param reqVO 请求参数
  190 + * @return 统计数据
  191 + */
  192 + List<CrmStatisticsCustomerDealCycleByUserRespVO> selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO);
  193 +
  194 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsPerformanceMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
  2 +
  3 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * CRM 员工业绩分析 Mapper
  11 + *
  12 + * @author scholar
  13 + */
  14 +@Mapper
  15 +public interface CrmStatisticsPerformanceMapper {
  16 +
  17 + /**
  18 + * 员工签约合同数量
  19 + *
  20 + * @param performanceReqVO 参数
  21 + * @return 员工签约合同数量
  22 + */
  23 + List<CrmStatisticsPerformanceRespVO> selectContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
  24 +
  25 + /**
  26 + * 员工签约合同金额
  27 + *
  28 + * @param performanceReqVO 参数
  29 + * @return 员工签约合同金额
  30 + */
  31 + List<CrmStatisticsPerformanceRespVO> selectContractPricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
  32 +
  33 + /**
  34 + * 员工回款金额
  35 + *
  36 + * @param performanceReqVO 参数
  37 + * @return 员工回款金额
  38 + */
  39 + List<CrmStatisticsPerformanceRespVO> selectReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
  40 +
  41 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsPortraitMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
  2 +
  3 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.*;
  4 +import org.apache.ibatis.annotations.Mapper;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * CRM 数据画像 Mapper
  10 + *
  11 + * @author HUIHUI
  12 + */
  13 +@Mapper
  14 +public interface CrmStatisticsPortraitMapper {
  15 +
  16 + List<CrmStatisticCustomerAreaRespVO> selectSummaryListGroupByAreaId(CrmStatisticsPortraitReqVO reqVO);
  17 +
  18 + List<CrmStatisticCustomerIndustryRespVO> selectCustomerIndustryListGroupByIndustryId(CrmStatisticsPortraitReqVO reqVO);
  19 +
  20 + List<CrmStatisticCustomerSourceRespVO> selectCustomerSourceListGroupBySource(CrmStatisticsPortraitReqVO reqVO);
  21 +
  22 + List<CrmStatisticCustomerLevelRespVO> selectCustomerLevelListGroupByLevel(CrmStatisticsPortraitReqVO reqVO);
  23 +
  24 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankingMapper.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankMapper.java
1 package cn.iocoder.yudao.module.crm.dal.mysql.statistics; 1 package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
2 2
3 -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;  
4 -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO; 3 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
5 import org.apache.ibatis.annotations.Mapper; 5 import org.apache.ibatis.annotations.Mapper;
6 6
7 import java.util.List; 7 import java.util.List;
@@ -12,7 +12,7 @@ import java.util.List; @@ -12,7 +12,7 @@ import java.util.List;
12 * @author anhaohao 12 * @author anhaohao
13 */ 13 */
14 @Mapper 14 @Mapper
15 -public interface CrmStatisticsRankingMapper { 15 +public interface CrmStatisticsRankMapper {
16 16
17 /** 17 /**
18 * 查询合同金额排行榜 18 * 查询合同金额排行榜
@@ -20,7 +20,7 @@ public interface CrmStatisticsRankingMapper { @@ -20,7 +20,7 @@ public interface CrmStatisticsRankingMapper {
20 * @param rankReqVO 参数 20 * @param rankReqVO 参数
21 * @return 合同金额排行榜 21 * @return 合同金额排行榜
22 */ 22 */
23 - List<CrmStatisticsRanKRespVO> selectContractPriceRank(CrmStatisticsRankReqVO rankReqVO); 23 + List<CrmStatisticsRankRespVO> selectContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
24 24
25 /** 25 /**
26 * 查询回款金额排行榜 26 * 查询回款金额排行榜
@@ -28,7 +28,7 @@ public interface CrmStatisticsRankingMapper { @@ -28,7 +28,7 @@ public interface CrmStatisticsRankingMapper {
28 * @param rankReqVO 参数 28 * @param rankReqVO 参数
29 * @return 回款金额排行榜 29 * @return 回款金额排行榜
30 */ 30 */
31 - List<CrmStatisticsRanKRespVO> selectReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO); 31 + List<CrmStatisticsRankRespVO> selectReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
32 32
33 /** 33 /**
34 * 查询签约合同数量排行榜 34 * 查询签约合同数量排行榜
@@ -36,7 +36,7 @@ public interface CrmStatisticsRankingMapper { @@ -36,7 +36,7 @@ public interface CrmStatisticsRankingMapper {
36 * @param rankReqVO 参数 36 * @param rankReqVO 参数
37 * @return 签约合同数量排行榜 37 * @return 签约合同数量排行榜
38 */ 38 */
39 - List<CrmStatisticsRanKRespVO> selectContractCountRank(CrmStatisticsRankReqVO rankReqVO); 39 + List<CrmStatisticsRankRespVO> selectContractCountRank(CrmStatisticsRankReqVO rankReqVO);
40 40
41 /** 41 /**
42 * 查询产品销量排行榜 42 * 查询产品销量排行榜
@@ -44,7 +44,7 @@ public interface CrmStatisticsRankingMapper { @@ -44,7 +44,7 @@ public interface CrmStatisticsRankingMapper {
44 * @param rankReqVO 参数 44 * @param rankReqVO 参数
45 * @return 产品销量排行榜 45 * @return 产品销量排行榜
46 */ 46 */
47 - List<CrmStatisticsRanKRespVO> selectProductSalesRank(CrmStatisticsRankReqVO rankReqVO); 47 + List<CrmStatisticsRankRespVO> selectProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
48 48
49 /** 49 /**
50 * 查询新增客户数排行榜 50 * 查询新增客户数排行榜
@@ -52,7 +52,7 @@ public interface CrmStatisticsRankingMapper { @@ -52,7 +52,7 @@ public interface CrmStatisticsRankingMapper {
52 * @param rankReqVO 参数 52 * @param rankReqVO 参数
53 * @return 新增客户数排行榜 53 * @return 新增客户数排行榜
54 */ 54 */
55 - List<CrmStatisticsRanKRespVO> selectCustomerCountRank(CrmStatisticsRankReqVO rankReqVO); 55 + List<CrmStatisticsRankRespVO> selectCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
56 56
57 /** 57 /**
58 * 查询联系人数量排行榜 58 * 查询联系人数量排行榜
@@ -60,7 +60,7 @@ public interface CrmStatisticsRankingMapper { @@ -60,7 +60,7 @@ public interface CrmStatisticsRankingMapper {
60 * @param rankReqVO 参数 60 * @param rankReqVO 参数
61 * @return 联系人数量排行榜 61 * @return 联系人数量排行榜
62 */ 62 */
63 - List<CrmStatisticsRanKRespVO> selectContactsCountRank(CrmStatisticsRankReqVO rankReqVO); 63 + List<CrmStatisticsRankRespVO> selectContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
64 64
65 /** 65 /**
66 * 查询跟进次数排行榜 66 * 查询跟进次数排行榜
@@ -68,7 +68,7 @@ public interface CrmStatisticsRankingMapper { @@ -68,7 +68,7 @@ public interface CrmStatisticsRankingMapper {
68 * @param rankReqVO 参数 68 * @param rankReqVO 参数
69 * @return 跟进次数排行榜 69 * @return 跟进次数排行榜
70 */ 70 */
71 - List<CrmStatisticsRanKRespVO> selectFollowCountRank(CrmStatisticsRankReqVO rankReqVO); 71 + List<CrmStatisticsRankRespVO> selectFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
72 72
73 /** 73 /**
74 * 查询跟进客户数排行榜 74 * 查询跟进客户数排行榜
@@ -76,6 +76,6 @@ public interface CrmStatisticsRankingMapper { @@ -76,6 +76,6 @@ public interface CrmStatisticsRankingMapper {
76 * @param rankReqVO 参数 76 * @param rankReqVO 参数
77 * @return 跟进客户数排行榜 77 * @return 跟进客户数排行榜
78 */ 78 */
79 - List<CrmStatisticsRanKRespVO> selectFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO); 79 + List<CrmStatisticsRankRespVO> selectFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
80 80
81 } 81 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/excel/core/AreaExcelColumnSelectFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.excel.core;
  2 +
  3 +import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction;
  4 +import cn.iocoder.yudao.framework.ip.core.Area;
  5 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
  6 +import org.springframework.stereotype.Service;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * 地区下拉框数据源的 {@link ExcelColumnSelectFunction} 实现类
  12 + *
  13 + * @author HUIHUI
  14 + */
  15 +@Service
  16 +public class AreaExcelColumnSelectFunction implements ExcelColumnSelectFunction {
  17 +
  18 + public static final String NAME = "getCrmAreaNameList"; // 防止和别的模块重名
  19 +
  20 + @Override
  21 + public String getName() {
  22 + return NAME;
  23 + }
  24 +
  25 + @Override
  26 + public List<String> getOptions() {
  27 + // 获取地区下拉数据
  28 + // TODO @puhui999:嘿嘿,这里改成省份、城市、区域,三个选项,难度大么?
  29 + Area area = AreaUtils.getArea(Area.ID_CHINA);
  30 + return AreaUtils.getAreaNodePathList(area.getChildren());
  31 + }
  32 +
  33 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/excel/package-info.java 0 → 100644
  1 +/**
  2 + * crm 模块的 excel 拓展封装
  3 + */
  4 +package cn.iocoder.yudao.module.crm.framework.excel;
0 \ No newline at end of file 5 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java
  1 +/**
  2 + * crm 模块的 operatelog 拓展封装
  3 + */
1 package cn.iocoder.yudao.module.crm.framework.operatelog; 4 package cn.iocoder.yudao.module.crm.framework.operatelog;
2 \ No newline at end of file 5 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java
  1 +/**
  2 + * crm 模块的 permission 拓展封装
  3 + */
1 package cn.iocoder.yudao.module.crm.framework.permission; 4 package cn.iocoder.yudao.module.crm.framework.permission;
2 \ No newline at end of file 5 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/web/package-info.java
1 /** 1 /**
2 - * trade 模块的 web 配置 2 + * crm 模块的 web 拓展封装
3 */ 3 */
4 package cn.iocoder.yudao.module.crm.framework.web; 4 package cn.iocoder.yudao.module.crm.framework.web;
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 { @@ -46,8 +46,8 @@ public interface CrmBusinessService {
46 /** 46 /**
47 * 更新商机相关跟进信息 47 * 更新商机相关跟进信息
48 * 48 *
49 - * @param id 编号  
50 - * @param contactNextTime 下次联系时间 49 + * @param id 编号
  50 + * @param contactNextTime 下次联系时间
51 * @param contactLastContent 最后联系内容 51 * @param contactLastContent 最后联系内容
52 */ 52 */
53 void updateBusinessFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent); 53 void updateBusinessFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
@@ -55,7 +55,7 @@ public interface CrmBusinessService { @@ -55,7 +55,7 @@ public interface CrmBusinessService {
55 /** 55 /**
56 * 更新商机的下次联系时间 56 * 更新商机的下次联系时间
57 * 57 *
58 - * @param ids 编号数组 58 + * @param ids 编号数组
59 * @param contactNextTime 下次联系时间 59 * @param contactNextTime 下次联系时间
60 */ 60 */
61 void updateBusinessContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime); 61 void updateBusinessContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime);
@@ -185,4 +185,13 @@ public interface CrmBusinessService { @@ -185,4 +185,13 @@ public interface CrmBusinessService {
185 return status.getName(); 185 return status.getName();
186 } 186 }
187 187
  188 + /**
  189 + * 获得商机列表
  190 + *
  191 + * @param customerId 客户编号
  192 + * @param ownerUserId 负责人编号
  193 + * @return 商机列表
  194 + */
  195 + List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
  196 +
188 } 197 }
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 { @@ -88,7 +88,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
88 success = CRM_BUSINESS_CREATE_SUCCESS) 88 success = CRM_BUSINESS_CREATE_SUCCESS)
89 public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) { 89 public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
90 // 1.1 校验产品项的有效性 90 // 1.1 校验产品项的有效性
91 - List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getProducts()); 91 + List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getBusinessProducts());
92 // 1.2 校验关联字段 92 // 1.2 校验关联字段
93 validateRelationDataExists(createReqVO); 93 validateRelationDataExists(createReqVO);
94 94
@@ -129,7 +129,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -129,7 +129,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
129 // 1.1 校验存在 129 // 1.1 校验存在
130 CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId()); 130 CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
131 // 1.2 校验产品项的有效性 131 // 1.2 校验产品项的有效性
132 - List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts()); 132 + List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getBusinessProducts());
133 // 1.3 校验关联字段 133 // 1.3 校验关联字段
134 validateRelationDataExists(updateReqVO); 134 validateRelationDataExists(updateReqVO);
135 135
@@ -202,9 +202,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -202,9 +202,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
202 } 202 }
203 } 203 }
204 204
205 - private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.Product> list) { 205 + private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.BusinessProduct> list) {
206 // 1. 校验产品存在 206 // 1. 校验产品存在
207 - productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.Product::getProductId)); 207 + productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.BusinessProduct::getProductId));
208 // 2. 转化为 CrmBusinessProductDO 列表 208 // 2. 转化为 CrmBusinessProductDO 列表
209 return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class, 209 return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class,
210 item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount())))); 210 item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount()))));
@@ -234,7 +234,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -234,7 +234,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
234 } 234 }
235 // 1.4 校验是不是状态没变更 235 // 1.4 校验是不是状态没变更
236 if ((reqVO.getStatusId() != null && reqVO.getStatusId().equals(business.getStatusId())) 236 if ((reqVO.getStatusId() != null && reqVO.getStatusId().equals(business.getStatusId()))
237 - || (reqVO.getEndStatus() != null && reqVO.getEndStatus().equals(business.getEndStatus()))) { 237 + || (reqVO.getEndStatus() != null && reqVO.getEndStatus().equals(business.getEndStatus()))) {
238 throw exception(BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS); 238 throw exception(BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS);
239 } 239 }
240 240
@@ -301,7 +301,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -301,7 +301,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
301 301
302 // 2.1 数据权限转移 302 // 2.1 数据权限转移
303 permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_BUSINESS.getType(), 303 permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_BUSINESS.getType(),
304 - reqVO.getNewOwnerUserId(), reqVO.getId(), CrmPermissionLevelEnum.OWNER.getLevel())); 304 + reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
305 // 2.2 设置新的负责人 305 // 2.2 设置新的负责人
306 businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); 306 businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
307 307
@@ -370,4 +370,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -370,4 +370,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
370 return businessMapper.selectCountByStatusTypeId(statusTypeId); 370 return businessMapper.selectCountByStatusTypeId(statusTypeId);
371 } 371 }
372 372
  373 + @Override
  374 + public List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
  375 + return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
  376 + }
  377 +
373 } 378 }
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 { @@ -75,8 +75,8 @@ public interface CrmContactService {
75 /** 75 /**
76 * 更新联系人的下次联系时间 76 * 更新联系人的下次联系时间
77 * 77 *
78 - * @param ids 编号数组  
79 - * @param contactNextTime 下次联系时间 78 + * @param ids 编号数组
  79 + * @param contactNextTime 下次联系时间
80 */ 80 */
81 void updateContactContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime); 81 void updateContactContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime);
82 82
@@ -160,4 +160,13 @@ public interface CrmContactService { @@ -160,4 +160,13 @@ public interface CrmContactService {
160 */ 160 */
161 Long getContactCountByCustomerId(Long customerId); 161 Long getContactCountByCustomerId(Long customerId);
162 162
  163 + /**
  164 + * 获得联系人列表
  165 + *
  166 + * @param customerId 客户编号
  167 + * @param ownerUserId 负责人编号
  168 + * @return 联系人列表
  169 + */
  170 + List<CrmContactDO> getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
  171 +
163 } 172 }
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 { @@ -298,4 +298,9 @@ public class CrmContactServiceImpl implements CrmContactService {
298 return contactMapper.selectCount(CrmContactDO::getCustomerId, customerId); 298 return contactMapper.selectCount(CrmContactDO::getCustomerId, customerId);
299 } 299 }
300 300
  301 + @Override
  302 + public List<CrmContactDO> getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
  303 + return contactMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
  304 + }
  305 +
301 } 306 }
302 \ No newline at end of file 307 \ No newline at end of file
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 { @@ -58,8 +58,8 @@ public interface CrmContractService {
58 /** 58 /**
59 * 更新合同相关的更进信息 59 * 更新合同相关的更进信息
60 * 60 *
61 - * @param id 合同编号  
62 - * @param contactNextTime 下次联系时间 61 + * @param id 合同编号
  62 + * @param contactNextTime 下次联系时间
63 * @param contactLastContent 最后联系内容 63 * @param contactLastContent 最后联系内容
64 */ 64 */
65 void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent); 65 void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
@@ -75,7 +75,7 @@ public interface CrmContractService { @@ -75,7 +75,7 @@ public interface CrmContractService {
75 /** 75 /**
76 * 更新合同流程审批结果 76 * 更新合同流程审批结果
77 * 77 *
78 - * @param id 合同编号 78 + * @param id 合同编号
79 * @param bpmResult BPM 审批结果 79 * @param bpmResult BPM 审批结果
80 */ 80 */
81 void updateContractAuditStatus(Long id, Integer bpmResult); 81 void updateContractAuditStatus(Long id, Integer bpmResult);
@@ -193,4 +193,13 @@ public interface CrmContractService { @@ -193,4 +193,13 @@ public interface CrmContractService {
193 */ 193 */
194 Long getRemindContractCount(Long userId); 194 Long getRemindContractCount(Long userId);
195 195
  196 + /**
  197 + * 获得合同列表
  198 + *
  199 + * @param customerId 客户编号
  200 + * @param ownerUserId 负责人编号
  201 + * @return 合同列表
  202 + */
  203 + List<CrmContractDO> getContractListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
  204 +
196 } 205 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -30,12 +30,14 @@ import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; @@ -30,12 +30,14 @@ import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
30 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; 30 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
31 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; 31 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
32 import cn.iocoder.yudao.module.crm.service.product.CrmProductService; 32 import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
  33 +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
33 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 34 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
34 import com.mzt.logapi.context.LogRecordContext; 35 import com.mzt.logapi.context.LogRecordContext;
35 import com.mzt.logapi.service.impl.DiffParseFunction; 36 import com.mzt.logapi.service.impl.DiffParseFunction;
36 import com.mzt.logapi.starter.annotation.LogRecord; 37 import com.mzt.logapi.starter.annotation.LogRecord;
37 import jakarta.annotation.Resource; 38 import jakarta.annotation.Resource;
38 import lombok.extern.slf4j.Slf4j; 39 import lombok.extern.slf4j.Slf4j;
  40 +import org.springframework.context.annotation.Lazy;
39 import org.springframework.stereotype.Service; 41 import org.springframework.stereotype.Service;
40 import org.springframework.transaction.annotation.Transactional; 42 import org.springframework.transaction.annotation.Transactional;
41 import org.springframework.validation.annotation.Validated; 43 import org.springframework.validation.annotation.Validated;
@@ -86,7 +88,9 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -86,7 +88,9 @@ public class CrmContractServiceImpl implements CrmContractService {
86 private CrmContactService contactService; 88 private CrmContactService contactService;
87 @Resource 89 @Resource
88 private CrmContractConfigService contractConfigService; 90 private CrmContractConfigService contractConfigService;
89 - 91 + @Resource
  92 + @Lazy // 延迟加载,避免循环依赖
  93 + private CrmReceivableService receivableService;
90 @Resource 94 @Resource
91 private AdminUserApi adminUserApi; 95 private AdminUserApi adminUserApi;
92 @Resource 96 @Resource
@@ -222,15 +226,19 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -222,15 +226,19 @@ public class CrmContractServiceImpl implements CrmContractService {
222 success = CRM_CONTRACT_DELETE_SUCCESS) 226 success = CRM_CONTRACT_DELETE_SUCCESS)
223 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) 227 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
224 public void deleteContract(Long id) { 228 public void deleteContract(Long id) {
225 - // TODO @puhui999:如果被 CrmReceivableDO 所使用,则不允许删除  
226 - // 校验存在 229 + // 1.1 校验存在
227 CrmContractDO contract = validateContractExists(id); 230 CrmContractDO contract = validateContractExists(id);
228 - // 删除 231 + // 1.2 如果被 CrmReceivableDO 所使用,则不允许删除
  232 + if (receivableService.getReceivableCountByContractId(contract.getId()) > 0) {
  233 + throw exception(CONTRACT_DELETE_FAIL);
  234 + }
  235 +
  236 + // 2.1 删除合同
229 contractMapper.deleteById(id); 237 contractMapper.deleteById(id);
230 - // 删除数据权限 238 + // 2.2 删除数据权限
231 crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTRACT.getType(), id); 239 crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTRACT.getType(), id);
232 240
233 - // 记录操作日志上下文 241 + // 3. 记录操作日志上下文
234 LogRecordContext.putVariable("contractName", contract.getName()); 242 LogRecordContext.putVariable("contractName", contract.getName());
235 } 243 }
236 244
@@ -399,4 +407,9 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -399,4 +407,9 @@ public class CrmContractServiceImpl implements CrmContractService {
399 return contractMapper.selectCountByRemind(userId, config); 407 return contractMapper.selectCountByRemind(userId, config);
400 } 408 }
401 409
  410 + @Override
  411 + public List<CrmContractDO> getContractListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
  412 + return contractMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
  413 + }
  414 +
402 } 415 }
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; @@ -9,7 +9,13 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
9 import cn.iocoder.yudao.framework.common.pojo.PageResult; 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
10 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 10 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  12 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
  13 +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
  14 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
12 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; 15 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
  16 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  17 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
  18 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
13 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 19 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
14 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; 20 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; 21 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
@@ -201,7 +207,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @@ -201,7 +207,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
201 CrmCustomerDO customer = validateCustomerExists(reqVO.getId()); 207 CrmCustomerDO customer = validateCustomerExists(reqVO.getId());
202 // 1.2 校验拥有客户是否到达上限 208 // 1.2 校验拥有客户是否到达上限
203 validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1); 209 validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1);
204 -  
205 // 2.1 数据权限转移 210 // 2.1 数据权限转移
206 permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(), 211 permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
207 reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel())); 212 reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
@@ -209,10 +214,45 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @@ -209,10 +214,45 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
209 customerMapper.updateById(new CrmCustomerDO().setId(reqVO.getId()) 214 customerMapper.updateById(new CrmCustomerDO().setId(reqVO.getId())
210 .setOwnerUserId(reqVO.getNewOwnerUserId()).setOwnerTime(LocalDateTime.now())); 215 .setOwnerUserId(reqVO.getNewOwnerUserId()).setOwnerTime(LocalDateTime.now()));
211 216
  217 + // 2.3 同时转移
  218 + if (CollUtil.isNotEmpty(reqVO.getToBizTypes())) {
  219 + transfer(reqVO, userId);
  220 + }
  221 +
212 // 3. 记录转移日志 222 // 3. 记录转移日志
213 LogRecordContext.putVariable("customer", customer); 223 LogRecordContext.putVariable("customer", customer);
214 } 224 }
215 225
  226 + /**
  227 + * 转移客户时,需要额外有【联系人】【商机】【合同】
  228 + *
  229 + * @param reqVO 请求
  230 + * @param userId 用户编号
  231 + */
  232 + private void transfer(CrmCustomerTransferReqVO reqVO, Long userId) {
  233 + if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTACT.getType())) {
  234 + List<CrmContactDO> contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getId(), userId);
  235 + contactList.forEach(item -> {
  236 + contactService.transferContact(new CrmContactTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
  237 + reqVO.getOldOwnerPermissionLevel()), userId);
  238 + });
  239 + }
  240 + if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_BUSINESS.getType())) {
  241 + List<CrmBusinessDO> businessList = businessService.getBusinessListByCustomerIdOwnerUserId(reqVO.getId(), userId);
  242 + businessList.forEach(item -> {
  243 + businessService.transferBusiness(new CrmBusinessTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
  244 + reqVO.getOldOwnerPermissionLevel()), userId);
  245 + });
  246 + }
  247 + if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTRACT.getType())) {
  248 + List<CrmContractDO> contractList = contractService.getContractListByCustomerIdOwnerUserId(reqVO.getId(), userId);
  249 + contractList.forEach(item -> {
  250 + contractService.transferContract(new CrmContractTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
  251 + reqVO.getOldOwnerPermissionLevel()), userId);
  252 + });
  253 + }
  254 + }
  255 +
216 @Override 256 @Override
217 @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_LOCK_SUB_TYPE, bizNo = "{{#lockReqVO.id}}", 257 @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_LOCK_SUB_TYPE, bizNo = "{{#lockReqVO.id}}",
218 success = CRM_CUSTOMER_LOCK_SUCCESS) 258 success = CRM_CUSTOMER_LOCK_SUCCESS)
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java
1 package cn.iocoder.yudao.module.crm.service.permission; 1 package cn.iocoder.yudao.module.crm.service.permission;
2 2
3 3
  4 +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
4 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; 5 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
5 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; 6 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
6 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 7 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
@@ -22,6 +23,14 @@ public interface CrmPermissionService { @@ -22,6 +23,14 @@ public interface CrmPermissionService {
22 /** 23 /**
23 * 创建数据权限 24 * 创建数据权限
24 * 25 *
  26 + * @param reqVO 创建信息
  27 + * @param userId 用户编号
  28 + */
  29 + void createPermission(CrmPermissionSaveReqVO reqVO, Long userId);
  30 +
  31 + /**
  32 + * 创建数据权限
  33 + *
25 * @param createReqBO 创建信息 34 * @param createReqBO 创建信息
26 * @return 编号 35 * @return 编号
27 */ 36 */
@@ -111,10 +120,10 @@ public interface CrmPermissionService { @@ -111,10 +120,10 @@ public interface CrmPermissionService {
111 /** 120 /**
112 * 校验是否有指定数据的操作权限 121 * 校验是否有指定数据的操作权限
113 * 122 *
114 - * @param bizType 数据类型,关联 {@link CrmBizTypeEnum}  
115 - * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()  
116 - * @param userId 用户编号  
117 - * @param level 权限级别 123 + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum}
  124 + * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
  125 + * @param userId 用户编号
  126 + * @param level 权限级别
118 * @return 是否有权限 127 * @return 是否有权限
119 */ 128 */
120 boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum level); 129 boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum level);
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; @@ -4,28 +4,34 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
7 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; 8 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
  9 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  10 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
  11 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
8 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; 12 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
9 import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper; 13 import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper;
10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 14 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 15 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  16 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
  17 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
  18 +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
  19 +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
12 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; 20 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
13 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; 21 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
14 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; 22 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
15 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 23 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  24 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
16 import jakarta.annotation.Resource; 25 import jakarta.annotation.Resource;
  26 +import org.springframework.context.annotation.Lazy;
17 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
18 import org.springframework.transaction.annotation.Transactional; 28 import org.springframework.transaction.annotation.Transactional;
19 import org.springframework.validation.annotation.Validated; 29 import org.springframework.validation.annotation.Validated;
20 30
21 -import java.util.Collection;  
22 -import java.util.Collections;  
23 -import java.util.List;  
24 -import java.util.Set; 31 +import java.util.*;
25 32
26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
27 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;  
28 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
29 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; 35 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
30 import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner; 36 import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner;
31 37
@@ -40,18 +46,119 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { @@ -40,18 +46,119 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
40 46
41 @Resource 47 @Resource
42 private CrmPermissionMapper permissionMapper; 48 private CrmPermissionMapper permissionMapper;
43 - 49 + @Resource
  50 + @Lazy // 解决依赖循环
  51 + private CrmContactService contactService;
  52 + @Resource
  53 + @Lazy // 解决依赖循环
  54 + private CrmBusinessService businessService;
  55 + @Resource
  56 + @Lazy // 解决依赖循环
  57 + private CrmContractService contractService;
44 @Resource 58 @Resource
45 private AdminUserApi adminUserApi; 59 private AdminUserApi adminUserApi;
46 60
  61 +
  62 + @Override
  63 + @Transactional(rollbackFor = Exception.class)
  64 + @CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER)
  65 + public void createPermission(CrmPermissionSaveReqVO reqVO, Long userId) {
  66 + // 1. 创建数据权限
  67 + createPermission0(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class));
  68 +
  69 + // 2. 处理【同时添加至】的权限
  70 + if (CollUtil.isEmpty(reqVO.getToBizTypes())) {
  71 + return;
  72 + }
  73 + List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();
  74 + buildContactPermissions(reqVO, userId, createPermissions);
  75 + buildBusinessPermissions(reqVO, userId, createPermissions);
  76 + buildContractPermissions(reqVO, userId, createPermissions);
  77 + if (CollUtil.isEmpty(createPermissions)) {
  78 + return;
  79 + }
  80 + createPermissionBatch(createPermissions);
  81 + }
  82 +
  83 + /**
  84 + * 处理同时添加至联系人
  85 + *
  86 + * @param reqVO 请求
  87 + * @param userId 操作人
  88 + * @param createPermissions 待添加权限列表
  89 + */
  90 + private void buildContactPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List<CrmPermissionCreateReqBO> createPermissions) {
  91 + // 1. 校验是否被同时添加
  92 + Integer type = CrmBizTypeEnum.CRM_CONTACT.getType();
  93 + if (!reqVO.getToBizTypes().contains(type)) {
  94 + return;
  95 + }
  96 + // 2. 添加数据权限
  97 + List<CrmContactDO> contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
  98 + contactList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
  99 + }
  100 +
  101 + /**
  102 + * 处理同时添加至商机
  103 + *
  104 + * @param reqVO 请求
  105 + * @param userId 操作人
  106 + * @param createPermissions 待添加权限列表
  107 + */
  108 + private void buildBusinessPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List<CrmPermissionCreateReqBO> createPermissions) {
  109 + // 1. 校验是否被同时添加
  110 + Integer type = CrmBizTypeEnum.CRM_BUSINESS.getType();
  111 + if (!reqVO.getToBizTypes().contains(type)) {
  112 + return;
  113 + }
  114 + // 2. 添加数据权限
  115 + List<CrmBusinessDO> businessList = businessService.getBusinessListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
  116 + businessList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
  117 + }
  118 +
  119 + /**
  120 + * 处理同时添加至合同
  121 + *
  122 + * @param reqVO 请求
  123 + * @param userId 操作人
  124 + * @param createPermissions 待添加权限列表
  125 + */
  126 + private void buildContractPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List<CrmPermissionCreateReqBO> createPermissions) {
  127 + // 1. 校验是否被同时添加
  128 + Integer type = CrmBizTypeEnum.CRM_CONTRACT.getType();
  129 + if (!reqVO.getToBizTypes().contains(type)) {
  130 + return;
  131 + }
  132 + // 2. 添加数据权限
  133 + List<CrmContractDO> contractList = contractService.getContractListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
  134 + contractList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
  135 + }
  136 +
  137 + private void createBizTypePermissions(CrmPermissionSaveReqVO reqVO, Integer type, Long bizId, String name,
  138 + List<CrmPermissionCreateReqBO> createPermissions) {
  139 + AdminUserRespDTO user = adminUserApi.getUser(reqVO.getUserId());
  140 + // 1. 需要考虑,被添加人,是不是应该有对应的权限了;
  141 + CrmPermissionDO permission = hasAnyPermission(type, bizId, reqVO.getUserId());
  142 + if (ObjUtil.isNotNull(permission)) {
  143 + throw exception(CRM_PERMISSION_CREATE_FAIL_EXISTS, user.getNickname(), CrmBizTypeEnum.getNameByType(type),
  144 + name, CrmPermissionLevelEnum.getNameByLevel(permission.getLevel()));
  145 + }
  146 + // 2. 添加数据权限
  147 + createPermissions.add(new CrmPermissionCreateReqBO().setBizType(type)
  148 + .setBizId(bizId).setUserId(reqVO.getUserId()).setLevel(reqVO.getLevel()));
  149 + }
  150 +
47 @Override 151 @Override
48 @Transactional(rollbackFor = Exception.class) 152 @Transactional(rollbackFor = Exception.class)
49 public Long createPermission(CrmPermissionCreateReqBO createReqBO) { 153 public Long createPermission(CrmPermissionCreateReqBO createReqBO) {
  154 + return createPermission0(createReqBO);
  155 + }
  156 +
  157 + private Long createPermission0(CrmPermissionCreateReqBO createReqBO) {
50 validatePermissionNotExists(Collections.singletonList(createReqBO)); 158 validatePermissionNotExists(Collections.singletonList(createReqBO));
51 // 1. 校验用户是否存在 159 // 1. 校验用户是否存在
52 adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId())); 160 adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId()));
53 -  
54 - // 2. 创建 161 + // 2. 插入权限
55 CrmPermissionDO permission = BeanUtils.toBean(createReqBO, CrmPermissionDO.class); 162 CrmPermissionDO permission = BeanUtils.toBean(createReqBO, CrmPermissionDO.class);
56 permissionMapper.insert(permission); 163 permissionMapper.insert(permission);
57 return permission.getId(); 164 return permission.getId();
@@ -170,7 +277,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { @@ -170,7 +277,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
170 throw exception(CRM_PERMISSION_DELETE_FAIL); 277 throw exception(CRM_PERMISSION_DELETE_FAIL);
171 } 278 }
172 // 校验操作人是否为负责人 279 // 校验操作人是否为负责人
173 - CrmPermissionDO permission = permissionMapper.selectByBizIdAndUserId(permissions.get(0).getBizId(), userId); 280 + CrmPermissionDO permission = permissionMapper.selectByBizAndUserId(permissions.get(0).getBizType(), permissions.get(0).getBizId(), userId);
174 if (permission == null) { 281 if (permission == null) {
175 throw exception(CRM_PERMISSION_DELETE_DENIED); 282 throw exception(CRM_PERMISSION_DELETE_DENIED);
176 } 283 }
@@ -220,4 +327,9 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { @@ -220,4 +327,9 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
220 ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), level.getLevel())); 327 ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), level.getLevel()));
221 } 328 }
222 329
  330 + public CrmPermissionDO hasAnyPermission(Integer bizType, Long bizId, Long userId) {
  331 + List<CrmPermissionDO> permissionList = permissionMapper.selectByBizTypeAndBizId(bizType, bizId);
  332 + return findFirst(permissionList, permission -> ObjUtil.equal(permission.getUserId(), userId));
  333 + }
  334 +
223 } 335 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
@@ -20,7 +20,6 @@ import com.mzt.logapi.context.LogRecordContext; @@ -20,7 +20,6 @@ import com.mzt.logapi.context.LogRecordContext;
20 import com.mzt.logapi.service.impl.DiffParseFunction; 20 import com.mzt.logapi.service.impl.DiffParseFunction;
21 import com.mzt.logapi.starter.annotation.LogRecord; 21 import com.mzt.logapi.starter.annotation.LogRecord;
22 import jakarta.annotation.Resource; 22 import jakarta.annotation.Resource;
23 -import org.springframework.context.annotation.Lazy;  
24 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
25 import org.springframework.transaction.annotation.Transactional; 24 import org.springframework.transaction.annotation.Transactional;
26 import org.springframework.validation.annotation.Validated; 25 import org.springframework.validation.annotation.Validated;
@@ -47,9 +46,6 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { @@ -47,9 +46,6 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
47 private CrmReceivablePlanMapper receivablePlanMapper; 46 private CrmReceivablePlanMapper receivablePlanMapper;
48 47
49 @Resource 48 @Resource
50 - @Lazy // 延迟加载,避免循环依赖  
51 - private CrmReceivableService receivableService;  
52 - @Resource  
53 private CrmContractService contractService; 49 private CrmContractService contractService;
54 @Resource 50 @Resource
55 private CrmPermissionService permissionService; 51 private CrmPermissionService permissionService;
@@ -144,7 +140,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { @@ -144,7 +140,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
144 // 2. 删除 140 // 2. 删除
145 receivablePlanMapper.deleteById(id); 141 receivablePlanMapper.deleteById(id);
146 // 3. 删除数据权限 142 // 3. 删除数据权限
147 - permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); 143 + permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), id);
148 144
149 // 4. 记录操作日志上下文 145 // 4. 记录操作日志上下文
150 LogRecordContext.putVariable("receivablePlan", receivablePlan); 146 LogRecordContext.putVariable("receivablePlan", receivablePlan);
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 { @@ -122,4 +122,12 @@ public interface CrmReceivableService {
122 */ 122 */
123 Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds); 123 Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds);
124 124
  125 + /**
  126 + * 根据合同编号查询回款数量
  127 + *
  128 + * @param contractId 合同编号
  129 + * @return 回款数量
  130 + */
  131 + Long getReceivableCountByContractId(Long contractId);
  132 +
125 } 133 }
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.transaction.annotation.Transactional; @@ -37,10 +37,7 @@ import org.springframework.transaction.annotation.Transactional;
37 import org.springframework.validation.annotation.Validated; 37 import org.springframework.validation.annotation.Validated;
38 38
39 import java.math.BigDecimal; 39 import java.math.BigDecimal;
40 -import java.util.Arrays;  
41 -import java.util.Collection;  
42 -import java.util.List;  
43 -import java.util.Map; 40 +import java.util.*;
44 41
45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
46 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; 43 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
@@ -81,7 +78,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -81,7 +78,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
81 @Resource 78 @Resource
82 private BpmProcessInstanceApi bpmProcessInstanceApi; 79 private BpmProcessInstanceApi bpmProcessInstanceApi;
83 80
84 - // TODO @puhui999:操作日志没记录上  
85 @Override 81 @Override
86 @Transactional(rollbackFor = Exception.class) 82 @Transactional(rollbackFor = Exception.class)
87 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}", 83 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}",
@@ -115,6 +111,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -115,6 +111,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
115 111
116 // 5. 记录操作日志上下文 112 // 5. 记录操作日志上下文
117 LogRecordContext.putVariable("receivable", receivable); 113 LogRecordContext.putVariable("receivable", receivable);
  114 + LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
118 return receivable.getId(); 115 return receivable.getId();
119 } 116 }
120 117
@@ -156,7 +153,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -156,7 +153,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
156 } 153 }
157 } 154 }
158 155
159 - // TODO @puhui999:操作日志没记录上  
160 @Override 156 @Override
161 @Transactional(rollbackFor = Exception.class) 157 @Transactional(rollbackFor = Exception.class)
162 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", 158 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
@@ -164,11 +160,14 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -164,11 +160,14 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
164 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) 160 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
165 public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) { 161 public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) {
166 Assert.notNull(updateReqVO.getId(), "回款编号不能为空"); 162 Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
167 - // 1.1 校验可回款金额超过上限  
168 - validateReceivablePriceExceedsLimit(updateReqVO);  
169 updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段 163 updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
170 - // 1.2 校验存在 164 + // 1.1 校验存在
171 CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId()); 165 CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());
  166 + updateReqVO.setOwnerUserId(receivable.getOwnerUserId()).setCustomerId(receivable.getCustomerId())
  167 + .setContractId(receivable.getContractId()).setPlanId(receivable.getPlanId()); // 设置已存在的值
  168 + // 1.2 校验可回款金额超过上限
  169 + validateReceivablePriceExceedsLimit(updateReqVO);
  170 +
172 // 1.3 只有草稿、审批中,可以编辑; 171 // 1.3 只有草稿、审批中,可以编辑;
173 if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), 172 if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
174 CrmAuditStatusEnum.PROCESS.getStatus())) { 173 CrmAuditStatusEnum.PROCESS.getStatus())) {
@@ -180,8 +179,17 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -180,8 +179,17 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
180 receivableMapper.updateById(updateObj); 179 receivableMapper.updateById(updateObj);
181 180
182 // 3. 记录操作日志上下文 181 // 3. 记录操作日志上下文
183 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));  
184 LogRecordContext.putVariable("receivable", receivable); 182 LogRecordContext.putVariable("receivable", receivable);
  183 + LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
  184 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
  185 + }
  186 +
  187 + private Integer getReceivablePeriod(Long planId) {
  188 + if (Objects.isNull(planId)) {
  189 + return null;
  190 + }
  191 + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(planId);
  192 + return receivablePlan.getPeriod();
185 } 193 }
186 194
187 @Override 195 @Override
@@ -212,15 +220,19 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -212,15 +220,19 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
212 if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) { 220 if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
213 throw exception(RECEIVABLE_DELETE_FAIL); 221 throw exception(RECEIVABLE_DELETE_FAIL);
214 } 222 }
215 - // TODO @puhui999:审批通过时,不允许删除; 223 + // 1.3 审批通过时,不允许删除
  224 + if (ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
  225 + throw exception(RECEIVABLE_DELETE_FAIL_IS_APPROVE);
  226 + }
216 227
217 - // 2. 删除 228 + // 2.1 删除回款
218 receivableMapper.deleteById(id); 229 receivableMapper.deleteById(id);
219 - // 3. 删除数据权限 230 + // 2.2 删除数据权限
220 permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id); 231 permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id);
221 232
222 - // 4. 记录操作日志上下文 233 + // 3. 记录操作日志上下文
223 LogRecordContext.putVariable("receivable", receivable); 234 LogRecordContext.putVariable("receivable", receivable);
  235 + LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
224 } 236 }
225 237
226 @Override 238 @Override
@@ -289,4 +301,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -289,4 +301,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
289 return receivableMapper.selectReceivablePriceMapByContractId(contractIds); 301 return receivableMapper.selectReceivablePriceMapByContractId(contractIds);
290 } 302 }
291 303
  304 + @Override
  305 + public Long getReceivableCountByContractId(Long contractId) {
  306 + return receivableMapper.selectCountByContractId(contractId);
  307 + }
  308 +
292 } 309 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.statistics;
  2 +
  3 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * CRM 客户分析 Service 接口
  9 + *
  10 + * @author dhb52
  11 + */
  12 +public interface CrmStatisticsCustomerService {
  13 +
  14 + /**
  15 + * 总量分析(按日期)
  16 + *
  17 + * @param reqVO 请求参数
  18 + * @return 统计数据
  19 + */
  20 + List<CrmStatisticsCustomerSummaryByDateRespVO> getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
  21 +
  22 + /**
  23 + * 总量分析(按用户)
  24 + *
  25 + * @param reqVO 请求参数
  26 + * @return 统计数据
  27 + */
  28 + List<CrmStatisticsCustomerSummaryByUserRespVO> getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
  29 +
  30 + /**
  31 + * 跟进次数分析(按日期)
  32 + *
  33 + * @param reqVO 请求参数
  34 + * @return 统计数据
  35 + */
  36 + List<CrmStatisticsFollowUpSummaryByDateRespVO> getFollowUpSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
  37 +
  38 + /**
  39 + * 跟进次数分析(按用户)
  40 + *
  41 + * @param reqVO 请求参数
  42 + * @return 统计数据
  43 + */
  44 + List<CrmStatisticsFollowUpSummaryByUserRespVO> getFollowUpSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
  45 +
  46 + /**
  47 + * 客户跟进次数分析(按类型)
  48 + *
  49 + * @param reqVO 请求参数
  50 + * @return 统计数据
  51 + */
  52 + List<CrmStatisticsFollowUpSummaryByTypeRespVO> getFollowUpSummaryByType(CrmStatisticsCustomerReqVO reqVO);
  53 +
  54 + /**
  55 + * 获取客户的首次合同、回款信息列表,用于【客户转化率】页面
  56 + *
  57 + * @param reqVO 请求参数
  58 + * @return 统计数据
  59 + */
  60 + List<CrmStatisticsCustomerContractSummaryRespVO> getContractSummary(CrmStatisticsCustomerReqVO reqVO);
  61 +
  62 + /**
  63 + * 公海客户分析(按日期)
  64 + *
  65 + * @param reqVO 请求参数
  66 + * @return 统计数据
  67 + */
  68 + List<CrmStatisticsPoolSummaryByDateRespVO> getPoolSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
  69 +
  70 + /**
  71 + * 公海客户分析(按用户)
  72 + *
  73 + * @param reqVO 请求参数
  74 + * @return 统计数据
  75 + */
  76 + List<CrmStatisticsPoolSummaryByUserRespVO> getPoolSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
  77 +
  78 + /**
  79 + * 客户成交周期(按日期)
  80 + *
  81 + * 成交周期的定义:客户 customer 在创建出来,到合同 contract 第一次成交的时间差
  82 + *
  83 + * @param reqVO 请求参数
  84 + * @return 统计数据
  85 + */
  86 + List<CrmStatisticsCustomerDealCycleByDateRespVO> getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO);
  87 +
  88 + /**
  89 + * 客户成交周期(按用户)
  90 + *
  91 + * @param reqVO 请求参数
  92 + * @return 统计数据
  93 + */
  94 + List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO);
  95 +
  96 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.statistics;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
  6 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
  8 +import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper;
  9 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  10 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  11 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  12 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  13 +import jakarta.annotation.Resource;
  14 +import org.springframework.stereotype.Service;
  15 +import org.springframework.validation.annotation.Validated;
  16 +
  17 +import java.math.BigDecimal;
  18 +import java.time.LocalDateTime;
  19 +import java.util.Collections;
  20 +import java.util.List;
  21 +import java.util.Map;
  22 +import java.util.stream.Stream;
  23 +
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  25 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
  26 +
  27 +/**
  28 + * CRM 客户分析 Service 实现类
  29 + *
  30 + * @author dhb52
  31 + */
  32 +@Service
  33 +@Validated
  34 +public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerService {
  35 +
  36 + @Resource
  37 + private CrmStatisticsCustomerMapper customerMapper;
  38 +
  39 + @Resource
  40 + private AdminUserApi adminUserApi;
  41 + @Resource
  42 + private DeptApi deptApi;
  43 +
  44 + @Override
  45 + public List<CrmStatisticsCustomerSummaryByDateRespVO> getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
  46 + // 1. 获得用户编号数组
  47 + reqVO.setUserIds(getUserIds(reqVO));
  48 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  49 + return Collections.emptyList();
  50 + }
  51 +
  52 + // 2. 按天统计,获取分项统计数据
  53 + List<CrmStatisticsCustomerSummaryByDateRespVO> customerCreateCountList = customerMapper.selectCustomerCreateCountGroupByDate(reqVO);
  54 + List<CrmStatisticsCustomerSummaryByDateRespVO> customerDealCountList = customerMapper.selectCustomerDealCountGroupByDate(reqVO);
  55 +
  56 + // 3. 按照日期间隔,合并数据
  57 + List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
  58 + return convertList(timeRanges, times -> {
  59 + Integer customerCreateCount = customerCreateCountList.stream()
  60 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
  61 + .mapToInt(CrmStatisticsCustomerSummaryByDateRespVO::getCustomerCreateCount).sum();
  62 + Integer customerDealCount = customerDealCountList.stream()
  63 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
  64 + .mapToInt(CrmStatisticsCustomerSummaryByDateRespVO::getCustomerDealCount).sum();
  65 + return new CrmStatisticsCustomerSummaryByDateRespVO()
  66 + .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
  67 + .setCustomerCreateCount(customerCreateCount).setCustomerDealCount(customerDealCount);
  68 + });
  69 + }
  70 +
  71 + @Override
  72 + public List<CrmStatisticsCustomerSummaryByUserRespVO> getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
  73 + // 1. 获得用户编号数组
  74 + reqVO.setUserIds(getUserIds(reqVO));
  75 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  76 + return Collections.emptyList();
  77 + }
  78 +
  79 + // 2. 按用户统计,获取分项统计数据
  80 + List<CrmStatisticsCustomerSummaryByUserRespVO> customerCreateCountList = customerMapper.selectCustomerCreateCountGroupByUser(reqVO);
  81 + List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCountList = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
  82 + List<CrmStatisticsCustomerSummaryByUserRespVO> contractPriceList = customerMapper.selectContractPriceGroupByUser(reqVO);
  83 + List<CrmStatisticsCustomerSummaryByUserRespVO> receivablePriceList = customerMapper.selectReceivablePriceGroupByUser(reqVO);
  84 +
  85 + // 3.1 按照用户,合并统计数据
  86 + List<CrmStatisticsCustomerSummaryByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
  87 + Integer customerCreateCount = customerCreateCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  88 + .mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerCreateCount).sum();
  89 + Integer customerDealCount = customerDealCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  90 + .mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount).sum();
  91 + BigDecimal contractPrice = contractPriceList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  92 + .reduce(BigDecimal.ZERO, (sum, vo) -> sum.add(vo.getContractPrice()), BigDecimal::add);
  93 + BigDecimal receivablePrice = receivablePriceList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  94 + .reduce(BigDecimal.ZERO, (sum, vo) -> sum.add(vo.getReceivablePrice()), BigDecimal::add);
  95 + return (CrmStatisticsCustomerSummaryByUserRespVO) new CrmStatisticsCustomerSummaryByUserRespVO()
  96 + .setCustomerCreateCount(customerCreateCount).setCustomerDealCount(customerDealCount)
  97 + .setContractPrice(contractPrice).setReceivablePrice(receivablePrice).setOwnerUserId(userId);
  98 + });
  99 + // 3.2 拼接用户信息
  100 + appendUserInfo(summaryList);
  101 + return summaryList;
  102 + }
  103 +
  104 + @Override
  105 + public List<CrmStatisticsFollowUpSummaryByDateRespVO> getFollowUpSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
  106 + // 1. 获得用户编号数组
  107 + reqVO.setUserIds(getUserIds(reqVO));
  108 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  109 + return Collections.emptyList();
  110 + }
  111 +
  112 + // 2. 按天统计,获取分项统计数据
  113 + List<CrmStatisticsFollowUpSummaryByDateRespVO> followUpRecordCountList = customerMapper.selectFollowUpRecordCountGroupByDate(reqVO);
  114 + List<CrmStatisticsFollowUpSummaryByDateRespVO> followUpCustomerCountList = customerMapper.selectFollowUpCustomerCountGroupByDate(reqVO);
  115 +
  116 + // 3. 按照时间间隔,合并统计数据
  117 + List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
  118 + return convertList(timeRanges, times -> {
  119 + Integer followUpRecordCount = followUpRecordCountList.stream()
  120 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
  121 + .mapToInt(CrmStatisticsFollowUpSummaryByDateRespVO::getFollowUpRecordCount).sum();
  122 + Integer followUpCustomerCount = followUpCustomerCountList.stream()
  123 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
  124 + .mapToInt(CrmStatisticsFollowUpSummaryByDateRespVO::getFollowUpCustomerCount).sum();
  125 + return new CrmStatisticsFollowUpSummaryByDateRespVO()
  126 + .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
  127 + .setFollowUpCustomerCount(followUpRecordCount).setFollowUpRecordCount(followUpCustomerCount);
  128 + });
  129 + }
  130 +
  131 + @Override
  132 + public List<CrmStatisticsFollowUpSummaryByUserRespVO> getFollowUpSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
  133 + // 1. 获得用户编号数组
  134 + reqVO.setUserIds(getUserIds(reqVO));
  135 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  136 + return Collections.emptyList();
  137 + }
  138 +
  139 + // 2. 按用户统计,获取分项统计数据
  140 + List<CrmStatisticsFollowUpSummaryByUserRespVO> followUpRecordCountList = customerMapper.selectFollowUpRecordCountGroupByUser(reqVO);
  141 + List<CrmStatisticsFollowUpSummaryByUserRespVO> followUpCustomerCountList = customerMapper.selectFollowUpCustomerCountGroupByUser(reqVO);
  142 +
  143 + // 3.1 按照用户,合并统计数据
  144 + List<CrmStatisticsFollowUpSummaryByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
  145 + Integer followUpRecordCount = followUpRecordCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  146 + .mapToInt(CrmStatisticsFollowUpSummaryByUserRespVO::getFollowUpRecordCount).sum();
  147 + Integer followUpCustomerCount = followUpCustomerCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  148 + .mapToInt(CrmStatisticsFollowUpSummaryByUserRespVO::getFollowUpCustomerCount).sum();
  149 + return (CrmStatisticsFollowUpSummaryByUserRespVO) new CrmStatisticsFollowUpSummaryByUserRespVO()
  150 + .setFollowUpCustomerCount(followUpRecordCount).setFollowUpRecordCount(followUpCustomerCount).setOwnerUserId(userId);
  151 + });
  152 + // 3.2 拼接用户信息
  153 + appendUserInfo(summaryList);
  154 + return summaryList;
  155 + }
  156 +
  157 + @Override
  158 + public List<CrmStatisticsFollowUpSummaryByTypeRespVO> getFollowUpSummaryByType(CrmStatisticsCustomerReqVO reqVO) {
  159 + // 1. 获得用户编号数组
  160 + reqVO.setUserIds(getUserIds(reqVO));
  161 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  162 + return Collections.emptyList();
  163 + }
  164 +
  165 + // 2. 获得跟进数据
  166 + return customerMapper.selectFollowUpRecordCountGroupByType(reqVO);
  167 + }
  168 +
  169 + @Override
  170 + public List<CrmStatisticsCustomerContractSummaryRespVO> getContractSummary(CrmStatisticsCustomerReqVO reqVO) {
  171 + // 1. 获得用户编号数组
  172 + reqVO.setUserIds(getUserIds(reqVO));
  173 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  174 + return Collections.emptyList();
  175 + }
  176 +
  177 + // 2. 按用户统计,获取统计数据
  178 + List<CrmStatisticsCustomerContractSummaryRespVO> summaryList = customerMapper.selectContractSummary(reqVO);
  179 +
  180 + // 3. 拼接信息
  181 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  182 + convertSetByFlatMap(summaryList, vo -> Stream.of(NumberUtils.parseLong(vo.getCreator()), vo.getOwnerUserId())));
  183 + summaryList.forEach(vo -> {
  184 + findAndThen(userMap, NumberUtils.parseLong(vo.getCreator()), user -> vo.setCreatorUserName(user.getNickname()));
  185 + findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname()));
  186 + });
  187 + return summaryList;
  188 + }
  189 +
  190 + @Override
  191 + public List<CrmStatisticsPoolSummaryByDateRespVO> getPoolSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
  192 + // 1. 获得用户编号数组
  193 + reqVO.setUserIds(getUserIds(reqVO));
  194 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  195 + return Collections.emptyList();
  196 + }
  197 +
  198 + // 2. 按天统计,获取分项统计数据
  199 + List<CrmStatisticsPoolSummaryByDateRespVO> customerPutCountList = customerMapper.selectPoolCustomerPutCountByDate(reqVO);
  200 + List<CrmStatisticsPoolSummaryByDateRespVO> customerTakeCountList = customerMapper.selectPoolCustomerTakeCountByDate(reqVO);
  201 +
  202 + // 3. 按照日期间隔,合并数据
  203 + List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
  204 + return convertList(timeRanges, times -> {
  205 + Integer customerPutCount = customerPutCountList.stream()
  206 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
  207 + .mapToInt(CrmStatisticsPoolSummaryByDateRespVO::getCustomerPutCount).sum();
  208 + Integer customerTakeCount = customerTakeCountList.stream()
  209 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
  210 + .mapToInt(CrmStatisticsPoolSummaryByDateRespVO::getCustomerTakeCount).sum();
  211 + return new CrmStatisticsPoolSummaryByDateRespVO()
  212 + .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
  213 + .setCustomerPutCount(customerPutCount).setCustomerTakeCount(customerTakeCount);
  214 + });
  215 + }
  216 +
  217 + @Override
  218 + public List<CrmStatisticsPoolSummaryByUserRespVO> getPoolSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
  219 + // 1. 获得用户编号数组
  220 + reqVO.setUserIds(getUserIds(reqVO));
  221 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  222 + return Collections.emptyList();
  223 + }
  224 +
  225 + // 2. 按用户统计,获取分项统计数据
  226 + List<CrmStatisticsPoolSummaryByUserRespVO> customerPutCountList = customerMapper.selectPoolCustomerPutCountByUser(reqVO);
  227 + List<CrmStatisticsPoolSummaryByUserRespVO> customerTakeCountList = customerMapper.selectPoolCustomerTakeCountByUser(reqVO);
  228 +
  229 + // 3.1 按照用户,合并统计数据
  230 + List<CrmStatisticsPoolSummaryByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
  231 + Integer customerPutCount = customerPutCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  232 + .mapToInt(CrmStatisticsPoolSummaryByUserRespVO::getCustomerPutCount).sum();
  233 + Integer customerTakeCount = customerTakeCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  234 + .mapToInt(CrmStatisticsPoolSummaryByUserRespVO::getCustomerTakeCount).sum();
  235 + return (CrmStatisticsPoolSummaryByUserRespVO) new CrmStatisticsPoolSummaryByUserRespVO()
  236 + .setCustomerPutCount(customerPutCount).setCustomerTakeCount(customerTakeCount)
  237 + .setOwnerUserId(userId);
  238 + });
  239 + // 3.2 拼接用户信息
  240 + appendUserInfo(summaryList);
  241 + return summaryList;
  242 + }
  243 +
  244 + @Override
  245 + public List<CrmStatisticsCustomerDealCycleByDateRespVO> getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO) {
  246 + // 1. 获得用户编号数组
  247 + reqVO.setUserIds(getUserIds(reqVO));
  248 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  249 + return Collections.emptyList();
  250 + }
  251 +
  252 + // 2. 按天统计,获取分项统计数据
  253 + List<CrmStatisticsCustomerDealCycleByDateRespVO> customerDealCycleList = customerMapper.selectCustomerDealCycleGroupByDate(reqVO);
  254 +
  255 + // 3. 按照日期间隔,合并统计数据
  256 + List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
  257 + return convertList(timeRanges, times -> {
  258 + Double customerDealCycle = customerDealCycleList.stream()
  259 + .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
  260 + .mapToDouble(CrmStatisticsCustomerDealCycleByDateRespVO::getCustomerDealCycle).sum();
  261 + return new CrmStatisticsCustomerDealCycleByDateRespVO()
  262 + .setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
  263 + .setCustomerDealCycle(customerDealCycle);
  264 + });
  265 + }
  266 +
  267 + @Override
  268 + public List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO) {
  269 + // 1. 获得用户编号数组
  270 + reqVO.setUserIds(getUserIds(reqVO));
  271 + if (CollUtil.isEmpty(reqVO.getUserIds())) {
  272 + return Collections.emptyList();
  273 + }
  274 +
  275 + // 2. 按用户统计,获取分项统计数据
  276 + List<CrmStatisticsCustomerDealCycleByUserRespVO> customerDealCycleList = customerMapper.selectCustomerDealCycleGroupByUser(reqVO);
  277 + List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCountList = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
  278 +
  279 + // 3.1 按照用户,合并统计数据
  280 + List<CrmStatisticsCustomerDealCycleByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
  281 + Double customerDealCycle = customerDealCycleList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  282 + .mapToDouble(CrmStatisticsCustomerDealCycleByUserRespVO::getCustomerDealCycle).sum();
  283 + Integer customerDealCount = customerDealCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
  284 + .mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount).sum();
  285 + return (CrmStatisticsCustomerDealCycleByUserRespVO) new CrmStatisticsCustomerDealCycleByUserRespVO()
  286 + .setCustomerDealCycle(customerDealCycle).setCustomerDealCount(customerDealCount).setOwnerUserId(userId);
  287 + });
  288 + // 3.2 拼接用户信息
  289 + appendUserInfo(summaryList);
  290 + return summaryList;
  291 + }
  292 +
  293 + /**
  294 + * 拼接用户信息(昵称)
  295 + *
  296 + * @param voList 统计数据
  297 + */
  298 + private <T extends CrmStatisticsCustomerByUserBaseRespVO> void appendUserInfo(List<T> voList) {
  299 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  300 + convertSet(voList, CrmStatisticsCustomerByUserBaseRespVO::getOwnerUserId));
  301 + voList.forEach(vo -> findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname())));
  302 + }
  303 +
  304 + /**
  305 + * 获取用户编号数组。如果用户编号为空, 则获得部门下的用户编号数组,包括子部门的所有用户编号
  306 + *
  307 + * @param reqVO 请求参数
  308 + * @return 用户编号数组
  309 + */
  310 + private List<Long> getUserIds(CrmStatisticsCustomerReqVO reqVO) {
  311 + // 情况一:选中某个用户
  312 + if (ObjUtil.isNotNull(reqVO.getUserId())) {
  313 + return List.of(reqVO.getUserId());
  314 + }
  315 + // 情况二:选中某个部门
  316 + // 2.1 获得部门列表
  317 + List<Long> deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId);
  318 + deptIds.add(reqVO.getDeptId());
  319 + // 2.2 获得用户编号
  320 + return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
  321 + }
  322 +
  323 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.statistics;
  2 +
  3 +
  4 +
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * CRM 员工绩效统计 Service 接口
  12 + *
  13 + * @author scholar
  14 + */
  15 +public interface CrmStatisticsPerformanceService {
  16 +
  17 + /**
  18 + * 员工签约合同数量分析
  19 + *
  20 + * @param performanceReqVO 排行参数
  21 + * @return 员工签约合同数量排行分析
  22 + */
  23 + List<CrmStatisticsPerformanceRespVO> getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
  24 +
  25 + /**
  26 + * 员工签约合同金额分析
  27 + *
  28 + * @param performanceReqVO 排行参数
  29 + * @return 员工签约合同金额分析
  30 + */
  31 + List<CrmStatisticsPerformanceRespVO> getContractPricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
  32 +
  33 + /**
  34 + * 员工获得回款金额分析
  35 + *
  36 + * @param performanceReqVO 排行参数
  37 + * @return 员工获得回款金额分析
  38 + */
  39 + List<CrmStatisticsPerformanceRespVO> getReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
  40 +
  41 +
  42 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.statistics;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
  7 +import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPerformanceMapper;
  8 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  9 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  10 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  11 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  12 +import org.springframework.stereotype.Service;
  13 +import org.springframework.validation.annotation.Validated;
  14 +
  15 +import jakarta.annotation.Resource;
  16 +import java.util.Collections;
  17 +import java.util.List;
  18 +import java.util.function.Function;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  21 +
  22 +/**
  23 + * CRM 员工业绩分析 Service 实现类
  24 + *
  25 + * @author scholar
  26 + */
  27 +@Service
  28 +@Validated
  29 +public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerformanceService {
  30 +
  31 + @Resource
  32 + private CrmStatisticsPerformanceMapper performanceMapper;
  33 +
  34 + @Resource
  35 + private AdminUserApi adminUserApi;
  36 + @Resource
  37 + private DeptApi deptApi;
  38 +
  39 + @Override
  40 + public List<CrmStatisticsPerformanceRespVO> getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO) {
  41 + // TODO @scholar:我们可以换个思路实现,减少数据库的计算量;
  42 + // 比如说,2024 年的合同数据,是不是 2022-12 到 2024-12-31,每个月的统计呢?
  43 + // 理解之后,我们可以数据 group by 年-月,20222-12 到 2024-12-31 的,然后内存在聚合出 CrmStatisticsPerformanceRespVO 这样
  44 + // 这样,我们就可以减少数据库的计算量,提升性能;同时 SQL 也会很简单,开发者理解起来也简单哈;
  45 + return getPerformance(performanceReqVO, performanceMapper::selectContractCountPerformance);
  46 + }
  47 +
  48 + @Override
  49 + public List<CrmStatisticsPerformanceRespVO> getContractPricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO) {
  50 + return getPerformance(performanceReqVO, performanceMapper::selectContractPricePerformance);
  51 + }
  52 +
  53 + @Override
  54 + public List<CrmStatisticsPerformanceRespVO> getReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO) {
  55 + return getPerformance(performanceReqVO, performanceMapper::selectReceivablePricePerformance);
  56 + }
  57 +
  58 + /**
  59 + * 获得员工业绩数据
  60 + *
  61 + * @param performanceReqVO 参数
  62 + * @param performanceFunction 排行榜方法
  63 + * @return 排行版数据
  64 + */
  65 + private List<CrmStatisticsPerformanceRespVO> getPerformance(CrmStatisticsPerformanceReqVO performanceReqVO, Function<CrmStatisticsPerformanceReqVO,
  66 + List<CrmStatisticsPerformanceRespVO>> performanceFunction) {
  67 +
  68 + // 1. 获得用户编号数组
  69 + final List<Long> userIds = getUserIds(performanceReqVO);
  70 + if (CollUtil.isEmpty(userIds)) {
  71 + return Collections.emptyList();
  72 + }
  73 + performanceReqVO.setUserIds(userIds);
  74 + // 2. 获得排行数据
  75 + List<CrmStatisticsPerformanceRespVO> performance = performanceFunction.apply(performanceReqVO);
  76 + if (CollUtil.isEmpty(performance)) {
  77 + return Collections.emptyList();
  78 + }
  79 + return performance;
  80 + }
  81 +
  82 + /**
  83 + * 获取用户编号数组。如果用户编号为空, 则获得部门下的用户编号数组,包括子部门的所有用户编号
  84 + *
  85 + * @param reqVO 请求参数
  86 + * @return 用户编号数组
  87 + */
  88 + private List<Long> getUserIds(CrmStatisticsPerformanceReqVO reqVO) {
  89 + // 情况一:选中某个用户
  90 + if (ObjUtil.isNotNull(reqVO.getUserId())) {
  91 + return List.of(reqVO.getUserId());
  92 + }
  93 + // 情况二:选中某个部门
  94 + // 2.1 获得部门列表
  95 + final Long deptId = reqVO.getDeptId();
  96 + List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId);
  97 + deptIds.add(deptId);
  98 + // 2.2 获得用户编号
  99 + return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
  100 + }
  101 +
  102 +}
0 \ No newline at end of file 103 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPortraitService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.statistics;
  2 +
  3 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.*;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * CRM 客户画像 Service 接口
  9 + *
  10 + * @author HUIHUI
  11 + */
  12 +public interface CrmStatisticsPortraitService {
  13 +
  14 + /**
  15 + * 获取客户地区统计数据
  16 + *
  17 + * @param reqVO 请求参数
  18 + * @return 统计数据
  19 + */
  20 + List<CrmStatisticCustomerAreaRespVO> getCustomerSummaryByArea(CrmStatisticsPortraitReqVO reqVO);
  21 +
  22 + /**
  23 + * 获取客户行业统计数据
  24 + *
  25 + * @param reqVO 请求参数
  26 + * @return 统计数据
  27 + */
  28 + List<CrmStatisticCustomerIndustryRespVO> getCustomerSummaryByIndustry(CrmStatisticsPortraitReqVO reqVO);
  29 +
  30 + /**
  31 + * 获取客户级别统计数据
  32 + *
  33 + * @param reqVO 请求参数
  34 + * @return 统计数据
  35 + */
  36 + List<CrmStatisticCustomerLevelRespVO> getCustomerSummaryByLevel(CrmStatisticsPortraitReqVO reqVO);
  37 +
  38 + /**
  39 + * 获取客户来源统计数据
  40 + *
  41 + * @param reqVO 请求参数
  42 + * @return 统计数据
  43 + */
  44 + List<CrmStatisticCustomerSourceRespVO> getCustomerSummaryBySource(CrmStatisticsPortraitReqVO reqVO);
  45 +
  46 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPortraitServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.statistics;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.ip.core.Area;
  6 +import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
  7 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
  8 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.*;
  9 +import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPortraitMapper;
  10 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  11 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  12 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  13 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  14 +import jakarta.annotation.Resource;
  15 +import org.springframework.stereotype.Service;
  16 +
  17 +import java.util.Collections;
  18 +import java.util.List;
  19 +import java.util.Map;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  23 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
  24 +
  25 +/**
  26 + * CRM 客户画像 Service 实现类
  27 + *
  28 + * @author HUIHUI
  29 + */
  30 +@Service
  31 +public class CrmStatisticsPortraitServiceImpl implements CrmStatisticsPortraitService {
  32 +
  33 + @Resource
  34 + private CrmStatisticsPortraitMapper portraitMapper;
  35 +
  36 + @Resource
  37 + private AdminUserApi adminUserApi;
  38 + @Resource
  39 + private DeptApi deptApi;
  40 +
  41 + @Override
  42 + public List<CrmStatisticCustomerAreaRespVO> getCustomerSummaryByArea(CrmStatisticsPortraitReqVO reqVO) {
  43 + // 1. 获得用户编号数组
  44 + List<Long> userIds = getUserIds(reqVO);
  45 + if (CollUtil.isEmpty(userIds)) {
  46 + return Collections.emptyList();
  47 + }
  48 + reqVO.setUserIds(userIds);
  49 +
  50 + // 2. 获取客户地区统计数据
  51 + List<CrmStatisticCustomerAreaRespVO> list = portraitMapper.selectSummaryListGroupByAreaId(reqVO);
  52 + if (CollUtil.isEmpty(list)) {
  53 + return Collections.emptyList();
  54 + }
  55 +
  56 + // 3. 拼接数据
  57 + List<Area> areaList = AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area);
  58 + areaList.add(new Area().setId(null).setName("未知")); // TODO @puhui999:是不是 65 find 的逻辑改下;不用 findAndThen,直接从 areaMap 拿;拿到就设置,不拿到就设置 null 和 未知;这样,58 本行可以删除掉完事了;这样代码更简单和一致
  59 + Map<Integer, Area> areaMap = convertMap(areaList, Area::getId);
  60 + return convertList(list, item -> {
  61 + Integer parentId = AreaUtils.getParentIdByType(item.getAreaId(), AreaTypeEnum.PROVINCE);
  62 + if (parentId == null) { // 找不到,归到未知
  63 + return item.setAreaId(null).setAreaName("未知");
  64 + }
  65 + findAndThen(areaMap, parentId, area -> item.setAreaId(parentId).setAreaName(area.getName()));
  66 + return item;
  67 + });
  68 + }
  69 +
  70 + @Override
  71 + public List<CrmStatisticCustomerIndustryRespVO> getCustomerSummaryByIndustry(CrmStatisticsPortraitReqVO reqVO) {
  72 + // 1. 获得用户编号数组
  73 + List<Long> userIds = getUserIds(reqVO);
  74 + if (CollUtil.isEmpty(userIds)) {
  75 + return Collections.emptyList();
  76 + }
  77 + reqVO.setUserIds(userIds);
  78 +
  79 + // 2. 获取客户行业统计数据
  80 + return portraitMapper.selectCustomerIndustryListGroupByIndustryId(reqVO);
  81 + }
  82 +
  83 + @Override
  84 + public List<CrmStatisticCustomerSourceRespVO> getCustomerSummaryBySource(CrmStatisticsPortraitReqVO reqVO) {
  85 + // 1. 获得用户编号数组
  86 + List<Long> userIds = getUserIds(reqVO);
  87 + if (CollUtil.isEmpty(userIds)) {
  88 + return Collections.emptyList();
  89 + }
  90 + reqVO.setUserIds(userIds);
  91 +
  92 + // 2. 获取客户行业统计数据
  93 + return portraitMapper.selectCustomerSourceListGroupBySource(reqVO);
  94 + }
  95 +
  96 + @Override
  97 + public List<CrmStatisticCustomerLevelRespVO> getCustomerSummaryByLevel(CrmStatisticsPortraitReqVO reqVO) {
  98 + // 1. 获得用户编号数组
  99 + List<Long> userIds = getUserIds(reqVO);
  100 + if (CollUtil.isEmpty(userIds)) {
  101 + return Collections.emptyList();
  102 + }
  103 + reqVO.setUserIds(userIds);
  104 +
  105 + // 2. 获取客户级别统计数据
  106 + return portraitMapper.selectCustomerLevelListGroupByLevel(reqVO);
  107 + }
  108 +
  109 + /**
  110 + * 获取用户编号数组。如果用户编号为空, 则获得部门下的用户编号数组,包括子部门的所有用户编号
  111 + *
  112 + * @param reqVO 请求参数
  113 + * @return 用户编号数组
  114 + */
  115 + private List<Long> getUserIds(CrmStatisticsPortraitReqVO reqVO) {
  116 + // 情况一:选中某个用户
  117 + if (ObjUtil.isNotNull(reqVO.getUserId())) {
  118 + return List.of(reqVO.getUserId());
  119 + }
  120 + // 情况二:选中某个部门
  121 + // 2.1 获得部门列表
  122 + List<Long> deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId);
  123 + deptIds.add(reqVO.getDeptId());
  124 + // 2.2 获得用户编号
  125 + return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
  126 + }
  127 +
  128 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingService.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankService.java
1 package cn.iocoder.yudao.module.crm.service.statistics; 1 package cn.iocoder.yudao.module.crm.service.statistics;
2 2
3 3
4 -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;  
5 -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO; 4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
6 6
7 import java.util.List; 7 import java.util.List;
8 8
@@ -11,7 +11,7 @@ import java.util.List; @@ -11,7 +11,7 @@ import java.util.List;
11 * 11 *
12 * @author anhaohao 12 * @author anhaohao
13 */ 13 */
14 -public interface CrmStatisticsRankingService { 14 +public interface CrmStatisticsRankService {
15 15
16 /** 16 /**
17 * 获得合同金额排行榜 17 * 获得合同金额排行榜
@@ -19,7 +19,7 @@ public interface CrmStatisticsRankingService { @@ -19,7 +19,7 @@ public interface CrmStatisticsRankingService {
19 * @param rankReqVO 排行参数 19 * @param rankReqVO 排行参数
20 * @return 合同金额排行榜 20 * @return 合同金额排行榜
21 */ 21 */
22 - List<CrmStatisticsRanKRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO); 22 + List<CrmStatisticsRankRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
23 23
24 /** 24 /**
25 * 获得回款金额排行榜 25 * 获得回款金额排行榜
@@ -27,7 +27,7 @@ public interface CrmStatisticsRankingService { @@ -27,7 +27,7 @@ public interface CrmStatisticsRankingService {
27 * @param rankReqVO 排行参数 27 * @param rankReqVO 排行参数
28 * @return 回款金额排行榜 28 * @return 回款金额排行榜
29 */ 29 */
30 - List<CrmStatisticsRanKRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO); 30 + List<CrmStatisticsRankRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
31 31
32 /** 32 /**
33 * 获得签约合同数量排行榜 33 * 获得签约合同数量排行榜
@@ -35,7 +35,7 @@ public interface CrmStatisticsRankingService { @@ -35,7 +35,7 @@ public interface CrmStatisticsRankingService {
35 * @param rankReqVO 排行参数 35 * @param rankReqVO 排行参数
36 * @return 签约合同数量排行榜 36 * @return 签约合同数量排行榜
37 */ 37 */
38 - List<CrmStatisticsRanKRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO); 38 + List<CrmStatisticsRankRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO);
39 39
40 /** 40 /**
41 * 获得产品销量排行榜 41 * 获得产品销量排行榜
@@ -43,7 +43,7 @@ public interface CrmStatisticsRankingService { @@ -43,7 +43,7 @@ public interface CrmStatisticsRankingService {
43 * @param rankReqVO 排行参数 43 * @param rankReqVO 排行参数
44 * @return 产品销量排行榜 44 * @return 产品销量排行榜
45 */ 45 */
46 - List<CrmStatisticsRanKRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO); 46 + List<CrmStatisticsRankRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
47 47
48 /** 48 /**
49 * 获得新增客户数排行榜 49 * 获得新增客户数排行榜
@@ -51,7 +51,7 @@ public interface CrmStatisticsRankingService { @@ -51,7 +51,7 @@ public interface CrmStatisticsRankingService {
51 * @param rankReqVO 排行参数 51 * @param rankReqVO 排行参数
52 * @return 新增客户数排行榜 52 * @return 新增客户数排行榜
53 */ 53 */
54 - List<CrmStatisticsRanKRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO); 54 + List<CrmStatisticsRankRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
55 55
56 /** 56 /**
57 * 获得联系人数量排行榜 57 * 获得联系人数量排行榜
@@ -59,7 +59,7 @@ public interface CrmStatisticsRankingService { @@ -59,7 +59,7 @@ public interface CrmStatisticsRankingService {
59 * @param rankReqVO 排行参数 59 * @param rankReqVO 排行参数
60 * @return 联系人数量排行榜 60 * @return 联系人数量排行榜
61 */ 61 */
62 - List<CrmStatisticsRanKRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO); 62 + List<CrmStatisticsRankRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
63 63
64 /** 64 /**
65 * 获得跟进次数排行榜 65 * 获得跟进次数排行榜
@@ -67,7 +67,7 @@ public interface CrmStatisticsRankingService { @@ -67,7 +67,7 @@ public interface CrmStatisticsRankingService {
67 * @param rankReqVO 排行参数 67 * @param rankReqVO 排行参数
68 * @return 跟进次数排行榜 68 * @return 跟进次数排行榜
69 */ 69 */
70 - List<CrmStatisticsRanKRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO); 70 + List<CrmStatisticsRankRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
71 71
72 /** 72 /**
73 * 获得跟进客户数排行榜 73 * 获得跟进客户数排行榜
@@ -75,6 +75,6 @@ public interface CrmStatisticsRankingService { @@ -75,6 +75,6 @@ public interface CrmStatisticsRankingService {
75 * @param rankReqVO 排行参数 75 * @param rankReqVO 排行参数
76 * @return 跟进客户数排行榜 76 * @return 跟进客户数排行榜
77 */ 77 */
78 - List<CrmStatisticsRanKRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO); 78 + List<CrmStatisticsRankRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
79 79
80 } 80 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingServiceImpl.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankServiceImpl.java
@@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.crm.service.statistics; @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.crm.service.statistics;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 4 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
5 -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;  
6 -import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;  
7 -import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsRankingMapper; 5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
  7 +import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsRankMapper;
8 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 8 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
9 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 9 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
10 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 10 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -29,10 +29,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -29,10 +29,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
29 */ 29 */
30 @Service 30 @Service
31 @Validated 31 @Validated
32 -public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingService { 32 +public class CrmStatisticsRankServiceImpl implements CrmStatisticsRankService {
33 33
34 @Resource 34 @Resource
35 - private CrmStatisticsRankingMapper rankMapper; 35 + private CrmStatisticsRankMapper rankMapper;
36 36
37 @Resource 37 @Resource
38 private AdminUserApi adminUserApi; 38 private AdminUserApi adminUserApi;
@@ -40,64 +40,64 @@ public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingServ @@ -40,64 +40,64 @@ public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingServ
40 private DeptApi deptApi; 40 private DeptApi deptApi;
41 41
42 @Override 42 @Override
43 - public List<CrmStatisticsRanKRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO) { 43 + public List<CrmStatisticsRankRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO) {
44 return getRank(rankReqVO, rankMapper::selectContractPriceRank); 44 return getRank(rankReqVO, rankMapper::selectContractPriceRank);
45 } 45 }
46 46
47 @Override 47 @Override
48 - public List<CrmStatisticsRanKRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO) { 48 + public List<CrmStatisticsRankRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO) {
49 return getRank(rankReqVO, rankMapper::selectReceivablePriceRank); 49 return getRank(rankReqVO, rankMapper::selectReceivablePriceRank);
50 } 50 }
51 51
52 @Override 52 @Override
53 - public List<CrmStatisticsRanKRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO) { 53 + public List<CrmStatisticsRankRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO) {
54 return getRank(rankReqVO, rankMapper::selectContractCountRank); 54 return getRank(rankReqVO, rankMapper::selectContractCountRank);
55 } 55 }
56 56
57 @Override 57 @Override
58 - public List<CrmStatisticsRanKRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO) { 58 + public List<CrmStatisticsRankRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO) {
59 return getRank(rankReqVO, rankMapper::selectProductSalesRank); 59 return getRank(rankReqVO, rankMapper::selectProductSalesRank);
60 } 60 }
61 61
62 @Override 62 @Override
63 - public List<CrmStatisticsRanKRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) { 63 + public List<CrmStatisticsRankRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
64 return getRank(rankReqVO, rankMapper::selectCustomerCountRank); 64 return getRank(rankReqVO, rankMapper::selectCustomerCountRank);
65 } 65 }
66 66
67 @Override 67 @Override
68 - public List<CrmStatisticsRanKRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO) { 68 + public List<CrmStatisticsRankRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO) {
69 return getRank(rankReqVO, rankMapper::selectContactsCountRank); 69 return getRank(rankReqVO, rankMapper::selectContactsCountRank);
70 } 70 }
71 71
72 @Override 72 @Override
73 - public List<CrmStatisticsRanKRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO) { 73 + public List<CrmStatisticsRankRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO) {
74 return getRank(rankReqVO, rankMapper::selectFollowCountRank); 74 return getRank(rankReqVO, rankMapper::selectFollowCountRank);
75 } 75 }
76 76
77 @Override 77 @Override
78 - public List<CrmStatisticsRanKRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) { 78 + public List<CrmStatisticsRankRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
79 return getRank(rankReqVO, rankMapper::selectFollowCustomerCountRank); 79 return getRank(rankReqVO, rankMapper::selectFollowCustomerCountRank);
80 } 80 }
81 81
82 /** 82 /**
83 * 获得排行版数据 83 * 获得排行版数据
84 * 84 *
85 - * @param rankReqVO 参数 85 + * @param rankReqVO 参数
86 * @param rankFunction 排行榜方法 86 * @param rankFunction 排行榜方法
87 * @return 排行版数据 87 * @return 排行版数据
88 */ 88 */
89 - private List<CrmStatisticsRanKRespVO> getRank(CrmStatisticsRankReqVO rankReqVO, Function<CrmStatisticsRankReqVO, List<CrmStatisticsRanKRespVO>> rankFunction) { 89 + private List<CrmStatisticsRankRespVO> getRank(CrmStatisticsRankReqVO rankReqVO, Function<CrmStatisticsRankReqVO, List<CrmStatisticsRankRespVO>> rankFunction) {
90 // 1. 获得用户编号数组 90 // 1. 获得用户编号数组
91 rankReqVO.setUserIds(getUserIds(rankReqVO.getDeptId())); 91 rankReqVO.setUserIds(getUserIds(rankReqVO.getDeptId()));
92 if (CollUtil.isEmpty(rankReqVO.getUserIds())) { 92 if (CollUtil.isEmpty(rankReqVO.getUserIds())) {
93 return Collections.emptyList(); 93 return Collections.emptyList();
94 } 94 }
95 // 2. 获得排行数据 95 // 2. 获得排行数据
96 - List<CrmStatisticsRanKRespVO> ranks = rankFunction.apply(rankReqVO); 96 + List<CrmStatisticsRankRespVO> ranks = rankFunction.apply(rankReqVO);
97 if (CollUtil.isEmpty(ranks)) { 97 if (CollUtil.isEmpty(ranks)) {
98 return Collections.emptyList(); 98 return Collections.emptyList();
99 } 99 }
100 - ranks.sort(Comparator.comparing(CrmStatisticsRanKRespVO::getCount).reversed()); 100 + ranks.sort(Comparator.comparing(CrmStatisticsRankRespVO::getCount).reversed());
101 // 3. 拼接用户信息 101 // 3. 拼接用户信息
102 appendUserInfo(ranks); 102 appendUserInfo(ranks);
103 return ranks; 103 return ranks;
@@ -108,8 +108,8 @@ public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingServ @@ -108,8 +108,8 @@ public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingServ
108 * 108 *
109 * @param ranks 排行榜数据 109 * @param ranks 排行榜数据
110 */ 110 */
111 - private void appendUserInfo(List<CrmStatisticsRanKRespVO> ranks) {  
112 - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(ranks, CrmStatisticsRanKRespVO::getOwnerUserId)); 111 + private void appendUserInfo(List<CrmStatisticsRankRespVO> ranks) {
  112 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(ranks, CrmStatisticsRankRespVO::getOwnerUserId));
113 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); 113 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
114 ranks.forEach(rank -> MapUtils.findAndThen(userMap, rank.getOwnerUserId(), user -> { 114 ranks.forEach(rank -> MapUtils.findAndThen(userMap, rank.getOwnerUserId(), user -> {
115 rank.setNickname(user.getNickname()); 115 rank.setNickname(user.getNickname());
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3 +<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper">
  4 +
  5 + <select id="selectCustomerCreateCountGroupByDate"
  6 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
  7 + SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
  8 + COUNT(*) AS customerCreateCount
  9 + FROM crm_customer
  10 + WHERE deleted = 0
  11 + AND owner_user_id IN
  12 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  13 + #{userId}
  14 + </foreach>
  15 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  16 + GROUP BY time
  17 + </select>
  18 +
  19 + <!-- TODO 芋艿:应该不用过滤时间 -->
  20 + <select id="selectCustomerDealCountGroupByDate"
  21 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
  22 + SELECT DATE_FORMAT(customer.create_time, '%Y-%m-%d') AS time,
  23 + COUNT(DISTINCT customer.id) AS customer_deal_count
  24 + FROM crm_customer AS customer
  25 + LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
  26 + WHERE customer.deleted = 0 AND contract.deleted = 0
  27 + AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  28 + AND customer.owner_user_id IN
  29 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  30 + #{userId}
  31 + </foreach>
  32 + AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  33 + GROUP BY time
  34 + </select>
  35 +
  36 + <select id="selectCustomerCreateCountGroupByUser"
  37 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
  38 + SELECT owner_user_id,
  39 + COUNT(*) AS customer_create_count
  40 + FROM crm_customer
  41 + WHERE deleted = 0
  42 + AND owner_user_id in
  43 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  44 + #{userId}
  45 + </foreach>
  46 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  47 + GROUP BY owner_user_id
  48 + </select>
  49 +
  50 + <select id="selectCustomerDealCountGroupByUser"
  51 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
  52 + SELECT customer.owner_user_id,
  53 + COUNT(DISTINCT customer.id) AS customer_deal_count
  54 + FROM crm_customer AS customer
  55 + LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
  56 + WHERE customer.deleted = 0 AND contract.deleted = 0
  57 + AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  58 + AND customer.owner_user_id IN
  59 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  60 + #{userId}
  61 + </foreach>
  62 + AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  63 + GROUP BY customer.owner_user_id
  64 + </select>
  65 +
  66 + <select id="selectContractPriceGroupByUser"
  67 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
  68 + SELECT owner_user_id,
  69 + IFNULL(SUM(total_price), 0) AS contract_price
  70 + FROM crm_contract
  71 + WHERE deleted = 0
  72 + AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  73 + AND owner_user_id in
  74 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  75 + #{userId}
  76 + </foreach>
  77 + AND order_date BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  78 + GROUP BY owner_user_id
  79 + </select>
  80 +
  81 + <select id="selectReceivablePriceGroupByUser"
  82 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
  83 + SELECT owner_user_id,
  84 + IFNULL(SUM(price), 0) AS receivable_price
  85 + FROM crm_receivable
  86 + WHERE deleted = 0
  87 + AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  88 + AND owner_user_id IN
  89 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  90 + #{userId}
  91 + </foreach>
  92 + AND return_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  93 + GROUP BY owner_user_id
  94 + </select>
  95 +
  96 + <select id="selectFollowUpRecordCountGroupByDate"
  97 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByDateRespVO">
  98 + SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
  99 + COUNT(*) AS follow_up_record_count
  100 + FROM crm_follow_up_record
  101 + WHERE creator IN
  102 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  103 + #{userId}
  104 + </foreach>
  105 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  106 + AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
  107 + GROUP BY time
  108 + </select>
  109 +
  110 + <select id="selectFollowUpCustomerCountGroupByDate"
  111 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByDateRespVO">
  112 + SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
  113 + COUNT(DISTINCT biz_id) AS follow_up_customer_count
  114 + FROM crm_follow_up_record
  115 + WHERE creator IN
  116 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  117 + #{userId}
  118 + </foreach>
  119 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  120 + AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
  121 + GROUP BY time
  122 + </select>
  123 +
  124 + <select id="selectFollowUpRecordCountGroupByUser"
  125 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByUserRespVO">
  126 + SELECT creator as owner_user_id,
  127 + COUNT(*) AS follow_up_record_count
  128 + FROM crm_follow_up_record
  129 + WHERE creator IN
  130 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  131 + #{userId}
  132 + </foreach>
  133 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  134 + AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
  135 + GROUP BY creator
  136 + </select>
  137 +
  138 + <select id="selectFollowUpCustomerCountGroupByUser"
  139 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByUserRespVO">
  140 + SELECT
  141 + creator as owner_user_id,
  142 + COUNT(DISTINCT biz_id) AS follow_up_customer_count
  143 + FROM crm_follow_up_record
  144 + WHERE creator IN
  145 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  146 + #{userId}
  147 + </foreach>
  148 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  149 + AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
  150 + GROUP BY creator
  151 + </select>
  152 +
  153 + <select id="selectFollowUpRecordCountGroupByType"
  154 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByTypeRespVO">
  155 + SELECT type AS follow_up_type,
  156 + COUNT(*) AS follow_up_record_count
  157 + FROM crm_follow_up_record
  158 + WHERE creator IN
  159 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  160 + #{userId}
  161 + </foreach>
  162 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  163 + AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
  164 + GROUP BY follow_up_type
  165 + </select>
  166 +
  167 + <select id="selectContractSummary"
  168 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerContractSummaryRespVO">
  169 + SELECT customer.name AS customer_name,
  170 + customer.industry_id,
  171 + customer.source,
  172 + customer.owner_user_id,
  173 + customer.creator,
  174 + customer.create_time,
  175 + contract.name AS contract_name,
  176 + contract.total_price,
  177 + contract.order_date,
  178 + IFNULL(receivable.price, 0) AS receivable_price
  179 + FROM crm_customer AS customer
  180 + INNER JOIN crm_contract AS contract ON customer.id = contract.customer_id
  181 + LEFT JOIN crm_receivable AS receivable ON contract.id = receivable.contract_id
  182 + WHERE customer.deleted = 0 AND contract.deleted = 0 AND receivable.deleted = 0
  183 + AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  184 + AND customer.owner_user_id IN
  185 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  186 + #{userId}
  187 + </foreach>
  188 + AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  189 + </select>
  190 +
  191 + <!-- TIMESTAMPDIFF 用于求差值;AVG 求平均;TRUNCATE 去掉小数点、只保留整数 -->
  192 + <select id="selectCustomerDealCycleGroupByDate"
  193 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByDateRespVO">
  194 + SELECT DATE_FORMAT(contract.order_date, '%Y-%m-%d') AS time,
  195 + IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle
  196 + FROM crm_customer AS customer
  197 + LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
  198 + WHERE customer.deleted = 0 AND contract.deleted = 0
  199 + AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  200 + AND customer.owner_user_id IN
  201 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  202 + #{userId}
  203 + </foreach>
  204 + AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  205 + GROUP BY time
  206 + </select>
  207 +
  208 + <select id="selectCustomerDealCycleGroupByUser"
  209 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByUserRespVO">
  210 + SELECT customer.owner_user_id,
  211 + IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle
  212 + FROM crm_customer AS customer
  213 + LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
  214 + WHERE customer.deleted = 0 AND contract.deleted = 0
  215 + AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  216 + AND customer.owner_user_id IN
  217 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  218 + #{userId}
  219 + </foreach>
  220 + AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
  221 + GROUP BY customer.owner_user_id
  222 + </select>
  223 +
  224 +</mapper>
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3 +<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPerformanceMapper">
  4 +
  5 + <select id="selectContractCountPerformance"
  6 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
  7 + SELECT
  8 + t.time as time,
  9 + COALESCE(t.currentMonthCount,0) as currentMonthCount,
  10 + COALESCE(y.lastMonthCount,0) as lastMonthCount,
  11 + COALESCE(z.lastYearCount,0) as lastYearCount
  12 + FROM
  13 + (SELECT
  14 + COUNT(1) AS currentMonthCount,
  15 + DATE_FORMAT(order_date, '%Y-%m') AS time
  16 + FROM crm_contract
  17 + WHERE deleted = 0
  18 + AND audit_status = 20
  19 + AND owner_user_id in
  20 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  21 + #{userId}
  22 + </foreach>
  23 + AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
  24 + GROUP BY time)t
  25 + LEFT JOIN
  26 + (SELECT
  27 + COUNT(1) AS lastMonthCount,
  28 + DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 MONTH), '%Y-%m') AS time
  29 + FROM crm_contract
  30 + WHERE deleted = 0
  31 + AND audit_status = 20
  32 + AND owner_user_id in
  33 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  34 + #{userId}
  35 + </foreach>
  36 + AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
  37 + or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1)
  38 + GROUP BY time)y ON t.time = y.time
  39 + LEFT JOIN
  40 + (SELECT
  41 + COUNT(1) AS lastYearCount,
  42 + DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 YEAR), '%Y-%m') AS time
  43 + FROM crm_contract
  44 + WHERE deleted = 0
  45 + AND audit_status = 20
  46 + AND owner_user_id in
  47 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  48 + #{userId}
  49 + </foreach>
  50 + AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1
  51 + GROUP BY time)z ON t.time = z.time
  52 + </select>
  53 + <select id="selectContractPricePerformance"
  54 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
  55 + SELECT
  56 + t.time as time,
  57 + COALESCE(t.currentMonthCount,0) as currentMonthCount,
  58 + COALESCE(y.lastMonthCount,0) as lastMonthCount,
  59 + COALESCE(z.lastYearCount,0) as lastYearCount
  60 + FROM
  61 + (SELECT
  62 + IFNULL(SUM(total_price), 0) AS currentMonthCount,
  63 + DATE_FORMAT(order_date, '%Y-%m') AS time
  64 + FROM crm_contract
  65 + WHERE deleted = 0
  66 + AND audit_status = 20
  67 + AND owner_user_id in
  68 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  69 + #{userId}
  70 + </foreach>
  71 + AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
  72 + GROUP BY time)t
  73 + LEFT JOIN
  74 + (SELECT
  75 + IFNULL(SUM(total_price), 0) AS lastMonthCount,
  76 + DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 MONTH), '%Y-%m') AS time
  77 + FROM crm_contract
  78 + WHERE deleted = 0
  79 + AND audit_status = 20
  80 + AND owner_user_id in
  81 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  82 + #{userId}
  83 + </foreach>
  84 + AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
  85 + or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1)
  86 + GROUP BY time)y ON t.time = y.time
  87 + LEFT JOIN
  88 + (SELECT
  89 + IFNULL(SUM(total_price), 0) AS lastYearCount,
  90 + DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 YEAR), '%Y-%m') AS time
  91 + FROM crm_contract
  92 + WHERE deleted = 0
  93 + AND audit_status = 20
  94 + AND owner_user_id in
  95 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  96 + #{userId}
  97 + </foreach>
  98 + AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1
  99 + GROUP BY time)z ON t.time = z.time
  100 + </select>
  101 +
  102 + <select id="selectReceivablePricePerformance"
  103 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
  104 + SELECT
  105 + t.time as time,
  106 + COALESCE(t.currentMonthCount,0) as currentMonthCount,
  107 + COALESCE(y.lastMonthCount,0) as lastMonthCount,
  108 + COALESCE(z.lastYearCount,0) as lastYearCount
  109 + FROM
  110 + (SELECT
  111 + IFNULL(SUM(price), 0) AS currentMonthCount,
  112 + DATE_FORMAT(return_time, '%Y-%m') AS time
  113 + FROM crm_receivable
  114 + WHERE deleted = 0
  115 + AND audit_status = 20
  116 + AND owner_user_id in
  117 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  118 + #{userId}
  119 + </foreach>
  120 + AND DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
  121 + GROUP BY time)t
  122 + LEFT JOIN
  123 + (SELECT
  124 + IFNULL(SUM(price), 0) AS lastMonthCount,
  125 + DATE_FORMAT(DATE_ADD(return_time,INTERVAL 1 MONTH), '%Y-%m') AS time
  126 + FROM crm_receivable
  127 + WHERE deleted = 0
  128 + AND audit_status = 20
  129 + AND owner_user_id in
  130 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  131 + #{userId}
  132 + </foreach>
  133 + AND (DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
  134 + or DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1)
  135 + GROUP BY time)y ON t.time = y.time
  136 + LEFT JOIN
  137 + (SELECT
  138 + IFNULL(SUM(price), 0) AS lastYearCount,
  139 + DATE_FORMAT(DATE_ADD(return_time,INTERVAL 1 YEAR), '%Y-%m') AS time
  140 + FROM crm_receivable
  141 + WHERE deleted = 0
  142 + AND audit_status = 20
  143 + AND owner_user_id in
  144 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  145 + #{userId}
  146 + </foreach>
  147 + AND DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1
  148 + GROUP BY time)z ON t.time = z.time
  149 + </select>
  150 +
  151 +
  152 +</mapper>
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPortraitMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3 +<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPortraitMapper">
  4 +
  5 + <select id="selectSummaryListGroupByAreaId"
  6 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerAreaRespVO">
  7 + SELECT area_id, COUNT(*) AS customerCount, SUM(deal_status) AS dealCount
  8 + FROM crm_customer
  9 + WHERE deleted = 0 AND area_id IS NOT NULL
  10 + AND owner_user_id IN
  11 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  12 + #{userId}
  13 + </foreach>
  14 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime}
  15 + AND #{times[1],javaType=java.time.LocalDateTime}
  16 + GROUP BY area_id
  17 + </select>
  18 +
  19 + <select id="selectCustomerIndustryListGroupByIndustryId"
  20 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerIndustryRespVO">
  21 + SELECT industry_id, COUNT(*) AS customerCount, SUM(deal_status) AS dealCount
  22 + FROM crm_customer
  23 + WHERE deleted = 0 AND industry_id IS NOT NULL
  24 + AND owner_user_id IN
  25 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  26 + #{userId}
  27 + </foreach>
  28 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime}
  29 + AND #{times[1],javaType=java.time.LocalDateTime}
  30 + GROUP BY industry_id
  31 + </select>
  32 +
  33 + <select id="selectCustomerSourceListGroupBySource"
  34 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerSourceRespVO">
  35 + SELECT source, COUNT(*) AS customerCount, SUM(deal_status) AS dealCount
  36 + FROM crm_customer
  37 + WHERE deleted = 0 AND source IS NOT NULL
  38 + AND owner_user_id IN
  39 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  40 + #{userId}
  41 + </foreach>
  42 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime}
  43 + AND #{times[1],javaType=java.time.LocalDateTime}
  44 + GROUP BY source
  45 + </select>
  46 +
  47 + <select id="selectCustomerLevelListGroupByLevel"
  48 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerLevelRespVO">
  49 + SELECT level, COUNT(*) AS customerCount, SUM(deal_status) AS dealCount
  50 + FROM crm_customer
  51 + WHERE deleted = 0 AND level IS NOT NULL
  52 + AND owner_user_id IN
  53 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  54 + #{userId}
  55 + </foreach>
  56 + AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime}
  57 + AND #{times[1],javaType=java.time.LocalDateTime}
  58 + GROUP BY level
  59 + </select>
  60 +
  61 +</mapper>
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml renamed to yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsRankMapper.xml
1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3 -<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsRankingMapper"> 3 +<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsRankMapper">
4 4
5 <select id="selectContractPriceRank" 5 <select id="selectContractPriceRank"
6 - resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO"> 6 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
7 SELECT IFNULL(SUM(total_price), 0) AS count, owner_user_id 7 SELECT IFNULL(SUM(total_price), 0) AS count, owner_user_id
8 FROM crm_contract 8 FROM crm_contract
9 WHERE deleted = 0 9 WHERE deleted = 0
10 - AND audit_status = 20 10 + AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
11 and owner_user_id in 11 and owner_user_id in
12 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
13 - #{userId}  
14 - </foreach>  
15 - AND order_date between #{times[0],javaType=java.time.LocalDateTime} and  
16 - #{times[1],javaType=java.time.LocalDateTime} 12 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  13 + #{userId}
  14 + </foreach>
  15 + AND order_date between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
17 GROUP BY owner_user_id 16 GROUP BY owner_user_id
18 </select> 17 </select>
19 18
20 <select id="selectReceivablePriceRank" 19 <select id="selectReceivablePriceRank"
21 - resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO"> 20 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
22 SELECT IFNULL(SUM(price), 0) AS count, owner_user_id 21 SELECT IFNULL(SUM(price), 0) AS count, owner_user_id
23 FROM crm_receivable 22 FROM crm_receivable
24 WHERE deleted = 0 23 WHERE deleted = 0
25 - AND audit_status = 20 24 + AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
26 AND owner_user_id in 25 AND owner_user_id in
27 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
28 - #{userId}  
29 - </foreach>  
30 - AND return_time between #{times[0],javaType=java.time.LocalDateTime} and  
31 - #{times[1],javaType=java.time.LocalDateTime} 26 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  27 + #{userId}
  28 + </foreach>
  29 + AND return_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
32 GROUP BY owner_user_id 30 GROUP BY owner_user_id
33 </select> 31 </select>
34 32
35 <select id="selectContractCountRank" 33 <select id="selectContractCountRank"
36 - resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO"> 34 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
37 SELECT COUNT(1) AS count, owner_user_id 35 SELECT COUNT(1) AS count, owner_user_id
38 FROM crm_contract 36 FROM crm_contract
39 WHERE deleted = 0 37 WHERE deleted = 0
40 - AND audit_status = 20 38 + AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
41 AND owner_user_id in 39 AND owner_user_id in
42 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
43 - #{userId}  
44 - </foreach>  
45 - AND order_date between #{times[0],javaType=java.time.LocalDateTime} and  
46 - #{times[1],javaType=java.time.LocalDateTime} 40 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  41 + #{userId}
  42 + </foreach>
  43 + AND order_date between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
47 GROUP BY owner_user_id 44 GROUP BY owner_user_id
48 </select> 45 </select>
49 46
50 - <!-- TODO 待定 这里是否需要关联 crm_contract_product 表,计算销售额 -->  
51 <select id="selectProductSalesRank" 47 <select id="selectProductSalesRank"
52 - resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO">  
53 - SELECT COUNT(1) AS count, owner_user_id  
54 - FROM crm_contract  
55 - WHERE deleted = 0  
56 - AND audit_status = 20  
57 - AND owner_user_id in  
58 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
59 - #{userId}  
60 - </foreach>  
61 - AND order_date between #{times[0],javaType=java.time.LocalDateTime} and  
62 - #{times[1],javaType=java.time.LocalDateTime}  
63 - GROUP BY owner_user_id 48 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
  49 + SELECT COUNT(product.count) AS count, contract.owner_user_id
  50 + FROM crm_contract_product product
  51 + INNER JOIN crm_contract contract ON product.contract_id = contract.id
  52 + WHERE contract.deleted = 0 AND contract.deleted = 0
  53 + AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
  54 + AND contract.owner_user_id in
  55 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  56 + #{userId}
  57 + </foreach>
  58 + AND contract.order_date between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
  59 + GROUP BY contract.owner_user_id
64 </select> 60 </select>
65 61
66 <select id="selectCustomerCountRank" 62 <select id="selectCustomerCountRank"
67 - resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO"> 63 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
68 SELECT COUNT(1) AS count, owner_user_id 64 SELECT COUNT(1) AS count, owner_user_id
69 FROM crm_customer 65 FROM crm_customer
70 WHERE deleted = 0 66 WHERE deleted = 0
71 AND owner_user_id in 67 AND owner_user_id in
72 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
73 - #{userId}  
74 - </foreach>  
75 - AND create_time between #{times[0],javaType=java.time.LocalDateTime} and  
76 - #{times[1],javaType=java.time.LocalDateTime} 68 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  69 + #{userId}
  70 + </foreach>
  71 + AND create_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
77 GROUP BY owner_user_id 72 GROUP BY owner_user_id
78 </select> 73 </select>
79 74
80 <select id="selectContactsCountRank" 75 <select id="selectContactsCountRank"
81 - resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO"> 76 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
82 SELECT COUNT(1) AS count, owner_user_id 77 SELECT COUNT(1) AS count, owner_user_id
83 FROM crm_contact 78 FROM crm_contact
84 WHERE deleted = 0 79 WHERE deleted = 0
85 AND owner_user_id in 80 AND owner_user_id in
86 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
87 - #{userId}  
88 - </foreach>  
89 - AND create_time between #{times[0],javaType=java.time.LocalDateTime} and  
90 - #{times[1],javaType=java.time.LocalDateTime} 81 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  82 + #{userId}
  83 + </foreach>
  84 + AND create_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
91 GROUP BY owner_user_id 85 GROUP BY owner_user_id
92 </select> 86 </select>
93 87
94 <select id="selectFollowCountRank" 88 <select id="selectFollowCountRank"
95 - resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO"> 89 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
96 SELECT COUNT(1) AS count, cc.owner_user_id 90 SELECT COUNT(1) AS count, cc.owner_user_id
97 FROM crm_follow_up_record AS cfur 91 FROM crm_follow_up_record AS cfur
98 LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids) 92 LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
99 WHERE cfur.deleted = 0 93 WHERE cfur.deleted = 0
100 AND cc.deleted = 0 94 AND cc.deleted = 0
101 AND cc.owner_user_id in 95 AND cc.owner_user_id in
102 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
103 - #{userId}  
104 - </foreach>  
105 - AND cfur.create_time between #{times[0],javaType=java.time.LocalDateTime} and  
106 - #{times[1],javaType=java.time.LocalDateTime} 96 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  97 + #{userId}
  98 + </foreach>
  99 + AND cfur.create_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
107 GROUP BY cc.owner_user_id 100 GROUP BY cc.owner_user_id
108 </select> 101 </select>
109 102
110 <select id="selectFollowCustomerCountRank" 103 <select id="selectFollowCustomerCountRank"
111 - resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO"> 104 + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
112 SELECT COUNT(DISTINCT cc.id) AS count, cc.owner_user_id 105 SELECT COUNT(DISTINCT cc.id) AS count, cc.owner_user_id
113 FROM crm_follow_up_record AS cfur 106 FROM crm_follow_up_record AS cfur
114 LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids) 107 LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
115 WHERE cfur.deleted = 0 108 WHERE cfur.deleted = 0
116 AND cc.deleted = 0 109 AND cc.deleted = 0
117 AND cc.owner_user_id in 110 AND cc.owner_user_id in
118 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
119 - #{userId}  
120 - </foreach>  
121 - AND cfur.create_time between #{times[0],javaType=java.time.LocalDateTime} and  
122 - #{times[1],javaType=java.time.LocalDateTime} 111 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  112 + #{userId}
  113 + </foreach>
  114 + AND cfur.create_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
123 GROUP BY cc.owner_user_id 115 GROUP BY cc.owner_user_id
124 </select> 116 </select>
125 117
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -61,14 +61,14 @@ public class ErpPurchaseOrderController { @@ -61,14 +61,14 @@ public class ErpPurchaseOrderController {
61 61
62 @PostMapping("/create") 62 @PostMapping("/create")
63 @Operation(summary = "创建采购订单") 63 @Operation(summary = "创建采购订单")
64 - @PreAuthorize("@ss.hasPermission('erp:purchase-create:create')") 64 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:create')")
65 public CommonResult<Long> createPurchaseOrder(@Valid @RequestBody ErpPurchaseOrderSaveReqVO createReqVO) { 65 public CommonResult<Long> createPurchaseOrder(@Valid @RequestBody ErpPurchaseOrderSaveReqVO createReqVO) {
66 return success(purchaseOrderService.createPurchaseOrder(createReqVO)); 66 return success(purchaseOrderService.createPurchaseOrder(createReqVO));
67 } 67 }
68 68
69 @PutMapping("/update") 69 @PutMapping("/update")
70 @Operation(summary = "更新采购订单") 70 @Operation(summary = "更新采购订单")
71 - @PreAuthorize("@ss.hasPermission('erp:purchase-create:update')") 71 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:update')")
72 public CommonResult<Boolean> updatePurchaseOrder(@Valid @RequestBody ErpPurchaseOrderSaveReqVO updateReqVO) { 72 public CommonResult<Boolean> updatePurchaseOrder(@Valid @RequestBody ErpPurchaseOrderSaveReqVO updateReqVO) {
73 purchaseOrderService.updatePurchaseOrder(updateReqVO); 73 purchaseOrderService.updatePurchaseOrder(updateReqVO);
74 return success(true); 74 return success(true);
@@ -76,7 +76,7 @@ public class ErpPurchaseOrderController { @@ -76,7 +76,7 @@ public class ErpPurchaseOrderController {
76 76
77 @PutMapping("/update-status") 77 @PutMapping("/update-status")
78 @Operation(summary = "更新采购订单的状态") 78 @Operation(summary = "更新采购订单的状态")
79 - @PreAuthorize("@ss.hasPermission('erp:purchase-create:update-status')") 79 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:update-status')")
80 public CommonResult<Boolean> updatePurchaseOrderStatus(@RequestParam("id") Long id, 80 public CommonResult<Boolean> updatePurchaseOrderStatus(@RequestParam("id") Long id,
81 @RequestParam("status") Integer status) { 81 @RequestParam("status") Integer status) {
82 purchaseOrderService.updatePurchaseOrderStatus(id, status); 82 purchaseOrderService.updatePurchaseOrderStatus(id, status);
@@ -86,7 +86,7 @@ public class ErpPurchaseOrderController { @@ -86,7 +86,7 @@ public class ErpPurchaseOrderController {
86 @DeleteMapping("/delete") 86 @DeleteMapping("/delete")
87 @Operation(summary = "删除采购订单") 87 @Operation(summary = "删除采购订单")
88 @Parameter(name = "ids", description = "编号数组", required = true) 88 @Parameter(name = "ids", description = "编号数组", required = true)
89 - @PreAuthorize("@ss.hasPermission('erp:purchase-create:delete')") 89 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:delete')")
90 public CommonResult<Boolean> deletePurchaseOrder(@RequestParam("ids") List<Long> ids) { 90 public CommonResult<Boolean> deletePurchaseOrder(@RequestParam("ids") List<Long> ids) {
91 purchaseOrderService.deletePurchaseOrder(ids); 91 purchaseOrderService.deletePurchaseOrder(ids);
92 return success(true); 92 return success(true);
@@ -95,7 +95,7 @@ public class ErpPurchaseOrderController { @@ -95,7 +95,7 @@ public class ErpPurchaseOrderController {
95 @GetMapping("/get") 95 @GetMapping("/get")
96 @Operation(summary = "获得采购订单") 96 @Operation(summary = "获得采购订单")
97 @Parameter(name = "id", description = "编号", required = true, example = "1024") 97 @Parameter(name = "id", description = "编号", required = true, example = "1024")
98 - @PreAuthorize("@ss.hasPermission('erp:purchase-create:query')") 98 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:query')")
99 public CommonResult<ErpPurchaseOrderRespVO> getPurchaseOrder(@RequestParam("id") Long id) { 99 public CommonResult<ErpPurchaseOrderRespVO> getPurchaseOrder(@RequestParam("id") Long id) {
100 ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.getPurchaseOrder(id); 100 ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.getPurchaseOrder(id);
101 if (purchaseOrder == null) { 101 if (purchaseOrder == null) {
@@ -115,7 +115,7 @@ public class ErpPurchaseOrderController { @@ -115,7 +115,7 @@ public class ErpPurchaseOrderController {
115 115
116 @GetMapping("/page") 116 @GetMapping("/page")
117 @Operation(summary = "获得采购订单分页") 117 @Operation(summary = "获得采购订单分页")
118 - @PreAuthorize("@ss.hasPermission('erp:purchase-create:query')") 118 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:query')")
119 public CommonResult<PageResult<ErpPurchaseOrderRespVO>> getPurchaseOrderPage(@Valid ErpPurchaseOrderPageReqVO pageReqVO) { 119 public CommonResult<PageResult<ErpPurchaseOrderRespVO>> getPurchaseOrderPage(@Valid ErpPurchaseOrderPageReqVO pageReqVO) {
120 PageResult<ErpPurchaseOrderDO> pageResult = purchaseOrderService.getPurchaseOrderPage(pageReqVO); 120 PageResult<ErpPurchaseOrderDO> pageResult = purchaseOrderService.getPurchaseOrderPage(pageReqVO);
121 return success(buildPurchaseOrderVOPageResult(pageResult)); 121 return success(buildPurchaseOrderVOPageResult(pageResult));
@@ -123,7 +123,7 @@ public class ErpPurchaseOrderController { @@ -123,7 +123,7 @@ public class ErpPurchaseOrderController {
123 123
124 @GetMapping("/export-excel") 124 @GetMapping("/export-excel")
125 @Operation(summary = "导出采购订单 Excel") 125 @Operation(summary = "导出采购订单 Excel")
126 - @PreAuthorize("@ss.hasPermission('erp:purchase-create:export')") 126 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:export')")
127 @ApiAccessLog(operateType = EXPORT) 127 @ApiAccessLog(operateType = EXPORT)
128 public void exportPurchaseOrderExcel(@Valid ErpPurchaseOrderPageReqVO pageReqVO, 128 public void exportPurchaseOrderExcel(@Valid ErpPurchaseOrderPageReqVO pageReqVO,
129 HttpServletResponse response) throws IOException { 129 HttpServletResponse response) throws IOException {
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm
@@ -94,7 +94,7 @@ const { t } = useI18n() // 国际化 @@ -94,7 +94,7 @@ const { t } = useI18n() // 国际化
94 const message = useMessage() // 消息弹窗 94 const message = useMessage() // 消息弹窗
95 95
96 const props = defineProps<{ 96 const props = defineProps<{
97 - ${subJoinColumn.javaField}: undefined // ${subJoinColumn.columnComment}(主表的关联字段) 97 + ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
98 }>() 98 }>()
99 const loading = ref(false) // 列表的加载中 99 const loading = ref(false) // 列表的加载中
100 const list = ref([]) // 列表的数据 100 const list = ref([]) // 列表的数据
@@ -103,17 +103,20 @@ const total = ref(0) // 列表的总页数 @@ -103,17 +103,20 @@ const total = ref(0) // 列表的总页数
103 const queryParams = reactive({ 103 const queryParams = reactive({
104 pageNo: 1, 104 pageNo: 1,
105 pageSize: 10, 105 pageSize: 10,
106 - ${subJoinColumn.javaField}: undefined 106 + ${subJoinColumn.javaField}: undefined as unknown
107 }) 107 })
108 108
109 /** 监听主表的关联字段的变化,加载对应的子表数据 */ 109 /** 监听主表的关联字段的变化,加载对应的子表数据 */
110 watch( 110 watch(
111 () => props.${subJoinColumn.javaField}, 111 () => props.${subJoinColumn.javaField},
112 - (val) => { 112 + (val: number) => {
  113 + if (!val) {
  114 + return
  115 + }
113 queryParams.${subJoinColumn.javaField} = val 116 queryParams.${subJoinColumn.javaField} = val
114 handleQuery() 117 handleQuery()
115 }, 118 },
116 - { immediate: false } 119 + { immediate: true, deep: true }
117 ) 120 )
118 #end 121 #end
119 122
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
@@ -101,7 +101,7 @@ public class AppProductSpuController { @@ -101,7 +101,7 @@ public class AppProductSpuController {
101 throw exception(SPU_NOT_EXISTS); 101 throw exception(SPU_NOT_EXISTS);
102 } 102 }
103 if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) { 103 if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
104 - throw exception(SPU_NOT_ENABLE); 104 + throw exception(SPU_NOT_ENABLE, spu.getName());
105 } 105 }
106 // 获得商品 SKU 106 // 获得商品 SKU
107 List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId()); 107 List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId());
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStorePageReqVO.java
@@ -4,14 +4,11 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -4,14 +4,11 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.validation.InEnum; 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
5 import lombok.*; 5 import lombok.*;
6 6
7 -import java.time.LocalTime;  
8 -import java.util.*;  
9 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
10 import cn.iocoder.yudao.framework.common.pojo.PageParam; 8 import cn.iocoder.yudao.framework.common.pojo.PageParam;
11 import org.springframework.format.annotation.DateTimeFormat; 9 import org.springframework.format.annotation.DateTimeFormat;
12 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
13 11
14 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_HOUR_MINUTE_SECOND;  
15 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; 12 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
16 13
17 @Schema(description = "管理后台 - 自提门店分页 Request VO") 14 @Schema(description = "管理后台 - 自提门店分页 Request VO")