# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/errorcode/config/ErrorCodeProperties.java
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactTransferReqVO.java
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractTransferReqVO.java
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionSaveReqVO.java
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
#	yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/db/DatabaseDocController.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/cart/vo/AppCartAddReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
#	yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/errorcode/ErrorCodeApi.java
#	yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/errorcode/dto/ErrorCodeAutoGenerateReqDTO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/errorcode/ErrorCodeApiImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/sensitiveword/SensitiveWordApiImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/errorcode/ErrorCodeController.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/errorcode/vo/ErrorCodeSaveReqVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordSaveVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailAccountConvert.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/errorcode/ErrorCodeService.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/errorcode/ErrorCodeServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordService.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/test-integration/java/cn/iocoder/yudao/module/system/job/SchedulerManagerTest.java
#	yudao-module-system/yudao-module-system-biz/src/test-integration/java/cn/iocoder/yudao/module/system/mq/RedisStreamTest.java
#	yudao-module-system/yudao-module-system-biz/src/test-integration/java/cn/iocoder/yudao/module/system/service/sms/SmsServiceIntegrationTest.java
#	yudao-module-system/yudao-module-system-biz/src/test-integration/resources/application-integration-test.yaml
#	yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/errorcode/ErrorCodeServiceTest.java
#	yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImplTest.java
pull/112/MERGE
YunaiV 2024-04-22 21:02:10 +08:00
commit 38df9d3911
191 changed files with 3288 additions and 3794 deletions

View File

@ -14,15 +14,14 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>2.0.1-jdk8-snapshot</revision>
<revision>2.0.1-snapshot</revision>
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
<!-- 统一依赖管理 -->
<spring.boot.version>2.7.18</spring.boot.version>
<spring.cloud.version>2021.0.5</spring.cloud.version>
<spring.cloud.alibaba.version>2021.0.4.0</spring.cloud.alibaba.version>
<spring.boot.version>3.2.1</spring.boot.version>
<spring.cloud.version>2023.0.0</spring.cloud.version>
<spring.cloud.alibaba.version>2022.0.0.0</spring.cloud.alibaba.version>
<!-- Web 相关 -->
<servlet.versoin>2.5</servlet.versoin>
<springdoc.version>1.6.15</springdoc.version>
<springdoc.version>2.2.0</springdoc.version>
<knife4j.version>4.3.0</knife4j.version>
<!-- DB 相关 -->
<druid.version>1.2.21</druid.version>
@ -31,33 +30,33 @@
<dynamic-datasource.version>4.3.0</dynamic-datasource.version>
<mybatis-plus-join.version>1.4.10</mybatis-plus-join.version>
<easy-trans.version>2.2.11</easy-trans.version>
<redisson.version>3.18.0</redisson.version> <!-- Spring Boot 2.X 最多使用 3.18.0 版本,否则会报 Tuple NoClassDefFoundError -->
<redisson.version>3.26.0</redisson.version>
<dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
<!-- 消息队列 -->
<rocketmq-spring.version>2.2.3</rocketmq-spring.version>
<!-- RPC 相关 -->
<!-- Config 配置中心相关 -->
<apollo.version>1.9.2</apollo.version>
<!-- Job 定时任务相关 -->
<xxl-job.version>2.3.1</xxl-job.version>
<xxl-job.version>2.4.0</xxl-job.version>
<!-- 服务保障相关 -->
<lock4j.version>2.2.7</lock4j.version>
<!-- 监控相关 -->
<skywalking.version>8.12.0</skywalking.version>
<spring-boot-admin.version>2.7.15</spring-boot-admin.version>
<skywalking.version>9.0.0</skywalking.version>
<spring-boot-admin.version>3.2.1</spring-boot-admin.version>
<opentracing.version>0.33.0</opentracing.version>
<!-- Test 测试相关 -->
<podam.version>7.2.11.RELEASE</podam.version> <!-- Spring Boot 2.X 最多使用 7.2.11 版本 -->
<podam.version>8.0.1.RELEASE</podam.version>
<jedis-mock.version>1.0.13</jedis-mock.version>
<mockito-inline.version>4.11.0</mockito-inline.version>
<mockito-inline.version>5.2.0</mockito-inline.version>
<!-- Bpm 工作流相关 -->
<flowable.version>6.8.0</flowable.version>
<flowable.version>7.0.1</flowable.version>
<!-- 工具类相关 -->
<captcha-plus.version>1.0.8</captcha-plus.version>
<captcha-plus.version>2.0.3</captcha-plus.version>
<jsoup.version>1.17.2</jsoup.version>
<lombok.version>1.18.30</lombok.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<hutool.version>5.8.25</hutool.version>
<hutool-5.version>5.8.25</hutool-5.version>
<hutool-6.version>6.0.0-M10</hutool-6.version>
<easyexcel.verion>3.3.3</easyexcel.verion>
<velocity.version>2.3</velocity.version>
<screw.version>1.0.5</screw.version>
@ -79,8 +78,8 @@
<aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version>
<aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
<tencentcloud-sdk-java.version>3.1.880</tencentcloud-sdk-java.version>
<justauth.version>1.0.8</justauth.version>
<jimureport.version>1.6.6</jimureport.version>
<justauth.version>2.0.5</justauth.version>
<jimureport.version>1.6.6-beta2</jimureport.version>
<xercesImpl.version>2.12.2</xercesImpl.version>
<weixin-java.version>4.6.0</weixin-java.version>
</properties>
@ -172,14 +171,14 @@
</dependency>
<dependency>
<groupId>org.springdoc</groupId> <!-- 接口文档 UI默认 -->
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc.version}</version>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId> <!-- 接口文档 UIknife4j -->
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId> <!-- 接口文档 UIknife4j【网关专属】 -->
@ -193,20 +192,15 @@
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
@ -216,7 +210,7 @@
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
<version>${dynamic-datasource.version}</version>
</dependency>
<dependency>
@ -225,12 +219,6 @@
<version>${mybatis-plus-join.version}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
<artifactId>easy-trans-spring-boot-starter</artifactId>
@ -257,6 +245,12 @@
<version>${easy-trans.version}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
@ -476,7 +470,12 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
<version>${hutool-5.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.hutool</groupId>
<artifactId>hutool-extra</artifactId>
<version>${hutool-6.version}</version>
</dependency>
<dependency>
@ -509,22 +508,6 @@
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId> <!-- 实现数据库文档 -->
<version>${screw.version}</version>
<exclusions>
<exclusion>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId> <!-- 移除 Freemarker 依赖,采用 Velocity 作为模板引擎 -->
</exclusion>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId> <!-- 最新版screw-core1.0.5依赖fastjson1.2.73存在漏洞,移除。 -->
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@ -654,7 +637,7 @@
<!-- 积木报表-->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot-starter</artifactId>
<artifactId>jimureport-spring-boot3-starter</artifactId>
<version>${jimureport.version}</version>
<exclusions>
<exclusion>

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.framework.common.enums;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
*
*
* @author dhb52
*/
@Getter
@AllArgsConstructor
public enum DateIntervalEnum implements IntArrayValuable {
DAY(1, "天"),
WEEK(2, "周"),
MONTH(3, "月"),
QUARTER(4, "季度"),
YEAR(5, "年")
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getInterval).toArray();
/**
*
*/
private final Integer interval;
/**
*
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
public static DateIntervalEnum valueOf(Integer interval) {
return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values());
}
}

View File

@ -6,74 +6,24 @@ import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstant
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* {@link ServiceException}
*
*
* String.format 使 {} 使 {@link #doFormat(int, String, Object...)}
*
* {@link #MESSAGES} 使
*
* 1. cn.iocoder.oceans.user.api.constants.ErrorCodeEnum + ServiceExceptionConfiguration
* 2. .properties
* 3. Apollo
* 4. db
*/
@Slf4j
public class ServiceExceptionUtil {
/**
*
*/
private static final ConcurrentMap<Integer, String> MESSAGES = new ConcurrentHashMap<>();
public static void putAll(Map<Integer, String> messages) {
ServiceExceptionUtil.MESSAGES.putAll(messages);
}
public static void put(Integer code, String message) {
ServiceExceptionUtil.MESSAGES.put(code, message);
}
public static void delete(Integer code, String message) {
ServiceExceptionUtil.MESSAGES.remove(code, message);
}
// ========== 和 ServiceException 的集成 ==========
public static ServiceException exception(ErrorCode errorCode) {
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
return exception0(errorCode.getCode(), messagePattern);
return exception0(errorCode.getCode(), errorCode.getMsg());
}
public static ServiceException exception(ErrorCode errorCode, Object... params) {
String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg());
return exception0(errorCode.getCode(), messagePattern, params);
}
/**
* ServiceException
*
* @param code
* @return
*/
public static ServiceException exception(Integer code) {
return exception0(code, MESSAGES.get(code));
}
/**
* ServiceException
*
* @param code
* @param params
* @return
*/
public static ServiceException exception(Integer code, Object... params) {
return exception0(code, MESSAGES.get(code), params);
return exception0(errorCode.getCode(), errorCode.getMsg(), params);
}
public static ServiceException exception0(Integer code, String messagePattern, Object... params) {

View File

@ -78,7 +78,7 @@ public class CollectionUtils {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
@ -87,7 +87,7 @@ public class CollectionUtils {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().map(mapper).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
@ -123,7 +123,7 @@ public class CollectionUtils {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
@ -132,7 +132,7 @@ public class CollectionUtils {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().map(mapper).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
@ -315,4 +315,4 @@ public class CollectionUtils {
return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
}
}
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
@ -40,6 +41,7 @@ public class MapUtils {
/**
* key value
* key null ,
* value null
*
* @param map
@ -47,7 +49,7 @@ public class MapUtils {
* @param consumer
*/
public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
if (CollUtil.isEmpty(map)) {
if (ObjUtil.isNull(key) || CollUtil.isEmpty(map)) {
return;
}
V value = map.get(key);

View File

@ -27,8 +27,6 @@ public class DateUtils {
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";
/**
* LocalDateTime Date
*
@ -67,19 +65,11 @@ public class DateUtils {
return new Date(System.currentTimeMillis() + duration.toMillis());
}
public static boolean isExpired(Date time) {
return System.currentTimeMillis() > time.getTime();
}
public static boolean isExpired(LocalDateTime time) {
LocalDateTime now = LocalDateTime.now();
return now.isAfter(time);
}
public static long diff(Date endTime, Date startTime) {
return endTime.getTime() - startTime.getTime();
}
/**
*
*
@ -136,37 +126,6 @@ public class DateUtils {
return a.isAfter(b) ? a : b;
}
/**
*
*
* @param field .<br/>eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,<br/>Calendar.HOUR_OF_DAY.
* @param amount
* @return
*/
public static Date addDate(int field, int amount) {
return addDate(null, field, amount);
}
/**
*
*
* @param date
* @param field {@link Calendar#DAY_OF_MONTH}
* @param amount
* @return
*/
public static Date addDate(Date date, int field, int amount) {
if (amount == 0) {
return date;
}
Calendar c = Calendar.getInstance();
if (date != null) {
c.setTime(date);
}
c.add(field, amount);
return c.getTime();
}
/**
*
*

View File

@ -1,13 +1,18 @@
package cn.iocoder.yudao.framework.common.util.date;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.*;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
/**
* {@link java.time.LocalDateTime}
@ -21,6 +26,22 @@ public class LocalDateTimeUtils {
*/
public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
/**
*
*
* {@link LocalDateTimeUtil#parse(CharSequence)}
*
* @param time
* @return
*/
public static LocalDateTime parse(String time) {
try {
return LocalDateTimeUtil.parse(time, DatePattern.NORM_DATE_PATTERN);
} catch (DateTimeParseException e) {
return LocalDateTimeUtil.parse(time);
}
}
public static LocalDateTime addTime(Duration duration) {
return LocalDateTime.now().plus(duration);
}
@ -54,6 +75,21 @@ public class LocalDateTimeUtils {
return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
}
/**
*
*
* @param startTime
* @param endTime
* @param time
* @return
*/
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, String time) {
if (startTime == null || endTime == null || time == null) {
return false;
}
return LocalDateTimeUtil.isIn(parse(time), startTime, endTime);
}
/**
*
*
@ -122,6 +158,16 @@ public class LocalDateTimeUtils {
return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
}
/**
*
*
* @param date
* @return
*/
public static int getQuarterOfYear(LocalDateTime date) {
return (date.getMonthValue() - 1) / 3 + 1;
}
/**
*
*
@ -168,4 +214,96 @@ public class LocalDateTimeUtils {
return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);
}
public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime,
LocalDateTime endTime,
Integer interval) {
// 1.1 找到枚举
DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
// 1.2 将时间对齐
startTime = LocalDateTimeUtil.beginOfDay(startTime);
endTime = LocalDateTimeUtil.endOfDay(endTime);
// 2. 循环,生成时间范围
List<LocalDateTime[]> timeRanges = new ArrayList<>();
switch (intervalEnum) {
case DateIntervalEnum.DAY:
while (startTime.isBefore(endTime)) {
timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});
startTime = startTime.plusDays(1);
}
break;
case DateIntervalEnum.WEEK:
while (startTime.isBefore(endTime)) {
LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1);
timeRanges.add(new LocalDateTime[]{startTime, endOfWeek});
startTime = endOfWeek.plusNanos(1);
}
break;
case DateIntervalEnum.MONTH:
while (startTime.isBefore(endTime)) {
LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1);
timeRanges.add(new LocalDateTime[]{startTime, endOfMonth});
startTime = endOfMonth.plusNanos(1);
}
break;
case DateIntervalEnum.QUARTER:
while (startTime.isBefore(endTime)) {
int quarterOfYear = getQuarterOfYear(startTime);
LocalDateTime quarterEnd = quarterOfYear == 4
? startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1)
: startTime.withMonth(quarterOfYear * 3 + 1).withDayOfMonth(1).minusNanos(1);
timeRanges.add(new LocalDateTime[]{startTime, quarterEnd});
startTime = quarterEnd.plusNanos(1);
}
break;
case DateIntervalEnum.YEAR:
while (startTime.isBefore(endTime)) {
LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1);
timeRanges.add(new LocalDateTime[]{startTime, endOfYear});
startTime = endOfYear.plusNanos(1);
}
break;
default:
throw new IllegalArgumentException("Invalid interval: " + interval);
}
// 3. 兜底,最后一个时间,需要保持在 endTime 之前
LocalDateTime[] lastTimeRange = CollUtil.getLast(timeRanges);
if (lastTimeRange != null) {
lastTimeRange[1] = endTime;
}
return timeRanges;
}
/**
*
*
* @param startTime
* @param endTime
* @param interval
* @return
*/
public static String formatDateRange(LocalDateTime startTime, LocalDateTime endTime, Integer interval) {
// 1. 找到枚举
DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
// 2. 循环,生成时间范围
switch (intervalEnum) {
case DateIntervalEnum.DAY:
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);
case DateIntervalEnum.WEEK:
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)
+ StrUtil.format("(第 {} 周)", LocalDateTimeUtil.weekOfYear(startTime));
case DateIntervalEnum.MONTH:
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN);
case DateIntervalEnum.QUARTER:
return StrUtil.format("{}-Q{}", startTime.getYear(), getQuarterOfYear(startTime));
case DateIntervalEnum.YEAR:
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN);
default:
throw new IllegalArgumentException("Invalid interval: " + interval);
}
}
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.dict.core;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import com.google.common.cache.CacheLoader;
@ -10,8 +11,7 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.time.Duration;
import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
import java.util.List;
/**
*
@ -25,17 +25,31 @@ public class DictFrameworkUtils {
private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();
// TODO @puhui999GET_DICT_DATA_CACHE、GET_DICT_DATA_LIST_CACHE、PARSE_DICT_DATA_CACHE 这 3 个缓存是有点重叠,可以思考下,有没可能减少 1 个。微信讨论好私聊,再具体改哈
/**
* {@link #getDictDataLabel(String, String)}
*/
private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = buildAsyncReloadingCache(
private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
@Override
public DictDataRespDTO load(KeyValue<String, String> key) {
return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()).getCheckedData(),
DICT_DATA_NULL);
return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()).getCheckedData(), DICT_DATA_NULL);
}
});
/**
* {@link #getDictDataLabelList(String)}
*/
private static final LoadingCache<String, List<String>> GET_DICT_DATA_LIST_CACHE = CacheUtils.buildAsyncReloadingCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<String, List<String>>() {
@Override
public List<String> load(String dictType) {
return dictDataApi.getDictDataLabelList(dictType);
}
});
@ -43,14 +57,13 @@ public class DictFrameworkUtils {
/**
* {@link #parseDictDataValue(String, String)}
*/
private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = buildAsyncReloadingCache(
private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
@Override
public DictDataRespDTO load(KeyValue<String, String> key) {
return ObjectUtil.defaultIfNull(dictDataApi.parseDictData(key.getKey(), key.getValue()).getCheckedData(),
DICT_DATA_NULL);
return ObjectUtil.defaultIfNull(dictDataApi.parseDictData(key.getKey(), key.getValue()).getCheckedData(), DICT_DATA_NULL);
}
});
@ -70,6 +83,11 @@ public class DictFrameworkUtils {
return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel();
}
@SneakyThrows
public static List<String> getDictDataLabelList(String dictType) {
return GET_DICT_DATA_LIST_CACHE.get(dictType);
}
@SneakyThrows
public static String parseDictDataValue(String dictType, String label) {
return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue();

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.framework.excel.core.annotations;
import java.lang.annotation.*;
/**
* Excel
*
* {@link #dictType()} {@link #functionName()}
*
* @author HUIHUI
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelColumnSelect {
/**
* @return
*/
String dictType() default "";
/**
* @return
*/
String functionName() default "";
}

