mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-22 15:21:53 +08:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
fdd5d55fc8
Binary file not shown.
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 204 KiB |
@ -235,8 +235,6 @@
|
||||
|
||||
![功能图](/.image/common/mall-preview.png)
|
||||
|
||||
_前端基于 crmeb uniapp 经过授权重构,优化代码实现,接入芋道快速开发平台_
|
||||
|
||||
演示地址:<https://doc.iocoder.cn/mall-preview/>
|
||||
|
||||
### 会员中心
|
||||
|
9
pom.xml
9
pom.xml
@ -15,13 +15,14 @@
|
||||
<!-- 各种 module 拓展 -->
|
||||
<module>yudao-module-system</module>
|
||||
<module>yudao-module-infra</module>
|
||||
<module>yudao-module-member</module>
|
||||
<!-- <module>yudao-module-member</module>-->
|
||||
<!-- <module>yudao-module-bpm</module>-->
|
||||
<!-- <module>yudao-module-report</module>-->
|
||||
<!-- <module>yudao-module-mp</module>-->
|
||||
<module>yudao-module-pay</module>
|
||||
<module>yudao-module-mall</module>
|
||||
<!-- <module>yudao-module-pay</module>-->
|
||||
<!-- <module>yudao-module-mall</module>-->
|
||||
<module>yudao-module-crm</module>
|
||||
<module>yudao-module-erp</module>
|
||||
<!-- 示例项目 -->
|
||||
<!-- <module>yudao-example</module>-->
|
||||
</modules>
|
||||
@ -41,7 +42,7 @@
|
||||
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
|
||||
<!-- 看看咋放到 bom 里 -->
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
<spring.boot.version>3.2.0</spring.boot.version>
|
||||
<spring.boot.version>3.2.2</spring.boot.version>
|
||||
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
@ -74,7 +74,7 @@ function stop() {
|
||||
if [ -n "$PID" ]; then
|
||||
echo -e ".\c"
|
||||
else
|
||||
echo '[stop] 停止 $BASE_PATH/$SERVER_NAME 成功'
|
||||
echo "[stop] 停止 $BASE_PATH/$SERVER_NAME 成功"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -17,47 +17,47 @@
|
||||
<revision>2.0.0-snapshot</revision>
|
||||
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.boot.version>3.2.0</spring.boot.version>
|
||||
<spring.boot.version>3.2.2</spring.boot.version>
|
||||
<!-- Web 相关 -->
|
||||
<springdoc.version>2.2.0</springdoc.version>
|
||||
<knife4j.version>4.3.0</knife4j.version>
|
||||
<!-- DB 相关 -->
|
||||
<druid.version>1.2.20</druid.version>
|
||||
<mybatis-plus.version>3.5.4.1</mybatis-plus.version>
|
||||
<mybatis-plus-generator.version>3.5.4.1</mybatis-plus-generator.version>
|
||||
<dynamic-datasource.version>4.2.0</dynamic-datasource.version>
|
||||
<mybatis-plus-join.version>1.4.8.1</mybatis-plus-join.version>
|
||||
<redisson.version>3.25.0</redisson.version>
|
||||
<druid.version>1.2.21</druid.version>
|
||||
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||
<mybatis-plus-generator.version>3.5.5</mybatis-plus-generator.version>
|
||||
<dynamic-datasource.version>4.3.0</dynamic-datasource.version>
|
||||
<mybatis-plus-join.version>1.4.10</mybatis-plus-join.version>
|
||||
<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>
|
||||
<!-- 服务保障相关 -->
|
||||
<lock4j.version>2.2.5</lock4j.version>
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<resilience4j.version>2.1.0</resilience4j.version>
|
||||
<!-- 监控相关 -->
|
||||
<skywalking.version>9.0.0</skywalking.version>
|
||||
<spring-boot-admin.version>3.1.8</spring-boot-admin.version>
|
||||
<spring-boot-admin.version>3.2.1</spring-boot-admin.version>
|
||||
<opentracing.version>0.33.0</opentracing.version>
|
||||
<!-- Test 测试相关 -->
|
||||
<podam.version>8.0.0.RELEASE</podam.version>
|
||||
<jedis-mock.version>1.0.12</jedis-mock.version>
|
||||
<podam.version>8.0.1.RELEASE</podam.version>
|
||||
<jedis-mock.version>1.0.13</jedis-mock.version>
|
||||
<mockito-inline.version>5.2.0</mockito-inline.version>
|
||||
<!-- Bpm 工作流相关 -->
|
||||
<flowable.version>7.0.0</flowable.version>
|
||||
<flowable.version>7.0.1</flowable.version>
|
||||
<!-- 工具类相关 -->
|
||||
<captcha-plus.version>2.0.3</captcha-plus.version>
|
||||
<jsoup.version>1.17.1</jsoup.version>
|
||||
<jsoup.version>1.17.2</jsoup.version>
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||
<hutool-5.version>5.8.23</hutool-5.version>
|
||||
<hutool-6.version>6.0.0-M8</hutool-6.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>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<guava.version>32.1.3-jre</guava.version>
|
||||
<guava.version>33.0.0-jre</guava.version>
|
||||
<guice.version>5.1.0</guice.version>
|
||||
<transmittable-thread-local.version>2.14.4</transmittable-thread-local.version>
|
||||
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||
<commons-net.version>3.10.0</commons-net.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
<tika-core.version>2.9.1</tika-core.version>
|
||||
@ -66,15 +66,15 @@
|
||||
<!-- 三方云服务相关 -->
|
||||
<okio.version>3.5.0</okio.version>
|
||||
<okhttp3.version>4.11.0</okhttp3.version>
|
||||
<commons-io.version>2.11.0</commons-io.version>
|
||||
<minio.version>8.5.6</minio.version>
|
||||
<commons-io.version>2.15.1</commons-io.version>
|
||||
<minio.version>8.5.7</minio.version>
|
||||
<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>2.0.5</justauth.version>
|
||||
<jimureport.version>1.6.6-beta2</jimureport.version>
|
||||
<xercesImpl.version>2.12.2</xercesImpl.version>
|
||||
<weixin-java.version>4.5.7.B</weixin-java.version>
|
||||
<weixin-java.version>4.6.0</weixin-java.version>
|
||||
<ureport2.version>2.2.9</ureport2.version>
|
||||
</properties>
|
||||
|
||||
@ -90,11 +90,6 @@
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-banner</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
|
||||
@ -116,11 +111,6 @@
|
||||
<artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-sms</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-pay</artifactId>
|
||||
@ -136,11 +126,6 @@
|
||||
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
|
||||
|
@ -11,7 +11,6 @@
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>yudao-common</module>
|
||||
<module>yudao-spring-boot-starter-banner</module>
|
||||
<module>yudao-spring-boot-starter-mybatis</module>
|
||||
<module>yudao-spring-boot-starter-redis</module>
|
||||
<module>yudao-spring-boot-starter-web</module>
|
||||
@ -28,18 +27,15 @@
|
||||
|
||||
<module>yudao-spring-boot-starter-biz-operatelog</module>
|
||||
<module>yudao-spring-boot-starter-biz-dict</module>
|
||||
<module>yudao-spring-boot-starter-biz-sms</module>
|
||||
|
||||
<module>yudao-spring-boot-starter-biz-pay</module>
|
||||
<module>yudao-spring-boot-starter-biz-tenant</module>
|
||||
<module>yudao-spring-boot-starter-biz-data-permission</module>
|
||||
<module>yudao-spring-boot-starter-biz-error-code</module>
|
||||
<module>yudao-spring-boot-starter-biz-ip</module>
|
||||
|
||||
<module>yudao-spring-boot-starter-flowable</module>
|
||||
<module>yudao-spring-boot-starter-captcha</module>
|
||||
<module>yudao-spring-boot-starter-websocket</module>
|
||||
<module>yudao-spring-boot-starter-desensitize</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>yudao-framework</artifactId>
|
||||
|
@ -9,7 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.SortingField;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
/**
|
||||
* {@link cn.iocoder.yudao.framework.common.pojo.PageParam} 工具类
|
||||
@ -60,7 +60,7 @@ public class PageUtils {
|
||||
*/
|
||||
public static <T> void buildDefaultSortingField(SortablePageParam sortablePageParam, Func1<T, ?> func) {
|
||||
if (sortablePageParam != null && CollUtil.isEmpty(sortablePageParam.getSortingFields())) {
|
||||
sortablePageParam.setSortingFields(List.of(buildSortingField(func)));
|
||||
sortablePageParam.setSortingFields(singletonList(buildSortingField(func)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration
|
@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>yudao-framework</artifactId>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
错误码 ErrorCode 的自动配置功能,提供如下功能:
|
||||
1. 远程读取:项目启动时,从 system-server 服务,读取数据库中的 ErrorCode 错误码,实现错误码的提示可配置;
|
||||
2. 自动更新:管理员在管理后台修数据库中的 ErrorCode 错误码时,项目自动从 system-server 服务加载最新的 ErrorCode 错误码;
|
||||
3. 自动写入:项目启动时,将项目本地的错误码写到 system-server 服务中,方便管理员在管理后台编辑;
|
||||
</description>
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行操作日志的记录 -->
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,主要是 ErrorCodeProperties 使用到 -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -1 +0,0 @@
|
||||
cn.iocoder.yudao.framework.errorcode.config.YudaoErrorCodeAutoConfiguration
|
@ -1,82 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-spring-boot-starter-biz-sms</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>短信拓展,支持阿里云、腾讯云</description>
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>io.opentracing</groupId>
|
||||
<artifactId>opentracing-util</artifactId> <!-- aliyun 短信需要,进行链路追踪 -->
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<optional>true</optional> <!-- 设置为可选,因为使用到 @VisibleForTesting 用于单元测试 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 三方云服务相关 -->
|
||||
|
||||
<!-- SMS SDK begin -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>aliyun-java-sdk-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
||||
</dependency>
|
||||
<!-- SMS SDK end -->
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -1,21 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.sms.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.impl.SmsClientFactoryImpl;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* 短信配置类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class YudaoSmsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public SmsClientFactory smsClientFactory() {
|
||||
return new SmsClientFactoryImpl();
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.sms.core.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* 短信框架的错误码枚举
|
||||
*
|
||||
* 短信框架,使用 2-001-000-000 段
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface SmsFrameworkErrorCodeConstants {
|
||||
|
||||
ErrorCode SMS_UNKNOWN = new ErrorCode(2_001_000_000, "未知错误,需要解析");
|
||||
|
||||
// ========== 权限 / 限流等相关 2-001-000-100 ==========
|
||||
|
||||
ErrorCode SMS_PERMISSION_DENY = new ErrorCode(2_001_000_100, "没有发送短信的权限");
|
||||
ErrorCode SMS_IP_DENY = new ErrorCode(2_001_000_100, "IP 不允许发送短信");
|
||||
|
||||
// 阿里云:将短信发送频率限制在正常的业务限流范围内。默认短信验证码:使用同一签名,对同一个手机号验证码,支持 1 条 / 分钟,5 条 / 小时,累计 10 条 / 天。
|
||||
ErrorCode SMS_SEND_BUSINESS_LIMIT_CONTROL = new ErrorCode(2_001_000_102, "指定手机的发送限流");
|
||||
// 阿里云:已经达到您在控制台设置的短信日发送量限额值。在国内消息设置 > 安全设置,修改发送总量阈值。
|
||||
ErrorCode SMS_SEND_DAY_LIMIT_CONTROL = new ErrorCode(2_001_000_103, "每天的发送限流");
|
||||
|
||||
ErrorCode SMS_SEND_CONTENT_INVALID = new ErrorCode(2_001_000_104, "短信内容有敏感词");
|
||||
|
||||
// 腾讯云:为避免骚扰用户,营销短信只允许在8点到22点发送。
|
||||
ErrorCode SMS_SEND_MARKET_LIMIT_CONTROL = new ErrorCode(2_001_000_105, "营销短信发送时间限制");
|
||||
|
||||
// ========== 模板相关 2-001-000-200 ==========
|
||||
ErrorCode SMS_TEMPLATE_INVALID = new ErrorCode(2_001_000_200, "短信模板不合法"); // 包括短信模板不存在
|
||||
ErrorCode SMS_TEMPLATE_PARAM_ERROR = new ErrorCode(2_001_000_201, "模板参数不正确");
|
||||
|
||||
// ========== 签名相关 2-001-000-300 ==========
|
||||
ErrorCode SMS_SIGN_INVALID = new ErrorCode(2_001_000_300, "短信签名不可用");
|
||||
|
||||
// ========== 账户相关 2-001-000-400 ==========
|
||||
ErrorCode SMS_ACCOUNT_MONEY_NOT_ENOUGH = new ErrorCode(2_001_000_400, "账户余额不足");
|
||||
ErrorCode SMS_ACCOUNT_INVALID = new ErrorCode(2_001_000_401, "apiKey 不存在");
|
||||
|
||||
// ========== 其它相关 2-001-000-900 开头 ==========
|
||||
ErrorCode SMS_API_PARAM_ERROR = new ErrorCode(2_001_000_900, "请求参数缺失");
|
||||
ErrorCode SMS_MOBILE_INVALID = new ErrorCode(2_001_000_901, "手机格式不正确");
|
||||
ErrorCode SMS_MOBILE_BLACK = new ErrorCode(2_001_000_902, "手机号在黑名单中");
|
||||
ErrorCode SMS_APP_ID_INVALID = new ErrorCode(2_001_000_903, "SdkAppId不合法");
|
||||
|
||||
ErrorCode EXCEPTION = new ErrorCode(2_001_000_999, "调用异常");
|
||||
|
||||
}
|
@ -1 +0,0 @@
|
||||
cn.iocoder.yudao.framework.sms.config.YudaoSmsAutoConfiguration
|
@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>yudao-framework</artifactId>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>yudao-spring-boot-starter-desensitize</artifactId>
|
||||
<description>脱敏组件:支持 JSON 返回数据时,将邮箱、手机等字段进行脱敏</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -77,13 +77,6 @@
|
||||
<groupId>com.github.yulichang</groupId>
|
||||
<artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
|
||||
</dependency>
|
||||
|
||||
<!-- TODO 芋艿:临时解决 spring boot 3.x 适配 -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Web 框架,全局异常、API 日志等</description>
|
||||
<description>Web 框架,全局异常、API 日志、脱敏、错误码等</description>
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<dependencies>
|
||||
@ -54,6 +54,11 @@
|
||||
<artifactId>yudao-module-infra-api</artifactId> <!-- 需要使用它,进行操作日志的记录 -->
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId> <!-- 需要使用它,进行错误码的记录 -->
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- xss -->
|
||||
<dependency>
|
||||
@ -61,6 +66,17 @@
|
||||
<artifactId>jsoup</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
* 脱敏组件:支持 JSON 返回数据时,将邮箱、手机等字段进行脱敏
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.desensitize.core;
|
||||
package cn.iocoder.yudao.framework.desensitize;
|
@ -3,3 +3,5 @@ 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.xss.config.YudaoXssAutoConfiguration
|
||||
cn.iocoder.yudao.framework.banner.config.YudaoBannerAutoConfiguration
|
||||
cn.iocoder.yudao.framework.errorcode.config.YudaoErrorCodeAutoConfiguration
|
@ -1,27 +1,23 @@
|
||||
package cn.iocoder.yudao.framework.desensitize.core;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.EmailDesensitize;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.RegexDesensitize;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.BankCardDesensitize;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseDesensitize;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.ChineseNameDesensitize;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneDesensitize;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.IdCardDesensitize;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.PasswordDesensitize;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.MobileDesensitize;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesensitize;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.*;
|
||||
import lombok.Data;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* {@link DesensitizeTest} 的单元测试
|
||||
*/
|
||||
public class DesensitizeTest extends BaseMockitoUnitTest {
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class DesensitizeTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
@ -1,8 +1,8 @@
|
||||
package cn.iocoder.yudao.framework.desensitize.core.annotation;
|
||||
|
||||
import cn.iocoder.yudao.framework.desensitize.core.DesensitizeTest;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.handler.AddressHandler;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
|
||||
import java.lang.annotation.Documented;
|
@ -18,7 +18,6 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<description>
|
||||
crm 包下,客户关系管理(Customer Relationship Management)。
|
||||
例如说:客户、联系人、商机、合同、回款等等
|
||||
|
@ -70,5 +70,11 @@
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- TODO @puhui999:放的位置,要整齐哈。 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -41,7 +41,7 @@ import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.E
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
|
||||
|
||||
@Tag(name = "管理后台 - 商机")
|
||||
@Tag(name = "管理后台 - CRM 商机")
|
||||
@RestController
|
||||
@RequestMapping("/crm/business")
|
||||
@Validated
|
||||
|
@ -40,7 +40,7 @@ import java.util.Set;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
|
||||
|
||||
@Tag(name = "管理后台 - 商机状态类型")
|
||||
@Tag(name = "管理后台 - CRM 商机状态类型")
|
||||
@RestController
|
||||
@RequestMapping("/crm/business-status-type")
|
||||
@Validated
|
||||
|
@ -99,7 +99,7 @@ public class CrmClueController {
|
||||
@PostMapping("/transform")
|
||||
@Operation(summary = "线索转化为客户")
|
||||
@PreAuthorize("@ss.hasPermission('crm:clue:update')")
|
||||
public CommonResult<Boolean> translateCustomer(@Valid @RequestBody CrmClueTransformReqVO reqVO) {
|
||||
public CommonResult<Boolean> translateCustomer(@Valid @RequestBody CrmClueTranslateReqVO reqVO) {
|
||||
clueService.translateCustomer(reqVO, getLoginUserId());
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
@ -30,4 +30,13 @@ public class CrmCluePageReqVO extends PageParam {
|
||||
@Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
|
||||
private Boolean pool; // null 则表示为不是公海数据
|
||||
|
||||
@Schema(description = "所属行业", example = "1")
|
||||
private Integer industryId;
|
||||
|
||||
@Schema(description = "客户等级", example = "1")
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "客户来源", example = "1")
|
||||
private Integer source;
|
||||
|
||||
}
|
||||
|
@ -77,4 +77,39 @@ public class CrmClueRespVO {
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
|
||||
@ExcelProperty(value = "所属行业", converter = DictConvert.class)
|
||||
@DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY)
|
||||
private Integer industryId;
|
||||
|
||||
@Schema(description = "客户等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
|
||||
@ExcelProperty(value = "客户等级", converter = DictConvert.class)
|
||||
@DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL)
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
|
||||
@ExcelProperty(value = "客户来源", converter = DictConvert.class)
|
||||
@DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE)
|
||||
private Integer source;
|
||||
|
||||
@Schema(description = "网址", example = "25682")
|
||||
@ExcelProperty("网址")
|
||||
private String website;
|
||||
|
||||
@Schema(description = "QQ", example = "25682")
|
||||
@ExcelProperty("QQ")
|
||||
private String qq;
|
||||
|
||||
@Schema(description = "wechat", example = "25682")
|
||||
@ExcelProperty("wechat")
|
||||
private String wechat;
|
||||
|
||||
@Schema(description = "email", example = "25682")
|
||||
@ExcelProperty("email")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "客户描述", example = "25682")
|
||||
@ExcelProperty("客户描述")
|
||||
private String description;
|
||||
|
||||
}
|
||||
|
@ -1,16 +1,25 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.Mobile;
|
||||
import cn.iocoder.yudao.framework.common.validation.Telephone;
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum;
|
||||
import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerIndustryParseFunction;
|
||||
import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerLevelParseFunction;
|
||||
import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerSourceParseFunction;
|
||||
import com.mzt.logapi.starter.annotation.DiffLogField;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 线索 创建/更新 Request VO")
|
||||
@Data
|
||||
@ -55,4 +64,42 @@ public class CrmClueSaveReqVO {
|
||||
@DiffLogField(name = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "所属行业", example = "1")
|
||||
@DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME)
|
||||
@DictFormat(CRM_CUSTOMER_INDUSTRY)
|
||||
private Integer industryId;
|
||||
|
||||
@Schema(description = "客户等级", example = "2")
|
||||
@DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME)
|
||||
@InEnum(CrmCustomerLevelEnum.class)
|
||||
private Integer level;
|
||||
|
||||
@Schema(description = "客户来源", example = "3")
|
||||
@DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME)
|
||||
private Integer source;
|
||||
|
||||
@Schema(description = "网址", example = "https://www.baidu.com")
|
||||
@DiffLogField(name = "网址")
|
||||
private String website;
|
||||
|
||||
@Schema(description = "QQ", example = "123456789")
|
||||
@DiffLogField(name = "QQ")
|
||||
@Size(max = 20, message = "QQ长度不能超过 20 个字符")
|
||||
private String qq;
|
||||
|
||||
@Schema(description = "微信", example = "123456789")
|
||||
@DiffLogField(name = "微信")
|
||||
@Size(max = 255, message = "微信长度不能超过 255 个字符")
|
||||
private String wechat;
|
||||
|
||||
@Schema(description = "邮箱", example = "123456789@qq.com")
|
||||
@DiffLogField(name = "邮箱")
|
||||
@Email(message = "邮箱格式不正确")
|
||||
@Size(max = 255, message = "邮箱长度不能超过 255 个字符")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "客户描述", example = "任意文字")
|
||||
@DiffLogField(name = "客户描述")
|
||||
@Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")
|
||||
private String description;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import java.util.Set;
|
||||
|
||||
@Schema(description = "管理后台 - 线索转化为客户 Request VO")
|
||||
@Data
|
||||
public class CrmClueTransformReqVO {
|
||||
public class CrmClueTranslateReqVO {
|
||||
|
||||
@Schema(description = "线索编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024, 1025]")
|
||||
@NotEmpty(message = "线索编号不能为空")
|
@ -2,14 +2,14 @@ package cn.iocoder.yudao.module.crm.controller.admin.operatelog;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogPageReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogV2RespVO;
|
||||
import cn.iocoder.yudao.module.crm.enums.LogRecordConstants;
|
||||
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
@ -51,16 +51,14 @@ public class CrmOperateLogController {
|
||||
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), CRM_RECEIVABLE_PLAN_TYPE);
|
||||
}
|
||||
|
||||
// TODO @puhui999:还是搞个 VO 出来哈
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得操作日志")
|
||||
@Parameter(name = "id", description = "客户编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
|
||||
public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
|
||||
@PreAuthorize("@ss.hasPermission('crm:operate-log:query')")
|
||||
public CommonResult<PageResult<CrmOperateLogV2RespVO>> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
|
||||
OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO();
|
||||
reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页,需要分页需注释
|
||||
reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId());
|
||||
return success(operateLogApi.getOperateLogPage(reqDTO));
|
||||
return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogV2RespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 跟进 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class CrmOperateLogV2RespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
private String userName;
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer userType;
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "修改客户")
|
||||
private String subType;
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
|
||||
private Long bizId;
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "将什么从什么改为了什么")
|
||||
private String action;
|
||||
|
||||
@Schema(description = "编号", example = "{orderId: 1}")
|
||||
private String extra;
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-01-01")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.convert.permission;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
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.CrmPermissionRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
|
||||
@ -29,13 +30,10 @@ public interface CrmPermissionConvert {
|
||||
|
||||
CrmPermissionConvert INSTANCE = Mappers.getMapper(CrmPermissionConvert.class);
|
||||
|
||||
// TODO @puhui999:这个要不也搞到 copy 里
|
||||
List<CrmPermissionRespVO> convert(List<CrmPermissionDO> permission);
|
||||
|
||||
default List<CrmPermissionRespVO> convert(List<CrmPermissionDO> permission, List<AdminUserRespDTO> userList,
|
||||
default List<CrmPermissionRespVO> convert(List<CrmPermissionDO> permissions, List<AdminUserRespDTO> userList,
|
||||
Map<Long, DeptRespDTO> deptMap, Map<Long, PostRespDTO> postMap) {
|
||||
Map<Long, AdminUserRespDTO> userMap = CollectionUtils.convertMap(userList, AdminUserRespDTO::getId);
|
||||
return CollectionUtils.convertList(convert(permission), item -> {
|
||||
return CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> {
|
||||
findAndThen(userMap, item.getUserId(), user -> {
|
||||
item.setNickname(user.getNickname());
|
||||
findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName()));
|
||||
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.clue;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
|
||||
import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
@ -72,16 +73,44 @@ public class CrmClueDO extends BaseDO {
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 负责人的用户编号
|
||||
*
|
||||
* 关联 AdminUserDO 的 id 字段
|
||||
*/
|
||||
private Long ownerUserId;
|
||||
|
||||
// TODO 芋艿:客户级别;
|
||||
// TODO 芋艿:线索来源;
|
||||
// TODO 芋艿:客户行业;
|
||||
|
||||
/**
|
||||
* 所属行业
|
||||
* 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_INDUSTRY}
|
||||
*/
|
||||
private Integer industryId;
|
||||
/**
|
||||
* 客户等级
|
||||
* 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_LEVEL}
|
||||
*/
|
||||
private Integer level;
|
||||
/**
|
||||
* 客户来源
|
||||
* 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_SOURCE}
|
||||
*/
|
||||
private Integer source;
|
||||
/**
|
||||
* 网址
|
||||
*/
|
||||
private String website;
|
||||
/**
|
||||
* QQ
|
||||
*/
|
||||
private String qq;
|
||||
/**
|
||||
* wechat
|
||||
*/
|
||||
private String wechat;
|
||||
/**
|
||||
* email
|
||||
*/
|
||||
private String email;
|
||||
/**
|
||||
* 客户描述
|
||||
*/
|
||||
private String description;
|
||||
}
|
||||
|
@ -114,10 +114,15 @@ public class CrmCustomerDO extends BaseDO {
|
||||
*/
|
||||
private String detailAddress;
|
||||
|
||||
/**
|
||||
* 最后接收时间
|
||||
*/
|
||||
private LocalDateTime receiveTime;
|
||||
/**
|
||||
* 最后跟进时间
|
||||
*/
|
||||
private LocalDateTime contactLastTime;
|
||||
|
||||
/**
|
||||
* 最后跟进内容
|
||||
*/
|
||||
|
@ -37,6 +37,9 @@ public interface CrmClueMapper extends BaseMapperX<CrmClueDO> {
|
||||
.likeIfPresent(CrmClueDO::getName, pageReqVO.getName())
|
||||
.likeIfPresent(CrmClueDO::getTelephone, pageReqVO.getTelephone())
|
||||
.likeIfPresent(CrmClueDO::getMobile, pageReqVO.getMobile())
|
||||
.eqIfPresent(CrmClueDO::getIndustryId, pageReqVO.getIndustryId())
|
||||
.eqIfPresent(CrmClueDO::getLevel, pageReqVO.getLevel())
|
||||
.eqIfPresent(CrmClueDO::getSource, pageReqVO.getSource())
|
||||
.orderByDesc(CrmClueDO::getId);
|
||||
return selectJoinPage(pageReqVO, CrmClueDO.class, query);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageR
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
|
||||
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
@ -99,4 +100,11 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
|
||||
return selectJoinPage(pageReqVO, CrmCustomerDO.class, query);
|
||||
}
|
||||
|
||||
default List<CrmCustomerDO> selectListByLockStatusAndOwnerUserIdNotNull(Boolean lockStatus) {
|
||||
return selectList(new LambdaQueryWrapper<CrmCustomerDO>()
|
||||
.eq(CrmCustomerDO::getLockStatus, lockStatus)
|
||||
// TODO @puhui999:not null 可以转化成大于 0
|
||||
.isNotNull(CrmCustomerDO::getOwnerUserId));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,17 @@
|
||||
package cn.iocoder.yudao.module.crm.framework.permission.core.util;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
|
||||
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum;
|
||||
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
|
||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
/**
|
||||
@ -22,6 +30,22 @@ public class CrmPermissionUtils {
|
||||
return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode());
|
||||
}
|
||||
|
||||
// TODO @puhui999:这个貌似直接放到 CrmPermissionService 会更好?
|
||||
/**
|
||||
* 校验权限
|
||||
*
|
||||
* @param bizType 数据类型,关联 {@link CrmBizTypeEnum}
|
||||
* @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
|
||||
* @param userId 用户编号
|
||||
* @param levelEnum 权限级别
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum levelEnum) {
|
||||
List<CrmPermissionDO> permissionList = SingletonManager.getCrmPermissionService().getPermissionListByBiz(bizType, bizId);
|
||||
return anyMatch(permissionList, permission ->
|
||||
ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), levelEnum.getLevel()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态内部类实现单例获取
|
||||
*
|
||||
@ -30,11 +54,16 @@ public class CrmPermissionUtils {
|
||||
private static class SingletonManager {
|
||||
|
||||
private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class);
|
||||
private static final CrmPermissionService CRM_PERMISSION_SERVICE = SpringUtil.getBean(CrmPermissionService.class);
|
||||
|
||||
public static PermissionApi getPermissionApi() {
|
||||
return PERMISSION_API;
|
||||
}
|
||||
|
||||
public static CrmPermissionService getCrmPermissionService() {
|
||||
return CRM_PERMISSION_SERVICE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.module.crm.job.customer;
|
||||
|
||||
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
|
||||
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
|
||||
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 客户自动掉入公海 Job
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
public class CrmCustomerAutoPutPoolJob implements JobHandler {
|
||||
|
||||
@Resource
|
||||
private CrmCustomerService customerService;
|
||||
|
||||
@Override
|
||||
@TenantJob
|
||||
public String execute(String param) {
|
||||
int count = customerService.customerAutoPutPoolBySystem();
|
||||
return String.format("掉入公海客户 %s 个", count);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TODO 芋艿:临时占位,后续可删除
|
||||
*/
|
||||
package cn.iocoder.yudao.module.crm.job;
|
@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransformReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTranslateReqVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
|
||||
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
|
||||
import jakarta.validation.Valid;
|
||||
@ -88,6 +88,6 @@ public interface CrmClueService {
|
||||
* @param reqVO 线索编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
void translateCustomer(CrmClueTransformReqVO reqVO, Long userId);
|
||||
void translateCustomer(CrmClueTranslateReqVO reqVO, Long userId);
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.crm.service.clue;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
@ -11,11 +10,10 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransformReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTranslateReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO;
|
||||
import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
|
||||
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
|
||||
@ -177,7 +175,7 @@ public class CrmClueServiceImpl implements CrmClueService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
|
||||
public void translateCustomer(CrmClueTransformReqVO reqVO, Long userId) {
|
||||
public void translateCustomer(CrmClueTranslateReqVO reqVO, Long userId) {
|
||||
// 1.1 校验线索都存在
|
||||
Set<Long> clueIds = reqVO.getIds();
|
||||
List<CrmClueDO> clues = getClueList(clueIds, userId);
|
||||
@ -192,47 +190,44 @@ public class CrmClueServiceImpl implements CrmClueService {
|
||||
throw exception(CLUE_ANY_CLUE_ALREADY_TRANSLATED, convertSet(translatedClues, CrmClueDO::getId));
|
||||
}
|
||||
|
||||
// 2. 遍历线索(未转化的线索),创建对应的客户
|
||||
// TODO @puhui999:这里不用过滤了;
|
||||
List<CrmClueDO> translateClues = filterList(clues, clue -> ObjUtil.equal(Boolean.FALSE, clue.getTransformStatus()));
|
||||
List<CrmCustomerDO> customers = customerService.createCustomerBatch(convertList(translateClues, clue ->
|
||||
BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class)), userId);
|
||||
|
||||
// TODO @puhui999:这里不用搞一个 clueCustomerIdMap 出来;可以考虑逐个创建,然后把 customerId 设置回 CrmClueDO;避免 name 匹配,极端会有问题哈;
|
||||
// TODO 是不是就直接 foreach 处理好了;因为本身量不大,for 处理性能 ok,可阅读性好
|
||||
Map<Long, Long> clueCustomerIdMap = new HashMap<>(translateClues.size());
|
||||
// 2.1 更新线索
|
||||
clueMapper.updateBatch(convertList(customers, customer -> {
|
||||
CrmClueDO firstClue = findFirst(translateClues, clue -> ObjUtil.equal(clue.getName(), customer.getName()));
|
||||
clueCustomerIdMap.put(firstClue.getId(), customer.getId());
|
||||
return new CrmClueDO().setId(firstClue.getId()).setTransformStatus(Boolean.TRUE).setCustomerId(customer.getId());
|
||||
}));
|
||||
// 2.3 复制跟进
|
||||
updateFollowUpRecords(clueCustomerIdMap);
|
||||
// 2.1 遍历线索(未转化的线索),创建对应的客户
|
||||
clues.forEach(clue -> {
|
||||
Long customerId = customerService.createCustomer(BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class), userId);
|
||||
clue.setCustomerId(customerId);
|
||||
});
|
||||
// 2.2 更新线索
|
||||
clueMapper.updateBatch(convertList(clues, clue -> new CrmClueDO().setId(clue.getId())
|
||||
.setTransformStatus(Boolean.TRUE).setCustomerId(clue.getCustomerId())));
|
||||
// 2.3 复制跟进记录
|
||||
copyFollowUpRecords(clues);
|
||||
|
||||
// 3. 记录操作日志
|
||||
for (CrmClueDO clue : translateClues) {
|
||||
// TODO @puhui999:这里优化下,translate 操作日志
|
||||
getSelf().receiveClueLog(clue);
|
||||
for (CrmClueDO clue : clues) {
|
||||
getSelf().translateCustomerLog(clue);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFollowUpRecords(Map<Long, Long> clueCustomerIdMap) {
|
||||
/**
|
||||
* 线索被转换客户后,需要将线索的跟进记录,复制到客户上
|
||||
*
|
||||
* @param clues 被转化的线索
|
||||
*/
|
||||
private void copyFollowUpRecords(List<CrmClueDO> clues) {
|
||||
List<CrmFollowUpRecordDO> followUpRecords = followUpRecordService.getFollowUpRecordByBiz(
|
||||
CrmBizTypeEnum.CRM_LEADS.getType(), clueCustomerIdMap.keySet());
|
||||
CrmBizTypeEnum.CRM_LEADS.getType(), convertSet(clues, CrmClueDO::getId));
|
||||
if (CollUtil.isEmpty(followUpRecords)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建跟进
|
||||
Map<Long, CrmClueDO> clueMap = convertMap(clues, CrmClueDO::getId);
|
||||
followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, followUpRecord ->
|
||||
BeanUtils.toBean(followUpRecord, CrmFollowUpCreateReqBO.class).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
|
||||
.setBizId(clueCustomerIdMap.get(followUpRecord.getBizId()))));
|
||||
.setBizId(clueMap.get(followUpRecord.getBizId()).getCustomerId())));
|
||||
}
|
||||
|
||||
@LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSLATE_SUB_TYPE, bizNo = "{{#clue.id}}",
|
||||
success = CRM_LEADS_TRANSLATE_SUCCESS)
|
||||
public void receiveClueLog(CrmClueDO clue) {
|
||||
public void translateCustomerLog(CrmClueDO clue) {
|
||||
// 记录操作日志上下文
|
||||
LogRecordContext.putVariable("clue", clue);
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper;
|
||||
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 jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
@ -19,7 +22,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
|
||||
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS;
|
||||
|
||||
// TODO @puhui999:数据权限的校验;每个操作;
|
||||
/**
|
||||
* 联系人与商机的关联 Service 实现类
|
||||
*
|
||||
@ -40,6 +42,7 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService
|
||||
private CrmContactService contactService;
|
||||
|
||||
@Override
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#createReqVO.contactId", level = CrmPermissionLevelEnum.WRITE)
|
||||
public void createContactBusinessList(CrmContactBusinessReqVO createReqVO) {
|
||||
CrmContactDO contact = contactService.getContact(createReqVO.getContactId());
|
||||
if (contact == null) {
|
||||
@ -65,6 +68,7 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService
|
||||
}
|
||||
|
||||
@Override
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#deleteReqVO.contactId", level = CrmPermissionLevelEnum.WRITE)
|
||||
public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) {
|
||||
CrmContactDO contact = contactService.getContact(deleteReqVO.getContactId());
|
||||
if (contact == null) {
|
||||
@ -76,11 +80,13 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService
|
||||
}
|
||||
|
||||
@Override
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.WRITE)
|
||||
public void deleteContactBusinessByContactId(Long contactId) {
|
||||
contactBusinessMapper.delete(CrmContactBusinessDO::getContactId,contactId);
|
||||
contactBusinessMapper.delete(CrmContactBusinessDO::getContactId, contactId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.READ)
|
||||
public List<CrmContactBusinessDO> getContactBusinessListByContactId(Long contactId) {
|
||||
return contactBusinessMapper.selectListByContactId(contactId);
|
||||
}
|
||||
|
@ -100,13 +100,13 @@ public interface CrmCustomerService {
|
||||
void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO);
|
||||
|
||||
/**
|
||||
* 批量创建客户
|
||||
* 创建客户
|
||||
*
|
||||
* @param customerCreateReqBOs 请求
|
||||
* @param userId 用户编号
|
||||
* @param customerCreateReq 请求信息
|
||||
* @param userId 用户编号
|
||||
* @return 客户列表
|
||||
*/
|
||||
List<CrmCustomerDO> createCustomerBatch(List<CrmCustomerCreateReqBO> customerCreateReqBOs, Long userId);
|
||||
Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId);
|
||||
|
||||
// ==================== 公海相关操作 ====================
|
||||
|
||||
@ -126,4 +126,12 @@ public interface CrmCustomerService {
|
||||
*/
|
||||
void receiveCustomer(List<Long> ids, Long ownerUserId, Boolean isReceive);
|
||||
|
||||
// TODO @puhui999:autoPutCustomerPool,注释说明是系统就好哈;
|
||||
/**
|
||||
* 【系统】客户自动掉入公海
|
||||
*
|
||||
* @return 掉入公海数量
|
||||
*/
|
||||
int customerAutoPutPoolBySystem();
|
||||
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.crm.service.customer;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLockReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
|
||||
@ -13,6 +15,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTrans
|
||||
import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
|
||||
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;
|
||||
import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
|
||||
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
|
||||
@ -31,6 +34,7 @@ import com.mzt.logapi.context.LogRecordContext;
|
||||
import com.mzt.logapi.service.impl.DiffParseFunction;
|
||||
import com.mzt.logapi.starter.annotation.LogRecord;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -43,12 +47,10 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
|
||||
import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT;
|
||||
import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_OWNER_LIMIT;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
/**
|
||||
@ -57,6 +59,7 @@ import static java.util.Collections.singletonList;
|
||||
* @author Wanwan
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@Validated
|
||||
public class CrmCustomerServiceImpl implements CrmCustomerService {
|
||||
|
||||
@ -69,6 +72,9 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
|
||||
private CrmCustomerLimitConfigService customerLimitConfigService;
|
||||
@Resource
|
||||
@Lazy
|
||||
private CrmCustomerPoolConfigService customerPoolConfigService;
|
||||
@Resource
|
||||
@Lazy
|
||||
private CrmContactService contactService;
|
||||
@Resource
|
||||
@Lazy
|
||||
@ -91,9 +97,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
|
||||
|
||||
// 2. 插入客户
|
||||
CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class)
|
||||
.setLockStatus(false).setDealStatus(false)
|
||||
.setContactLastTime(LocalDateTime.now());
|
||||
// TODO @puhui999:可能要加个 receiveTime 字段,记录最后接收时间
|
||||
.setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now());
|
||||
customerMapper.insert(customer);
|
||||
|
||||
// 3. 创建数据权限
|
||||
@ -214,24 +218,24 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<CrmCustomerDO> createCustomerBatch(List<CrmCustomerCreateReqBO> customerCreateReqBOs, Long userId) {
|
||||
if (CollUtil.isEmpty(customerCreateReqBOs)) {
|
||||
return emptyList();
|
||||
}
|
||||
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}",
|
||||
success = CRM_CUSTOMER_CREATE_SUCCESS)
|
||||
public Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId) {
|
||||
// 1. 插入客户
|
||||
CrmCustomerDO customer = BeanUtils.toBean(customerCreateReq, CrmCustomerDO.class).setOwnerUserId(userId)
|
||||
.setLockStatus(false).setDealStatus(false).setReceiveTime(LocalDateTime.now());
|
||||
customerMapper.insert(customer);
|
||||
|
||||
// 创建客户
|
||||
List<CrmCustomerDO> customers = convertList(customerCreateReqBOs, customerBO ->
|
||||
BeanUtils.toBean(customerBO, CrmCustomerDO.class).setOwnerUserId(userId));
|
||||
customerMapper.insertBatch(customers);
|
||||
// 2. 创建数据权限
|
||||
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
|
||||
.setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
|
||||
|
||||
// 创建负责人数据权限
|
||||
permissionService.createPermissionBatch(convertList(customers, customer -> new CrmPermissionCreateReqBO()
|
||||
.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()).setBizId(customer.getId()).setUserId(userId)
|
||||
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel())));
|
||||
return customers;
|
||||
// 3. 记录操作日志上下文
|
||||
LogRecordContext.putVariable("customer", customer);
|
||||
return customer.getId();
|
||||
}
|
||||
|
||||
// ==================== 公海相关操作 ====================
|
||||
// ==================== 公海相关操作 ====================
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@ -249,17 +253,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
|
||||
// 1.3. 校验客户是否锁定
|
||||
validateCustomerIsLocked(customer, true);
|
||||
|
||||
// 2.1 设置负责人为 NULL
|
||||
int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null);
|
||||
if (updateOwnerUserIncr == 0) {
|
||||
throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL);
|
||||
}
|
||||
// 2.2 删除负责人数据权限
|
||||
permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(),
|
||||
CrmPermissionLevelEnum.OWNER.getLevel());
|
||||
|
||||
// 3. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的
|
||||
contactService.updateOwnerUserIdByCustomerId(customer.getId(), null);
|
||||
// 2. 客户放入公海
|
||||
putCustomerPool(customer);
|
||||
|
||||
// 记录操作日志上下文
|
||||
LogRecordContext.putVariable("customerName", customer.getName());
|
||||
@ -317,6 +312,52 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int customerAutoPutPoolBySystem() {
|
||||
CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
|
||||
if (poolConfig == null || !poolConfig.getEnabled()) {
|
||||
return 0;
|
||||
}
|
||||
// 1. 获取没有锁定的不在公海的客户
|
||||
List<CrmCustomerDO> customerList = customerMapper.selectListByLockStatusAndOwnerUserIdNotNull(Boolean.FALSE);
|
||||
List<CrmCustomerDO> poolCustomerList = CollectionUtils.filterList(customerList, customer -> {
|
||||
// TODO @puhui999:建议这里作为一个查询条件哈,不放内存里过滤;
|
||||
// 1.1 未成交放入公海
|
||||
if (!customer.getDealStatus()) {
|
||||
return (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0;
|
||||
}
|
||||
// 1.2 未跟进放入公海
|
||||
LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime());
|
||||
return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0;
|
||||
});
|
||||
|
||||
// 2. 逐个放入公海
|
||||
int count = 0;
|
||||
for (CrmCustomerDO customer : poolCustomerList) {
|
||||
try {
|
||||
getSelf().putCustomerPool(customer);
|
||||
count++;
|
||||
} catch (Throwable e) {
|
||||
log.error("[customerAutoPutPoolBySystem][Customer 客户({}) 放入公海异常]", customer.getId(), e);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void putCustomerPool(CrmCustomerDO customer) {
|
||||
// 1. 设置负责人为 NULL
|
||||
int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null);
|
||||
if (updateOwnerUserIncr == 0) {
|
||||
throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL);
|
||||
}
|
||||
// 2. 删除负责人数据权限
|
||||
permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(),
|
||||
CrmPermissionLevelEnum.OWNER.getLevel());
|
||||
|
||||
// 3. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的
|
||||
contactService.updateOwnerUserIdByCustomerId(customer.getId(), null);
|
||||
}
|
||||
|
||||
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_RECEIVE_SUB_TYPE, bizNo = "{{#customer.id}}",
|
||||
success = CRM_CUSTOMER_RECEIVE_SUCCESS)
|
||||
public void receiveCustomerLog(CrmCustomerDO customer, String ownerUserName) {
|
||||
|
@ -28,9 +28,9 @@ public interface CrmFollowUpRecordService {
|
||||
/**
|
||||
* 创建更进
|
||||
*
|
||||
* @param followUpCreateReqBOs 请求
|
||||
* @param list 请求
|
||||
*/
|
||||
void createFollowUpRecordBatch(List<CrmFollowUpCreateReqBO> followUpCreateReqBOs);
|
||||
void createFollowUpRecordBatch(List<CrmFollowUpCreateReqBO> list);
|
||||
|
||||
/**
|
||||
* 删除跟进记录 (数据权限基于 bizType、 bizId)
|
||||
|
@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.mysql.followup.CrmFollowUpRecordMapper;
|
||||
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
|
||||
@ -31,10 +30,10 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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.convertList;
|
||||
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_DELETE_DENIED;
|
||||
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils.hasPermission;
|
||||
|
||||
/**
|
||||
* 跟进记录 Service 实现类
|
||||
@ -94,26 +93,25 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
|
||||
customerService.updateCustomerFollowUp(updateFollowUpReqBO);
|
||||
}
|
||||
|
||||
// TODO @puhui999:这两个,不更新 contactLastTime、contactLastContent,只更新 nextTime
|
||||
// 3.1 更新 contactIds 对应的记录
|
||||
// 3.1 更新 contactIds 对应的记录,不更新 lastTime 和 lastContent
|
||||
if (CollUtil.isNotEmpty(createReqVO.getContactIds())) {
|
||||
contactService.updateContactFollowUpBatch(convertList(createReqVO.getContactIds(), updateFollowUpReqBO::setBizId));
|
||||
contactService.updateContactFollowUpBatch(convertList(createReqVO.getContactIds(),
|
||||
contactId -> updateFollowUpReqBO.setBizId(contactId).setContactLastTime(null).setContactLastContent(null)));
|
||||
}
|
||||
// 3.2 需要更新 businessIds、contactIds 对应的记录
|
||||
// 3.2 需要更新 businessIds 对应的记录,不更新 lastTime 和 lastContent
|
||||
if (CollUtil.isNotEmpty(createReqVO.getBusinessIds())) {
|
||||
businessService.updateBusinessFollowUpBatch(convertList(createReqVO.getBusinessIds(), updateFollowUpReqBO::setBizId));
|
||||
businessService.updateBusinessFollowUpBatch(convertList(createReqVO.getBusinessIds(),
|
||||
businessId -> updateFollowUpReqBO.setBizId(businessId).setContactLastTime(null).setContactLastContent(null)));
|
||||
}
|
||||
return followUpRecord.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createFollowUpRecordBatch(List<CrmFollowUpCreateReqBO> followUpCreateReqBOs) {
|
||||
if (CollUtil.isEmpty(followUpCreateReqBOs)) {
|
||||
public void createFollowUpRecordBatch(List<CrmFollowUpCreateReqBO> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<CrmFollowUpRecordDO> followUpRecords = BeanUtils.toBean(followUpCreateReqBOs, CrmFollowUpRecordDO.class);
|
||||
crmFollowUpRecordMapper.insertBatch(followUpRecords);
|
||||
crmFollowUpRecordMapper.insertBatch(BeanUtils.toBean(list, CrmFollowUpRecordDO.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,12 +119,7 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
|
||||
// 校验存在
|
||||
CrmFollowUpRecordDO followUpRecord = validateFollowUpRecordExists(id);
|
||||
// 校验权限
|
||||
// TODO @puhui999:是不是封装一个 hasPermission,更简介一点;
|
||||
List<CrmPermissionDO> permissionList = permissionService.getPermissionListByBiz(
|
||||
followUpRecord.getBizType(), followUpRecord.getBizId());
|
||||
boolean hasPermission = anyMatch(permissionList, permission ->
|
||||
ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), CrmPermissionLevelEnum.OWNER.getLevel()));
|
||||
if (!hasPermission) {
|
||||
if (!hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(), userId, CrmPermissionLevelEnum.OWNER)) {
|
||||
throw exception(FOLLOW_UP_RECORD_DELETE_DENIED);
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@ public class CrmUpdateFollowUpReqBO {
|
||||
|
||||
@Schema(description = "最后跟进时间")
|
||||
@DiffLogField(name = "最后跟进时间")
|
||||
@NotNull(message = "最后跟进时间不能为空")
|
||||
private LocalDateTime contactLastTime;
|
||||
|
||||
@Schema(description = "下次联系时间")
|
||||
@ -30,7 +29,6 @@ public class CrmUpdateFollowUpReqBO {
|
||||
|
||||
@Schema(description = "最后更进内容")
|
||||
@DiffLogField(name = "最后更进内容")
|
||||
@NotNull(message = "最后更进内容不能为空")
|
||||
private String contactLastContent;
|
||||
|
||||
}
|
||||
|
24
yudao-module-erp/pom.xml
Normal file
24
yudao-module-erp/pom.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modules>
|
||||
<module>yudao-module-erp-api</module>
|
||||
<module>yudao-module-erp-biz</module>
|
||||
</modules>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-module-erp</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
erp 包下,企业资源管理(Enterprise Resource Planning)。
|
||||
例如说:采购、销售、库存、财务、产品等等
|
||||
</description>
|
||||
|
||||
</project>
|
@ -3,17 +3,18 @@
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>yudao-framework</artifactId>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-erp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-spring-boot-starter-banner</artifactId>
|
||||
<artifactId>yudao-module-erp-api</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Banner 用于在 console 控制台,打印开发文档、接口文档等</description>
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
<description>
|
||||
erp 模块 API,暴露给其它模块调用
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@ -21,10 +22,12 @@
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 参数校验 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* erp API 包,定义暴露给其它模块的 API
|
||||
*/
|
||||
package cn.iocoder.yudao.module.erp.api;
|
@ -0,0 +1,10 @@
|
||||
package cn.iocoder.yudao.module.erp.enums;
|
||||
|
||||
/**
|
||||
* ERP 字典类型的枚举类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface DictTypeConstants {
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package cn.iocoder.yudao.module.erp.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* ERP 错误码枚举类
|
||||
* <p>
|
||||
* erp 系统,使用 1-030-000-000 段
|
||||
*/
|
||||
public interface ErrorCodeConstants {
|
||||
|
||||
// ========== 销售订单(1-030-000-000) ==========
|
||||
ErrorCode SALE_ORDER_NOT_EXISTS = new ErrorCode(1_030_000_000, "销售订单不存在");
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package cn.iocoder.yudao.module.erp.enums;
|
||||
|
||||
/**
|
||||
* ERP 操作日志枚举
|
||||
* 目的:统一管理,也减少 Service 里各种“复杂”字符串
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface LogRecordConstants {
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package cn.iocoder.yudao.module.erp.enums.sale;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* ERP 销售订单的状态枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum ErpSaleOrderStatusEnum implements IntArrayValuable {
|
||||
|
||||
AUDIT_NONE(0, "未审核"),
|
||||
AUDIT_PASS(10, "已审核"),
|
||||
SALE_PART(20, "部分销售"),
|
||||
SALE_ALL(21, "完成销售"),
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ErpSaleOrderStatusEnum::getStatus).toArray();
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private final Integer status;
|
||||
/**
|
||||
* 状态名
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
72
yudao-module-erp/yudao-module-erp-biz/pom.xml
Normal file
72
yudao-module-erp/yudao-module-erp-biz/pom.xml
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-erp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-module-erp-biz</artifactId>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
erp 包下,企业资源管理(Enterprise Resource Planning)。
|
||||
例如说:采购、销售、库存、财务、产品等等
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-erp-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-excel</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,4 @@
|
||||
### 请求 /transfer
|
||||
GET {{baseUrl}}/erp/sale-order/demo
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
@ -0,0 +1,100 @@
|
||||
package cn.iocoder.yudao.module.erp.controller.admin.sale;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
|
||||
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderRespVO;
|
||||
import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
|
||||
import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
|
||||
import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
|
||||
|
||||
|
||||
@Tag(name = "管理后台 - ERP 销售订单")
|
||||
@RestController
|
||||
@RequestMapping("/erp/sale-order")
|
||||
@Validated
|
||||
public class ErpSaleOrderController {
|
||||
|
||||
@Resource
|
||||
private ErpSaleOrderService saleOrderService;
|
||||
|
||||
// TODO 芋艿:待 review
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建ERP 销售订单")
|
||||
@PreAuthorize("@ss.hasPermission('erp:sale-order:create')")
|
||||
public CommonResult<Long> createSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO createReqVO) {
|
||||
return success(saleOrderService.createSaleOrder(createReqVO));
|
||||
}
|
||||
|
||||
// TODO 芋艿:待 review
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新ERP 销售订单")
|
||||
@PreAuthorize("@ss.hasPermission('erp:sale-order:update')")
|
||||
public CommonResult<Boolean> updateSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO updateReqVO) {
|
||||
saleOrderService.updateSaleOrder(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// TODO 芋艿:待 review
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除ERP 销售订单")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('erp:sale-order:delete')")
|
||||
public CommonResult<Boolean> deleteSaleOrder(@RequestParam("id") Long id) {
|
||||
saleOrderService.deleteSaleOrder(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// TODO 芋艿:待 review
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得ERP 销售订单")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('erp:sale-order:query')")
|
||||
public CommonResult<ErpSaleOrderRespVO> getSaleOrder(@RequestParam("id") Long id) {
|
||||
ErpSaleOrderDO saleOrder = saleOrderService.getSaleOrder(id);
|
||||
return success(BeanUtils.toBean(saleOrder, ErpSaleOrderRespVO.class));
|
||||
}
|
||||
|
||||
// TODO 芋艿:待 review
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得ERP 销售订单分页")
|
||||
@PreAuthorize("@ss.hasPermission('erp:sale-order:query')")
|
||||
public CommonResult<PageResult<ErpSaleOrderRespVO>> getSaleOrderPage(@Valid ErpSaleOrderPageReqVO pageReqVO) {
|
||||
PageResult<ErpSaleOrderDO> pageResult = saleOrderService.getSaleOrderPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, ErpSaleOrderRespVO.class));
|
||||
}
|
||||
|
||||
// TODO 芋艿:待 review
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出ERP 销售订单 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('erp:sale-order:export')")
|
||||
@OperateLog(type = EXPORT)
|
||||
public void exportSaleOrderExcel(@Valid ErpSaleOrderPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<ErpSaleOrderDO> list = saleOrderService.getSaleOrderPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "ERP 销售订单.xls", "数据", ErpSaleOrderRespVO.class,
|
||||
BeanUtils.toBean(list, ErpSaleOrderRespVO.class));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - ERP 销售订单分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ErpSaleOrderPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "销售单编号", example = "XS001")
|
||||
private String no;
|
||||
|
||||
@Schema(description = "客户编号", example = "1724")
|
||||
private Long customerId;
|
||||
|
||||
@Schema(description = "下单时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] orderTime;
|
||||
|
||||
@Schema(description = "备注", example = "你猜")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "销售状态", example = "2")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
private String creator;
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
// TODO 芋艿:导出最后搞
|
||||
@Schema(description = "管理后台 - ERP 销售订单 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class ErpSaleOrderRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "销售单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001")
|
||||
@ExcelProperty("销售单编号")
|
||||
private String no;
|
||||
|
||||
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724")
|
||||
@ExcelProperty("客户编号")
|
||||
private Long customerId;
|
||||
|
||||
@Schema(description = "下单时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("下单时间")
|
||||
private LocalDateTime orderTime;
|
||||
|
||||
// TODO 芋艿:example 后面
|
||||
@Schema(description = "销售员编号数组")
|
||||
@ExcelProperty("销售员编号数组")
|
||||
private String salePersonIds;
|
||||
|
||||
@Schema(description = "合计价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "26094")
|
||||
@ExcelProperty("合计价格,单位:元")
|
||||
private BigDecimal totalPrice;
|
||||
|
||||
@Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88")
|
||||
@ExcelProperty("优惠率,百分比")
|
||||
private BigDecimal discountPercent;
|
||||
|
||||
@Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "44.52")
|
||||
@ExcelProperty("优惠金额,单位:元")
|
||||
private BigDecimal discountPrice;
|
||||
|
||||
@Schema(description = "支付金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "322.40")
|
||||
@ExcelProperty("支付金额,单位:元")
|
||||
private BigDecimal payPrice;
|
||||
|
||||
@Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "71.27")
|
||||
@ExcelProperty("定金金额,单位:元")
|
||||
private BigDecimal depositPrice;
|
||||
|
||||
@Schema(description = "附件地址", example = "https://www.iocoder.cn")
|
||||
@ExcelProperty("附件地址")
|
||||
private String fileUrl;
|
||||
|
||||
@Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89")
|
||||
@ExcelProperty("结算账户编号")
|
||||
private Long accountId;
|
||||
|
||||
@Schema(description = "备注", example = "你猜")
|
||||
@ExcelProperty("备注")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "销售状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
@ExcelProperty("销售状态")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order;
|
||||
|
||||
import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSalesOrderItemDO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - ERP 销售订单新增/修改 Request VO")
|
||||
@Data
|
||||
public class ErpSaleOrderSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "销售单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001")
|
||||
@NotEmpty(message = "销售单编号不能为空")
|
||||
private String no;
|
||||
|
||||
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724")
|
||||
@NotNull(message = "客户编号不能为空")
|
||||
private Long customerId;
|
||||
|
||||
@Schema(description = "下单时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "下单时间不能为空")
|
||||
private LocalDateTime orderTime;
|
||||
|
||||
@Schema(description = "销售员编号数组")
|
||||
private String salePersonIds;
|
||||
|
||||
@Schema(description = "合计价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "26094")
|
||||
@NotNull(message = "合计价格,单位:元不能为空")
|
||||
private BigDecimal totalPrice;
|
||||
|
||||
@Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88")
|
||||
@NotNull(message = "优惠率,百分比不能为空")
|
||||
private BigDecimal discountPercent;
|
||||
|
||||
@Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "4452")
|
||||
@NotNull(message = "优惠金额,单位:元不能为空")
|
||||
private BigDecimal discountPrice;
|
||||
|
||||
// TODO 芋艿:后面删除
|
||||
// @Schema(description = "支付金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "32240")
|
||||
// @NotNull(message = "支付金额,单位:元不能为空")
|
||||
// private BigDecimal payPrice;
|
||||
|
||||
@Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
|
||||
@NotNull(message = "定金金额,单位:元不能为空")
|
||||
private BigDecimal depositPrice;
|
||||
|
||||
@Schema(description = "附件地址", example = "https://www.iocoder.cn")
|
||||
private String fileUrl;
|
||||
|
||||
@Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31189")
|
||||
@NotNull(message = "结算账户编号不能为空")
|
||||
private Long accountId;
|
||||
|
||||
@Schema(description = "备注", example = "你猜")
|
||||
private String description;
|
||||
|
||||
// TODO 芋艿:后面删除
|
||||
// @Schema(description = "销售状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
// @NotNull(message = "销售状态不能为空")
|
||||
// private Integer status;
|
||||
|
||||
@Schema(description = "ERP 销售订单明细列表")
|
||||
private List<ErpSalesOrderItemDO> salesOrderItems;
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 提供 RESTful API 给前端:
|
||||
* 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目
|
||||
* 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分
|
||||
*/
|
||||
package cn.iocoder.yudao.module.erp.controller;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user