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 16 <module>yudao-module-system</module>
17 17 <module>yudao-module-infra</module>
18 18 <!-- <module>yudao-module-member</module>-->
19   -<!-- <module>yudao-module-bpm</module>-->
  19 + <module>yudao-module-bpm</module>
20 20 <!-- <module>yudao-module-report</module>-->
21 21 <!-- <module>yudao-module-mp</module>-->
22 22 <!-- <module>yudao-module-pay</module>-->
23 23 <!-- <module>yudao-module-mall</module>-->
24   -<!-- <module>yudao-module-crm</module>-->
  24 + <module>yudao-module-crm</module>
25 25 <!-- <module>yudao-module-erp</module>-->
26 26 <!-- 示例项目 -->
27 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 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 7  
8 8 import java.time.Duration;
9 9 import java.util.concurrent.Executor;
  10 +import java.util.concurrent.ExecutorService;
10 11 import java.util.concurrent.Executors;
11 12  
12 13 /**
... ... @@ -17,8 +18,11 @@ import java.util.concurrent.Executors;
17 18 public class CacheUtils {
18 19  
19 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 26 return CacheBuilder.newBuilder()
23 27 // 只阻塞当前数据加载线程,其他线程返回旧值
24 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 78 if (CollUtil.isEmpty(from)) {
79 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 84 public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
... ... @@ -87,7 +87,7 @@ public class CollectionUtils {
87 87 if (CollUtil.isEmpty(from)) {
88 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 93 public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
... ... @@ -123,7 +123,7 @@ public class CollectionUtils {
123 123 if (CollUtil.isEmpty(from)) {
124 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 129 public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
... ... @@ -132,7 +132,7 @@ public class CollectionUtils {
132 132 if (CollUtil.isEmpty(from)) {
133 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 138 public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
... ... @@ -315,4 +315,4 @@ public class CollectionUtils {
315 315 return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
316 316 }
317 317  
318 318 -}
  319 +}
319 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
  5 +import cn.hutool.core.util.ObjUtil;
5 6 import cn.iocoder.yudao.framework.common.core.KeyValue;
6 7 import com.google.common.collect.Maps;
7 8 import com.google.common.collect.Multimap;
... ... @@ -40,6 +41,7 @@ public class MapUtils {
40 41  
41 42 /**
42 43 * 从哈希表查找到 key 对应的 value,然后进一步处理
  44 + * key 为 null 时, 不处理
43 45 * 注意,如果查找到的 value 为 null 时,不进行处理
44 46 *
45 47 * @param map 哈希表
... ... @@ -47,7 +49,7 @@ public class MapUtils {
47 49 * @param consumer 进一步处理的逻辑
48 50 */
49 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 53 return;
52 54 }
53 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 27  
28 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 31 * 将 LocalDateTime 转换成 Date
34 32 *
... ... @@ -67,19 +65,11 @@ public class DateUtils {
67 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 68 public static boolean isExpired(LocalDateTime time) {
75 69 LocalDateTime now = LocalDateTime.now();
76 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 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 132 * @param date 日期
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
1 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 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 12 import java.time.temporal.ChronoUnit;
10 13 import java.time.temporal.TemporalAdjusters;
  14 +import java.util.ArrayList;
  15 +import java.util.List;
11 16  
12 17 /**
13 18 * 时间工具类,用于 {@link java.time.LocalDateTime}
... ... @@ -21,6 +26,22 @@ public class LocalDateTimeUtils {
21 26 */
22 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 45 public static LocalDateTime addTime(Duration duration) {
25 46 return LocalDateTime.now().plus(duration);
26 47 }
... ... @@ -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 96 * @param startTime 开始时间
... ... @@ -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 174 * @param dateTime 日期
... ... @@ -168,4 +214,96 @@ public class LocalDateTimeUtils {
168 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 11 import lombok.extern.slf4j.Slf4j;
12 12  
13 13 import java.time.Duration;
  14 +import java.util.List;
14 15  
15 16 /**
16 17 * 字典工具类
... ... @@ -24,6 +25,7 @@ public class DictFrameworkUtils {
24 25  
25 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 30 * 针对 {@link #getDictDataLabel(String, String)} 的缓存
29 31 */
... ... @@ -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 58 * 针对 {@link #parseDictDataValue(String, String)} 的缓存
43 59 */
44 60 private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
... ... @@ -68,6 +84,11 @@ public class DictFrameworkUtils {
68 84 }
69 85  
70 86 @SneakyThrows
  87 + public static List<String> getDictDataLabelList(String dictType) {
  88 + return GET_DICT_DATA_LIST_CACHE.get(dictType);
  89 + }
  90 +
  91 + @SneakyThrows
71 92 public static String parseDictDataValue(String dictType, String label) {
72 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 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 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 15 import com.alibaba.excel.write.handler.SheetWriteHandler;
8 16 import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
9 17 import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
  18 +import lombok.extern.slf4j.Slf4j;
10 19 import org.apache.poi.hssf.usermodel.HSSFDataValidation;
11 20 import org.apache.poi.ss.usermodel.*;
12 21 import org.apache.poi.ss.util.CellRangeAddressList;
13 22  
  23 +import java.lang.reflect.Field;
14 24 import java.util.Comparator;
  25 +import java.util.HashMap;
15 26 import java.util.List;
16 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 32 * 基于固定 sheet 实现下拉框
21 33 *
22 34 * @author HUIHUI
23 35 */
  36 +@Slf4j
24 37 public class SelectSheetWriteHandler implements SheetWriteHandler {
25 38  
26 39 /**
... ... @@ -36,21 +49,56 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
36 49  
37 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 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 104 @Override
... ... @@ -62,18 +110,20 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
62 110 // 1. 获取相应操作对象
63 111 DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper(); // 需要设置下拉框的 sheet 页的数据验证助手
64 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 116 // 2. 创建数据字典的 sheet 页
67 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 119 int rowLength = keyValue.getValue().size();
70   - // 2.1 设置字典 sheet 页的值 每一列一个字典项
  120 + // 2.1 设置字典 sheet 页的值,每一列一个字典项
71 121 for (int i = 0; i < rowLength; i++) {
72 122 Row row = dictSheet.getRow(i);
73 123 if (row == null) {
74 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 128 // 2.2 设置单元格下拉选择
79 129 setColumnSelect(writeSheetHolder, workbook, helper, keyValue);
... ... @@ -84,10 +134,10 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
84 134 * 设置单元格下拉选择
85 135 */
86 136 private static void setColumnSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper,
87   - KeyValue<ExcelColumn, List<String>> keyValue) {
  137 + KeyValue<Integer, List<String>> keyValue) {
88 138 // 1.1 创建可被其他单元格引用的名称
89 139 Name name = workbook.createName();
90   - String excelColumn = keyValue.getKey().name();
  140 + String excelColumn = ExcelUtil.indexToColName(keyValue.getKey());
91 141 // 1.2 下拉框数据来源 eg:字典sheet!$B1:$B2
92 142 String refers = DICT_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + keyValue.getValue().size();
93 143 name.setNameName("dict" + keyValue.getKey()); // 设置名称的名字
... ... @@ -97,7 +147,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
97 147 DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); // 设置引用约束
98 148 // 设置下拉单元格的首行、末行、首列、末列
99 149 CellRangeAddressList rangeAddressList = new CellRangeAddressList(FIRST_ROW, LAST_ROW,
100   - keyValue.getKey().getColNum(), keyValue.getKey().getColNum());
  150 + keyValue.getKey(), keyValue.getKey());
101 151 DataValidation validation = helper.createValidation(constraint, rangeAddressList);
102 152 if (validation instanceof HSSFDataValidation) {
103 153 validation.setSuppressDropDownArrow(false);
... ...
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
1 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 3 import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
6 4 import com.alibaba.excel.EasyExcel;
7 5 import com.alibaba.excel.converters.longconverter.LongStringConverter;
... ... @@ -34,27 +32,11 @@ public class ExcelUtils {
34 32 */
35 33 public static <T> void write(HttpServletResponse response, String filename, String sheetName,
36 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 35 // 输出 Excel
54 36 EasyExcel.write(response.getOutputStream(), head)
55 37 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
56 38 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
57   - .registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框
  39 + .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
58 40 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
59 41 .sheet(sheetName).doWrite(data);
60 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 40 // 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间
41 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 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 16 @EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
17 17 @AutoConfiguration
18 18 @Slf4j
19   -public class YudaoOperateLogV2Configuration {
  19 +public class YudaoOperateLogConfiguration {
20 20  
21 21 @Bean
22 22 @Primary
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 1 cn.iocoder.yudao.framework.security.config.YudaoSecurityAutoConfiguration
2 2 cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter
3   -cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogV2Configuration
4 3 \ No newline at end of file
  4 +cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogConfiguration
5 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 15 ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态");
16 16 ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态");
17 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 20 // ========== 线索管理 1-020-001-000 ==========
20 21 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
... ... @@ -40,6 +41,7 @@ public interface ErrorCodeConstants {
40 41 ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试");
41 42 ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态");
42 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 46 // ========== 回款计划 1-020-005-000 ==========
45 47 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
... ... @@ -72,6 +74,7 @@ public interface ErrorCodeConstants {
72 74 ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限");
73 75 ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人");
74 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 79 // ========== 产品 1_020_008_000 ==========
77 80 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
... ... @@ -100,4 +103,6 @@ public interface ErrorCodeConstants {
100 103 ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
101 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 142  
143 143 String CRM_RECEIVABLE_TYPE = "CRM 回款";
144 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 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 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 150 String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批";
151 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 1 package cn.iocoder.yudao.module.crm.enums.permission;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.util.ObjUtil;
4 5 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
5 6 import lombok.AllArgsConstructor;
... ... @@ -50,4 +51,10 @@ public enum CrmPermissionLevelEnum implements IntArrayValuable {
50 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 66 private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段
67 67  
68 68 @Schema(description = "产品列表")
69   - private List<Product> products;
  69 + private List<BusinessProduct> businessProducts;
70 70  
71 71 @Schema(description = "产品列表")
72 72 @Data
73 73 @NoArgsConstructor
74 74 @AllArgsConstructor
75   - public static class Product {
  75 + public static class BusinessProduct {
76 76  
77 77 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
78 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 3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
4 4 import io.swagger.v3.oas.annotations.media.Schema;
5 5 import jakarta.validation.constraints.NotNull;
  6 +import lombok.AllArgsConstructor;
6 7 import lombok.Data;
  8 +import lombok.NoArgsConstructor;
7 9  
8 10 @Schema(description = "管理后台 - 商机转移 Request VO")
9 11 @Data
  12 +@NoArgsConstructor
  13 +@AllArgsConstructor
10 14 public class CrmBusinessTransferReqVO {
11 15  
12 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 2  
3 3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
4 4 import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.AllArgsConstructor;
5 6 import lombok.Data;
6 7  
7 8 import jakarta.validation.constraints.NotNull;
  9 +import lombok.NoArgsConstructor;
8 10  
9 11 @Schema(description = "管理后台 - CRM 联系人转移 Request VO")
10 12 @Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
11 15 public class CrmContactTransferReqVO {
12 16  
13 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 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
5 5 import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.AllArgsConstructor;
6 7 import lombok.Data;
7 8  
8 9 import jakarta.validation.constraints.NotNull;
  10 +import lombok.NoArgsConstructor;
9 11  
10 12 @Schema(description = "管理后台 - CRM 合同转移 Request VO")
11 13 @Data
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
12 16 public class CrmContractTransferReqVO {
13 17  
14 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 11 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
12 12 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
13 13 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
14   -import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;
15 14 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
16   -import cn.iocoder.yudao.framework.ip.core.Area;
17 15 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
18 16 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
19 17 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
... ... @@ -38,7 +36,6 @@ import org.springframework.web.bind.annotation.*;
38 36  
39 37 import java.io.IOException;
40 38 import java.time.LocalDateTime;
41   -import java.util.ArrayList;
42 39 import java.util.Arrays;
43 40 import java.util.List;
44 41 import java.util.Map;
... ... @@ -49,7 +46,6 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
49 46 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
50 47 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
51 48 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
52   -import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
53 49 import static java.util.Collections.singletonList;
54 50  
55 51 @Tag(name = "管理后台 - CRM 客户")
... ... @@ -67,8 +63,6 @@ public class CrmCustomerController {
67 63 private DeptApi deptApi;
68 64 @Resource
69 65 private AdminUserApi adminUserApi;
70   - @Resource
71   - private DictDataApi dictDataApi;
72 66  
73 67 @PostMapping("/create")
74 68 @Operation(summary = "创建客户")
... ... @@ -142,7 +136,7 @@ public class CrmCustomerController {
142 136 return java.util.Collections.emptyList();
143 137 }
144 138 // 1.1 获取创建人、负责人列表
145   - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
  139 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(list,
146 140 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
147 141 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
148 142 // 1.2 获取距离进入公海的时间
... ... @@ -265,25 +259,7 @@ public class CrmCustomerController {
265 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 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 1 package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2  
3 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
4 5 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
5 6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
  7 +import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunction;
6 8 import com.alibaba.excel.annotation.ExcelProperty;
7 9 import lombok.AllArgsConstructor;
8 10 import lombok.Builder;
... ... @@ -41,6 +43,7 @@ public class CrmCustomerImportExcelVO {
41 43 private String email;
42 44  
43 45 @ExcelProperty(value = "地区", converter = AreaConvert.class)
  46 + @ExcelColumnSelect(functionName = AreaExcelColumnSelectFunction.NAME)
44 47 private Integer areaId;
45 48  
46 49 @ExcelProperty("详细地址")
... ... @@ -48,14 +51,17 @@ public class CrmCustomerImportExcelVO {
48 51  
49 52 @ExcelProperty(value = "所属行业", converter = DictConvert.class)
50 53 @DictFormat(CRM_CUSTOMER_INDUSTRY)
  54 + @ExcelColumnSelect(dictType = CRM_CUSTOMER_INDUSTRY)
51 55 private Integer industryId;
52 56  
53 57 @ExcelProperty(value = "客户等级", converter = DictConvert.class)
54 58 @DictFormat(CRM_CUSTOMER_LEVEL)
  59 + @ExcelColumnSelect(dictType = CRM_CUSTOMER_LEVEL)
55 60 private Integer level;
56 61  
57 62 @ExcelProperty(value = "客户来源", converter = DictConvert.class)
58 63 @DictFormat(CRM_CUSTOMER_SOURCE)
  64 + @ExcelColumnSelect(dictType = CRM_CUSTOMER_SOURCE)
59 65 private Integer source;
60 66  
61 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 5 import jakarta.validation.constraints.NotNull;
6 6 import lombok.Data;
7 7  
  8 +import java.util.List;
  9 +
8 10 @Schema(description = "管理后台 - CRM 客户转移 Request VO")
9 11 @Data
10 12 public class CrmCustomerTransferReqVO {
... ... @@ -28,4 +30,10 @@ public class CrmCustomerTransferReqVO {
28 30 @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
29 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 1 package cn.iocoder.yudao.module.crm.controller.admin.permission;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 7 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8   -import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO;
9 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 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 15 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
  16 +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
12 17 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
13 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 22 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
15 23 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
16 24 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
... ... @@ -27,13 +35,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
27 35 import jakarta.annotation.Resource;
28 36 import jakarta.validation.Valid;
29 37 import org.springframework.security.access.prepost.PreAuthorize;
  38 +import org.springframework.transaction.annotation.Transactional;
30 39 import org.springframework.validation.annotation.Validated;
31 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 43 import java.util.stream.Stream;
38 44  
39 45 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -50,7 +56,6 @@ public class CrmPermissionController {
50 56  
51 57 @Resource
52 58 private CrmPermissionService permissionService;
53   -
54 59 @Resource
55 60 private AdminUserApi adminUserApi;
56 61 @Resource
... ... @@ -61,9 +66,8 @@ public class CrmPermissionController {
61 66 @PostMapping("/create")
62 67 @Operation(summary = "创建数据权限")
63 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 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 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 6 import io.swagger.v3.oas.annotations.media.Schema;
  7 +import jakarta.validation.constraints.NotNull;
4 8 import lombok.Data;
5 9  
6 10 import java.time.LocalDateTime;
... ... @@ -8,11 +12,29 @@ import java.util.Set;
8 12  
9 13 @Schema(description = "管理后台 - CRM 数据权限 Response VO")
10 14 @Data
11   -public class CrmPermissionRespVO extends CrmPermissionBaseVO {
  15 +public class CrmPermissionRespVO {
12 16  
13 17 @Schema(description = "数据权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
14 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 38 @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
17 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 8  
9 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 14 @Data
18   -public class CrmPermissionBaseVO {
  15 +public class CrmPermissionSaveReqVO {
19 16  
20 17 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
21 18 @NotNull(message = "用户编号不能为空")
... ... @@ -35,4 +32,11 @@ public class CrmPermissionBaseVO {
35 32 @NotNull(message = "权限级别不能为空")
36 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 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
2 2  
3 3 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
  4 +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 5 import com.alibaba.excel.annotation.ExcelProperty;
5 6 import io.swagger.v3.oas.annotations.media.Schema;
6 7 import lombok.Data;
... ... @@ -8,53 +9,69 @@ import lombok.Data;
8 9 import java.math.BigDecimal;
9 10 import java.time.LocalDateTime;
10 11  
11   -// TODO @puhui999:缺导出
12 12 @Schema(description = "管理后台 - CRM 回款计划 Response VO")
13 13 @Data
  14 +@ExcelIgnoreUnannotated
14 15 public class CrmReceivablePlanRespVO {
15 16  
16 17 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  18 + @ExcelProperty("编号")
17 19 private Long id;
18 20  
19 21 @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  22 + @ExcelProperty("期数")
20 23 private Integer period;
21 24  
22 25 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  26 + @ExcelProperty("客户编号")
23 27 private Long customerId;
24 28 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
  29 + @ExcelProperty("客户名字")
25 30 private String customerName;
26 31  
27 32 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  33 + @ExcelProperty("合同编号")
28 34 private Long contractId;
29 35 @Schema(description = "合同编号", example = "Q110")
  36 + @ExcelProperty("合同编号")
30 37 private String contractNo;
31 38  
32 39 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  40 + @ExcelProperty("负责人编号")
33 41 private Long ownerUserId;
34 42 @Schema(description = "负责人", example = "test")
  43 + @ExcelProperty("负责人")
35 44 private String ownerUserName;
36 45  
37 46 @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  47 + @ExcelProperty("计划回款日期")
38 48 private LocalDateTime returnTime;
39 49  
40 50 @Schema(description = "计划回款方式", example = "1")
  51 + @ExcelProperty("计划回款方式")
41 52 private Integer returnType;
42 53  
43 54 @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  55 + @ExcelProperty("计划回款金额")
44 56 private BigDecimal price;
45 57  
46 58 @Schema(description = "回款编号", example = "19852")
  59 + @ExcelProperty("回款编号")
47 60 private Long receivableId;
48 61 @Schema(description = "回款信息")
  62 + @ExcelProperty("回款信息")
49 63 private CrmReceivableRespVO receivable;
50 64  
51 65 @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  66 + @ExcelProperty("提前几天提醒")
52 67 private Integer remindDays;
53 68  
54 69 @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  70 + @ExcelProperty("提醒日期")
55 71 private LocalDateTime remindTime;
56 72  
57 73 @Schema(description = "备注", example = "备注")
  74 + @ExcelProperty("备注")
58 75 private String remark;
59 76  
60 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 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
2 2  
3 3 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
  4 +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 5 import com.alibaba.excel.annotation.ExcelProperty;
5 6 import io.swagger.v3.oas.annotations.media.Schema;
6 7 import lombok.Data;
... ... @@ -8,37 +9,47 @@ import lombok.Data;
8 9 import java.math.BigDecimal;
9 10 import java.time.LocalDateTime;
10 11  
11   -// TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能
12 12 @Schema(description = "管理后台 - CRM 回款 Response VO")
13 13 @Data
  14 +@ExcelIgnoreUnannotated
14 15 public class CrmReceivableRespVO {
15 16  
16 17 @Schema(description = "编号", example = "25787")
  18 + @ExcelProperty("编号")
17 19 private Long id;
18 20  
19 21 @Schema(description = "回款编号", example = "31177")
  22 + @ExcelProperty("回款编号")
20 23 private String no;
21 24  
22 25 @Schema(description = "回款计划编号", example = "1024")
  26 + @ExcelProperty("回款计划编号")
23 27 private Long planId;
24 28  
25 29 @Schema(description = "回款方式", example = "2")
  30 + @ExcelProperty("回款方式")
26 31 private Integer returnType;
27 32  
28 33 @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  34 + @ExcelProperty("回款金额")
29 35 private BigDecimal price;
30 36  
31 37 @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  38 + @ExcelProperty("计划回款日期")
32 39 private LocalDateTime returnTime;
33 40  
34 41 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  42 + @ExcelProperty("客户编号")
35 43 private Long customerId;
36 44 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
  45 + @ExcelProperty("客户名字")
37 46 private String customerName;
38 47  
39 48 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  49 + @ExcelProperty("合同编号")
40 50 private Long contractId;
41 51 @Schema(description = "合同信息")
  52 + @ExcelProperty("合同信息")
42 53 private CrmContractRespVO contract;
43 54  
44 55 @Schema(description = "负责人的用户编号", example = "25682")
... ... @@ -56,20 +67,26 @@ public class CrmReceivableRespVO {
56 67 private String processInstanceId;
57 68  
58 69 @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  70 + @ExcelProperty("审批状态")
59 71 private Integer auditStatus;
60 72  
61   - @Schema(description = "备注", example = "备注")
  73 + @Schema(description = "工作流编号", example = "备注")
  74 + @ExcelProperty("工作流编号")
62 75 private String remark;
63 76  
64 77 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  78 + @ExcelProperty("创建时间")
65 79 private LocalDateTime createTime;
66 80  
67 81 @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  82 + @ExcelProperty("更新时间")
68 83 private LocalDateTime updateTime;
69 84  
70 85 @Schema(description = "创建人", example = "25682")
  86 + @ExcelProperty("创建人")
71 87 private String creator;
72 88 @Schema(description = "创建人名字", example = "test")
  89 + @ExcelProperty("创建人名字")
73 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 1 package cn.iocoder.yudao.module.crm.controller.admin.statistics;
2 2  
3 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 7 import io.swagger.v3.oas.annotations.Operation;
8 8 import io.swagger.v3.oas.annotations.tags.Tag;
9 9 import jakarta.annotation.Resource;
... ... @@ -18,7 +18,6 @@ import java.util.List;
18 18  
19 19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20 20  
21   -
22 21 @Tag(name = "管理后台 - CRM 排行榜统计")
23 22 @RestController
24 23 @RequestMapping("/crm/statistics-rank")
... ... @@ -26,62 +25,62 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
26 25 public class CrmStatisticsRankController {
27 26  
28 27 @Resource
29   - private CrmStatisticsRankingService rankingService;
  28 + private CrmStatisticsRankService rankService;
30 29  
31 30 @GetMapping("/get-contract-price-rank")
32 31 @Operation(summary = "获得合同金额排行榜")
33 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 37 @GetMapping("/get-receivable-price-rank")
39 38 @Operation(summary = "获得回款金额排行榜")
40 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 44 @GetMapping("/get-contract-count-rank")
46 45 @Operation(summary = "获得签约合同数量排行榜")
47 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 51 @GetMapping("/get-product-sales-rank")
53 52 @Operation(summary = "获得产品销量排行榜")
54 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 58 @GetMapping("/get-customer-count-rank")
60 59 @Operation(summary = "获得新增客户数排行榜")
61 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 65 @GetMapping("/get-contacts-count-rank")
67 66 @Operation(summary = "获得新增联系人数排行榜")
68 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 72 @GetMapping("/get-follow-count-rank")
74 73 @Operation(summary = "获得跟进次数排行榜")
75 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 79 @GetMapping("/get-follow-customer-count-rank")
81 80 @Operation(summary = "获得跟进客户数排行榜")
82 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 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotEmpty;
... ... @@ -21,7 +21,7 @@ public class CrmStatisticsRankReqVO {
21 21  
22 22 /**
23 23 * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来
24   - *
  24 + * <p>
25 25 * 后续,可能会支持选择部分用户进行查询
26 26 */
27 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 3 import io.swagger.v3.oas.annotations.media.Schema;
4 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 10 @Data
9   -public class CrmStatisticsRanKRespVO {
  11 +public class CrmStatisticsRankRespVO {
10 12  
11 13 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
12 14 private Long ownerUserId;
... ... @@ -22,8 +24,10 @@ public class CrmStatisticsRanKRespVO {
22 24 *
23 25 * 1. 金额:合同金额排行、回款金额排行
24 26 * 2. 个数:签约合同排行、产品销量排行、产品销量排行、新增客户数排行、新增联系人排行、跟进次数排行、跟进客户数排行
  27 + *
  28 + * 为什么使用 BigDecimal 的原因:
25 29 */
26 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 6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
7 7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
8 8 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  9 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
9 10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
10 11 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
11 12 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
12 13 import org.apache.ibatis.annotations.Mapper;
13 14  
14 15 import java.util.Collection;
  16 +import java.util.List;
15 17  
16 18 /**
17 19 * 商机 Mapper
... ... @@ -57,4 +59,10 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
57 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 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 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 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 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 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 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 5 import org.apache.ibatis.annotations.Mapper;
6 6  
7 7 import java.util.List;
... ... @@ -12,7 +12,7 @@ import java.util.List;
12 12 * @author anhaohao
13 13 */
14 14 @Mapper
15   -public interface CrmStatisticsRankingMapper {
  15 +public interface CrmStatisticsRankMapper {
16 16  
17 17 /**
18 18 * 查询合同金额排行榜
... ... @@ -20,7 +20,7 @@ public interface CrmStatisticsRankingMapper {
20 20 * @param rankReqVO 参数
21 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 28 * @param rankReqVO 参数
29 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 36 * @param rankReqVO 参数
37 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 44 * @param rankReqVO 参数
45 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 52 * @param rankReqVO 参数
53 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 60 * @param rankReqVO 参数
61 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 68 * @param rankReqVO 参数
69 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 76 * @param rankReqVO 参数
77 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 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 4 package cn.iocoder.yudao.module.crm.framework.operatelog;
2 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 4 package cn.iocoder.yudao.module.crm.framework.permission;
2 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 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 46 /**
47 47 * 更新商机相关跟进信息
48 48 *
49   - * @param id 编号
50   - * @param contactNextTime 下次联系时间
  49 + * @param id 编号
  50 + * @param contactNextTime 下次联系时间
51 51 * @param contactLastContent 最后联系内容
52 52 */
53 53 void updateBusinessFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
... ... @@ -55,7 +55,7 @@ public interface CrmBusinessService {
55 55 /**
56 56 * 更新商机的下次联系时间
57 57 *
58   - * @param ids 编号数组
  58 + * @param ids 编号数组
59 59 * @param contactNextTime 下次联系时间
60 60 */
61 61 void updateBusinessContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime);
... ... @@ -185,4 +185,13 @@ public interface CrmBusinessService {
185 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 88 success = CRM_BUSINESS_CREATE_SUCCESS)
89 89 public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
90 90 // 1.1 校验产品项的有效性
91   - List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getProducts());
  91 + List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getBusinessProducts());
92 92 // 1.2 校验关联字段
93 93 validateRelationDataExists(createReqVO);
94 94  
... ... @@ -129,7 +129,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
129 129 // 1.1 校验存在
130 130 CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
131 131 // 1.2 校验产品项的有效性
132   - List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts());
  132 + List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getBusinessProducts());
133 133 // 1.3 校验关联字段
134 134 validateRelationDataExists(updateReqVO);
135 135  
... ... @@ -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 206 // 1. 校验产品存在
207   - productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.Product::getProductId));
  207 + productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.BusinessProduct::getProductId));
208 208 // 2. 转化为 CrmBusinessProductDO 列表
209 209 return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class,
210 210 item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount()))));
... ... @@ -234,7 +234,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
234 234 }
235 235 // 1.4 校验是不是状态没变更
236 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 238 throw exception(BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS);
239 239 }
240 240  
... ... @@ -301,7 +301,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
301 301  
302 302 // 2.1 数据权限转移
303 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 305 // 2.2 设置新的负责人
306 306 businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
307 307  
... ... @@ -370,4 +370,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
370 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 75 /**
76 76 * 更新联系人的下次联系时间
77 77 *
78   - * @param ids 编号数组
79   - * @param contactNextTime 下次联系时间
  78 + * @param ids 编号数组
  79 + * @param contactNextTime 下次联系时间
80 80 */
81 81 void updateContactContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime);
82 82  
... ... @@ -160,4 +160,13 @@ public interface CrmContactService {
160 160 */
161 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 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 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 58 /**
59 59 * 更新合同相关的更进信息
60 60 *
61   - * @param id 合同编号
62   - * @param contactNextTime 下次联系时间
  61 + * @param id 合同编号
  62 + * @param contactNextTime 下次联系时间
63 63 * @param contactLastContent 最后联系内容
64 64 */
65 65 void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
... ... @@ -75,7 +75,7 @@ public interface CrmContractService {
75 75 /**
76 76 * 更新合同流程审批结果
77 77 *
78   - * @param id 合同编号
  78 + * @param id 合同编号
79 79 * @param bpmResult BPM 审批结果
80 80 */
81 81 void updateContractAuditStatus(Long id, Integer bpmResult);
... ... @@ -193,4 +193,13 @@ public interface CrmContractService {
193 193 */
194 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 30 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
31 31 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
32 32 import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
  33 +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
33 34 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
34 35 import com.mzt.logapi.context.LogRecordContext;
35 36 import com.mzt.logapi.service.impl.DiffParseFunction;
36 37 import com.mzt.logapi.starter.annotation.LogRecord;
37 38 import jakarta.annotation.Resource;
38 39 import lombok.extern.slf4j.Slf4j;
  40 +import org.springframework.context.annotation.Lazy;
39 41 import org.springframework.stereotype.Service;
40 42 import org.springframework.transaction.annotation.Transactional;
41 43 import org.springframework.validation.annotation.Validated;
... ... @@ -86,7 +88,9 @@ public class CrmContractServiceImpl implements CrmContractService {
86 88 private CrmContactService contactService;
87 89 @Resource
88 90 private CrmContractConfigService contractConfigService;
89   -
  91 + @Resource
  92 + @Lazy // 延迟加载,避免循环依赖
  93 + private CrmReceivableService receivableService;
90 94 @Resource
91 95 private AdminUserApi adminUserApi;
92 96 @Resource
... ... @@ -222,15 +226,19 @@ public class CrmContractServiceImpl implements CrmContractService {
222 226 success = CRM_CONTRACT_DELETE_SUCCESS)
223 227 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
224 228 public void deleteContract(Long id) {
225   - // TODO @puhui999:如果被 CrmReceivableDO 所使用,则不允许删除
226   - // 校验存在
  229 + // 1.1 校验存在
227 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 237 contractMapper.deleteById(id);
230   - // 删除数据权限
  238 + // 2.2 删除数据权限
231 239 crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTRACT.getType(), id);
232 240  
233   - // 记录操作日志上下文
  241 + // 3. 记录操作日志上下文
234 242 LogRecordContext.putVariable("contractName", contract.getName());
235 243 }
236 244  
... ... @@ -399,4 +407,9 @@ public class CrmContractServiceImpl implements CrmContractService {
399 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 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
10 10 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
11 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 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 19 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
14 20 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
15 21 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
... ... @@ -201,7 +207,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
201 207 CrmCustomerDO customer = validateCustomerExists(reqVO.getId());
202 208 // 1.2 校验拥有客户是否到达上限
203 209 validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1);
204   -
205 210 // 2.1 数据权限转移
206 211 permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
207 212 reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
... ... @@ -209,10 +214,45 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
209 214 customerMapper.updateById(new CrmCustomerDO().setId(reqVO.getId())
210 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 222 // 3. 记录转移日志
213 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 256 @Override
217 257 @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_LOCK_SUB_TYPE, bizNo = "{{#lockReqVO.id}}",
218 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 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 5 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
5 6 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
6 7 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
... ... @@ -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 34 * @param createReqBO 创建信息
26 35 * @return 编号
27 36 */
... ... @@ -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 127 * @return 是否有权限
119 128 */
120 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 4 import cn.hutool.core.util.ObjUtil;
5 5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
7 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 12 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
9 13 import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper;
10 14 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 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 20 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
13 21 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
14 22 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
15 23 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  24 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
16 25 import jakarta.annotation.Resource;
  26 +import org.springframework.context.annotation.Lazy;
17 27 import org.springframework.stereotype.Service;
18 28 import org.springframework.transaction.annotation.Transactional;
19 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 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 35 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
30 36 import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner;
31 37  
... ... @@ -40,18 +46,119 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
40 46  
41 47 @Resource
42 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 58 @Resource
45 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 151 @Override
48 152 @Transactional(rollbackFor = Exception.class)
49 153 public Long createPermission(CrmPermissionCreateReqBO createReqBO) {
  154 + return createPermission0(createReqBO);
  155 + }
  156 +
  157 + private Long createPermission0(CrmPermissionCreateReqBO createReqBO) {
50 158 validatePermissionNotExists(Collections.singletonList(createReqBO));
51 159 // 1. 校验用户是否存在
52 160 adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId()));
53   -
54   - // 2. 创建
  161 + // 2. 插入权限
55 162 CrmPermissionDO permission = BeanUtils.toBean(createReqBO, CrmPermissionDO.class);
56 163 permissionMapper.insert(permission);
57 164 return permission.getId();
... ... @@ -170,7 +277,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
170 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 281 if (permission == null) {
175 282 throw exception(CRM_PERMISSION_DELETE_DENIED);
176 283 }
... ... @@ -220,4 +327,9 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
220 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 20 import com.mzt.logapi.service.impl.DiffParseFunction;
21 21 import com.mzt.logapi.starter.annotation.LogRecord;
22 22 import jakarta.annotation.Resource;
23   -import org.springframework.context.annotation.Lazy;
24 23 import org.springframework.stereotype.Service;
25 24 import org.springframework.transaction.annotation.Transactional;
26 25 import org.springframework.validation.annotation.Validated;
... ... @@ -47,9 +46,6 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
47 46 private CrmReceivablePlanMapper receivablePlanMapper;
48 47  
49 48 @Resource
50   - @Lazy // 延迟加载,避免循环依赖
51   - private CrmReceivableService receivableService;
52   - @Resource
53 49 private CrmContractService contractService;
54 50 @Resource
55 51 private CrmPermissionService permissionService;
... ... @@ -144,7 +140,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
144 140 // 2. 删除
145 141 receivablePlanMapper.deleteById(id);
146 142 // 3. 删除数据权限
147   - permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
  143 + permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), id);
148 144  
149 145 // 4. 记录操作日志上下文
150 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 122 */
123 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 37 import org.springframework.validation.annotation.Validated;
38 38  
39 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 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
46 43 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
... ... @@ -81,7 +78,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
81 78 @Resource
82 79 private BpmProcessInstanceApi bpmProcessInstanceApi;
83 80  
84   - // TODO @puhui999:操作日志没记录上
85 81 @Override
86 82 @Transactional(rollbackFor = Exception.class)
87 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 111  
116 112 // 5. 记录操作日志上下文
117 113 LogRecordContext.putVariable("receivable", receivable);
  114 + LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
118 115 return receivable.getId();
119 116 }
120 117  
... ... @@ -156,7 +153,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
156 153 }
157 154 }
158 155  
159   - // TODO @puhui999:操作日志没记录上
160 156 @Override
161 157 @Transactional(rollbackFor = Exception.class)
162 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 160 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
165 161 public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) {
166 162 Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
167   - // 1.1 校验可回款金额超过上限
168   - validateReceivablePriceExceedsLimit(updateReqVO);
169 163 updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
170   - // 1.2 校验存在
  164 + // 1.1 校验存在
171 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 171 // 1.3 只有草稿、审批中,可以编辑;
173 172 if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
174 173 CrmAuditStatusEnum.PROCESS.getStatus())) {
... ... @@ -180,8 +179,17 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
180 179 receivableMapper.updateById(updateObj);
181 180  
182 181 // 3. 记录操作日志上下文
183   - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
184 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 195 @Override
... ... @@ -212,15 +220,19 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
212 220 if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
213 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 229 receivableMapper.deleteById(id);
219   - // 3. 删除数据权限
  230 + // 2.2 删除数据权限
220 231 permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id);
221 232  
222   - // 4. 记录操作日志上下文
  233 + // 3. 记录操作日志上下文
223 234 LogRecordContext.putVariable("receivable", receivable);
  235 + LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
224 236 }
225 237  
226 238 @Override
... ... @@ -289,4 +301,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
289 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 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 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 7 import java.util.List;
8 8  
... ... @@ -11,7 +11,7 @@ import java.util.List;
11 11 *
12 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 19 * @param rankReqVO 排行参数
20 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 27 * @param rankReqVO 排行参数
28 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 35 * @param rankReqVO 排行参数
36 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 43 * @param rankReqVO 排行参数
44 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 51 * @param rankReqVO 排行参数
52 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 59 * @param rankReqVO 排行参数
60 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 67 * @param rankReqVO 排行参数
68 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 75 * @param rankReqVO 排行参数
76 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 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 8 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
9 9 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
10 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 29 */
30 30 @Service
31 31 @Validated
32   -public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingService {
  32 +public class CrmStatisticsRankServiceImpl implements CrmStatisticsRankService {
33 33  
34 34 @Resource
35   - private CrmStatisticsRankingMapper rankMapper;
  35 + private CrmStatisticsRankMapper rankMapper;
36 36  
37 37 @Resource
38 38 private AdminUserApi adminUserApi;
... ... @@ -40,64 +40,64 @@ public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingServ
40 40 private DeptApi deptApi;
41 41  
42 42 @Override
43   - public List<CrmStatisticsRanKRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO) {
  43 + public List<CrmStatisticsRankRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO) {
44 44 return getRank(rankReqVO, rankMapper::selectContractPriceRank);
45 45 }
46 46  
47 47 @Override
48   - public List<CrmStatisticsRanKRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO) {
  48 + public List<CrmStatisticsRankRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO) {
49 49 return getRank(rankReqVO, rankMapper::selectReceivablePriceRank);
50 50 }
51 51  
52 52 @Override
53   - public List<CrmStatisticsRanKRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO) {
  53 + public List<CrmStatisticsRankRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO) {
54 54 return getRank(rankReqVO, rankMapper::selectContractCountRank);
55 55 }
56 56  
57 57 @Override
58   - public List<CrmStatisticsRanKRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO) {
  58 + public List<CrmStatisticsRankRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO) {
59 59 return getRank(rankReqVO, rankMapper::selectProductSalesRank);
60 60 }
61 61  
62 62 @Override
63   - public List<CrmStatisticsRanKRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
  63 + public List<CrmStatisticsRankRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
64 64 return getRank(rankReqVO, rankMapper::selectCustomerCountRank);
65 65 }
66 66  
67 67 @Override
68   - public List<CrmStatisticsRanKRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO) {
  68 + public List<CrmStatisticsRankRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO) {
69 69 return getRank(rankReqVO, rankMapper::selectContactsCountRank);
70 70 }
71 71  
72 72 @Override
73   - public List<CrmStatisticsRanKRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO) {
  73 + public List<CrmStatisticsRankRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO) {
74 74 return getRank(rankReqVO, rankMapper::selectFollowCountRank);
75 75 }
76 76  
77 77 @Override
78   - public List<CrmStatisticsRanKRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
  78 + public List<CrmStatisticsRankRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
79 79 return getRank(rankReqVO, rankMapper::selectFollowCustomerCountRank);
80 80 }
81 81  
82 82 /**
83 83 * 获得排行版数据
84 84 *
85   - * @param rankReqVO 参数
  85 + * @param rankReqVO 参数
86 86 * @param rankFunction 排行榜方法
87 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 90 // 1. 获得用户编号数组
91 91 rankReqVO.setUserIds(getUserIds(rankReqVO.getDeptId()));
92 92 if (CollUtil.isEmpty(rankReqVO.getUserIds())) {
93 93 return Collections.emptyList();
94 94 }
95 95 // 2. 获得排行数据
96   - List<CrmStatisticsRanKRespVO> ranks = rankFunction.apply(rankReqVO);
  96 + List<CrmStatisticsRankRespVO> ranks = rankFunction.apply(rankReqVO);
97 97 if (CollUtil.isEmpty(ranks)) {
98 98 return Collections.emptyList();
99 99 }
100   - ranks.sort(Comparator.comparing(CrmStatisticsRanKRespVO::getCount).reversed());
  100 + ranks.sort(Comparator.comparing(CrmStatisticsRankRespVO::getCount).reversed());
101 101 // 3. 拼接用户信息
102 102 appendUserInfo(ranks);
103 103 return ranks;
... ... @@ -108,8 +108,8 @@ public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingServ
108 108 *
109 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 113 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
114 114 ranks.forEach(rank -> MapUtils.findAndThen(userMap, rank.getOwnerUserId(), user -> {
115 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 1 <?xml version="1.0" encoding="UTF-8"?>
2 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 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 7 SELECT IFNULL(SUM(total_price), 0) AS count, owner_user_id
8 8 FROM crm_contract
9 9 WHERE deleted = 0
10   - AND audit_status = 20
  10 + AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
11 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 16 GROUP BY owner_user_id
18 17 </select>
19 18  
20 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 21 SELECT IFNULL(SUM(price), 0) AS count, owner_user_id
23 22 FROM crm_receivable
24 23 WHERE deleted = 0
25   - AND audit_status = 20
  24 + AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
26 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 30 GROUP BY owner_user_id
33 31 </select>
34 32  
35 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 35 SELECT COUNT(1) AS count, owner_user_id
38 36 FROM crm_contract
39 37 WHERE deleted = 0
40   - AND audit_status = 20
  38 + AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
41 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 44 GROUP BY owner_user_id
48 45 </select>
49 46  
50   - <!-- TODO 待定 这里是否需要关联 crm_contract_product 表,计算销售额 -->
51 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 60 </select>
65 61  
66 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 64 SELECT COUNT(1) AS count, owner_user_id
69 65 FROM crm_customer
70 66 WHERE deleted = 0
71 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 72 GROUP BY owner_user_id
78 73 </select>
79 74  
80 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 77 SELECT COUNT(1) AS count, owner_user_id
83 78 FROM crm_contact
84 79 WHERE deleted = 0
85 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 85 GROUP BY owner_user_id
92 86 </select>
93 87  
94 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 90 SELECT COUNT(1) AS count, cc.owner_user_id
97 91 FROM crm_follow_up_record AS cfur
98 92 LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
99 93 WHERE cfur.deleted = 0
100 94 AND cc.deleted = 0
101 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 100 GROUP BY cc.owner_user_id
108 101 </select>
109 102  
110 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 105 SELECT COUNT(DISTINCT cc.id) AS count, cc.owner_user_id
113 106 FROM crm_follow_up_record AS cfur
114 107 LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
115 108 WHERE cfur.deleted = 0
116 109 AND cc.deleted = 0
117 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 115 GROUP BY cc.owner_user_id
124 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 61  
62 62 @PostMapping("/create")
63 63 @Operation(summary = "创建采购订单")
64   - @PreAuthorize("@ss.hasPermission('erp:purchase-create:create')")
  64 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:create')")
65 65 public CommonResult<Long> createPurchaseOrder(@Valid @RequestBody ErpPurchaseOrderSaveReqVO createReqVO) {
66 66 return success(purchaseOrderService.createPurchaseOrder(createReqVO));
67 67 }
68 68  
69 69 @PutMapping("/update")
70 70 @Operation(summary = "更新采购订单")
71   - @PreAuthorize("@ss.hasPermission('erp:purchase-create:update')")
  71 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:update')")
72 72 public CommonResult<Boolean> updatePurchaseOrder(@Valid @RequestBody ErpPurchaseOrderSaveReqVO updateReqVO) {
73 73 purchaseOrderService.updatePurchaseOrder(updateReqVO);
74 74 return success(true);
... ... @@ -76,7 +76,7 @@ public class ErpPurchaseOrderController {
76 76  
77 77 @PutMapping("/update-status")
78 78 @Operation(summary = "更新采购订单的状态")
79   - @PreAuthorize("@ss.hasPermission('erp:purchase-create:update-status')")
  79 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:update-status')")
80 80 public CommonResult<Boolean> updatePurchaseOrderStatus(@RequestParam("id") Long id,
81 81 @RequestParam("status") Integer status) {
82 82 purchaseOrderService.updatePurchaseOrderStatus(id, status);
... ... @@ -86,7 +86,7 @@ public class ErpPurchaseOrderController {
86 86 @DeleteMapping("/delete")
87 87 @Operation(summary = "删除采购订单")
88 88 @Parameter(name = "ids", description = "编号数组", required = true)
89   - @PreAuthorize("@ss.hasPermission('erp:purchase-create:delete')")
  89 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:delete')")
90 90 public CommonResult<Boolean> deletePurchaseOrder(@RequestParam("ids") List<Long> ids) {
91 91 purchaseOrderService.deletePurchaseOrder(ids);
92 92 return success(true);
... ... @@ -95,7 +95,7 @@ public class ErpPurchaseOrderController {
95 95 @GetMapping("/get")
96 96 @Operation(summary = "获得采购订单")
97 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 99 public CommonResult<ErpPurchaseOrderRespVO> getPurchaseOrder(@RequestParam("id") Long id) {
100 100 ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.getPurchaseOrder(id);
101 101 if (purchaseOrder == null) {
... ... @@ -115,7 +115,7 @@ public class ErpPurchaseOrderController {
115 115  
116 116 @GetMapping("/page")
117 117 @Operation(summary = "获得采购订单分页")
118   - @PreAuthorize("@ss.hasPermission('erp:purchase-create:query')")
  118 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:query')")
119 119 public CommonResult<PageResult<ErpPurchaseOrderRespVO>> getPurchaseOrderPage(@Valid ErpPurchaseOrderPageReqVO pageReqVO) {
120 120 PageResult<ErpPurchaseOrderDO> pageResult = purchaseOrderService.getPurchaseOrderPage(pageReqVO);
121 121 return success(buildPurchaseOrderVOPageResult(pageResult));
... ... @@ -123,7 +123,7 @@ public class ErpPurchaseOrderController {
123 123  
124 124 @GetMapping("/export-excel")
125 125 @Operation(summary = "导出采购订单 Excel")
126   - @PreAuthorize("@ss.hasPermission('erp:purchase-create:export')")
  126 + @PreAuthorize("@ss.hasPermission('erp:purchase-order:export')")
127 127 @ApiAccessLog(operateType = EXPORT)
128 128 public void exportPurchaseOrderExcel(@Valid ErpPurchaseOrderPageReqVO pageReqVO,
129 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 94 const message = useMessage() // 消息弹窗
95 95  
96 96 const props = defineProps<{
97   - ${subJoinColumn.javaField}: undefined // ${subJoinColumn.columnComment}(主表的关联字段)
  97 + ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}(主表的关联字段)
98 98 }>()
99 99 const loading = ref(false) // 列表的加载中
100 100 const list = ref([]) // 列表的数据
... ... @@ -103,17 +103,20 @@ const total = ref(0) // 列表的总页数
103 103 const queryParams = reactive({
104 104 pageNo: 1,
105 105 pageSize: 10,
106   - ${subJoinColumn.javaField}: undefined
  106 + ${subJoinColumn.javaField}: undefined as unknown
107 107 })
108 108  
109 109 /** 监听主表的关联字段的变化,加载对应的子表数据 */
110 110 watch(
111 111 () => props.${subJoinColumn.javaField},
112   - (val) => {
  112 + (val: number) => {
  113 + if (!val) {
  114 + return
  115 + }
113 116 queryParams.${subJoinColumn.javaField} = val
114 117 handleQuery()
115 118 },
116   - { immediate: false }
  119 + { immediate: true, deep: true }
117 120 )
118 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 101 throw exception(SPU_NOT_EXISTS);
102 102 }
103 103 if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
104   - throw exception(SPU_NOT_ENABLE);
  104 + throw exception(SPU_NOT_ENABLE, spu.getName());
105 105 }
106 106 // 获得商品 SKU
107 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 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
5 5 import lombok.*;
6 6  
7   -import java.time.LocalTime;
8   -import java.util.*;
9 7 import io.swagger.v3.oas.annotations.media.Schema;
10 8 import cn.iocoder.yudao.framework.common.pojo.PageParam;
11 9 import org.springframework.format.annotation.DateTimeFormat;
12 10 import java.time.LocalDateTime;
13 11  
14   -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_HOUR_MINUTE_SECOND;
15 12 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
16 13  
17 14 @Schema(description = "管理后台 - 自提门店分页 Request VO")
... ...