View File

@ -1,27 +0,0 @@
package cn.iocoder.yudao.framework.excel.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
// TODO @puhui999列表有办法通过 field name 么?主要考虑一个点,可能导入模版的顺序可能会变
/**
* Excel
* 26
*
* @author HUIHUI
*/
@Getter
@AllArgsConstructor
public enum ExcelColumn {
A(0), B(1), C(2), D(3), E(4), F(5), G(6), H(7), I(8),
J(9), K(10), L(11), M(12), N(13), O(14), P(15), Q(16),
R(17), S(18), T(19), U(20), V(21), W(22), X(23), Y(24),
Z(25);
/**
*
*/
private final int colNum;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.framework.excel.core.function;
import java.util.List;
/**
* Excel
*
*
* @author HUIHUI
*/
public interface ExcelColumnSelectFunction {
/**
*
*
* @return
*/
String getName();
/**
*
*
* @return
*/
List<String> getOptions();
}

View File

@ -2,25 +2,38 @@ package cn.iocoder.yudao.framework.excel.core.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFDataValidation;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* sheet
*
* @author HUIHUI
*/
@Slf4j
public class SelectSheetWriteHandler implements SheetWriteHandler {
/**
@ -36,21 +49,56 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
private static final String DICT_SHEET_NAME = "字典sheet";
// TODO @puhui999Map<ExcelColumn, List<String>> 可以么?之前用 keyvalue 的原因,返回给前端,无法用 linkedhashmap默认 key 会乱序
private final List<KeyValue<ExcelColumn, List<String>>> selectMap;
/**
* key: value:
*/
private final Map<Integer, List<String>> selectMap = new HashMap<>();
public SelectSheetWriteHandler(List<KeyValue<ExcelColumn, List<String>>> selectMap) {
if (CollUtil.isEmpty(selectMap)) {
this.selectMap = null;
public SelectSheetWriteHandler(Class<?> head) {
// 加载下拉数据获取接口
Map<String, ExcelColumnSelectFunction> beansMap = SpringUtil.getBeanFactory().getBeansOfType(ExcelColumnSelectFunction.class);
if (MapUtil.isEmpty(beansMap)) {
return;
}
// 校验一下 key 是否唯一
Map<String, Long> nameCounts = selectMap.stream()
.collect(Collectors.groupingBy(item -> item.getKey().name(), Collectors.counting()));
Assert.isFalse(nameCounts.entrySet().stream().allMatch(entry -> entry.getValue() > 1), "下拉数据 key 重复请排查!!!");
selectMap.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错
this.selectMap = selectMap;
// 解析下拉数据
int colIndex = 0;
for (Field field : head.getDeclaredFields()) {
if (field.isAnnotationPresent(ExcelColumnSelect.class)) {
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if (excelProperty != null && excelProperty.index() != -1) {
colIndex = excelProperty.index();
}
getSelectDataList(colIndex, field);
}
colIndex++;
}
}
/**
* {@link #selectMap}
*
* @param colIndex
* @param field
*/
private void getSelectDataList(int colIndex, Field field) {
ExcelColumnSelect columnSelect = field.getAnnotation(ExcelColumnSelect.class);
String dictType = columnSelect.dictType();
String functionName = columnSelect.functionName();
Assert.isTrue(ObjectUtil.isNotEmpty(dictType) || ObjectUtil.isNotEmpty(functionName),
"Field({}) 的 @ExcelColumnSelect 注解dictType 和 functionName 不能同时为空", field.getName());
// 情况一:使用 dictType 获得下拉数据
if (StrUtil.isNotEmpty(dictType)) { // 情况一: 字典数据 (默认)
selectMap.put(colIndex, DictFrameworkUtils.getDictDataLabelList(dictType));
return;
}
// 情况二:使用 functionName 获得下拉数据
Map<String, ExcelColumnSelectFunction> functionMap = SpringUtil.getApplicationContext().getBeansOfType(ExcelColumnSelectFunction.class);
ExcelColumnSelectFunction function = CollUtil.findOne(functionMap.values(), item -> item.getName().equals(functionName));
Assert.notNull(function, "未找到对应的 function({})", functionName);
selectMap.put(colIndex, function.getOptions());
}
@Override
@ -62,18 +110,20 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
// 1. 获取相应操作对象
DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper(); // 需要设置下拉框的 sheet 页的数据验证助手
Workbook workbook = writeWorkbookHolder.getWorkbook(); // 获得工作簿
List<KeyValue<Integer, List<String>>> keyValues = convertList(selectMap.entrySet(), entry -> new KeyValue<>(entry.getKey(), entry.getValue()));
keyValues.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错
// 2. 创建数据字典的 sheet 页
Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME);
for (KeyValue<ExcelColumn, List<String>> keyValue : selectMap) {
for (KeyValue<Integer, List<String>> keyValue : keyValues) {
int rowLength = keyValue.getValue().size();
// 2.1 设置字典 sheet 页的值 每一列一个字典项
// 2.1 设置字典 sheet 页的值每一列一个字典项
for (int i = 0; i < rowLength; i++) {
Row row = dictSheet.getRow(i);
if (row == null) {
row = dictSheet.createRow(i);
}
row.createCell(keyValue.getKey().getColNum()).setCellValue(keyValue.getValue().get(i));
row.createCell(keyValue.getKey()).setCellValue(keyValue.getValue().get(i));
}
// 2.2 设置单元格下拉选择
setColumnSelect(writeSheetHolder, workbook, helper, keyValue);
@ -84,10 +134,10 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
*
*/
private static void setColumnSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper,
KeyValue<ExcelColumn, List<String>> keyValue) {
KeyValue<Integer, List<String>> keyValue) {
// 1.1 创建可被其他单元格引用的名称
Name name = workbook.createName();
String excelColumn = keyValue.getKey().name();
String excelColumn = ExcelUtil.indexToColName(keyValue.getKey());
// 1.2 下拉框数据来源 eg:字典sheet!$B1:$B2
String refers = DICT_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + keyValue.getValue().size();
name.setNameName("dict" + keyValue.getKey()); // 设置名称的名字
@ -97,7 +147,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); // 设置引用约束
// 设置下拉单元格的首行、末行、首列、末列
CellRangeAddressList rangeAddressList = new CellRangeAddressList(FIRST_ROW, LAST_ROW,
keyValue.getKey().getColNum(), keyValue.getKey().getColNum());
keyValue.getKey(), keyValue.getKey());
DataValidation validation = helper.createValidation(constraint, rangeAddressList);
if (validation instanceof HSSFDataValidation) {
validation.setSuppressDropDownArrow(false);

View File

@ -1,7 +1,5 @@
package cn.iocoder.yudao.framework.excel.core.util;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;
import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
@ -34,27 +32,11 @@ public class ExcelUtils {
*/
public static <T> void write(HttpServletResponse response, String filename, String sheetName,
Class<T> head, List<T> data) throws IOException {
write(response, filename, sheetName, head, data, null);
}
/**
* Excel
*
* @param response
* @param filename
* @param sheetName Excel sheet
* @param head Excel head
* @param data
* @param selectMap Map<>
* @throws IOException
*/
public static <T> void write(HttpServletResponse response, String filename, String sheetName,
Class<T> head, List<T> data, List<KeyValue<ExcelColumn, List<String>>> selectMap) throws IOException {
// 输出 Excel
EasyExcel.write(response.getOutputStream(), head)
.autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
.registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框
.registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
.registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
.sheet(sheetName).doWrite(data);
// 设置 header 和 contentType。写在最后的原因是避免报错时响应 contentType 已经被修改了

View File

@ -40,12 +40,15 @@ public class TimeoutRedisCacheManager extends RedisCacheManager {
// 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间
if (cacheConfig != null) {
// 移除 # 后面的 : 以及后面的内容,避免影响解析
names[1] = StrUtil.subBefore(names[1], StrUtil.COLON, false);
String ttlStr = StrUtil.subBefore(names[1], StrUtil.COLON, false); // 获得 ttlStr 时间部分
names[1] = StrUtil.subAfter(names[1], ttlStr, false); // 移除掉 ttlStr 时间部分
// 解析时间
Duration duration = parseDuration(names[1]);
Duration duration = parseDuration(ttlStr);
cacheConfig = cacheConfig.entryTtl(duration);
}
return super.createRedisCache(name, cacheConfig);
// 创建 RedisCache 对象,需要忽略掉 ttlStr
return super.createRedisCache(names[0] + names[1], cacheConfig);
}
/**

View File

@ -16,7 +16,7 @@ import org.springframework.context.annotation.Primary;
@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
@AutoConfiguration
@Slf4j
public class YudaoOperateLogV2Configuration {
public class YudaoOperateLogConfiguration {
@Bean
@Primary

View File

@ -11,5 +11,5 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
*/
@AutoConfiguration
@EnableFeignClients(clients = {OperateLogApi.class}) // 主要是引入相关的 API 服务
public class YudaoOperateLogV2RpcAutoConfiguration {
public class YudaoOperateLogRpcAutoConfiguration {
}

View File

@ -1,5 +1,5 @@
cn.iocoder.yudao.framework.security.config.YudaoSecurityRpcAutoConfiguration
cn.iocoder.yudao.framework.security.config.YudaoSecurityAutoConfiguration
cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogV2Configuration
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogV2RpcAutoConfiguration
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogConfiguration
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogRpcAutoConfiguration

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.framework.errorcode.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
*
*
* @author dlyan
*/
@ConfigurationProperties("yudao.error-code")
@Data
@Validated
public class ErrorCodeProperties {
/**
*
*/
private Boolean enable = true;
/**
*
*/
@NotNull(message = "错误码枚举类不能为空")
private List<String> constantsClassList;
}

View File

@ -1,39 +0,0 @@
package cn.iocoder.yudao.framework.errorcode.config;
import cn.iocoder.yudao.framework.errorcode.core.generator.ErrorCodeAutoGenerator;
import cn.iocoder.yudao.framework.errorcode.core.generator.ErrorCodeAutoGeneratorImpl;
import cn.iocoder.yudao.framework.errorcode.core.loader.ErrorCodeLoader;
import cn.iocoder.yudao.framework.errorcode.core.loader.ErrorCodeLoaderImpl;
import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
*
*
* @author
*/
@AutoConfiguration
@ConditionalOnProperty(prefix = "yudao.error-code", value = "enable", matchIfMissing = true) // 允许使用 yudao.error-code.enable=false 禁用访问日志
@EnableConfigurationProperties(ErrorCodeProperties.class)
@EnableScheduling // 开启调度任务的功能,因为 ErrorCodeRemoteLoader 通过定时刷新错误码
public class YudaoErrorCodeAutoConfiguration {
@Bean
public ErrorCodeAutoGenerator errorCodeAutoGenerator(@Value("${spring.application.name}") String applicationName,
ErrorCodeProperties errorCodeProperties,
ErrorCodeApi errorCodeApi) {
return new ErrorCodeAutoGeneratorImpl(applicationName, errorCodeProperties.getConstantsClassList(), errorCodeApi);
}
@Bean
public ErrorCodeLoader errorCodeLoader(@Value("${spring.application.name}") String applicationName,
ErrorCodeApi errorCodeApi) {
return new ErrorCodeLoaderImpl(applicationName, errorCodeApi);
}
}

View File

@ -1,15 +0,0 @@
package cn.iocoder.yudao.framework.errorcode.config;
import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* Feign
*
* @author
*/
@AutoConfiguration
@EnableFeignClients(clients = ErrorCodeApi.class) // 主要是引入相关的 API 服务
public class YudaoErrorCodeRpcAutoConfiguration {
}

View File

@ -1,15 +0,0 @@
package cn.iocoder.yudao.framework.errorcode.core.generator;
/**
*
*
* @author dylan
*/
public interface ErrorCodeAutoGenerator {
/**
*
*/
void execute();
}

View File

@ -1,108 +0,0 @@
package cn.iocoder.yudao.framework.errorcode.core.generator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
import cn.iocoder.yudao.module.system.api.errorcode.dto.ErrorCodeAutoGenerateReqDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* ErrorCodeAutoGenerator
* {@link #constantsClassList} system
*
* @author dylan
*/
@RequiredArgsConstructor
@Slf4j
public class ErrorCodeAutoGeneratorImpl implements ErrorCodeAutoGenerator {
/**
*
*/
private final String applicationName;
/**
*
*/
private final List<String> constantsClassList;
/**
* Api
*/
private final ErrorCodeApi errorCodeApi;
@Override
@EventListener(ApplicationReadyEvent.class)
@Async // 异步,保证项目的启动过程,毕竟非关键流程
public void execute() {
// 第一步,解析错误码
List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs = parseErrorCode();
log.info("[execute][解析到错误码数量为 ({}) 个]", autoGenerateDTOs.size());
// 第二步,写入到 system 服务
try {
errorCodeApi.autoGenerateErrorCodeList(autoGenerateDTOs).checkError();
log.info("[execute][写入到 system 组件完成]");
} catch (Exception ex) {
log.error("[execute][写入到 system 组件失败({})]", ExceptionUtil.getRootCauseMessage(ex));
}
}
/**
* constantsClassList
*
* @return
*/
private List<ErrorCodeAutoGenerateReqDTO> parseErrorCode() {
// 校验 errorCodeConstantsClass 参数
if (CollUtil.isEmpty(constantsClassList)) {
log.info("[execute][未配置 yudao.error-code.constants-class-list 配置项,不进行自动写入到 system 服务中]");
return new ArrayList<>();
}
// 解析错误码
List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs = new ArrayList<>();
constantsClassList.forEach(constantsClass -> {
try {
// 解析错误码枚举类
Class<?> errorCodeConstantsClazz = ClassUtil.loadClass(constantsClass);
// 解析错误码
autoGenerateDTOs.addAll(parseErrorCode(errorCodeConstantsClazz));
} catch (Exception ex) {
log.warn("[parseErrorCode][constantsClass({}) 加载失败({})]", constantsClass,
ExceptionUtil.getRootCauseMessage(ex));
}
});
return autoGenerateDTOs;
}
/**
*
*
* @return
*/
private List<ErrorCodeAutoGenerateReqDTO> parseErrorCode(Class<?> constantsClass) {
List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs = new ArrayList<>();
Arrays.stream(constantsClass.getFields()).forEach(field -> {
if (field.getType() != ErrorCode.class) {
return;
}
// 转换成 ErrorCodeAutoGenerateReqDTO 对象
ErrorCode errorCode = (ErrorCode) ReflectUtil.getFieldValue(constantsClass, field);
autoGenerateDTOs.add(new ErrorCodeAutoGenerateReqDTO().setApplicationName(applicationName)
.setCode(errorCode.getCode()).setMessage(errorCode.getMsg()));
});
return autoGenerateDTOs;
}
}

View File

@ -1,34 +0,0 @@
package cn.iocoder.yudao.framework.errorcode.core.loader;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
/**
*
*
* {@link ServiceExceptionUtil} MESSAGES
*
* @author dlyan
*/
public interface ErrorCodeLoader {
/**
*
*
* @param code
* @param msg
*/
default void putErrorCode(Integer code, String msg) {
ServiceExceptionUtil.put(code, msg);
}
/**
*
*/
void refreshErrorCodes();
/**
*
*/
void loadErrorCodes();
}

View File

@ -1,82 +0,0 @@
package cn.iocoder.yudao.framework.errorcode.core.loader;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.module.system.api.errorcode.ErrorCodeApi;
import cn.iocoder.yudao.module.system.api.errorcode.dto.ErrorCodeRespDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.LocalDateTime;
import java.util.List;
/**
* ErrorCodeLoader infra
*
* {@link #REFRESH_ERROR_CODE_PERIOD}
*
* @author dlyan
*/
@RequiredArgsConstructor
@Slf4j
public class ErrorCodeLoaderImpl implements ErrorCodeLoader {
/**
*
*/
private static final int REFRESH_ERROR_CODE_PERIOD = 60 * 1000;
/**
*
*/
private final String applicationName;
/**
* Api
*/
private final ErrorCodeApi errorCodeApi;
/**
*
*/
private LocalDateTime maxUpdateTime;
@Override
@EventListener(ApplicationReadyEvent.class)
@Async // 异步,保证项目的启动过程,毕竟非关键流程
public void loadErrorCodes() {
loadErrorCodes0();
}
@Override
@Scheduled(fixedDelay = REFRESH_ERROR_CODE_PERIOD, initialDelay = REFRESH_ERROR_CODE_PERIOD)
public void refreshErrorCodes() {
loadErrorCodes0();
}
private void loadErrorCodes0() {
try {
// 加载错误码
List<ErrorCodeRespDTO> errorCodeRespDTOs = errorCodeApi.getErrorCodeList(applicationName, maxUpdateTime).getCheckedData();
if (CollUtil.isEmpty(errorCodeRespDTOs)) {
return;
}
log.info("[loadErrorCodes0][加载到 ({}) 个错误码]", errorCodeRespDTOs.size());
// 刷新错误码的缓存
errorCodeRespDTOs.forEach(errorCodeRespDTO -> {
// 写入到错误码的缓存
putErrorCode(errorCodeRespDTO.getCode(), errorCodeRespDTO.getMessage());
// 记录下更新时间,方便增量更新
maxUpdateTime = DateUtils.max(maxUpdateTime, errorCodeRespDTO.getUpdateTime());
});
} catch (Exception ex) {
log.error("[loadErrorCodes0][加载错误码失败({})]", ExceptionUtil.getRootCauseMessage(ex));
}
}
}

View File

@ -1,10 +0,0 @@
/**
* ErrorCode
*
* 1. system-service ErrorCode
* 2. ErrorCode system-service ErrorCode
* 3. system-server 便
*
* @author
*/
package cn.iocoder.yudao.framework.errorcode;

View File

@ -3,6 +3,4 @@ cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration
cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
cn.iocoder.yudao.framework.apilog.config.YudaoApiLogRpcAutoConfiguration
cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration
cn.iocoder.yudao.framework.errorcode.config.YudaoErrorCodeRpcAutoConfiguration
cn.iocoder.yudao.framework.errorcode.config.YudaoErrorCodeAutoConfiguration
cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration

View File

@ -128,6 +128,4 @@ yudao:
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
access-log: # 访问日志的配置项
enable: false
error-code: # 错误码相关配置项
enable: false
demo: false # 关闭演示模式

View File

@ -117,9 +117,6 @@ yudao:
timeout: 5m
width: 160
height: 60
error-code: # 错误码相关配置项
constants-class-list:
- cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants
tenant: # 多租户相关配置项
enable: true

View File

@ -15,6 +15,7 @@ public interface ErrorCodeConstants {
ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态");
ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态");
ErrorCode CONTRACT_NO_EXISTS = new ErrorCode(1_020_000_004, "生成合同序列号重复,请重试");
ErrorCode CONTRACT_DELETE_FAIL = new ErrorCode(1_020_000_005, "删除合同失败,原因:有被回款所使用");
// ========== 线索管理 1-020-001-000 ==========
ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
@ -40,6 +41,7 @@ public interface ErrorCodeConstants {
ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试");
ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态");
ErrorCode RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_004_007, "创建回款失败,原因:回款金额超出合同金额,目前剩余可退:{} 元");
ErrorCode RECEIVABLE_DELETE_FAIL_IS_APPROVE = new ErrorCode(1_020_004_008, "删除回款失败,原因:回款审批已通过");
// ========== 回款计划 1-020-005-000 ==========
ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
@ -72,6 +74,7 @@ public interface ErrorCodeConstants {
ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限");
ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人");
ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限");
ErrorCode CRM_PERMISSION_CREATE_FAIL_EXISTS = new ErrorCode(1_020_007_009, "同时添加数据权限失败,原因:用户【{}】已有模块【{}】数据【{}】的【{}】权限");
// ========== 产品 1_020_008_000 ==========
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
@ -100,4 +103,6 @@ public interface ErrorCodeConstants {
ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限");
// ========== 数据统计 1_020_014_000 ==========
}

View File

@ -142,11 +142,11 @@ public interface LogRecordConstants {
String CRM_RECEIVABLE_TYPE = "CRM 回款";
String CRM_RECEIVABLE_CREATE_SUB_TYPE = "创建回款";
String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款";
String CRM_RECEIVABLE_UPDATE_SUB_TYPE = "更新回款";
String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}";
String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款: {_DIFF{#updateReqVO}}";
String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款";
String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款";
String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批";
String CRM_RECEIVABLE_SUBMIT_SUCCESS = "提交编号为【{{#receivableNo}}】的回款审批成功";

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.enums.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
@ -50,4 +51,10 @@ public enum CrmPermissionLevelEnum implements IntArrayValuable {
return ObjUtil.equal(WRITE.level, level);
}
public static String getNameByLevel(Integer level) {
CrmPermissionLevelEnum typeEnum = CollUtil.findOne(CollUtil.newArrayList(CrmPermissionLevelEnum.values()),
item -> ObjUtil.equal(item.level, level));
return typeEnum == null ? null : typeEnum.getName();
}
}

View File

@ -66,13 +66,13 @@ public class CrmBusinessSaveReqVO {
private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段
@Schema(description = "产品列表")
private List<Product> products;
private List<BusinessProduct> businessProducts;
@Schema(description = "产品列表")
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Product {
public static class BusinessProduct {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
@NotNull(message = "产品编号不能为空")

View File

@ -2,11 +2,15 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 商机转移 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CrmBusinessTransferReqVO {
@Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")

View File

@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.validation.constraints.NotNull;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - CRM 联系人转移 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CrmContactTransferReqVO {
@Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")

View File

@ -3,12 +3,15 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.validation.constraints.NotNull;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - CRM 合同转移 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CrmContractTransferReqVO {
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@ -11,9 +10,7 @@ import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@ -22,7 +19,6 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
@ -38,7 +34,6 @@ import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@ -49,7 +44,6 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
import static java.util.Collections.singletonList;
@Tag(name = "管理后台 - CRM 客户")
@ -67,8 +61,6 @@ public class CrmCustomerController {
private DeptApi deptApi;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DictDataApi dictDataApi;
@PostMapping("/create")
@Operation(summary = "创建客户")
@ -142,7 +134,7 @@ public class CrmCustomerController {
return java.util.Collections.emptyList();
}
// 1.1 获取创建人、负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(list,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 1.2 获取距离进入公海的时间
@ -265,25 +257,7 @@ public class CrmCustomerController {
.areaId(null).detailAddress("").remark("").build()
);
// 输出
ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list, builderSelectMap());
}
private List<KeyValue<ExcelColumn, List<String>>> builderSelectMap() {
List<KeyValue<ExcelColumn, List<String>>> selectMap = new ArrayList<>();
// 获取地区下拉数据
// TODO @puhui999嘿嘿这里改成省份、城市、区域三个选项难度大么
Area area = AreaUtils.getArea(Area.ID_CHINA);
selectMap.add(new KeyValue<>(ExcelColumn.G, AreaUtils.getAreaNodePathList(area.getChildren())));
// 获取客户所属行业
List<String> customerIndustries = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_INDUSTRY);
selectMap.add(new KeyValue<>(ExcelColumn.I, customerIndustries));
// 获取客户等级
List<String> customerLevels = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_LEVEL);
selectMap.add(new KeyValue<>(ExcelColumn.J, customerLevels));
// 获取客户来源
List<String> customerSources = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_SOURCE);
selectMap.add(new KeyValue<>(ExcelColumn.K, customerSources));
return selectMap;
ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list);
}
@PostMapping("/import")

View File

@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunction;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -41,6 +43,7 @@ public class CrmCustomerImportExcelVO {
private String email;
@ExcelProperty(value = "地区", converter = AreaConvert.class)
@ExcelColumnSelect(functionName = AreaExcelColumnSelectFunction.NAME)
private Integer areaId;
@ExcelProperty("详细地址")
@ -48,14 +51,17 @@ public class CrmCustomerImportExcelVO {
@ExcelProperty(value = "所属行业", converter = DictConvert.class)
@DictFormat(CRM_CUSTOMER_INDUSTRY)
@ExcelColumnSelect(dictType = CRM_CUSTOMER_INDUSTRY)
private Integer industryId;
@ExcelProperty(value = "客户等级", converter = DictConvert.class)
@DictFormat(CRM_CUSTOMER_LEVEL)
@ExcelColumnSelect(dictType = CRM_CUSTOMER_LEVEL)
private Integer level;
@ExcelProperty(value = "客户来源", converter = DictConvert.class)
@DictFormat(CRM_CUSTOMER_SOURCE)
@ExcelColumnSelect(dictType = CRM_CUSTOMER_SOURCE)
private Integer source;
@ExcelProperty("备注")

View File

@ -5,6 +5,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - CRM 客户转移 Request VO")
@Data
public class CrmCustomerTransferReqVO {
@ -28,4 +30,10 @@ public class CrmCustomerTransferReqVO {
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
/**
* checkbox
*/
@Schema(description = "同时转移", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
private List<Integer> toBizTypes;
}

View File

@ -5,14 +5,13 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@ -50,7 +49,6 @@ public class CrmPermissionController {
@Resource
private CrmPermissionService permissionService;
@Resource
private AdminUserApi adminUserApi;
@Resource
@ -61,9 +59,8 @@ public class CrmPermissionController {
@PostMapping("/create")
@Operation(summary = "创建数据权限")
@PreAuthorize("@ss.hasPermission('crm:permission:create')")
@CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER)
public CommonResult<Boolean> addPermission(@Valid @RequestBody CrmPermissionCreateReqVO reqVO) {
permissionService.createPermission(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class));
public CommonResult<Boolean> create(@Valid @RequestBody CrmPermissionSaveReqVO reqVO) {
permissionService.createPermission(reqVO, getLoginUserId());
return success(true);
}

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.permission.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - CRM 数据权限创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmPermissionCreateReqVO extends CrmPermissionBaseVO {
}

View File

@ -1,6 +1,10 @@
package cn.iocoder.yudao.module.crm.controller.admin.permission.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
@ -8,11 +12,29 @@ import java.util.Set;
@Schema(description = "管理后台 - CRM 数据权限 Response VO")
@Data
public class CrmPermissionRespVO extends CrmPermissionBaseVO {
public class CrmPermissionRespVO {
@Schema(description = "数据权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
private Long id;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "CRM 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(CrmBizTypeEnum.class)
@NotNull(message = "CRM 类型不能为空")
private Integer bizType;
@Schema(description = "CRM 类型数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "CRM 类型数据编号不能为空")
private Long bizId;
@Schema(description = "权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(CrmPermissionLevelEnum.class)
@NotNull(message = "权限级别不能为空")
private Integer level;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String nickname;

View File

@ -4,18 +4,14 @@ import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* Base VO VO 使
* VO Swagger
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - CRM 数据权限创建/更新 Request VO")
@Data
public class CrmPermissionBaseVO {
public class CrmPermissionSaveReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotNull(message = "用户编号不能为空")
@ -35,4 +31,11 @@ public class CrmPermissionBaseVO {
@NotNull(message = "权限级别不能为空")
private Integer level;
/**
* checkbox
*
*/
@Schema(description = "同时添加", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
private List<Integer> toBizTypes;
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -8,53 +9,69 @@ import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
// TODO @puhui999缺导出
@Schema(description = "管理后台 - CRM 回款计划 Response VO")
@Data
@ExcelIgnoreUnannotated
public class CrmReceivablePlanRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("编号")
private Long id;
@Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("期数")
private Integer period;
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("客户编号")
private Long customerId;
@Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
@ExcelProperty("客户名字")
private String customerName;
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("合同编号")
private Long contractId;
@Schema(description = "合同编号", example = "Q110")
@ExcelProperty("合同编号")
private String contractNo;
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("负责人编号")
private Long ownerUserId;
@Schema(description = "负责人", example = "test")
@ExcelProperty("负责人")
private String ownerUserName;
@Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
@ExcelProperty("计划回款日期")
private LocalDateTime returnTime;
@Schema(description = "计划回款方式", example = "1")
@ExcelProperty("计划回款方式")
private Integer returnType;
@Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
@ExcelProperty("计划回款金额")
private BigDecimal price;
@Schema(description = "回款编号", example = "19852")
@ExcelProperty("回款编号")
private Long receivableId;
@Schema(description = "回款信息")
@ExcelProperty("回款信息")
private CrmReceivableRespVO receivable;
@Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("提前几天提醒")
private Integer remindDays;
@Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
@ExcelProperty("提醒日期")
private LocalDateTime remindTime;
@Schema(description = "备注", example = "备注")
@ExcelProperty("备注")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -8,37 +9,47 @@ import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
// TODO 芋艿:导出的 VO可以考虑使用 @Excel 注解,实现导出功能
@Schema(description = "管理后台 - CRM 回款 Response VO")
@Data
@ExcelIgnoreUnannotated
public class CrmReceivableRespVO {
@Schema(description = "编号", example = "25787")
@ExcelProperty("编号")
private Long id;
@Schema(description = "回款编号", example = "31177")
@ExcelProperty("回款编号")
private String no;
@Schema(description = "回款计划编号", example = "1024")
@ExcelProperty("回款计划编号")
private Long planId;
@Schema(description = "回款方式", example = "2")
@ExcelProperty("回款方式")
private Integer returnType;
@Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
@ExcelProperty("回款金额")
private BigDecimal price;
@Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
@ExcelProperty("计划回款日期")
private LocalDateTime returnTime;
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("客户编号")
private Long customerId;
@Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
@ExcelProperty("客户名字")
private String customerName;
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("合同编号")
private Long contractId;
@Schema(description = "合同信息")
@ExcelProperty("合同信息")
private CrmContractRespVO contract;
@Schema(description = "负责人的用户编号", example = "25682")
@ -56,20 +67,26 @@ public class CrmReceivableRespVO {
private String processInstanceId;
@Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@ExcelProperty("审批状态")
private Integer auditStatus;
@Schema(description = "备注", example = "备注")
@Schema(description = "工作流编号", example = "备注")
@ExcelProperty("工作流编号")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("更新时间")
private LocalDateTime updateTime;
@Schema(description = "创建人", example = "25682")
@ExcelProperty("创建人")
private String creator;
@Schema(description = "创建人名字", example = "test")
@ExcelProperty("创建人名字")
private String creatorName;
}

View File

@ -0,0 +1,55 @@
# == 1. 客户总量分析 ==
### 1.1 客户总量分析(按日期)
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 1.2 客户总量统计(按用户)
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 2. 客户跟进次数分析 ==
### 2.1 客户跟进次数分析(按日期)
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 2.2 客户总量统计(按用户)
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 3. 客户跟进方式分析 ==
### 3.1 客户跟进方式分析
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 4. 客户成交周期 ==
### 4.1 合同摘要信息(客户转化率页面)
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 5. 客户成交周期 ==
### 5.1 获取客户公海分析(按日期)
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 5.2 获取客户公海分析(按用户)
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 6. 客户成交周期 ==
### 6.1 客户成交周期(按日期)
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 6.2 获取客户成交周期(按用户)
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
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -0,0 +1,101 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsCustomerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - CRM 客户统计")
@RestController
@RequestMapping("/crm/statistics-customer")
@Validated
public class CrmStatisticsCustomerController {
@Resource
private CrmStatisticsCustomerService customerService;
@GetMapping("/get-customer-summary-by-date")
@Operation(summary = "获取客户总量分析(按日期)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsCustomerSummaryByDateRespVO>> getCustomerSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getCustomerSummaryByDate(reqVO));
}
@GetMapping("/get-customer-summary-by-user")
@Operation(summary = "获取客户总量分析(按用户)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsCustomerSummaryByUserRespVO>> getCustomerSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getCustomerSummaryByUser(reqVO));
}
@GetMapping("/get-follow-up-summary-by-date")
@Operation(summary = "获取客户跟进次数分析(按日期)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsFollowUpSummaryByDateRespVO>> getFollowupSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getFollowUpSummaryByDate(reqVO));
}
@GetMapping("/get-follow-up-summary-by-user")
@Operation(summary = "获取客户跟进次数分析(按用户)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsFollowUpSummaryByUserRespVO>> getFollowUpSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getFollowUpSummaryByUser(reqVO));
}
@GetMapping("/get-follow-up-summary-by-type")
@Operation(summary = "获取客户跟进次数分析(按类型)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsFollowUpSummaryByTypeRespVO>> getFollowUpSummaryByType(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getFollowUpSummaryByType(reqVO));
}
@GetMapping("/get-contract-summary")
@Operation(summary = "获取客户的首次合同、回款信息列表", description = "用于【客户转化率】页面")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsCustomerContractSummaryRespVO>> getContractSummary(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getContractSummary(reqVO));
}
@GetMapping("/get-pool-summary-by-date")
@Operation(summary = "获取公海客户分析(按日期)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsPoolSummaryByDateRespVO>> getPoolSummaryByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getPoolSummaryByDate(reqVO));
}
@GetMapping("/get-pool-summary-by-user")
@Operation(summary = "获取公海客户分析(按用户)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsPoolSummaryByUserRespVO>> getPoolSummaryByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getPoolSummaryByUser(reqVO));
}
@GetMapping("/get-customer-deal-cycle-by-date")
@Operation(summary = "获取客户成交周期(按日期)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsCustomerDealCycleByDateRespVO>> getCustomerDealCycleByDate(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getCustomerDealCycleByDate(reqVO));
}
@GetMapping("/get-customer-deal-cycle-by-user")
@Operation(summary = "获取客户成交周期(按用户)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsCustomerDealCycleByUserRespVO>> getCustomerDealCycleByUser(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getCustomerDealCycleByUser(reqVO));
}
// TODO dhb52【成交周期分析】里有按照员工已实现、地区未实现、产品未实现需要在看看哈可以把 CustomerDealCycle 拆成 3 个 tab员工客户成交周期分析、地区客户成交周期分析、产品客户成交周期分析
}

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsPerformanceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - CRM 员工业绩统计")
@RestController
@RequestMapping("/crm/statistics-performance")
@Validated
public class CrmStatisticsPerformanceController {
@Resource
private CrmStatisticsPerformanceService performanceService;
@GetMapping("/get-contract-count-performance")
@Operation(summary = "合同数量统计", description = "用于【合同数量分析】页面")
@PreAuthorize("@ss.hasPermission('crm:statistics-performance:query')")
public CommonResult<List<CrmStatisticsPerformanceRespVO>> getContractCountPerformance(@Valid CrmStatisticsPerformanceReqVO performanceReqVO) {
return success(performanceService.getContractCountPerformance(performanceReqVO));
}
@GetMapping("/get-contract-price-performance")
@Operation(summary = "合同金额统计")
@PreAuthorize("@ss.hasPermission('crm:statistics-performance:query')")
public CommonResult<List<CrmStatisticsPerformanceRespVO>> getContractPriceStaffPerformance(@Valid CrmStatisticsPerformanceReqVO performanceReqVO) {
return success(performanceService.getContractPricePerformance(performanceReqVO));
}
@GetMapping("/get-receivable-price-performance")
@Operation(summary = "回款金额统计")
@PreAuthorize("@ss.hasPermission('crm:statistics-performance:query')")
public CommonResult<List<CrmStatisticsPerformanceRespVO>> getReceivablePriceStaffPerformance(@Valid CrmStatisticsPerformanceReqVO performanceReqVO) {
return success(performanceService.getReceivablePricePerformance(performanceReqVO));
}
}

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.*;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsPortraitService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - CRM 客户画像")
@RestController
@RequestMapping("/crm/statistics-portrait")
@Validated
public class CrmStatisticsPortraitController {
@Resource
private CrmStatisticsPortraitService statisticsPortraitService;
@GetMapping("/get-customer-area-summary")
@Operation(summary = "获取客户地区统计数据", description = "用于【城市分布分析】页面")
@PreAuthorize("@ss.hasPermission('crm:statistics-portrait:query')")
public CommonResult<List<CrmStatisticCustomerAreaRespVO>> getCustomerAreaSummary(@Valid CrmStatisticsPortraitReqVO reqVO) {
return success(statisticsPortraitService.getCustomerSummaryByArea(reqVO));
}
@GetMapping("/get-customer-industry-summary")
@Operation(summary = "获取客户行业统计数据", description = "用于【客户行业分析】页面")
@PreAuthorize("@ss.hasPermission('crm:statistics-portrait:query')")
public CommonResult<List<CrmStatisticCustomerIndustryRespVO>> getCustomerIndustrySummary(@Valid CrmStatisticsPortraitReqVO reqVO) {
return success(statisticsPortraitService.getCustomerSummaryByIndustry(reqVO));
}
@GetMapping("/get-customer-level-summary")
@Operation(summary = "获取客户级别统计数据", description = "用于【客户级别分析】页面")
@PreAuthorize("@ss.hasPermission('crm:statistics-portrait:query')")
public CommonResult<List<CrmStatisticCustomerLevelRespVO>> getCustomerLevelSummary(@Valid CrmStatisticsPortraitReqVO reqVO) {
return success(statisticsPortraitService.getCustomerSummaryByLevel(reqVO));
}
@GetMapping("/get-customer-source-summary")
@Operation(summary = "获取客户来源统计数据", description = "用于【客户来源分析】页面")
@PreAuthorize("@ss.hasPermission('crm:statistics-portrait:query')")
public CommonResult<List<CrmStatisticCustomerSourceRespVO>> getCustomerSourceSummary(@Valid CrmStatisticsPortraitReqVO reqVO) {
return success(statisticsPortraitService.getCustomerSummaryBySource(reqVO));
}
}

View File

@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsRankingService;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsRankService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
@ -18,7 +18,6 @@ import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - CRM 排行榜统计")
@RestController
@RequestMapping("/crm/statistics-rank")
@ -26,62 +25,62 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
public class CrmStatisticsRankController {
@Resource
private CrmStatisticsRankingService rankingService;
private CrmStatisticsRankService rankService;
@GetMapping("/get-contract-price-rank")
@Operation(summary = "获得合同金额排行榜")
@PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
public CommonResult<List<CrmStatisticsRanKRespVO>> getContractPriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankingService.getContractPriceRank(rankingReqVO));
public CommonResult<List<CrmStatisticsRankRespVO>> getContractPriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankService.getContractPriceRank(rankingReqVO));
}
@GetMapping("/get-receivable-price-rank")
@Operation(summary = "获得回款金额排行榜")
@PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
public CommonResult<List<CrmStatisticsRanKRespVO>> getReceivablePriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankingService.getReceivablePriceRank(rankingReqVO));
public CommonResult<List<CrmStatisticsRankRespVO>> getReceivablePriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankService.getReceivablePriceRank(rankingReqVO));
}
@GetMapping("/get-contract-count-rank")
@Operation(summary = "获得签约合同数量排行榜")
@PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
public CommonResult<List<CrmStatisticsRanKRespVO>> getContractCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankingService.getContractCountRank(rankingReqVO));
public CommonResult<List<CrmStatisticsRankRespVO>> getContractCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankService.getContractCountRank(rankingReqVO));
}
@GetMapping("/get-product-sales-rank")
@Operation(summary = "获得产品销量排行榜")
@PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
public CommonResult<List<CrmStatisticsRanKRespVO>> getProductSalesRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankingService.getProductSalesRank(rankingReqVO));
public CommonResult<List<CrmStatisticsRankRespVO>> getProductSalesRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankService.getProductSalesRank(rankingReqVO));
}
@GetMapping("/get-customer-count-rank")
@Operation(summary = "获得新增客户数排行榜")
@PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
public CommonResult<List<CrmStatisticsRanKRespVO>> getCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankingService.getCustomerCountRank(rankingReqVO));
public CommonResult<List<CrmStatisticsRankRespVO>> getCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankService.getCustomerCountRank(rankingReqVO));
}
@GetMapping("/get-contacts-count-rank")
@Operation(summary = "获得新增联系人数排行榜")
@PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
public CommonResult<List<CrmStatisticsRanKRespVO>> getContactsCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankingService.getContactsCountRank(rankingReqVO));
public CommonResult<List<CrmStatisticsRankRespVO>> getContactsCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankService.getContactsCountRank(rankingReqVO));
}
@GetMapping("/get-follow-count-rank")
@Operation(summary = "获得跟进次数排行榜")
@PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
public CommonResult<List<CrmStatisticsRanKRespVO>> getFollowCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankingService.getFollowCountRank(rankingReqVO));
public CommonResult<List<CrmStatisticsRankRespVO>> getFollowCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankService.getFollowCountRank(rankingReqVO));
}
@GetMapping("/get-follow-customer-count-rank")
@Operation(summary = "获得跟进客户数排行榜")
@PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
public CommonResult<List<CrmStatisticsRanKRespVO>> getFollowCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankingService.getFollowCustomerCountRank(rankingReqVO));
public CommonResult<List<CrmStatisticsRankRespVO>> getFollowCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
return success(rankService.getFollowCustomerCountRank(rankingReqVO));
}
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* Base Response VO
*
* VO ownerUserIdownerUserName
*/
@Data
public class CrmStatisticsCustomerByUserBaseRespVO {
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long ownerUserId;
@Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
private String ownerUserName;
}

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - CRM 客户转化率分析 VO")
@Data
public class CrmStatisticsCustomerContractSummaryRespVO {
@Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
private String customerName;
@Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "演示合同")
private String contractName;
@Schema(description = "合同总金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1200.00")
private BigDecimal totalPrice;
@Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1200.00")
private BigDecimal receivablePrice;
@Schema(description = "客户行业编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer industryId;
@Schema(description = "客户来源编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer source;
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long ownerUserId;
@Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
private String ownerUserName;
@Schema(description = "创建人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private String creator;
@Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED, example = "源码")
private String creatorUserName;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-01 13:24:26")
private LocalDateTime createTime;
@Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02 00:00:00")
private LocalDateTime orderDate;
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 客户成交周期分析(按日期) VO")
@Data
public class CrmStatisticsCustomerDealCycleByDateRespVO {
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
private String time;
@Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
private Double customerDealCycle;
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 成交周期分析(按用户) VO")
@Data
public class CrmStatisticsCustomerDealCycleByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
@Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
private Double customerDealCycle;
@Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerDealCount;
}

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 数据统计的员工客户分析 Request VO")
@Data
public class CrmStatisticsCustomerReqVO {
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "部门 id 不能为空")
private Long deptId;
/**
* id, ,
*/
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Long userId;
/**
* userIds 便 deptId
*
*/
@Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
private List<Long> userIds;
@Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
private Integer interval;
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Size(min = 2, max = 2, message = "请选择时间范围")
private LocalDateTime[] times;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 客户总量分析(按日期) VO")
@Data
public class CrmStatisticsCustomerSummaryByDateRespVO {
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
private String time;
@Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerCreateCount;
@Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerDealCount;
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 客户总量分析(按用户) VO")
@Data
public class CrmStatisticsCustomerSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
@Schema(description = "新建客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerCreateCount;
@Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerDealCount;
@Schema(description = "合同总金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
private BigDecimal contractPrice;
@Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
private BigDecimal receivablePrice;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 跟进次数分析(按日期) VO")
@Data
public class CrmStatisticsFollowUpSummaryByDateRespVO {
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
private String time;
@Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer followUpRecordCount;
@Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer followUpCustomerCount;
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 跟进次数分析(按类型) VO")
@Data
public class CrmStatisticsFollowUpSummaryByTypeRespVO {
@Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer followUpType;
@Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer followUpRecordCount;
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 跟进次数分析(按用户) VO")
@Data
public class CrmStatisticsFollowUpSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
@Schema(description = "跟进次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer followUpRecordCount;
@Schema(description = "跟进客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer followUpCustomerCount;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 公海客户分析(按日期) VO")
@Data
public class CrmStatisticsPoolSummaryByDateRespVO {
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
private String time;
@Schema(description = "进入公海客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerPutCount;
@Schema(description = "公海领取客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerTakeCount;
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 公海客户分析(按用户) VO")
@Data
public class CrmStatisticsPoolSummaryByUserRespVO extends CrmStatisticsCustomerByUserBaseRespVO {
@Schema(description = "进入公海客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerPutCount;
@Schema(description = "公海领取客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerTakeCount;
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 员工业绩统计 Request VO")
@Data
public class CrmStatisticsPerformanceReqVO {
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "部门 id 不能为空")
private Long deptId;
/**
* id, ,
*/
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Long userId;
/**
* userIds 便 deptId
* <p>
*
*/
@Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
private List<Long> userIds;
// TODO @scholar应该传递的是 int year年份
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@NotEmpty(message = "时间范围不能为空")
private LocalDateTime[] times;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 员工业绩统计 Response VO")
@Data
public class CrmStatisticsPerformanceRespVO {
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
private String time;
@Schema(description = "当月统计结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private BigDecimal currentMonthCount;
@Schema(description = "上月统计结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private BigDecimal lastMonthCount;
@Schema(description = "去年同期统计结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private BigDecimal lastYearCount;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 客户省份分析 VO")
@Data
public class CrmStatisticCustomerAreaRespVO {
@Schema(description = "省份编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer areaId;
@Schema(description = "省份名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "浙江省")
private String areaName;
@Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerCount;
@Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer dealCount;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 客户行业分析 VO")
@Data
public class CrmStatisticCustomerIndustryRespVO {
@Schema(description = "客户行业ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer industryId;
@Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerCount;
@Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer dealCount;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 客户级别分析 VO")
@Data
public class CrmStatisticCustomerLevelRespVO {
@Schema(description = "客户级别编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer level;
@Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerCount;
@Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer dealCount;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 客户来源分析 VO")
@Data
public class CrmStatisticCustomerSourceRespVO {
@Schema(description = "客户来源编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer source;
@Schema(description = "客户个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerCount;
@Schema(description = "成交个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer dealCount;
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 客户画像 Request VO")
@Data
public class CrmStatisticsPortraitReqVO {
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "部门 id 不能为空")
private Long deptId;
/**
* id, ,
*/
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Long userId;
/**
* userIds 便 deptId
*
*/
@Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
private List<Long> userIds;
/**
* , -, , -
* Mapper
*/
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] times;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo;
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotEmpty;
@ -21,7 +21,7 @@ public class CrmStatisticsRankReqVO {
/**
* userIds 便 deptId
*
* <p>
*
*/
@Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")

View File

@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo;
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM BI 排行榜统计 Response VO")
@Schema(description = "管理后台 - CRM 排行榜统计 Response VO")
@Data
public class CrmStatisticsRanKRespVO {
public class CrmStatisticsRankRespVO {
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long ownerUserId;
@ -22,8 +24,10 @@ public class CrmStatisticsRanKRespVO {
*
* 1.
* 2.
*
* 使 BigDecimal
*/
@Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer count;
private BigDecimal count;
}

View File

@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* Mapper
@ -57,4 +58,10 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId);
}
default List<CrmBusinessDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId){
return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
.eq(CrmBusinessDO::getCustomerId, customerId)
.eq(CrmBusinessDO::getOwnerUserId, ownerUserId));
}
}

View File

@ -73,4 +73,9 @@ public interface CrmContactMapper extends BaseMapperX<CrmContactDO> {
return selectList(CrmContactDO::getCustomerId, customerId);
}
default List<CrmContactDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return selectList(CrmContactDO::getCustomerId, customerId,
CrmContactDO::getOwnerUserId, ownerUserId);
}
}

View File

@ -117,4 +117,10 @@ public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
return selectCount(query);
}
default List<CrmContractDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return selectList(new LambdaQueryWrapperX<CrmContractDO>()
.eq(CrmContractDO::getCustomerId, customerId)
.eq(CrmContractDO::getOwnerUserId, ownerUserId));
}
}

View File

@ -53,9 +53,11 @@ public interface CrmPermissionMapper extends BaseMapperX<CrmPermissionDO> {
CrmPermissionDO::getUserId, userId);
}
default CrmPermissionDO selectByBizIdAndUserId(Long bizId, Long userId) {
return selectOne(CrmPermissionDO::getBizId, bizId,
CrmPermissionDO::getUserId, userId);
default CrmPermissionDO selectByBizAndUserId(Integer bizType, Long bizId, Long userId) {
return selectOne(new LambdaQueryWrapperX<CrmPermissionDO>()
.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId)
.eq(CrmPermissionDO::getUserId, userId));
}
default int deletePermission(Integer bizType, Long bizId) {

View File

@ -99,4 +99,8 @@ public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> {
return convertMap(result, obj -> (Long) obj.get("contract_id"), obj -> (BigDecimal) obj.get("total_price"));
}
default Long selectCountByContractId(Long contractId) {
return selectCount(CrmReceivableDO::getContractId, contractId);
}
}

View File

@ -0,0 +1,194 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* CRM Mapper
*
* @author dhb52
*/
@Mapper
public interface CrmStatisticsCustomerMapper {
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerCreateCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerDealCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerCreateCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO @param reqVO @param reqVO
* @return
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerDealCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
* @return @return @param reqVO
* @return
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> selectContractPriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> selectReceivablePriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsFollowUpSummaryByDateRespVO> selectFollowUpRecordCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsFollowUpSummaryByDateRespVO> selectFollowUpCustomerCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsFollowUpSummaryByUserRespVO> selectFollowUpRecordCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsFollowUpSummaryByUserRespVO> selectFollowUpCustomerCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerContractSummaryRespVO> selectContractSummary(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsFollowUpSummaryByTypeRespVO> selectFollowUpRecordCountGroupByType(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
default List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO) {
LocalDateTime currrentDate = LocalDateTimeUtil.beginOfDay(reqVO.getTimes()[0]);
LocalDateTime endDate = LocalDateTimeUtil.endOfDay(reqVO.getTimes()[1]);
List<CrmStatisticsPoolSummaryByDateRespVO> voList = new ArrayList<>();
while (currrentDate.isBefore(endDate)) {
voList.add(new CrmStatisticsPoolSummaryByDateRespVO()
.setTime(LocalDateTimeUtil.format(currrentDate, "yyyy-MM-dd"))
.setCustomerPutCount(RandomUtil.randomInt(0, 10))
.setCustomerTakeCount(RandomUtil.randomInt(0, 10)));
currrentDate = currrentDate.plusDays(1);
}
return voList;
}
/**
* ()
*
* @param reqVO
* @return
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
default List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO) {
return selectPoolCustomerPutCountByDate(reqVO);
}
/**
* ()
*
* @param reqVO
* @return
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
default List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO) {
return convertList(reqVO.getUserIds(), userId ->
(CrmStatisticsPoolSummaryByUserRespVO) new CrmStatisticsPoolSummaryByUserRespVO()
.setCustomerPutCount(RandomUtil.randomInt(0, 10))
.setCustomerTakeCount(RandomUtil.randomInt(0, 10))
.setOwnerUserId(userId));
}
/**
* ()
*
* @param reqVO
* @return
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record 表
default List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO) {
return selectPoolCustomerPutCountByUser(reqVO);
}
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerDealCycleByDateRespVO> selectCustomerDealCycleGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerDealCycleByUserRespVO> selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO);
}

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* CRM Mapper
*
* @author scholar
*/
@Mapper
public interface CrmStatisticsPerformanceMapper {
/**
*
*
* @param performanceReqVO
* @return
*/
List<CrmStatisticsPerformanceRespVO> selectContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
*
*
* @param performanceReqVO
* @return
*/
List<CrmStatisticsPerformanceRespVO> selectContractPricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
*
*
* @param performanceReqVO
* @return
*/
List<CrmStatisticsPerformanceRespVO> selectReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.*;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* CRM Mapper
*
* @author HUIHUI
*/
@Mapper
public interface CrmStatisticsPortraitMapper {
List<CrmStatisticCustomerAreaRespVO> selectSummaryListGroupByAreaId(CrmStatisticsPortraitReqVO reqVO);
List<CrmStatisticCustomerIndustryRespVO> selectCustomerIndustryListGroupByIndustryId(CrmStatisticsPortraitReqVO reqVO);
List<CrmStatisticCustomerSourceRespVO> selectCustomerSourceListGroupBySource(CrmStatisticsPortraitReqVO reqVO);
List<CrmStatisticCustomerLevelRespVO> selectCustomerLevelListGroupByLevel(CrmStatisticsPortraitReqVO reqVO);
}

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@ -12,7 +12,7 @@ import java.util.List;
* @author anhaohao
*/
@Mapper
public interface CrmStatisticsRankingMapper {
public interface CrmStatisticsRankMapper {
/**
*
@ -20,7 +20,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO
* @return
*/
List<CrmStatisticsRanKRespVO> selectContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
List<CrmStatisticsRankRespVO> selectContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
/**
*
@ -28,7 +28,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO
* @return
*/
List<CrmStatisticsRanKRespVO> selectReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
List<CrmStatisticsRankRespVO> selectReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
/**
*
@ -36,7 +36,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO
* @return
*/
List<CrmStatisticsRanKRespVO> selectContractCountRank(CrmStatisticsRankReqVO rankReqVO);
List<CrmStatisticsRankRespVO> selectContractCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
*
@ -44,7 +44,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO
* @return
*/
List<CrmStatisticsRanKRespVO> selectProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
List<CrmStatisticsRankRespVO> selectProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
/**
*
@ -52,7 +52,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO
* @return
*/
List<CrmStatisticsRanKRespVO> selectCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
List<CrmStatisticsRankRespVO> selectCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
*
@ -60,7 +60,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO
* @return
*/
List<CrmStatisticsRanKRespVO> selectContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
List<CrmStatisticsRankRespVO> selectContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
*
@ -68,7 +68,7 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO
* @return
*/
List<CrmStatisticsRanKRespVO> selectFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
List<CrmStatisticsRankRespVO> selectFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
*
@ -76,6 +76,6 @@ public interface CrmStatisticsRankingMapper {
* @param rankReqVO
* @return
*/
List<CrmStatisticsRanKRespVO> selectFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
List<CrmStatisticsRankRespVO> selectFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.crm.framework.excel.core;
import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* {@link ExcelColumnSelectFunction}
*
* @author HUIHUI
*/
@Service
public class AreaExcelColumnSelectFunction implements ExcelColumnSelectFunction {
public static final String NAME = "getCrmAreaNameList"; // 防止和别的模块重名
@Override
public String getName() {
return NAME;
}
@Override
public List<String> getOptions() {
// 获取地区下拉数据
// TODO @puhui999嘿嘿这里改成省份、城市、区域三个选项难度大么
Area area = AreaUtils.getArea(Area.ID_CHINA);
return AreaUtils.getAreaNodePathList(area.getChildren());
}
}

View File

@ -0,0 +1,4 @@
/**
* crm excel
*/
package cn.iocoder.yudao.module.crm.framework.excel;

View File

@ -1 +1,4 @@
/**
* crm operatelog
*/
package cn.iocoder.yudao.module.crm.framework.operatelog;

View File

@ -1 +1,4 @@
/**
* crm permission
*/
package cn.iocoder.yudao.module.crm.framework.permission;

View File

@ -46,8 +46,8 @@ public interface CrmBusinessService {
/**
*
*
* @param id
* @param contactNextTime
* @param id
* @param contactNextTime
* @param contactLastContent
*/
void updateBusinessFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
@ -55,7 +55,7 @@ public interface CrmBusinessService {
/**
*
*
* @param ids
* @param ids
* @param contactNextTime
*/
void updateBusinessContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime);
@ -185,4 +185,13 @@ public interface CrmBusinessService {
return status.getName();
}
/**
*
*
* @param customerId
* @param ownerUserId
* @return
*/
List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
}

View File

@ -88,7 +88,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
success = CRM_BUSINESS_CREATE_SUCCESS)
public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
// 1.1 校验产品项的有效性
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getProducts());
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getBusinessProducts());
// 1.2 校验关联字段
validateRelationDataExists(createReqVO);
@ -129,7 +129,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
// 1.1 校验存在
CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
// 1.2 校验产品项的有效性
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts());
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getBusinessProducts());
// 1.3 校验关联字段
validateRelationDataExists(updateReqVO);
@ -202,9 +202,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
}
}
private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.Product> list) {
private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.BusinessProduct> list) {
// 1. 校验产品存在
productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.Product::getProductId));
productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.BusinessProduct::getProductId));
// 2. 转化为 CrmBusinessProductDO 列表
return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class,
item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount()))));
@ -234,7 +234,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
}
// 1.4 校验是不是状态没变更
if ((reqVO.getStatusId() != null && reqVO.getStatusId().equals(business.getStatusId()))
|| (reqVO.getEndStatus() != null && reqVO.getEndStatus().equals(business.getEndStatus()))) {
|| (reqVO.getEndStatus() != null && reqVO.getEndStatus().equals(business.getEndStatus()))) {
throw exception(BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS);
}
@ -301,7 +301,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
// 2.1 数据权限转移
permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_BUSINESS.getType(),
reqVO.getNewOwnerUserId(), reqVO.getId(), CrmPermissionLevelEnum.OWNER.getLevel()));
reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
// 2.2 设置新的负责人
businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
@ -370,4 +370,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
return businessMapper.selectCountByStatusTypeId(statusTypeId);
}
@Override
public List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
}
}

View File

@ -75,8 +75,8 @@ public interface CrmContactService {
/**
*
*
* @param ids
* @param contactNextTime
* @param ids
* @param contactNextTime
*/
void updateContactContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime);
@ -160,4 +160,13 @@ public interface CrmContactService {
*/
Long getContactCountByCustomerId(Long customerId);
/**
*
*
* @param customerId
* @param ownerUserId
* @return
*/
List<CrmContactDO> getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
}

View File

@ -298,4 +298,9 @@ public class CrmContactServiceImpl implements CrmContactService {
return contactMapper.selectCount(CrmContactDO::getCustomerId, customerId);
}
@Override
public List<CrmContactDO> getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return contactMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
}
}

View File

@ -58,8 +58,8 @@ public interface CrmContractService {
/**
*
*
* @param id
* @param contactNextTime
* @param id
* @param contactNextTime
* @param contactLastContent
*/
void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
@ -75,7 +75,7 @@ public interface CrmContractService {
/**
*
*
* @param id
* @param id
* @param bpmResult BPM
*/
void updateContractAuditStatus(Long id, Integer bpmResult);
@ -193,4 +193,13 @@ public interface CrmContractService {
*/
Long getRemindContractCount(Long userId);
/**
*
*
* @param customerId
* @param ownerUserId
* @return
*/
List<CrmContractDO> getContractListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
}

View File

@ -30,11 +30,13 @@ import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@ -86,7 +88,9 @@ public class CrmContractServiceImpl implements CrmContractService {
private CrmContactService contactService;
@Resource
private CrmContractConfigService contractConfigService;
@Resource
@Lazy // 延迟加载,避免循环依赖
private CrmReceivableService receivableService;
@Resource
private AdminUserApi adminUserApi;
@Resource
@ -222,15 +226,19 @@ public class CrmContractServiceImpl implements CrmContractService {
success = CRM_CONTRACT_DELETE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void deleteContract(Long id) {
// TODO @puhui999如果被 CrmReceivableDO 所使用,则不允许删除
// 校验存在
// 1.1 校验存在
CrmContractDO contract = validateContractExists(id);
// 删除
// 1.2 如果被 CrmReceivableDO 所使用,则不允许删除
if (receivableService.getReceivableCountByContractId(contract.getId()) > 0) {
throw exception(CONTRACT_DELETE_FAIL);
}
// 2.1 删除合同
contractMapper.deleteById(id);
// 删除数据权限
// 2.2 删除数据权限
crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTRACT.getType(), id);
// 记录操作日志上下文
// 3. 记录操作日志上下文
LogRecordContext.putVariable("contractName", contract.getName());
}
@ -399,4 +407,9 @@ public class CrmContractServiceImpl implements CrmContractService {
return contractMapper.selectCountByRemind(userId, config);
}
@Override
public List<CrmContractDO> getContractListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return contractMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
}
}

View File

@ -9,7 +9,13 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
@ -201,7 +207,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
CrmCustomerDO customer = validateCustomerExists(reqVO.getId());
// 1.2 校验拥有客户是否到达上限
validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1);
// 2.1 数据权限转移
permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
@ -209,10 +214,45 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
customerMapper.updateById(new CrmCustomerDO().setId(reqVO.getId())
.setOwnerUserId(reqVO.getNewOwnerUserId()).setOwnerTime(LocalDateTime.now()));
// 2.3 同时转移
if (CollUtil.isNotEmpty(reqVO.getToBizTypes())) {
transfer(reqVO, userId);
}
// 3. 记录转移日志
LogRecordContext.putVariable("customer", customer);
}
/**
*
*
* @param reqVO
* @param userId
*/
private void transfer(CrmCustomerTransferReqVO reqVO, Long userId) {
if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTACT.getType())) {
List<CrmContactDO> contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getId(), userId);
contactList.forEach(item -> {
contactService.transferContact(new CrmContactTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
reqVO.getOldOwnerPermissionLevel()), userId);
});
}
if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_BUSINESS.getType())) {
List<CrmBusinessDO> businessList = businessService.getBusinessListByCustomerIdOwnerUserId(reqVO.getId(), userId);
businessList.forEach(item -> {
businessService.transferBusiness(new CrmBusinessTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
reqVO.getOldOwnerPermissionLevel()), userId);
});
}
if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTRACT.getType())) {
List<CrmContractDO> contractList = contractService.getContractListByCustomerIdOwnerUserId(reqVO.getId(), userId);
contractList.forEach(item -> {
contractService.transferContract(new CrmContractTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
reqVO.getOldOwnerPermissionLevel()), userId);
});
}
}
@Override
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_LOCK_SUB_TYPE, bizNo = "{{#lockReqVO.id}}",
success = CRM_CUSTOMER_LOCK_SUCCESS)

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.service.permission;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
@ -19,6 +20,14 @@ import java.util.List;
*/
public interface CrmPermissionService {
/**
*
*
* @param reqVO
* @param userId
*/
void createPermission(CrmPermissionSaveReqVO reqVO, Long userId);
/**
*
*
@ -111,10 +120,10 @@ public interface CrmPermissionService {
/**
*
*
* @param bizType {@link CrmBizTypeEnum}
* @param bizId {@link CrmBizTypeEnum} DO#getId()
* @param userId
* @param level
* @param bizType {@link CrmBizTypeEnum}
* @param bizId {@link CrmBizTypeEnum} DO#getId()
* @param userId
* @param level
* @return
*/
boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum level);

View File

@ -4,28 +4,34 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import javax.annotation.Resource;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner;
@ -40,18 +46,119 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
@Resource
private CrmPermissionMapper permissionMapper;
@Resource
@Lazy // 解决依赖循环
private CrmContactService contactService;
@Resource
@Lazy // 解决依赖循环
private CrmBusinessService businessService;
@Resource
@Lazy // 解决依赖循环
private CrmContractService contractService;
@Resource
private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
@CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER)
public void createPermission(CrmPermissionSaveReqVO reqVO, Long userId) {
// 1. 创建数据权限
createPermission0(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class));
// 2. 处理【同时添加至】的权限
if (CollUtil.isEmpty(reqVO.getToBizTypes())) {
return;
}
List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();
buildContactPermissions(reqVO, userId, createPermissions);
buildBusinessPermissions(reqVO, userId, createPermissions);
buildContractPermissions(reqVO, userId, createPermissions);
if (CollUtil.isEmpty(createPermissions)) {
return;
}
createPermissionBatch(createPermissions);
}
/**
*
*
* @param reqVO
* @param userId
* @param createPermissions
*/
private void buildContactPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List<CrmPermissionCreateReqBO> createPermissions) {
// 1. 校验是否被同时添加
Integer type = CrmBizTypeEnum.CRM_CONTACT.getType();
if (!reqVO.getToBizTypes().contains(type)) {
return;
}
// 2. 添加数据权限
List<CrmContactDO> contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
contactList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
}
/**
*
*
* @param reqVO
* @param userId
* @param createPermissions
*/
private void buildBusinessPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List<CrmPermissionCreateReqBO> createPermissions) {
// 1. 校验是否被同时添加
Integer type = CrmBizTypeEnum.CRM_BUSINESS.getType();
if (!reqVO.getToBizTypes().contains(type)) {
return;
}
// 2. 添加数据权限
List<CrmBusinessDO> businessList = businessService.getBusinessListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
businessList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
}
/**
*
*
* @param reqVO
* @param userId
* @param createPermissions
*/
private void buildContractPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List<CrmPermissionCreateReqBO> createPermissions) {
// 1. 校验是否被同时添加
Integer type = CrmBizTypeEnum.CRM_CONTRACT.getType();
if (!reqVO.getToBizTypes().contains(type)) {
return;
}
// 2. 添加数据权限
List<CrmContractDO> contractList = contractService.getContractListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
contractList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
}
private void createBizTypePermissions(CrmPermissionSaveReqVO reqVO, Integer type, Long bizId, String name,
List<CrmPermissionCreateReqBO> createPermissions) {
AdminUserRespDTO user = adminUserApi.getUser(reqVO.getUserId()).getCheckedData();
// 1. 需要考虑,被添加人,是不是应该有对应的权限了;
CrmPermissionDO permission = hasAnyPermission(type, bizId, reqVO.getUserId());
if (ObjUtil.isNotNull(permission)) {
throw exception(CRM_PERMISSION_CREATE_FAIL_EXISTS, user.getNickname(), CrmBizTypeEnum.getNameByType(type),
name, CrmPermissionLevelEnum.getNameByLevel(permission.getLevel()));
}
// 2. 添加数据权限
createPermissions.add(new CrmPermissionCreateReqBO().setBizType(type)
.setBizId(bizId).setUserId(reqVO.getUserId()).setLevel(reqVO.getLevel()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createPermission(CrmPermissionCreateReqBO createReqBO) {
return createPermission0(createReqBO);
}
private Long createPermission0(CrmPermissionCreateReqBO createReqBO) {
validatePermissionNotExists(Collections.singletonList(createReqBO));
// 1. 校验用户是否存在
adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId()));
// 2. 创建
// 2. 插入权限
CrmPermissionDO permission = BeanUtils.toBean(createReqBO, CrmPermissionDO.class);
permissionMapper.insert(permission);
return permission.getId();
@ -170,7 +277,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
throw exception(CRM_PERMISSION_DELETE_FAIL);
}
// 校验操作人是否为负责人
CrmPermissionDO permission = permissionMapper.selectByBizIdAndUserId(permissions.get(0).getBizId(), userId);
CrmPermissionDO permission = permissionMapper.selectByBizAndUserId(permissions.get(0).getBizType(), permissions.get(0).getBizId(), userId);
if (permission == null) {
throw exception(CRM_PERMISSION_DELETE_DENIED);
}
@ -220,4 +327,9 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), level.getLevel()));
}
public CrmPermissionDO hasAnyPermission(Integer bizType, Long bizId, Long userId) {
List<CrmPermissionDO> permissionList = permissionMapper.selectByBizTypeAndBizId(bizType, bizId);
return findFirst(permissionList, permission -> ObjUtil.equal(permission.getUserId(), userId));
}
}

View File

@ -19,9 +19,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import javax.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@ -47,9 +45,6 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
@Resource
private CrmReceivablePlanMapper receivablePlanMapper;
@Resource
@Lazy // 延迟加载,避免循环依赖
private CrmReceivableService receivableService;
@Resource
private CrmContractService contractService;
@Resource
@ -145,7 +140,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
// 2. 删除
receivablePlanMapper.deleteById(id);
// 3. 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), id);
// 4. 记录操作日志上下文
LogRecordContext.putVariable("receivablePlan", receivablePlan);

View File

@ -122,4 +122,12 @@ public interface CrmReceivableService {
*/
Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds);
/**
*
*
* @param contractId
* @return
*/
Long getReceivableCountByContractId(Long contractId);
}

View File

@ -37,10 +37,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
@ -81,7 +78,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
@Resource
private BpmProcessInstanceApi bpmProcessInstanceApi;
// TODO @puhui999操作日志没记录上
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}",
@ -115,6 +111,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
// 5. 记录操作日志上下文
LogRecordContext.putVariable("receivable", receivable);
LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
return receivable.getId();
}
@ -156,7 +153,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
}
}
// TODO @puhui999操作日志没记录上
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
@ -164,11 +160,14 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
@CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) {
Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
// 1.1 校验可回款金额超过上限
validateReceivablePriceExceedsLimit(updateReqVO);
updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
// 1.2 校验存在
// 1.1 校验存在
CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());
updateReqVO.setOwnerUserId(receivable.getOwnerUserId()).setCustomerId(receivable.getCustomerId())
.setContractId(receivable.getContractId()).setPlanId(receivable.getPlanId()); // 设置已存在的值
// 1.2 校验可回款金额超过上限
validateReceivablePriceExceedsLimit(updateReqVO);
// 1.3 只有草稿、审批中,可以编辑;
if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
CrmAuditStatusEnum.PROCESS.getStatus())) {
@ -180,8 +179,17 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
receivableMapper.updateById(updateObj);
// 3. 记录操作日志上下文
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
LogRecordContext.putVariable("receivable", receivable);
LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
}
private Integer getReceivablePeriod(Long planId) {
if (Objects.isNull(planId)) {
return null;
}
CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(planId);
return receivablePlan.getPeriod();
}
@Override
@ -212,15 +220,19 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
throw exception(RECEIVABLE_DELETE_FAIL);
}
// TODO @puhui999审批通过时不允许删除
// 1.3 审批通过时,不允许删除
if (ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
throw exception(RECEIVABLE_DELETE_FAIL_IS_APPROVE);
}
// 2. 删除
// 2.1 删除回款
receivableMapper.deleteById(id);
// 3. 删除数据权限
// 2.2 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id);
// 4. 记录操作日志上下文
// 3. 记录操作日志上下文
LogRecordContext.putVariable("receivable", receivable);
LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
}
@Override
@ -289,4 +301,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
return receivableMapper.selectReceivablePriceMapByContractId(contractIds);
}
@Override
public Long getReceivableCountByContractId(Long contractId) {
return receivableMapper.selectCountByContractId(contractId);
}
}

View File

@ -0,0 +1,96 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
import java.util.List;
/**
* CRM Service
*
* @author dhb52
*/
public interface CrmStatisticsCustomerService {
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerSummaryByDateRespVO> getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsFollowUpSummaryByDateRespVO> getFollowUpSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsFollowUpSummaryByUserRespVO> getFollowUpSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsFollowUpSummaryByTypeRespVO> getFollowUpSummaryByType(CrmStatisticsCustomerReqVO reqVO);
/**
*
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerContractSummaryRespVO> getContractSummary(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsPoolSummaryByDateRespVO> getPoolSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsPoolSummaryByUserRespVO> getPoolSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* customer contract
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerDealCycleByDateRespVO> getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO);
}

View File

@ -0,0 +1,323 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
/**
* CRM Service
*
* @author dhb52
*/
@Service
@Validated
public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerService {
@Resource
private CrmStatisticsCustomerMapper customerMapper;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Override
public List<CrmStatisticsCustomerSummaryByDateRespVO> getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按天统计,获取分项统计数据
List<CrmStatisticsCustomerSummaryByDateRespVO> customerCreateCountList = customerMapper.selectCustomerCreateCountGroupByDate(reqVO);
List<CrmStatisticsCustomerSummaryByDateRespVO> customerDealCountList = customerMapper.selectCustomerDealCountGroupByDate(reqVO);
// 3. 按照日期间隔,合并数据
List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
return convertList(timeRanges, times -> {
Integer customerCreateCount = customerCreateCountList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToInt(CrmStatisticsCustomerSummaryByDateRespVO::getCustomerCreateCount).sum();
Integer customerDealCount = customerDealCountList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToInt(CrmStatisticsCustomerSummaryByDateRespVO::getCustomerDealCount).sum();
return new CrmStatisticsCustomerSummaryByDateRespVO()
.setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
.setCustomerCreateCount(customerCreateCount).setCustomerDealCount(customerDealCount);
});
}
@Override
public List<CrmStatisticsCustomerSummaryByUserRespVO> getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按用户统计,获取分项统计数据
List<CrmStatisticsCustomerSummaryByUserRespVO> customerCreateCountList = customerMapper.selectCustomerCreateCountGroupByUser(reqVO);
List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCountList = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
List<CrmStatisticsCustomerSummaryByUserRespVO> contractPriceList = customerMapper.selectContractPriceGroupByUser(reqVO);
List<CrmStatisticsCustomerSummaryByUserRespVO> receivablePriceList = customerMapper.selectReceivablePriceGroupByUser(reqVO);
// 3.1 按照用户,合并统计数据
List<CrmStatisticsCustomerSummaryByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
Integer customerCreateCount = customerCreateCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerCreateCount).sum();
Integer customerDealCount = customerDealCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount).sum();
BigDecimal contractPrice = contractPriceList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.reduce(BigDecimal.ZERO, (sum, vo) -> sum.add(vo.getContractPrice()), BigDecimal::add);
BigDecimal receivablePrice = receivablePriceList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.reduce(BigDecimal.ZERO, (sum, vo) -> sum.add(vo.getReceivablePrice()), BigDecimal::add);
return (CrmStatisticsCustomerSummaryByUserRespVO) new CrmStatisticsCustomerSummaryByUserRespVO()
.setCustomerCreateCount(customerCreateCount).setCustomerDealCount(customerDealCount)
.setContractPrice(contractPrice).setReceivablePrice(receivablePrice).setOwnerUserId(userId);
});
// 3.2 拼接用户信息
appendUserInfo(summaryList);
return summaryList;
}
@Override
public List<CrmStatisticsFollowUpSummaryByDateRespVO> getFollowUpSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按天统计,获取分项统计数据
List<CrmStatisticsFollowUpSummaryByDateRespVO> followUpRecordCountList = customerMapper.selectFollowUpRecordCountGroupByDate(reqVO);
List<CrmStatisticsFollowUpSummaryByDateRespVO> followUpCustomerCountList = customerMapper.selectFollowUpCustomerCountGroupByDate(reqVO);
// 3. 按照时间间隔,合并统计数据
List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
return convertList(timeRanges, times -> {
Integer followUpRecordCount = followUpRecordCountList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToInt(CrmStatisticsFollowUpSummaryByDateRespVO::getFollowUpRecordCount).sum();
Integer followUpCustomerCount = followUpCustomerCountList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToInt(CrmStatisticsFollowUpSummaryByDateRespVO::getFollowUpCustomerCount).sum();
return new CrmStatisticsFollowUpSummaryByDateRespVO()
.setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
.setFollowUpCustomerCount(followUpRecordCount).setFollowUpRecordCount(followUpCustomerCount);
});
}
@Override
public List<CrmStatisticsFollowUpSummaryByUserRespVO> getFollowUpSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按用户统计,获取分项统计数据
List<CrmStatisticsFollowUpSummaryByUserRespVO> followUpRecordCountList = customerMapper.selectFollowUpRecordCountGroupByUser(reqVO);
List<CrmStatisticsFollowUpSummaryByUserRespVO> followUpCustomerCountList = customerMapper.selectFollowUpCustomerCountGroupByUser(reqVO);
// 3.1 按照用户,合并统计数据
List<CrmStatisticsFollowUpSummaryByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
Integer followUpRecordCount = followUpRecordCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.mapToInt(CrmStatisticsFollowUpSummaryByUserRespVO::getFollowUpRecordCount).sum();
Integer followUpCustomerCount = followUpCustomerCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.mapToInt(CrmStatisticsFollowUpSummaryByUserRespVO::getFollowUpCustomerCount).sum();
return (CrmStatisticsFollowUpSummaryByUserRespVO) new CrmStatisticsFollowUpSummaryByUserRespVO()
.setFollowUpCustomerCount(followUpRecordCount).setFollowUpRecordCount(followUpCustomerCount).setOwnerUserId(userId);
});
// 3.2 拼接用户信息
appendUserInfo(summaryList);
return summaryList;
}
@Override
public List<CrmStatisticsFollowUpSummaryByTypeRespVO> getFollowUpSummaryByType(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 获得跟进数据
return customerMapper.selectFollowUpRecordCountGroupByType(reqVO);
}
@Override
public List<CrmStatisticsCustomerContractSummaryRespVO> getContractSummary(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按用户统计,获取统计数据
List<CrmStatisticsCustomerContractSummaryRespVO> summaryList = customerMapper.selectContractSummary(reqVO);
// 3. 拼接信息
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSetByFlatMap(summaryList, vo -> Stream.of(NumberUtils.parseLong(vo.getCreator()), vo.getOwnerUserId())));
summaryList.forEach(vo -> {
findAndThen(userMap, NumberUtils.parseLong(vo.getCreator()), user -> vo.setCreatorUserName(user.getNickname()));
findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname()));
});
return summaryList;
}
@Override
public List<CrmStatisticsPoolSummaryByDateRespVO> getPoolSummaryByDate(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按天统计,获取分项统计数据
List<CrmStatisticsPoolSummaryByDateRespVO> customerPutCountList = customerMapper.selectPoolCustomerPutCountByDate(reqVO);
List<CrmStatisticsPoolSummaryByDateRespVO> customerTakeCountList = customerMapper.selectPoolCustomerTakeCountByDate(reqVO);
// 3. 按照日期间隔,合并数据
List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
return convertList(timeRanges, times -> {
Integer customerPutCount = customerPutCountList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToInt(CrmStatisticsPoolSummaryByDateRespVO::getCustomerPutCount).sum();
Integer customerTakeCount = customerTakeCountList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToInt(CrmStatisticsPoolSummaryByDateRespVO::getCustomerTakeCount).sum();
return new CrmStatisticsPoolSummaryByDateRespVO()
.setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
.setCustomerPutCount(customerPutCount).setCustomerTakeCount(customerTakeCount);
});
}
@Override
public List<CrmStatisticsPoolSummaryByUserRespVO> getPoolSummaryByUser(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按用户统计,获取分项统计数据
List<CrmStatisticsPoolSummaryByUserRespVO> customerPutCountList = customerMapper.selectPoolCustomerPutCountByUser(reqVO);
List<CrmStatisticsPoolSummaryByUserRespVO> customerTakeCountList = customerMapper.selectPoolCustomerTakeCountByUser(reqVO);
// 3.1 按照用户,合并统计数据
List<CrmStatisticsPoolSummaryByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
Integer customerPutCount = customerPutCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.mapToInt(CrmStatisticsPoolSummaryByUserRespVO::getCustomerPutCount).sum();
Integer customerTakeCount = customerTakeCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.mapToInt(CrmStatisticsPoolSummaryByUserRespVO::getCustomerTakeCount).sum();
return (CrmStatisticsPoolSummaryByUserRespVO) new CrmStatisticsPoolSummaryByUserRespVO()
.setCustomerPutCount(customerPutCount).setCustomerTakeCount(customerTakeCount)
.setOwnerUserId(userId);
});
// 3.2 拼接用户信息
appendUserInfo(summaryList);
return summaryList;
}
@Override
public List<CrmStatisticsCustomerDealCycleByDateRespVO> getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按天统计,获取分项统计数据
List<CrmStatisticsCustomerDealCycleByDateRespVO> customerDealCycleList = customerMapper.selectCustomerDealCycleGroupByDate(reqVO);
// 3. 按照日期间隔,合并统计数据
List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
return convertList(timeRanges, times -> {
Double customerDealCycle = customerDealCycleList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToDouble(CrmStatisticsCustomerDealCycleByDateRespVO::getCustomerDealCycle).sum();
return new CrmStatisticsCustomerDealCycleByDateRespVO()
.setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
.setCustomerDealCycle(customerDealCycle);
});
}
@Override
public List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按用户统计,获取分项统计数据
List<CrmStatisticsCustomerDealCycleByUserRespVO> customerDealCycleList = customerMapper.selectCustomerDealCycleGroupByUser(reqVO);
List<CrmStatisticsCustomerSummaryByUserRespVO> customerDealCountList = customerMapper.selectCustomerDealCountGroupByUser(reqVO);
// 3.1 按照用户,合并统计数据
List<CrmStatisticsCustomerDealCycleByUserRespVO> summaryList = convertList(reqVO.getUserIds(), userId -> {
Double customerDealCycle = customerDealCycleList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.mapToDouble(CrmStatisticsCustomerDealCycleByUserRespVO::getCustomerDealCycle).sum();
Integer customerDealCount = customerDealCountList.stream().filter(vo -> userId.equals(vo.getOwnerUserId()))
.mapToInt(CrmStatisticsCustomerSummaryByUserRespVO::getCustomerDealCount).sum();
return (CrmStatisticsCustomerDealCycleByUserRespVO) new CrmStatisticsCustomerDealCycleByUserRespVO()
.setCustomerDealCycle(customerDealCycle).setCustomerDealCount(customerDealCount).setOwnerUserId(userId);
});
// 3.2 拼接用户信息
appendUserInfo(summaryList);
return summaryList;
}
/**
*
*
* @param voList
*/
private <T extends CrmStatisticsCustomerByUserBaseRespVO> void appendUserInfo(List<T> voList) {
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(voList, CrmStatisticsCustomerByUserBaseRespVO::getOwnerUserId));
voList.forEach(vo -> findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname())));
}
/**
* ,
*
* @param reqVO
* @return
*/
private List<Long> getUserIds(CrmStatisticsCustomerReqVO reqVO) {
// 情况一:选中某个用户
if (ObjUtil.isNotNull(reqVO.getUserId())) {
return List.of(reqVO.getUserId());
}
// 情况二:选中某个部门
// 2.1 获得部门列表
List<Long> deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()).getCheckedData(), DeptRespDTO::getId);
deptIds.add(reqVO.getDeptId());
// 2.2 获得用户编号
return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId);
}
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import java.util.List;
/**
* CRM Service
*
* @author scholar
*/
public interface CrmStatisticsPerformanceService {
/**
*
*
* @param performanceReqVO
* @return
*/
List<CrmStatisticsPerformanceRespVO> getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
*
*
* @param performanceReqVO
* @return
*/
List<CrmStatisticsPerformanceRespVO> getContractPricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
*
*
* @param performanceReqVO
* @return
*/
List<CrmStatisticsPerformanceRespVO> getReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
}

View File

@ -0,0 +1,102 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPerformanceMapper;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* CRM Service
*
* @author scholar
*/
@Service
@Validated
public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerformanceService {
@Resource
private CrmStatisticsPerformanceMapper performanceMapper;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Override
public List<CrmStatisticsPerformanceRespVO> getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO) {
// TODO @scholar我们可以换个思路实现减少数据库的计算量
// 比如说2024 年的合同数据,是不是 2022-12 到 2024-12-31每个月的统计呢
// 理解之后,我们可以数据 group by 年-月20222-12 到 2024-12-31 的,然后内存在聚合出 CrmStatisticsPerformanceRespVO 这样
// 这样,我们就可以减少数据库的计算量,提升性能;同时 SQL 也会很简单,开发者理解起来也简单哈;
return getPerformance(performanceReqVO, performanceMapper::selectContractCountPerformance);
}
@Override
public List<CrmStatisticsPerformanceRespVO> getContractPricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO) {
return getPerformance(performanceReqVO, performanceMapper::selectContractPricePerformance);
}
@Override
public List<CrmStatisticsPerformanceRespVO> getReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO) {
return getPerformance(performanceReqVO, performanceMapper::selectReceivablePricePerformance);
}
/**
*
*
* @param performanceReqVO
* @param performanceFunction
* @return
*/
private List<CrmStatisticsPerformanceRespVO> getPerformance(CrmStatisticsPerformanceReqVO performanceReqVO, Function<CrmStatisticsPerformanceReqVO,
List<CrmStatisticsPerformanceRespVO>> performanceFunction) {
// 1. 获得用户编号数组
final List<Long> userIds = getUserIds(performanceReqVO);
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
performanceReqVO.setUserIds(userIds);
// 2. 获得排行数据
List<CrmStatisticsPerformanceRespVO> performance = performanceFunction.apply(performanceReqVO);
if (CollUtil.isEmpty(performance)) {
return Collections.emptyList();
}
return performance;
}
/**
* ,
*
* @param reqVO
* @return
*/
private List<Long> getUserIds(CrmStatisticsPerformanceReqVO reqVO) {
// 情况一:选中某个用户
if (ObjUtil.isNotNull(reqVO.getUserId())) {
return List.of(reqVO.getUserId());
}
// 情况二:选中某个部门
// 2.1 获得部门列表
final Long deptId = reqVO.getDeptId();
List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId).getCheckedData(), DeptRespDTO::getId);
deptIds.add(deptId);
// 2.2 获得用户编号
return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId);
}
}

View File

@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.*;
import java.util.List;
/**
* CRM Service
*
* @author HUIHUI
*/
public interface CrmStatisticsPortraitService {
/**
*
*
* @param reqVO
* @return
*/
List<CrmStatisticCustomerAreaRespVO> getCustomerSummaryByArea(CrmStatisticsPortraitReqVO reqVO);
/**
*
*
* @param reqVO
* @return
*/
List<CrmStatisticCustomerIndustryRespVO> getCustomerSummaryByIndustry(CrmStatisticsPortraitReqVO reqVO);
/**
*
*
* @param reqVO
* @return
*/
List<CrmStatisticCustomerLevelRespVO> getCustomerSummaryByLevel(CrmStatisticsPortraitReqVO reqVO);
/**
*
*
* @param reqVO
* @return
*/
List<CrmStatisticCustomerSourceRespVO> getCustomerSummaryBySource(CrmStatisticsPortraitReqVO reqVO);
}

Some files were not shown because too many files have changed in this diff Show More