Commit 9203f485e9fa5e561dadc961ff869cf0e888c1ed

Authored by owen
2 parents 09445840 a974d055

Merge remote-tracking branch 'origin/develop' into develop

# Conflicts:
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/article/AppArticleController.java
Showing 203 changed files with 2017 additions and 1410 deletions

Too many changes.

To preserve performance only 100 of 203 files are displayed.

README.md
... ... @@ -31,7 +31,7 @@
31 31  
32 32 ![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
33 33  
34   -* Java 后端:`master` 分支为 JDK 21 + Spring Boot 3.2.0,`master-jdk8` 分支为 JDK8 + Spring Boot 2.7.18
  34 +* Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7.18,`master-jdk21` 分支为 JDK21 + Spring Boot 3.2.0
35 35 * 管理后台的电脑端:Vue3 提供 `element-plus`、`vben(ant-design-vue)` 两个版本,Vue2 提供 `element-ui` 版本
36 36 * 管理后台的移动端:采用 `uni-app` 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
37 37 * 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
... ... @@ -78,15 +78,21 @@
78 78  
79 79 【完整版】包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM 等功能
80 80  
81   -* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro> 的 `master` 分支
82   -* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro> 的 `master-jdk8` 分支
  81 +* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro> 的 `master` 分支
  82 +* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro> 的 `master-jdk21` 分支
  83 +
  84 +两个分支的功能是一致的,可以放心使用!
83 85  
84 86 ### ➡️️ 精简版
85 87  
86 88 【精简版】只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM 等功能
87 89  
88   -* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/yudaocode/yudao-boot-mini> 的 `master` 分支
89   -* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/yudaocode/yudao-boot-mini> 的 `master-jdk8` 分支
  90 +* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/yudaocode/yudao-boot-mini> 的 `master` 分支
  91 +* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/yudaocode/yudao-boot-mini> 的 `master-jdk21` 分支
  92 +
  93 +如果你想把【完整版】的功能,迁移到【精简版】,可以参考 [《迁移功能到精简版》](https://doc.iocoder.cn/migrate-module/) 文档。
  94 +
  95 +如果你想把【完整版】的功能,迁移到【精简版】,可以参考 [《迁移功能到精简版》](https://doc.iocoder.cn/migrate-module/) 文档。
90 96  
91 97 ## 😎 开源协议
92 98  
... ...
sql/mysql/optinal/crm.sql
1 1 -- `ruoyi-vue-pro`.crm_contact_business_link definition
2 2  
3   -CREATE TABLE `crm_contact_business_link` (
  3 +CREATE TABLE `crm_contact_business` (
4 4 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
5 5 `contact_id` int(11) DEFAULT NULL COMMENT '联系人id',
6 6 `business_id` int(11) DEFAULT NULL COMMENT '商机id',
... ... @@ -10,6 +10,5 @@ CREATE TABLE `crm_contact_business_link` (
10 10 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
11 11 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
12 12 `tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '租户编号',
13   - PRIMARY KEY (`id`),
14   - UNIQUE KEY `crm_contact_business_link_un` (`contact_id`,`business_id`,`deleted`,`tenant_id`)
  13 + PRIMARY KEY (`id`)
15 14 ) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='联系人商机关联表';
16 15 \ No newline at end of file
... ...
yudao-dependencies/pom.xml
... ... @@ -26,7 +26,7 @@
26 26 <mybatis-plus.version>3.5.4.1</mybatis-plus.version>
27 27 <mybatis-plus-generator.version>3.5.4.1</mybatis-plus-generator.version>
28 28 <dynamic-datasource.version>4.2.0</dynamic-datasource.version>
29   - <mybatis-plus-join.version>1.4.7.2</mybatis-plus-join.version>
  29 + <mybatis-plus-join.version>1.4.8.1</mybatis-plus-join.version>
30 30 <redisson.version>3.25.0</redisson.version>
31 31 <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
32 32 <!-- 消息队列 -->
... ... @@ -51,7 +51,7 @@
51 51 <mapstruct.version>1.5.5.Final</mapstruct.version>
52 52 <hutool-5.version>5.8.23</hutool-5.version>
53 53 <hutool-6.version>6.0.0-M8</hutool-6.version>
54   - <easyexcel.verion>3.3.2</easyexcel.verion>
  54 + <easyexcel.verion>3.3.3</easyexcel.verion>
55 55 <velocity.version>2.3</velocity.version>
56 56 <screw.version>1.0.5</screw.version>
57 57 <fastjson.version>1.2.83</fastjson.version>
... ... @@ -72,7 +72,7 @@
72 72 <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
73 73 <tencentcloud-sdk-java.version>3.1.880</tencentcloud-sdk-java.version>
74 74 <justauth.version>2.0.5</justauth.version>
75   - <jimureport.version>1.6.1</jimureport.version>
  75 + <jimureport.version>1.6.6-beta2</jimureport.version>
76 76 <xercesImpl.version>2.12.2</xercesImpl.version>
77 77 <weixin-java.version>4.5.7.B</weixin-java.version>
78 78 <ureport2.version>2.2.9</ureport2.version>
... ... @@ -651,7 +651,7 @@
651 651 <!-- 积木报表-->
652 652 <dependency>
653 653 <groupId>org.jeecgframework.jimureport</groupId>
654   - <artifactId>jimureport-spring-boot-starter</artifactId>
  654 + <artifactId>jimureport-spring-boot3-starter</artifactId>
655 655 <version>${jimureport.version}</version>
656 656 <exclusions>
657 657 <exclusion>
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/TerminalEnum.java
... ... @@ -15,6 +15,7 @@ import java.util.Arrays;
15 15 @Getter
16 16 public enum TerminalEnum implements IntArrayValuable {
17 17  
  18 + UNKNOWN(0, "未知"), // 目的:在无法解析到 terminal 时,使用它
18 19 WECHAT_MINI_PROGRAM(10, "微信小程序"),
19 20 WECHAT_WAP(11, "微信公众号"),
20 21 H5(20, "H5 网页"),
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java
1 1 package cn.iocoder.yudao.framework.common.util.cache;
2 2  
  3 +import com.alibaba.ttl.threadpool.TtlExecutors;
3 4 import com.google.common.cache.CacheBuilder;
4 5 import com.google.common.cache.CacheLoader;
5 6 import com.google.common.cache.LoadingCache;
6 7  
7 8 import java.time.Duration;
  9 +import java.util.concurrent.Executor;
8 10 import java.util.concurrent.Executors;
9 11  
10 12 /**
... ... @@ -15,11 +17,13 @@ import java.util.concurrent.Executors;
15 17 public class CacheUtils {
16 18  
17 19 public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
  20 + Executor executor = Executors.newCachedThreadPool( // TODO 芋艿:可能要思考下,未来要不要做成可配置
  21 + TtlExecutors.getDefaultDisableInheritableThreadFactory()); // TTL 保证 ThreadLocal 可以透传
18 22 return CacheBuilder.newBuilder()
19 23 // 只阻塞当前数据加载线程,其他线程返回旧值
20 24 .refreshAfterWrite(duration)
21 25 // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
22   - .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO 芋艿:可能要思考下,未来要不要做成可配置
  26 + .build(CacheLoader.asyncReloading(loader, executor));
23 27 }
24 28  
25 29 }
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/servlet/ServletUtils.java
... ... @@ -88,8 +88,6 @@ public class ServletUtils {
88 88 return JakartaServletUtil.getClientIP(request);
89 89 }
90 90  
91   - // TODO @疯狂:terminal 还是从 ServletUtils 里拿,更容易全局治理;
92   -
93 91 public static boolean isJsonRequest(ServletRequest request) {
94 92 return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);
95 93 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/resources/area.csv
... ... @@ -490,6 +490,37 @@ id,name,type,parentId
490 490 441700,阳江市,3,440000
491 491 441800,清远市,3,440000
492 492 441900,东莞市,3,440000
  493 +441901,莞城区,4,441900
  494 +441902,南城区,4,441900
  495 +441904,万江区,4,441900
  496 +441905,石碣镇,4,441900
  497 +441906,石龙镇,4,441900
  498 +441907,茶山镇,4,441900
  499 +441908,石排镇,4,441900
  500 +441909,企石镇,4,441900
  501 +441910,横沥镇,4,441900
  502 +441911,桥头镇,4,441900
  503 +441912,谢岗镇,4,441900
  504 +441913,东坑镇,4,441900
  505 +441914,常平镇,4,441900
  506 +441915,寮步镇,4,441900
  507 +441916,大朗镇,4,441900
  508 +441917,麻涌镇,4,441900
  509 +441918,中堂镇,4,441900
  510 +441919,高埗镇,4,441900
  511 +441920,樟木头镇,4,441900
  512 +441921,大岭山镇,4,441900
  513 +441922,望牛墩镇,4,441900
  514 +441923,黄江镇,4,441900
  515 +441924,洪梅镇,4,441900
  516 +441925,清溪镇,4,441900
  517 +441926,沙田镇,4,441900
  518 +441927,道滘镇,4,441900
  519 +441928,塘厦镇,4,441900
  520 +441929,虎门镇,4,441900
  521 +441930,厚街镇,4,441900
  522 +441931,凤岗镇,4,441900
  523 +441932,长安镇,4,441900
493 524 442000,中山市,3,440000
494 525 445100,潮州市,3,440000
495 526 445200,揭阳市,3,440000
... ... @@ -3605,4 +3636,4 @@ id,name,type,parentId
3605 3636 659008,可克达拉市,4,659000
3606 3637 659009,昆玉市,4,659000
3607 3638 659010,胡杨河市,4,659000
3608 3639 -659011,新星市,4,659000
  3640 +659011,新星市,4,659000
3609 3641 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-biz-operatelog/pom.xml
... ... @@ -46,6 +46,7 @@
46 46 <groupId>com.google.guava</groupId>
47 47 <artifactId>guava</artifactId>
48 48 </dependency>
  49 +
49 50 </dependencies>
50 51  
51 52 </project>
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
... ... @@ -71,6 +71,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
71 71 if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
72 72 payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
73 73 }
  74 +// payConfig.setCertSerialNo();
74 75  
75 76 // 创建 client 客户端
76 77 client = new WxPayServiceImpl();
... ...
yudao-framework/yudao-spring-boot-starter-biz-sms/src/main/java/cn/iocoder/yudao/framework/sms/core/client/impl/AbstractSmsClient.java
... ... @@ -41,6 +41,7 @@ public abstract class AbstractSmsClient implements SmsClient {
41 41 return;
42 42 }
43 43 log.info("[refresh][配置({})发生变化,重新初始化]", properties);
  44 + this.properties = properties;
44 45 // 初始化
45 46 this.init();
46 47 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java
1 1 package cn.iocoder.yudao.framework.tenant.core.context;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
4 5 import com.alibaba.ttl.TransmittableThreadLocal;
5 6  
... ... @@ -21,7 +22,7 @@ public class TenantContextHolder {
21 22 private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
22 23  
23 24 /**
24   - * 获得租户编号。
  25 + * 获得租户编号
25 26 *
26 27 * @return 租户编号
27 28 */
... ... @@ -30,6 +31,16 @@ public class TenantContextHolder {
30 31 }
31 32  
32 33 /**
  34 + * 获得租户编号 String
  35 + *
  36 + * @return 租户编号
  37 + */
  38 + public static String getTenantIdStr() {
  39 + Long tenantId = getTenantId();
  40 + return StrUtil.toStringOrNull(tenantId);
  41 + }
  42 +
  43 + /**
33 44 * 获得租户编号。如果不存在,则抛出 NullPointerException 异常
34 45 *
35 46 * @return 租户编号
... ...
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
1 1 package cn.iocoder.yudao.framework.excel.core.util;
2 2  
3 3 import com.alibaba.excel.EasyExcel;
  4 +import com.alibaba.excel.converters.longconverter.LongStringConverter;
4 5 import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
5 6 import org.springframework.web.multipart.MultipartFile;
6 7  
7 8 import jakarta.servlet.http.HttpServletResponse;
8 9 import java.io.IOException;
9 10 import java.net.URLEncoder;
  11 +import java.nio.charset.StandardCharsets;
10 12 import java.util.List;
11 13  
12 14 /**
... ... @@ -33,9 +35,10 @@ public class ExcelUtils {
33 35 EasyExcel.write(response.getOutputStream(), head)
34 36 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
35 37 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
  38 + .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
36 39 .sheet(sheetName).doWrite(data);
37 40 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
38   - response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
  41 + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8));
39 42 response.setContentType("application/vnd.ms-excel;charset=UTF-8");
40 43 }
41 44  
... ...
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java
... ... @@ -81,7 +81,7 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
81 81 }
82 82 // 腾讯云必须有 region,否则会报错
83 83 if (config.getEndpoint().contains(ENDPOINT_TENCENT)) {
84   - return StrUtil.subAfter(config.getEndpoint(), ".cos.", false)
  84 + return StrUtil.subAfter(config.getEndpoint(), "cos.", false)
85 85 .replaceAll("." + ENDPOINT_TENCENT, ""); // 去除 Endpoint
86 86 }
87 87 return null;
... ...
yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/config/YudaoFlowableConfiguration.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.flowable.config;
3 3 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
4 4 import cn.iocoder.yudao.framework.flowable.core.web.FlowableWebFilter;
5 5 import org.springframework.boot.autoconfigure.AutoConfiguration;
  6 +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
6 7 import org.springframework.boot.web.servlet.FilterRegistrationBean;
7 8 import org.springframework.context.annotation.Bean;
8 9 import org.springframework.core.task.AsyncListenableTaskExecutor;
... ... @@ -17,6 +18,7 @@ public class YudaoFlowableConfiguration {
17 18 * 如果不创建,会导致项目启动时,Flowable 报错的问题
18 19 */
19 20 @Bean
  21 + @ConditionalOnMissingBean
20 22 public AsyncListenableTaskExecutor taskExecutor() {
21 23 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
22 24 executor.setCorePoolSize(8);
... ... @@ -40,4 +42,5 @@ public class YudaoFlowableConfiguration {
40 42 registrationBean.setOrder(WebFilterOrderEnum.FLOWABLE_FILTER);
41 43 return registrationBean;
42 44 }
  45 +
43 46 }
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
... ... @@ -65,6 +65,12 @@
65 65 <dependency>
66 66 <groupId>com.baomidou</groupId>
67 67 <artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
  68 + <exclusions>
  69 + <exclusion>
  70 + <groupId>org.springframework.boot</groupId>
  71 + <artifactId>spring-boot-starter-undertow</artifactId>
  72 + </exclusion>
  73 + </exclusions>
68 74 </dependency>
69 75  
70 76 <dependency>
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
... ... @@ -146,8 +146,8 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
146 146 *
147 147 * @param entities 实体们
148 148 */
149   - default void insertBatch(Collection<T> entities) {
150   - Db.saveBatch(entities);
  149 + default Boolean insertBatch(Collection<T> entities) {
  150 + return Db.saveBatch(entities);
151 151 }
152 152  
153 153 /**
... ... @@ -156,28 +156,28 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
156 156 * @param entities 实体们
157 157 * @param size 插入数量 Db.saveBatch 默认为 1000
158 158 */
159   - default void insertBatch(Collection<T> entities, int size) {
160   - Db.saveBatch(entities, size);
  159 + default Boolean insertBatch(Collection<T> entities, int size) {
  160 + return Db.saveBatch(entities, size);
161 161 }
162 162  
163   - default void updateBatch(T update) {
164   - update(update, new QueryWrapper<>());
  163 + default int updateBatch(T update) {
  164 + return update(update, new QueryWrapper<>());
165 165 }
166 166  
167   - default void updateBatch(Collection<T> entities) {
168   - Db.updateBatchById(entities);
  167 + default Boolean updateBatch(Collection<T> entities) {
  168 + return Db.updateBatchById(entities);
169 169 }
170 170  
171   - default void updateBatch(Collection<T> entities, int size) {
172   - Db.updateBatchById(entities, size);
  171 + default Boolean updateBatch(Collection<T> entities, int size) {
  172 + return Db.updateBatchById(entities, size);
173 173 }
174 174  
175   - default void insertOrUpdate(T entity) {
176   - Db.saveOrUpdate(entity);
  175 + default Boolean insertOrUpdate(T entity) {
  176 + return Db.saveOrUpdate(entity);
177 177 }
178 178  
179   - default void insertOrUpdateBatch(Collection<T> collection) {
180   - Db.saveOrUpdateBatch(collection);
  179 + default Boolean insertOrUpdateBatch(Collection<T> collection) {
  180 + return Db.saveOrUpdateBatch(collection);
181 181 }
182 182  
183 183 default int delete(String field, String value) {
... ...
yudao-framework/yudao-spring-boot-starter-security/pom.xml
... ... @@ -12,7 +12,10 @@
12 12 <packaging>jar</packaging>
13 13  
14 14 <name>${project.artifactId}</name>
15   - <description>用户的认证、权限的校验</description>
  15 + <description>
  16 + 1. security:用户的认证、权限的校验,实现「谁」可以做「什么事」
  17 + 2. operatelog:操作日志,实现「谁」在「什么时间」对「什么」做了「什么事」
  18 + </description>
16 19 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
17 20  
18 21 <dependencies>
... ... @@ -50,6 +53,13 @@
50 53 <artifactId>guava</artifactId>
51 54 </dependency>
52 55  
  56 + <dependency>
  57 + <!-- Spring Boot 通用操作日志组件,基于注解实现 -->
  58 + <!-- 此组件解决的问题是:「谁」在「什么时间」对「什么」做了「什么事」 -->
  59 + <groupId>io.github.mouzt</groupId>
  60 + <artifactId>bizlog-sdk</artifactId>
  61 + </dependency>
  62 +
53 63 <!-- 业务组件 -->
54 64 <dependency>
55 65 <groupId>cn.iocoder.boot</groupId>
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/config/YudaoOperateLogV2Configuration.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.operatelog.config;
  2 +
  3 +import cn.iocoder.yudao.framework.operatelog.core.service.ILogRecordServiceImpl;
  4 +import com.mzt.logapi.service.ILogRecordService;
  5 +import com.mzt.logapi.starter.annotation.EnableLogRecord;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.boot.autoconfigure.AutoConfiguration;
  8 +import org.springframework.context.annotation.Bean;
  9 +import org.springframework.context.annotation.Primary;
  10 +
  11 +/**
  12 + * 操作日志配置类
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
  17 +@AutoConfiguration
  18 +@Slf4j
  19 +public class YudaoOperateLogV2Configuration {
  20 +
  21 + @Bean
  22 + @Primary
  23 + public ILogRecordService iLogRecordServiceImpl() {
  24 + return new ILogRecordServiceImpl();
  25 + }
  26 +
  27 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/package-info.java renamed to yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/package-info.java
1 1 /**
2   - * 商机(销售机会)
  2 + * 占位,无特殊作用
3 3 */
4   -package cn.iocoder.yudao.module.crm.dal.dataobject.business;
  4 +package cn.iocoder.yudao.framework.operatelog.core;
5 5 \ No newline at end of file
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/bizlog/service/ILogRecordServiceImpl.java renamed to yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/ILogRecordServiceImpl.java
1   -package cn.iocoder.yudao.module.system.framework.bizlog.service;
  1 +package cn.iocoder.yudao.framework.operatelog.core.service;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
4 4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
5 5 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
6   -import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
7   -import cn.iocoder.yudao.module.system.service.logger.bo.OperateLogV2CreateReqBO;
  6 +import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
  7 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
8 8 import com.mzt.logapi.beans.LogRecord;
9 9 import com.mzt.logapi.service.ILogRecordService;
10 10 import jakarta.annotation.Resource;
11 11 import jakarta.servlet.http.HttpServletRequest;
12 12 import lombok.extern.slf4j.Slf4j;
13   -import org.springframework.stereotype.Service;
14 13  
15   -import java.util.Collections;
16 14 import java.util.List;
17 15  
18   -// TODO @puhui999:这个应该搞到 operatelog 组件里哈;
  16 +// TODO @puhui999:LogRecordServiceImpl 改成这个名字哈
19 17 /**
20 18 * 操作日志 ILogRecordService 实现类
21 19 *
22   - * 基于 {@link OperateLogService} 实现,记录操作日志
  20 + * 基于 {@link OperateLogApi} 实现,记录操作日志
23 21 *
24 22 * @author HUIHUI
25 23 */
26 24 @Slf4j
27   -@Service
28 25 public class ILogRecordServiceImpl implements ILogRecordService {
29 26  
30 27 @Resource
31   - private OperateLogService operateLogService;
  28 + private OperateLogApi operateLogApi;
32 29  
33 30 @Override
34 31 public void record(LogRecord logRecord) {
35   - OperateLogV2CreateReqBO reqBO = new OperateLogV2CreateReqBO();
36   - // 补全通用字段
37   - reqBO.setTraceId(TracerUtils.getTraceId());
  32 + // 1. 补全通用字段
  33 + OperateLogV2CreateReqDTO reqDTO = new OperateLogV2CreateReqDTO();
  34 + reqDTO.setTraceId(TracerUtils.getTraceId());
38 35 // 补充用户信息
39   - fillUserFields(reqBO);
  36 + fillUserFields(reqDTO);
40 37 // 补全模块信息
41   - fillModuleFields(reqBO, logRecord);
  38 + fillModuleFields(reqDTO, logRecord);
42 39 // 补全请求信息
43   - fillRequestFields(reqBO);
44   - // 异步记录日志
45   - operateLogService.createOperateLogV2(reqBO);
46   - log.info("操作日志 ===> {}", reqBO);
  40 + fillRequestFields(reqDTO);
  41 +
  42 + // 2. 异步记录日志
  43 + operateLogApi.createOperateLogV2(reqDTO);
  44 + // TODO 测试结束删除或搞个开关
  45 + log.info("操作日志 ===> {}", reqDTO);
47 46 }
48 47  
49   - private static void fillUserFields(OperateLogV2CreateReqBO reqBO) {
50   - reqBO.setUserId(WebFrameworkUtils.getLoginUserId());
51   - reqBO.setUserType(WebFrameworkUtils.getLoginUserType());
  48 + private static void fillUserFields(OperateLogV2CreateReqDTO reqDTO) {
  49 + // TODO @puhui999:使用 SecurityFrameworkUtils。因为要考虑,rpc、mq、job,它其实不是 web;
  50 + reqDTO.setUserId(WebFrameworkUtils.getLoginUserId());
  51 + reqDTO.setUserType(WebFrameworkUtils.getLoginUserType());
52 52 }
53 53  
54   - public static void fillModuleFields(OperateLogV2CreateReqBO reqBO, LogRecord logRecord) {
55   - reqBO.setModule(logRecord.getType()); // 大模块类型如 crm-客户
56   - reqBO.setName(logRecord.getSubType());// 操作名称如 转移客户
57   - reqBO.setBizId(Long.parseLong(logRecord.getBizNo())); // 操作模块业务编号
58   - reqBO.setContent(logRecord.getAction());// 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
59   - reqBO.setExtra(logRecord.getExtra()); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
  54 + public static void fillModuleFields(OperateLogV2CreateReqDTO reqDTO, LogRecord logRecord) {
  55 + reqDTO.setType(logRecord.getType()); // 大模块类型,例如:CRM 客户
  56 + reqDTO.setSubType(logRecord.getSubType());// 操作名称,例如:转移客户
  57 + reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 业务编号,例如:客户编号
  58 + reqDTO.setAction(logRecord.getAction());// 操作内容,例如:修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
  59 + reqDTO.setExtra(logRecord.getExtra()); // 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 ),例如说,记录订单编号,{ orderId: "1"}
60 60 }
61 61  
62   - private static void fillRequestFields(OperateLogV2CreateReqBO reqBO) {
  62 + private static void fillRequestFields(OperateLogV2CreateReqDTO reqDTO) {
63 63 // 获得 Request 对象
64 64 HttpServletRequest request = ServletUtils.getRequest();
65 65 if (request == null) {
66 66 return;
67 67 }
68 68 // 补全请求信息
69   - reqBO.setRequestMethod(request.getMethod());
70   - reqBO.setRequestUrl(request.getRequestURI());
71   - reqBO.setUserIp(ServletUtils.getClientIP(request));
72   - reqBO.setUserAgent(ServletUtils.getUserAgent(request));
  69 + reqDTO.setRequestMethod(request.getMethod());
  70 + reqDTO.setRequestUrl(request.getRequestURI());
  71 + reqDTO.setUserIp(ServletUtils.getClientIP(request));
  72 + reqDTO.setUserAgent(ServletUtils.getUserAgent(request));
73 73 }
74 74  
75 75 @Override
76 76 public List<LogRecord> queryLog(String bizNo, String type) {
77   - return Collections.emptyList();
  77 + throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询");
78 78 }
79 79  
80 80 @Override
81 81 public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
82   - return Collections.emptyList();
  82 + throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询");
83 83 }
84 84  
85 85 -}
  86 +}
86 87 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/package-info.java 0 → 100644
  1 +/**
  2 + * 基于 mzt-log 框架
  3 + * 实现操作日志功能
  4 + *
  5 + * @author HUIHUI
  6 + */
  7 +package cn.iocoder.yudao.framework.operatelog;
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 1 cn.iocoder.yudao.framework.security.config.YudaoSecurityAutoConfiguration
2   -cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter
3 2 \ No newline at end of file
  3 +cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter
  4 +cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogV2Configuration
4 5 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
1 1 package cn.iocoder.yudao.framework.web.core.util;
2 2  
3 3 import cn.hutool.core.util.NumberUtil;
  4 +import cn.hutool.extra.servlet.ServletUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
4 6 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  8 +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
6 9 import cn.iocoder.yudao.framework.web.config.WebProperties;
7 10 import org.springframework.web.context.request.RequestAttributes;
8 11 import org.springframework.web.context.request.RequestContextHolder;
... ... @@ -25,6 +28,13 @@ public class WebFrameworkUtils {
25 28  
26 29 public static final String HEADER_TENANT_ID = "tenant-id";
27 30  
  31 + /**
  32 + * 终端的 Header
  33 + *
  34 + * @see cn.iocoder.yudao.framework.common.enums.TerminalEnum
  35 + */
  36 + public static final String HEADER_TERMINAL = "terminal";
  37 +
28 38 private static WebProperties properties;
29 39  
30 40 public WebFrameworkUtils(WebProperties webProperties) {
... ... @@ -107,6 +117,15 @@ public class WebFrameworkUtils {
107 117 return getLoginUserId(request);
108 118 }
109 119  
  120 + public static Integer getTerminal() {
  121 + HttpServletRequest request = getRequest();
  122 + if (request == null) {
  123 + return TerminalEnum.UNKNOWN.getTerminal();
  124 + }
  125 + String terminalValue = request.getHeader(HEADER_TERMINAL);
  126 + return NumberUtil.parseInt(terminalValue, TerminalEnum.UNKNOWN.getTerminal());
  127 + }
  128 +
110 129 public static void setCommonResult(ServletRequest request, CommonResult<?> result) {
111 130 request.setAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT, result);
112 131 }
... ...
yudao-framework/yudao-spring-boot-starter-websocket/pom.xml
... ... @@ -38,17 +38,6 @@
38 38 <artifactId>spring-boot-starter-websocket</artifactId>
39 39 </dependency>
40 40  
41   - <!-- Web 相关 -->
42   - <dependency>
43   - <!-- 为什么是 websocket 依赖 security 呢?而不是 security 拓展 websocket 呢?
44   - 因为 websocket 和 LoginUser 当前登录的用户有一定的相关性,具体可见 WebSocketSessionManagerImpl 逻辑。
45   - 如果让 security 拓展 websocket 的话,会导致 websocket 组件的封装很散,进而增大理解成本。
46   - -->
47   - <groupId>cn.iocoder.boot</groupId>
48   - <artifactId>yudao-spring-boot-starter-security</artifactId>
49   - <scope>provided</scope>
50   - </dependency>
51   -
52 41 <!-- 消息队列相关 -->
53 42 <dependency>
54 43 <groupId>cn.iocoder.boot</groupId>
... ...
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/session/WebSocketSessionManagerImpl.java
... ... @@ -66,7 +66,7 @@ public class WebSocketSessionManagerImpl implements WebSocketSessionManager {
66 66 @Override
67 67 public void removeSession(WebSocketSession session) {
68 68 // 移除从 idSessions 中
69   - idSessions.remove(session.getId(), session);
  69 + idSessions.remove(session.getId());
70 70 // 移除从 idSessions 中
71 71 LoginUser user = WebSocketFrameworkUtils.getLoginUser(session);
72 72 if (user == null) {
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
... ... @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8 8 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
9 9 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
  10 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
10 11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
11 12 import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
12 13 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
... ... @@ -71,7 +72,8 @@ public class BpmModelServiceImpl implements BpmModelService {
71 72 modelQuery.modelCategory(pageVO.getCategory());
72 73 }
73 74 // 执行查询
74   - List<Model> models = modelQuery.orderByCreateTime().desc()
  75 + List<Model> models = modelQuery.modelTenantId(TenantContextHolder.getTenantIdStr())
  76 + .orderByCreateTime().desc()
75 77 .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
76 78  
77 79 // 获得 Form Map
... ... @@ -107,6 +109,7 @@ public class BpmModelServiceImpl implements BpmModelService {
107 109 // 创建流程定义
108 110 Model model = repositoryService.newModel();
109 111 BpmModelConvert.INSTANCE.copy(model, createReqVO);
  112 + model.setTenantId(TenantContextHolder.getTenantIdStr());
110 113 // 保存流程定义
111 114 repositoryService.saveModel(model);
112 115 // 保存 BPMN XML
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
... ... @@ -6,6 +6,8 @@ import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
8 8 import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
  9 +import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
  10 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
9 11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
10 12 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
11 13 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
... ... @@ -124,6 +126,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
124 126 Deployment deploy = repositoryService.createDeployment()
125 127 .key(createReqDTO.getKey()).name(createReqDTO.getName()).category(createReqDTO.getCategory())
126 128 .addBytes(createReqDTO.getKey() + BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes())
  129 + .tenantId(TenantContextHolder.getTenantIdStr())
127 130 .deploy();
128 131  
129 132 // 设置 ProcessDefinition 的 category 分类
... ... @@ -234,6 +237,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
234 237 definitionQuery.active();
235 238 }
236 239 // 执行查询
  240 + definitionQuery.processDefinitionTenantId(TenantContextHolder.getTenantIdStr());
237 241 List<ProcessDefinition> processDefinitions = definitionQuery.list();
238 242 if (CollUtil.isEmpty(processDefinitions)) {
239 243 return Collections.emptyList();
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
1   -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.flowable.core.context.FlowableContextHolder; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; 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 lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; import jakarta.validation.Valid; import java.time.LocalDateTime; import java.util.*; 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.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult<BpmProcessInstanceExtDO> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List<String> processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getAssignee()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> assignee) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 设置上下文信息 // TODO @hai:要不往 variables 存到一个全局固定 key 里,减少对上下文的依赖 FlowableContextHolder.setAssignee(assignee); // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables).setAssignee(assignee)); return instance.getId(); } @Override public List<Long> getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey) { // 1. 先从上下文获取,首次提交数据库中查询不到 List<Long> result = FlowableContextHolder.getAssigneeByTaskDefinitionKey(taskDefinitionKey); if (CollUtil.isNotEmpty(result)) { return result; } // 2. 从数据库中获取 BpmProcessInstanceExtDO instance = processInstanceExtMapper.selectByProcessInstanceId(processInstanceId); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } if (CollUtil.isNotEmpty(instance.getAssignee())) { return instance.getAssignee().get(taskDefinitionKey); } return Collections.emptyList(); } }
2 1 \ No newline at end of file
  2 +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.flowable.core.context.FlowableContextHolder; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; 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 lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; import jakarta.validation.Valid; import java.time.LocalDateTime; import java.util.*; 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.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult<BpmProcessInstanceExtDO> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List<String> processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getAssignee()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> assignee) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 设置上下文信息 // TODO @hai:要不往 variables 存到一个全局固定 key 里,减少对上下文的依赖 FlowableContextHolder.setAssignee(assignee); // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables).setAssignee(assignee)); return instance.getId(); } @Override public List<Long> getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey) { // 1. 先从上下文获取,首次提交数据库中查询不到 List<Long> result = FlowableContextHolder.getAssigneeByTaskDefinitionKey(taskDefinitionKey); if (CollUtil.isNotEmpty(result)) { return result; } // 2. 从数据库中获取 BpmProcessInstanceExtDO instance = processInstanceExtMapper.selectByProcessInstanceId(processInstanceId); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } if (CollUtil.isNotEmpty(instance.getAssignee())) { return instance.getAssignee().get(taskDefinitionKey); } return Collections.emptyList(); } }
3 3 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/package-info.java deleted
1   -package cn.iocoder.yudao.module.bpm.service.task;
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
... ... @@ -39,6 +39,10 @@ public interface ErrorCodeConstants {
39 39 ErrorCode CUSTOMER_IN_POOL = new ErrorCode(1_020_006_004, "客户【{}】放入公海失败,原因:已经是公海客户");
40 40 ErrorCode CUSTOMER_LOCKED_PUT_POOL_FAIL = new ErrorCode(1_020_006_005, "客户【{}】放入公海失败,原因:客户已锁定");
41 41 ErrorCode CUSTOMER_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_006_006, "更新客户【{}】负责人失败, 原因:系统异常");
  42 + ErrorCode CUSTOMER_LOCK_FAIL_IS_LOCK = new ErrorCode(1_020_006_007, "锁定客户失败,它已经处于锁定状态");
  43 + ErrorCode CUSTOMER_UNLOCK_FAIL_IS_UNLOCK = new ErrorCode(1_020_006_008, "锁定客户失败,它已经处于未锁定状态");
  44 + ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限");
  45 + ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限");
42 46  
43 47 // ========== 权限管理 1_020_007_000 ==========
44 48 ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
1 1 package cn.iocoder.yudao.module.crm.enums;
2 2  
3   -// TODO 芋艿:操作日志;看看这个类怎么搞个好点的规范;
4 3 /**
5 4 * CRM 操作日志枚举
6 5 *
... ... @@ -22,6 +21,13 @@ public interface LogRecordConstants {
22 21 //======================= 客户转移操作日志 =======================
23 22  
24 23 String TRANSFER_CUSTOMER_LOG_SUCCESS = "把客户【{{#crmCustomer.name}}】的负责人从【{getAdminUserById{#crmCustomer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
25   - String TRANSFER_CUSTOMER_LOG_FAIL = ""; // TODO @puhui999:这个可以删除哈,一般不搞失败的日志
  24 +
  25 + // TODO @puhui999:这里格式是不是可以这样;目的是:统一管理,也减少 Service 里各种“复杂”字符串
  26 + // ======================= Customer 客户 =======================
  27 + String CUSTOMER_TYPE = "CRM 客户";
  28 + String CUSTOMER_CREATE_SUB_TYPE = "创建客户";
  29 + String CUSTOMER_CREATE_SUCCESS = "更新了客户{_DIFF{#updateReqVO}}";
  30 +
  31 + String CUSTOMER_UPDATE_SUB_TYPE = "更新客户";
26 32  
27 33 }
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java
... ... @@ -18,19 +18,19 @@ public enum CrmCustomerLimitConfigTypeEnum implements IntArrayValuable {
18 18 /**
19 19 * 拥有客户数限制
20 20 */
21   - CUSTOMER_QUANTITY_LIMIT(1, "拥有客户数限制"),
  21 + CUSTOMER_OWNER_LIMIT(1, "拥有客户数限制"),
22 22 /**
23 23 * 锁定客户数限制
24 24 */
25 25 CUSTOMER_LOCK_LIMIT(2, "锁定客户数限制"),
26 26 ;
27 27  
28   - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLimitConfigTypeEnum::getCode).toArray();
  28 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLimitConfigTypeEnum::getType).toArray();
29 29  
30 30 /**
31 31 * 状态
32 32 */
33   - private final Integer code;
  33 + private final Integer type;
34 34 /**
35 35 * 状态名
36 36 */
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums.permission;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * Crm 数据权限角色枚举
  8 + *
  9 + * @author HUIHUI
  10 + */
  11 +@Getter
  12 +@AllArgsConstructor
  13 +public enum CrmPermissionRoleCodeEnum {
  14 +
  15 + CRM_ADMIN("crm_admin", "CRM 管理员");
  16 +
  17 + /**
  18 + * 角色标识
  19 + */
  20 + private String code;
  21 + /**
  22 + * 角色名称
  23 + */
  24 + private String name;
  25 +
  26 +}
  27 +
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
... ... @@ -7,8 +7,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
8 8 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
9 9 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
10   -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO;
11   -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
12 10 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
13 11 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
14 12 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
... ... @@ -30,12 +28,10 @@ import org.springframework.web.bind.annotation.*;
30 28  
31 29 import java.io.IOException;
32 30 import java.util.List;
33   -import java.util.Objects;
34   -import java.util.Set;
35   -import java.util.stream.Collectors;
36 31  
37 32 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
38 33 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
  34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
39 35 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
40 36 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
41 37  
... ... @@ -47,16 +43,14 @@ public class CrmBusinessController {
47 43  
48 44 @Resource
49 45 private CrmBusinessService businessService;
50   -
51 46 @Resource
52 47 private CrmCustomerService customerService;
53   -
54 48 @Resource
55 49 private CrmBusinessStatusTypeService businessStatusTypeService;
56   -
57 50 @Resource
58 51 private CrmBusinessStatusService businessStatusService;
59 52  
  53 + // TODO @商机待定:CrmBusinessCreateReqVO、CrmBusinessUpdateReqVO、CrmBusinessRespVO 按照新的 VO 规范
60 54 @PostMapping("/create")
61 55 @Operation(summary = "创建商机")
62 56 @PreAuthorize("@ss.hasPermission('crm:business:create')")
... ... @@ -95,27 +89,7 @@ public class CrmBusinessController {
95 89 @PreAuthorize("@ss.hasPermission('crm:business:query')")
96 90 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) {
97 91 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId());
98   - if (CollUtil.isEmpty(pageResult.getList())) {
99   - return success(PageResult.empty(pageResult.getTotal()));
100   - }
101   - // 处理客户名称回显
102   - // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈;
103   - Set<Long> customerIds = pageResult.getList().stream()
104   - .map(CrmBusinessDO::getCustomerId).filter(Objects::nonNull).collect(Collectors.toSet());
105   - List<CrmCustomerDO> customerList = customerService.getCustomerList(customerIds, getLoginUserId());
106   - // 处理商机状态类型名称回显
107   - Set<Long> statusTypeIds = pageResult.getList().stream()
108   - .map(CrmBusinessDO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet());
109   - CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO();
110   - queryStatusTypeVO.setIdList(statusTypeIds);
111   - List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO);
112   - // 处理商机状态名称回显
113   - Set<Long> statusIds = pageResult.getList().stream()
114   - .map(CrmBusinessDO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet());
115   - CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
116   - queryVO.setIdList(statusIds);
117   - List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);
118   - return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList));
  92 + return success(buildBusinessDetailPageResult(pageResult));
119 93 }
120 94  
121 95 @GetMapping("/page-by-customer")
... ... @@ -123,24 +97,15 @@ public class CrmBusinessController {
123 97 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByCustomer(@Valid CrmBusinessPageReqVO pageReqVO) {
124 98 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
125 99 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO);
126   - // 处理客户名称回显
127   - // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈;
128   - Set<Long> customerIds = pageResult.getList().stream()
129   - .map(CrmBusinessDO::getCustomerId).filter(Objects::nonNull).collect(Collectors.toSet());
130   - List<CrmCustomerDO> customerList = customerService.getCustomerList(customerIds, getLoginUserId());
131   - // 处理商机状态类型名称回显
132   - Set<Long> statusTypeIds = pageResult.getList().stream()
133   - .map(CrmBusinessDO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet());
134   - CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO();
135   - queryStatusTypeVO.setIdList(statusTypeIds);
136   - List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO);
137   - // 处理商机状态名称回显
138   - Set<Long> statusIds = pageResult.getList().stream()
139   - .map(CrmBusinessDO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet());
140   - CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
141   - queryVO.setIdList(statusIds);
142   - List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);
143   - return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList));
  100 + return success(buildBusinessDetailPageResult(pageResult));
  101 + }
  102 +
  103 + @GetMapping("/page-by-contact")
  104 + @Operation(summary = "获得联系人的商机分页")
  105 + @PreAuthorize("@ss.hasPermission('crm:business:query')")
  106 + public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) {
  107 + PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByContact(pageReqVO);
  108 + return success(buildBusinessDetailPageResult(pageResult));
144 109 }
145 110  
146 111 @GetMapping("/export-excel")
... ... @@ -152,8 +117,27 @@ public class CrmBusinessController {
152 117 exportReqVO.setPageSize(PAGE_SIZE_NONE);
153 118 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId());
154 119 // 导出 Excel
155   - ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessExcelVO.class,
156   - CrmBusinessConvert.INSTANCE.convertList02(pageResult.getList()));
  120 + ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class,
  121 + buildBusinessDetailPageResult(pageResult).getList());
  122 + }
  123 +
  124 + /**
  125 + * 构建详细的商机分页结果
  126 + *
  127 + * @param pageResult 简单的商机分页结果
  128 + * @return 详细的商机分页结果
  129 + */
  130 + private PageResult<CrmBusinessRespVO> buildBusinessDetailPageResult(PageResult<CrmBusinessDO> pageResult) {
  131 + if (CollUtil.isEmpty(pageResult.getList())) {
  132 + return PageResult.empty(pageResult.getTotal());
  133 + }
  134 + List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.getBusinessStatusTypeList(
  135 + convertSet(pageResult.getList(), CrmBusinessDO::getStatusTypeId));
  136 + List<CrmBusinessStatusDO> statusList = businessStatusService.getBusinessStatusList(
  137 + convertSet(pageResult.getList(), CrmBusinessDO::getStatusId));
  138 + List<CrmCustomerDO> customerList = customerService.getCustomerList(
  139 + convertSet(pageResult.getList(), CrmBusinessDO::getCustomerId));
  140 + return CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList);
157 141 }
158 142  
159 143 @PutMapping("/transfer")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java
... ... @@ -20,6 +20,9 @@ public class CrmBusinessPageReqVO extends PageParam {
20 20 @Schema(description = "客户编号", example = "10795")
21 21 private Long customerId;
22 22  
  23 + @Schema(description = "联系人编号", example = "10795")
  24 + private Long contactId;
  25 +
23 26 @Schema(description = "场景类型", example = "1")
24 27 @InEnum(CrmSceneTypeEnum.class)
25 28 private Integer sceneType; // 场景类型,为 null 时则表示全部
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
... ... @@ -6,6 +6,7 @@ import cn.hutool.core.util.NumberUtil;
6 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
10 11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
11 12 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
... ... @@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;
13 14 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
14 15 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
15 16 import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
  17 +import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
16 18 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
17 19 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
18 20 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
... ... @@ -54,10 +56,13 @@ public class CrmContactController {
54 56 private CrmContactService contactService;
55 57 @Resource
56 58 private CrmCustomerService customerService;
57   -
58 59 @Resource
59 60 private AdminUserApi adminUserApi;
60 61  
  62 + @Resource
  63 + private CrmContactBusinessService contactBusinessLinkService;
  64 +
  65 + // TODO @zyna:CrmContactCreateReqVO、CrmContactUpdateReqVO、CrmContactRespVO 按照新的 VO 规范搞哈;可以参考 dept 模块
61 66 @PostMapping("/create")
62 67 @Operation(summary = "创建联系人")
63 68 @PreAuthorize("@ss.hasPermission('crm:contact:create')")
... ... @@ -96,7 +101,7 @@ public class CrmContactController {
96 101 NumberUtil.parseLong(contact.getCreator()), contact.getOwnerUserId())));
97 102 // 2. 获取客户信息
98 103 List<CrmCustomerDO> customerList = customerService.getCustomerList(
99   - Collections.singletonList(contact.getCustomerId()), getLoginUserId());
  104 + Collections.singletonList(contact.getCustomerId()));
100 105 // 3. 直属上级
101 106 List<CrmContactDO> parentContactList = contactService.getContactList(
102 107 Collections.singletonList(contact.getParentId()), getLoginUserId());
... ... @@ -107,10 +112,11 @@ public class CrmContactController {
107 112 @Operation(summary = "获得联系人列表")
108 113 @PreAuthorize("@ss.hasPermission('crm:contact:query')")
109 114 public CommonResult<List<CrmContactSimpleRespVO>> getSimpleContactList() {
  115 + // TODO @zyna:建议 contactService 单独搞个 list 接口哈
110 116 CrmContactPageReqVO pageReqVO = new CrmContactPageReqVO();
111 117 pageReqVO.setPageSize(PAGE_SIZE_NONE);
112 118 List<CrmContactDO> list = contactService.getContactPage(pageReqVO, getLoginUserId()).getList();
113   - return success(CrmContactConvert.INSTANCE.convertAllList(list));
  119 + return success(BeanUtils.toBean(list, CrmContactSimpleRespVO.class));
114 120 }
115 121  
116 122 @GetMapping("/page")
... ... @@ -118,7 +124,7 @@ public class CrmContactController {
118 124 @PreAuthorize("@ss.hasPermission('crm:contact:query')")
119 125 public CommonResult<PageResult<CrmContactRespVO>> getContactPage(@Valid CrmContactPageReqVO pageVO) {
120 126 PageResult<CrmContactDO> pageResult = contactService.getContactPage(pageVO, getLoginUserId());
121   - return success(convertDetailContactPage(pageResult));
  127 + return success(buildContactDetailPage(pageResult));
122 128 }
123 129  
124 130 @GetMapping("/page-by-customer")
... ... @@ -126,7 +132,7 @@ public class CrmContactController {
126 132 public CommonResult<PageResult<CrmContactRespVO>> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) {
127 133 Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
128 134 PageResult<CrmContactDO> pageResult = contactService.getContactPageByCustomerId(pageVO);
129   - return success(convertDetailContactPage(pageResult));
  135 + return success(buildContactDetailPage(pageResult));
130 136 }
131 137  
132 138 @GetMapping("/export-excel")
... ... @@ -138,23 +144,23 @@ public class CrmContactController {
138 144 exportReqVO.setPageNo(PAGE_SIZE_NONE);
139 145 PageResult<CrmContactDO> pageResult = contactService.getContactPage(exportReqVO, getLoginUserId());
140 146 ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class,
141   - convertDetailContactPage(pageResult).getList());
  147 + buildContactDetailPage(pageResult).getList());
142 148 }
143 149  
144 150 /**
145   - * 转换成详细的联系人分页,即读取关联信息
  151 + * 构建详细的联系人分页结果
146 152 *
147   - * @param pageResult 联系人分页
148   - * @return 详细的联系人分页
  153 + * @param pageResult 简单的联系人分页结果
  154 + * @return 详细的联系人分页结果
149 155 */
150   - private PageResult<CrmContactRespVO> convertDetailContactPage(PageResult<CrmContactDO> pageResult) {
  156 + private PageResult<CrmContactRespVO> buildContactDetailPage(PageResult<CrmContactDO> pageResult) {
151 157 List<CrmContactDO> contactList = pageResult.getList();
152 158 if (CollUtil.isEmpty(contactList)) {
153 159 return PageResult.empty(pageResult.getTotal());
154 160 }
155 161 // 1. 获取客户列表
156 162 List<CrmCustomerDO> crmCustomerDOList = customerService.getCustomerList(
157   - convertSet(contactList, CrmContactDO::getCustomerId), getLoginUserId());
  163 + convertSet(contactList, CrmContactDO::getCustomerId));
158 164 // 2. 获取创建人、负责人列表
159 165 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
160 166 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
... ... @@ -172,4 +178,22 @@ public class CrmContactController {
172 178 return success(true);
173 179 }
174 180  
  181 + // ================== 关联/取关联系人 ===================
  182 +
  183 + @PostMapping("/create-business-list")
  184 + @Operation(summary = "创建联系人与联系人的关联")
  185 + @PreAuthorize("@ss.hasPermission('crm:contact:create-business')")
  186 + public CommonResult<Boolean> createContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO createReqVO) {
  187 + contactBusinessLinkService.createContactBusinessList(createReqVO);
  188 + return success(true);
  189 + }
  190 +
  191 + @DeleteMapping("/delete-business-list")
  192 + @Operation(summary = "删除联系人与联系人的关联")
  193 + @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
  194 + public CommonResult<Boolean> deleteContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO deleteReqVO) {
  195 + contactBusinessLinkService.deleteContactBusinessList(deleteReqVO);
  196 + return success(true);
  197 + }
  198 +
175 199 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBaseVO.java
... ... @@ -8,11 +8,11 @@ import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
8 8 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
9 9 import com.alibaba.excel.annotation.ExcelProperty;
10 10 import io.swagger.v3.oas.annotations.media.Schema;
  11 +import jakarta.validation.constraints.Email;
  12 +import jakarta.validation.constraints.NotNull;
11 13 import lombok.Data;
12 14 import org.springframework.format.annotation.DateTimeFormat;
13 15  
14   -import jakarta.validation.constraints.Email;
15   -import jakarta.validation.constraints.NotNull;
16 16 import java.time.LocalDateTime;
17 17  
18 18 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
... ... @@ -75,29 +75,29 @@ public class CrmContactBaseVO {
75 75 @ExcelProperty(value = "邮箱",order = 4)
76 76 private String email;
77 77  
  78 + @Schema(description = "地区编号", example = "20158")
  79 + private Integer areaId;
  80 +
78 81 @ExcelProperty(value = "地址",order = 5)
79 82 @Schema(description = "地址")
80   - private String address;
81   -
82   - @Schema(description = "下次联系时间")
83   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
84   - @ExcelProperty(value = "下次联系时间",order = 6)
85   - private LocalDateTime nextTime;
  83 + private String detailAddress;
86 84  
87 85 @Schema(description = "备注", example = "你说的对")
88 86 @ExcelProperty(value = "备注",order = 6)
89 87 private String remark;
90 88  
91   - @Schema(description = "最后跟进时间")
92   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
93   - @ExcelProperty(value = "最后跟进时间",order = 6)
94   - private LocalDateTime lastTime;
95   -
96 89 @Schema(description = "负责人用户编号", example = "14334")
97 90 @NotNull(message = "负责人不能为空")
98 91 private Long ownerUserId;
99 92  
100   - @Schema(description = "地区编号", example = "20158")
101   - private Integer areaId;
  93 + @Schema(description = "最后跟进时间")
  94 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  95 + @ExcelProperty(value = "最后跟进时间",order = 6)
  96 + private LocalDateTime contactLastTime;
  97 +
  98 + @Schema(description = "下次联系时间")
  99 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
  100 + @ExcelProperty(value = "下次联系时间",order = 6)
  101 + private LocalDateTime contactNextTime;
102 102  
103 103 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 用于关联,取消关联的操作
  11 +@Data
  12 +public class CrmContactBusinessReqVO {
  13 +
  14 + @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
  15 + @NotNull(message="联系人不能为空")
  16 + private Long contactId;
  17 +
  18 + @Schema(description = "商机编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
  19 + @NotEmpty(message="商机不能为空")
  20 + private List<Long> businessIds;
  21 +
  22 +}
0 23 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java deleted
1   -package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
5   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
7   -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
8   -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkRespVO;
9   -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
10   -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
11   -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
12   -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
13   -import cn.iocoder.yudao.module.crm.service.contactbusinesslink.CrmContactBusinessLinkService;
14   -import io.swagger.v3.oas.annotations.Operation;
15   -import io.swagger.v3.oas.annotations.Parameter;
16   -import io.swagger.v3.oas.annotations.tags.Tag;
17   -import org.springframework.security.access.prepost.PreAuthorize;
18   -import org.springframework.transaction.annotation.Transactional;
19   -import org.springframework.validation.annotation.Validated;
20   -import org.springframework.web.bind.annotation.*;
21   -
22   -import jakarta.annotation.Resource;
23   -import jakarta.validation.Valid;
24   -import java.util.List;
25   -
26   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
27   -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
28   -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
29   -
30   -@Tag(name = "管理后台 - CRM 联系人商机关联")
31   -@RestController
32   -@RequestMapping("/crm/contact-business-link")
33   -@Validated
34   -public class CrmContactBusinessLinkController {
35   -
36   - @Resource
37   - private CrmContactBusinessLinkService contactBusinessLinkService;
38   - @Resource
39   - private CrmBusinessService crmBusinessService;
40   -
41   - // TODO @zyna:createContactBusinessLink 和 createContactBusinessLinkBatch 是不是合并成一个接口?contactId、List<businessId>
42   - @PostMapping("/create")
43   - @Operation(summary = "创建联系人商机关联")
44   - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')")
45   - public CommonResult<Long> createContactBusinessLink(@Valid @RequestBody CrmContactBusinessLinkSaveReqVO createReqVO) {
46   - return success(contactBusinessLinkService.createContactBusinessLink(createReqVO));
47   - }
48   -
49   - @PostMapping("/create-batch")
50   - @Operation(summary = "创建联系人商机关联")
51   - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')")
52   - @Transactional(rollbackFor = Exception.class)
53   - public CommonResult<Boolean> createContactBusinessLinkBatch(
54   - @Valid @RequestBody List<CrmContactBusinessLinkSaveReqVO> createReqVO) {
55   - createReqVO.stream().forEach(item -> {
56   - CrmBusinessDO crmBusinessDO = crmBusinessService.getBusiness(item.getBusinessId());
57   - if(crmBusinessDO == null){
58   - throw exception(BUSINESS_NOT_EXISTS);
59   - }
60   - });
61   - contactBusinessLinkService.createContactBusinessLinkBatch(createReqVO);
62   - return success(true);
63   - }
64   -
65   - // TODO @zyna:这个接口是不是可以删除掉了哈?应该不存在更新。
66   - @PutMapping("/update")
67   - @Operation(summary = "更新联系人商机关联")
68   - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:update')")
69   - public CommonResult<Boolean> updateContactBusinessLink(@Valid @RequestBody CrmContactBusinessLinkSaveReqVO updateReqVO) {
70   - contactBusinessLinkService.updateContactBusinessLink(updateReqVO);
71   - return success(true);
72   - }
73   -
74   - // TODO @zyna:删除,是不是传递 ids?
75   - @DeleteMapping("/delete-batch")
76   - @Operation(summary = "批量删除联系人商机关联")
77   - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:delete')")
78   - public CommonResult<Boolean> deleteContactBusinessLinkBatch(@Valid @RequestBody List<CrmContactBusinessLinkSaveReqVO> deleteList) {
79   - contactBusinessLinkService.deleteContactBusinessLink(deleteList);
80   - return success(true);
81   - }
82   -
83   - // TODO @zyna:这个接口是不是可以删除掉了哈?应该不存在单个读取;
84   - @GetMapping("/get")
85   - @Operation(summary = "获得联系人商机关联")
86   - @Parameter(name = "id", description = "编号", required = true, example = "1024")
87   - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')")
88   - public CommonResult<CrmContactBusinessLinkRespVO> getContactBusinessLink(@RequestParam("id") Long id) {
89   - CrmContactBusinessLinkDO contactBusinessLink = contactBusinessLinkService.getContactBusinessLink(id);
90   - return success(BeanUtils.toBean(contactBusinessLink, CrmContactBusinessLinkRespVO.class));
91   - }
92   -
93   - // TODO @zyna:这个可以转化下,使用客户编号去查询,就是使用 CrmBusinessController 的 getBusinessPageByCustomer 接口;目的是:复用
94   - @GetMapping("/page-by-contact")
95   - @Operation(summary = "获得联系人商机关联")
96   - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')")
97   - public CommonResult<PageResult<CrmBusinessRespVO>> getContactBusinessLinkByContact(
98   - @Valid CrmContactBusinessLinkPageReqVO pageReqVO) {
99   - PageResult<CrmBusinessRespVO> contactBusinessLink = contactBusinessLinkService.getContactBusinessLinkPageByContact(pageReqVO);
100   - return success(contactBusinessLink);
101   - }
102   -
103   - // TODO @zyna:这个优化下,搞到 CrmBusinessController 里去,加一个 CrmBusinessController 的 getBusinessPageByContact 接口;目的是:
104   - @GetMapping("/page")
105   - @Operation(summary = "获得联系人商机关联分页")
106   - @PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')")
107   - public CommonResult<PageResult<CrmContactBusinessLinkRespVO>> getContactBusinessLinkPage(
108   - @Valid CrmContactBusinessLinkPageReqVO pageReqVO) {
109   - PageResult<CrmContactBusinessLinkDO> pageResult = contactBusinessLinkService.getContactBusinessLinkPage(pageReqVO);
110   - return success(BeanUtils.toBean(pageResult, CrmContactBusinessLinkRespVO.class));
111   - }
112   -
113   - // TODO @zyna:最终梳理完后,应该就 2 个接口,要不直接合并到 CrmContactController 中,不作为独立模块,就关联、接触关联。其实和 user 设置它有哪些岗位、部门是类似的。
114   -
115   -}
116 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkRespVO.java deleted
1   -package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo;
2   -
3   -import com.alibaba.excel.annotation.ExcelProperty;
4   -import io.swagger.v3.oas.annotations.media.Schema;
5   -import lombok.Data;
6   -
7   -import java.time.LocalDateTime;
8   -
9   -@Schema(description = "管理后台 - CRM 联系人商机关联 Response VO")
10   -@Data
11   -public class CrmContactBusinessLinkRespVO {
12   -
13   - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "17220")
14   - @ExcelProperty("主键")
15   - private Long id;
16   -
17   - @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
18   - private Long contactId;
19   -
20   - @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
21   - private Long businessId;
22   -
23   - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
24   - private LocalDateTime createTime;
25   -
26   -}
27 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkSaveReqVO.java deleted
1   -package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.Data;
5   -
6   -import jakarta.validation.constraints.NotNull;
7   -
8   -@Schema(description = "管理后台 - CRM 联系人商机关联新增/修改 Request VO")
9   -@Data
10   -public class CrmContactBusinessLinkSaveReqVO {
11   -
12   - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "17220")
13   - private Long id;
14   -
15   - @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
16   - @NotNull(message="联系人不能为空")
17   - private Long contactId;
18   -
19   - @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
20   - @NotNull(message="商机不能为空")
21   - private Long businessId;
22   -
23   -}
24 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
... ... @@ -88,7 +88,7 @@ public class CrmContractController {
88 88 @PreAuthorize("@ss.hasPermission('crm:contract:query')")
89 89 public CommonResult<PageResult<ContractRespVO>> getContractPage(@Valid CrmContractPageReqVO pageVO) {
90 90 PageResult<CrmContractDO> pageResult = contractService.getContractPage(pageVO, getLoginUserId());
91   - return success(convertDetailContractPage(pageResult));
  91 + return success(buildContractDetailPage(pageResult));
92 92 }
93 93  
94 94 @GetMapping("/page-by-customer")
... ... @@ -96,7 +96,7 @@ public class CrmContractController {
96 96 public CommonResult<PageResult<ContractRespVO>> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) {
97 97 Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
98 98 PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageVO);
99   - return success(convertDetailContractPage(pageResult));
  99 + return success(buildContractDetailPage(pageResult));
100 100 }
101 101  
102 102 @GetMapping("/export-excel")
... ... @@ -112,19 +112,19 @@ public class CrmContractController {
112 112 }
113 113  
114 114 /**
115   - * 转换成详细的合同分页,即读取关联信息
  115 + * 构建详细的合同分页结果
116 116 *
117   - * @param pageResult 合同分页
118   - * @return 详细的合同分页
  117 + * @param pageResult 简单的合同分页结果
  118 + * @return 详细的合同分页结果
119 119 */
120   - private PageResult<ContractRespVO> convertDetailContractPage(PageResult<CrmContractDO> pageResult) {
  120 + private PageResult<ContractRespVO> buildContractDetailPage(PageResult<CrmContractDO> pageResult) {
121 121 List<CrmContractDO> contactList = pageResult.getList();
122 122 if (CollUtil.isEmpty(contactList)) {
123 123 return PageResult.empty(pageResult.getTotal());
124 124 }
125 125 // 1. 获取客户列表
126 126 List<CrmCustomerDO> customerList = customerService.getCustomerList(
127   - convertSet(contactList, CrmContractDO::getCustomerId), getLoginUserId());
  127 + convertSet(contactList, CrmContractDO::getCustomerId));
128 128 // 2. 获取创建人、负责人列表
129 129 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
130 130 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http
1 1 ### 请求 /transfer
2 2 PUT {{baseUrl}}/crm/customer/transfer
3   -Content-Type: application/json
  3 +Content-Type: application/-id: {{adminTenentId}}json
4 4 Authorization: Bearer {{token}}
5   -tenant-id: {{adminTenentId}}
  5 +tenant
6 6  
7 7 {
8 8 "id": 10,
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 7 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
7 8 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
8 9 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
... ... @@ -12,6 +13,7 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
12 13 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
13 14 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
14 15 import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
  16 +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
15 17 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
16 18 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
17 19 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
... ... @@ -56,15 +58,18 @@ public class CrmCustomerController {
56 58 @Resource
57 59 private OperateLogApi operateLogApi;
58 60  
  61 + // TODO @puhui999:把 CrmCustomerCreateReqVO、CrmCustomerUpdateReqVO、CrmCustomerRespVO 按照新的规范,搞一下哈;
59 62 @PostMapping("/create")
60 63 @Operation(summary = "创建客户")
  64 + @OperateLog(enable = false) // TODO 关闭原有日志记录;@puhui999:注解都先删除。先记录,没关系。我们下个迭代,就都删除掉操作日志了;
61 65 @PreAuthorize("@ss.hasPermission('crm:customer:create')")
62 66 public CommonResult<Long> createCustomer(@Valid @RequestBody CrmCustomerCreateReqVO createReqVO) {
63 67 return success(customerService.createCustomer(createReqVO, getLoginUserId()));
64 68 }
65 69  
66 70 @PutMapping("/update")
67   - //@Operation(summary = "更新客户")
  71 + @Operation(summary = "更新客户")
  72 + @OperateLog(enable = false) // TODO 关闭原有日志记录
68 73 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
69 74 public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
70 75 customerService.updateCustomer(updateReqVO);
... ... @@ -73,6 +78,7 @@ public class CrmCustomerController {
73 78  
74 79 @DeleteMapping("/delete")
75 80 @Operation(summary = "删除客户")
  81 + @OperateLog(enable = false) // TODO 关闭原有日志记录
76 82 @Parameter(name = "id", description = "编号", required = true)
77 83 @PreAuthorize("@ss.hasPermission('crm:customer:delete')")
78 84 public CommonResult<Boolean> deleteCustomer(@RequestParam("id") Long id) {
... ... @@ -97,6 +103,7 @@ public class CrmCustomerController {
97 103 return success(CrmCustomerConvert.INSTANCE.convert(customer, userMap, deptMap));
98 104 }
99 105  
  106 + // TODO @puhui999:这个查询会查出多个;微信发你图了
100 107 @GetMapping("/page")
101 108 @Operation(summary = "获得客户分页")
102 109 @PreAuthorize("@ss.hasPermission('crm:customer:query')")
... ... @@ -108,6 +115,7 @@ public class CrmCustomerController {
108 115 }
109 116  
110 117 // 2. 拼接数据
  118 + // TODO @puhui999:距离进入公海的时间
111 119 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
112 120 convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId())));
113 121 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
... ... @@ -128,38 +136,30 @@ public class CrmCustomerController {
128 136 }
129 137  
130 138 @PutMapping("/transfer")
131   - //@Operation(summary = "客户转移")
  139 + @Operation(summary = "转移客户")
  140 + @OperateLog(enable = false) // TODO 关闭原有日志记录
132 141 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
133 142 public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
134 143 customerService.transferCustomer(reqVO, getLoginUserId());
135 144 return success(true);
136 145 }
137 146  
138   - // TODO @puhui999:operate-log-list 或者 operate-log-page 如果分页
139   - @GetMapping("/operate-log")
  147 + // TODO @puhui999:是不是接口只要传递 bizId,由 Controller 自己组装出 OperateLogV2PageReqDTO
  148 + @GetMapping("/operate-log-page")
140 149 @Operation(summary = "获得客户操作日志")
141   - @Parameter(name = "id", description = "编号", required = true, example = "1024")
142 150 @PreAuthorize("@ss.hasPermission('crm:customer:query')")
143   - // TODO @puhui999:最好有读权限;方法名改成 getCustomerOperateLog
144   - public CommonResult<List<OperateLogV2RespDTO>> getOperateLog(@RequestParam("id") Long id) {
145   - // 1. 获取客户
146   - // TODO @puhui999:这个校验可以去掉哈;
147   - CrmCustomerDO customer = customerService.getCustomer(id);
148   - if (customer == null) {
149   - return success(null);
150   - }
151   -
152   - // 2. 获取操作日志
153   - // TODO @puhui999:操作日志,返回可能要分页哈;
154   - return success(operateLogApi.getOperateLogByModuleAndBizId(CRM_CUSTOMER, id));
  151 + public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(CrmCustomerOperateLogPageReqVO reqVO) {
  152 + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
  153 + reqVO.setBizType(CRM_CUSTOMER);
  154 + return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class)));
155 155 }
156 156  
157   - // TODO @Joey:单独建一个属于自己业务的 ReqVO;因为前端如果模拟请求,是不是可以更新其它字段了;
158 157 @PutMapping("/lock")
159 158 @Operation(summary = "锁定/解锁客户")
  159 + @OperateLog(enable = false) // TODO 关闭原有日志记录
160 160 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
161   - public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
162   - customerService.lockCustomer(updateReqVO);
  161 + public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerLockReqVO lockReqVO) {
  162 + customerService.lockCustomer(lockReqVO, getLoginUserId());
163 163 return success(true);
164 164 }
165 165  
... ... @@ -167,6 +167,7 @@ public class CrmCustomerController {
167 167  
168 168 @PutMapping("/put-pool")
169 169 @Operation(summary = "数据放入公海")
  170 + @OperateLog(enable = false) // TODO 关闭原有日志记录
170 171 @Parameter(name = "id", description = "客户编号", required = true, example = "1024")
171 172 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
172 173 public CommonResult<Boolean> putCustomerPool(@RequestParam("id") Long id) {
... ... @@ -183,6 +184,7 @@ public class CrmCustomerController {
183 184 return success(true);
184 185 }
185 186  
  187 + // TODO @puhui999:需要搞个 VO 类
186 188 @PutMapping("/distribute")
187 189 @Operation(summary = "分配公海给对应负责人")
188 190 @Parameters({
... ... @@ -192,7 +194,6 @@ public class CrmCustomerController {
192 194 @PreAuthorize("@ss.hasPermission('crm:customer:distribute')")
193 195 public CommonResult<Boolean> distributeCustomer(@RequestParam(value = "ids") List<Long> ids,
194 196 @RequestParam(value = "ownerUserId") Long ownerUserId) {
195   - // 领取公海数据
196 197 customerService.receiveCustomer(ids, ownerUserId);
197 198 return success(true);
198 199 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java
... ... @@ -43,6 +43,7 @@ public class CrmCustomerLimitConfigController {
43 43 @Resource
44 44 private AdminUserApi adminUserApi;
45 45  
  46 + // TODO @puhui999:可以把 vo 改下哈
46 47 @PostMapping("/create")
47 48 @Operation(summary = "创建客户限制配置")
48 49 @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:create')")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java
... ... @@ -26,6 +26,7 @@ public class CrmCustomerPoolConfigController {
26 26 @Resource
27 27 private CrmCustomerPoolConfigService customerPoolConfigService;
28 28  
  29 + // TODO @puhui999:可以把 vo 改下哈
29 30 @GetMapping("/get")
30 31 @Operation(summary = "获取客户公海规则设置")
31 32 @PreAuthorize("@ss.hasPermission('crm:customer-pool-config:query')")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 客户锁定/解锁 Request VO")
  7 +@Data
  8 +public class CrmCustomerLockReqVO {
  9 +
  10 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
  11 + private Long id;
  12 +
  13 + @Schema(description = "客户锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  14 + private Boolean lockStatus;
  15 +
  16 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerOperateLogPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - crm 客户操作日志分页 Request VO")
  8 +@Data
  9 +public class CrmCustomerOperateLogPageReqVO extends PageParam {
  10 +
  11 + @Schema(description = "模块数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + private Long bizId;
  13 +
  14 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  15 + private Long userId;
  16 +
  17 + @Schema(description = "模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + private String bizType;
  19 +
  20 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java
... ... @@ -28,4 +28,6 @@ public class CrmCustomerTransferReqVO {
28 28 @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
29 29 private Integer oldOwnerPermissionLevel;
30 30  
  31 + // TODO @puhui999:联系人、商机、合同的转移
  32 +
31 33 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java
... ... @@ -16,9 +16,9 @@ import java.util.Objects;
16 16 @ToString(callSuper = true)
17 17 public class CrmCustomerPoolConfigSaveReqVO extends CrmCustomerPoolConfigBaseVO {
18 18  
19   - // TODO @wanwan:AssertTrue 必须 is 开头哈;注意需要 json 忽略下,避免被序列化;
  19 + // TODO @puhui999:AssertTrue 必须 is 开头哈;注意需要 json 忽略下,避免被序列化;
20 20 @AssertTrue(message = "客户公海规则设置不正确")
21   - // TODO @wanwan:这个方法,是不是拆成 2 个,一个校验 contactExpireDays、一个校验 dealExpireDays;
  21 + // TODO @puhui999:这个方法,是不是拆成 2 个,一个校验 contactExpireDays、一个校验 dealExpireDays;
22 22 public boolean poolEnableValid() {
23 23 if (!BooleanUtil.isTrue(getEnabled())) {
24 24 return true;
... ... @@ -27,7 +27,7 @@ public class CrmCustomerPoolConfigSaveReqVO extends CrmCustomerPoolConfigBaseVO
27 27 }
28 28  
29 29 @AssertTrue(message = "客户公海规则设置不正确")
30   - // TODO @wanwan:这个方法,是不是改成 isNotifyDaysValid() 更好点?本质校验的是 notifyDays 是否为空
  30 + // TODO @puhui999:这个方法,是不是改成 isNotifyDaysValid() 更好点?本质校验的是 notifyDays 是否为空
31 31 public boolean notifyEnableValid() {
32 32 if (!BooleanUtil.isTrue(getNotifyEnabled())) {
33 33 return true;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java
... ... @@ -8,8 +8,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionR
8 8 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
9 9 import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert;
10 10 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
11   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
12 11 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  12 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
13 13 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
14 14 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
15 15 import cn.iocoder.yudao.module.system.api.dept.PostApi;
... ... @@ -21,12 +21,12 @@ import io.swagger.v3.oas.annotations.Operation;
21 21 import io.swagger.v3.oas.annotations.Parameter;
22 22 import io.swagger.v3.oas.annotations.Parameters;
23 23 import io.swagger.v3.oas.annotations.tags.Tag;
  24 +import jakarta.annotation.Resource;
  25 +import jakarta.validation.Valid;
24 26 import org.springframework.security.access.prepost.PreAuthorize;
25 27 import org.springframework.validation.annotation.Validated;
26 28 import org.springframework.web.bind.annotation.*;
27 29  
28   -import jakarta.annotation.Resource;
29   -import jakarta.validation.Valid;
30 30 import java.util.*;
31 31  
32 32 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -103,6 +103,7 @@ public class CrmPermissionController {
103 103 // 拼接数据
104 104 List<AdminUserRespDTO> userList = adminUserApi.getUserList(convertSet(permission, CrmPermissionDO::getUserId));
105 105 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId));
  106 + // TODO @puhui999:可能 postIds 为空的时候,会导致报错,看看怎么 fix 下
106 107 Set<Long> postIds = CollectionUtils.convertSetByFlatMap(userList, AdminUserRespDTO::getPostIds, Collection::stream);
107 108 Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
108 109 return success(CrmPermissionConvert.INSTANCE.convert(permission, userList, deptMap, postMap));
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
... ... @@ -93,7 +93,7 @@ public class CrmReceivableController {
93 93 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
94 94 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) {
95 95 PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId());
96   - return success(convertDetailReceivablePage(pageResult));
  96 + return success(buildReceivableDetailPage(pageResult));
97 97 }
98 98  
99 99 @GetMapping("/page-by-customer")
... ... @@ -101,7 +101,7 @@ public class CrmReceivableController {
101 101 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) {
102 102 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
103 103 PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO);
104   - return success(convertDetailReceivablePage(pageResult));
  104 + return success(buildReceivableDetailPage(pageResult));
105 105 }
106 106  
107 107 // TODO 芋艿:后面在优化导出
... ... @@ -115,23 +115,23 @@ public class CrmReceivableController {
115 115 PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId());
116 116 // 导出 Excel
117 117 ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class,
118   - convertDetailReceivablePage(pageResult).getList());
  118 + buildReceivableDetailPage(pageResult).getList());
119 119 }
120 120  
121 121 /**
122   - * 转换成详细的回款分页,即读取关联信息
  122 + * 构建详细的回款分页结果
123 123 *
124   - * @param pageResult 回款分页
125   - * @return 详细的回款分页
  124 + * @param pageResult 简单的回款分页结果
  125 + * @return 详细的回款分页结果
126 126 */
127   - private PageResult<CrmReceivableRespVO> convertDetailReceivablePage(PageResult<CrmReceivableDO> pageResult) {
  127 + private PageResult<CrmReceivableRespVO> buildReceivableDetailPage(PageResult<CrmReceivableDO> pageResult) {
128 128 List<CrmReceivableDO> receivableList = pageResult.getList();
129 129 if (CollUtil.isEmpty(receivableList)) {
130 130 return PageResult.empty(pageResult.getTotal());
131 131 }
132 132 // 1. 获取客户列表
133 133 List<CrmCustomerDO> customerList = customerService.getCustomerList(
134   - convertSet(receivableList, CrmReceivableDO::getCustomerId), getLoginUserId());
  134 + convertSet(receivableList, CrmReceivableDO::getCustomerId));
135 135 // 2. 获取创建人、负责人列表
136 136 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList,
137 137 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
... ... @@ -123,10 +123,10 @@ public class CrmReceivablePlanController {
123 123 }
124 124  
125 125 /**
126   - * 转换成详细的回款计划分页,即读取关联信息
  126 + * 构建详细的回款计划分页结果
127 127 *
128   - * @param pageResult 回款计划分页
129   - * @return 详细的回款计划分页
  128 + * @param pageResult 简单的回款计划分页结果
  129 + * @return 详细的回款计划分页结果
130 130 */
131 131 private PageResult<CrmReceivablePlanRespVO> convertDetailReceivablePlanPage(PageResult<CrmReceivablePlanDO> pageResult) {
132 132 List<CrmReceivablePlanDO> receivablePlanList = pageResult.getList();
... ... @@ -135,7 +135,7 @@ public class CrmReceivablePlanController {
135 135 }
136 136 // 1. 获取客户列表
137 137 List<CrmCustomerDO> customerList = customerService.getCustomerList(
138   - convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId), getLoginUserId());
  138 + convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId));
139 139 // 2. 获取创建人、负责人列表
140 140 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList,
141 141 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java
1 1 package cn.iocoder.yudao.module.crm.convert.business;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 5 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
5 6 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
6 7 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
... ... @@ -9,7 +10,6 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
9 10 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
10 11 import org.mapstruct.Mapper;
11 12 import org.mapstruct.Mapping;
12   -import org.mapstruct.Mappings;
13 13 import org.mapstruct.factory.Mappers;
14 14  
15 15 import java.util.List;
... ... @@ -34,25 +34,23 @@ public interface CrmBusinessConvert {
34 34 CrmBusinessRespVO convert(CrmBusinessDO bean);
35 35 List<CrmBusinessRespVO> convert(List<CrmBusinessDO> bean);
36 36  
37   - PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> page);
38   -
39 37 List<CrmBusinessExcelVO> convertList02(List<CrmBusinessDO> list);
40 38  
41 39 @Mapping(target = "bizId", source = "reqVO.id")
42 40 CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId);
43 41  
44   - default PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> page, List<CrmCustomerDO> customerList,
  42 + default PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> pageResult, List<CrmCustomerDO> customerList,
45 43 List<CrmBusinessStatusTypeDO> statusTypeList, List<CrmBusinessStatusDO> statusList) {
46   - PageResult<CrmBusinessRespVO> result = convertPage(page);
  44 + PageResult<CrmBusinessRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmBusinessRespVO.class);
47 45 // 拼接关联字段
48 46 Map<Long, String> customerMap = convertMap(customerList, CrmCustomerDO::getId, CrmCustomerDO::getName);
49 47 Map<Long, String> statusTypeMap = convertMap(statusTypeList, CrmBusinessStatusTypeDO::getId, CrmBusinessStatusTypeDO::getName);
50 48 Map<Long, String> statusMap = convertMap(statusList, CrmBusinessStatusDO::getId, CrmBusinessStatusDO::getName);
51   - result.getList().forEach(type -> type
  49 + voPageResult.getList().forEach(type -> type
52 50 .setCustomerName(customerMap.get(type.getCustomerId()))
53 51 .setStatusTypeName(statusTypeMap.get(type.getStatusTypeId()))
54 52 .setStatusName(statusMap.get(type.getStatusId())));
55   - return result;
  53 + return voPageResult;
56 54 }
57 55  
58 56 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
1 1 package cn.iocoder.yudao.module.crm.convert.contact;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 6 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
5 7 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
6 8 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
... ... @@ -13,12 +15,10 @@ import org.mapstruct.factory.Mappers;
13 15  
14 16 import java.util.List;
15 17 import java.util.Map;
16   -import java.util.stream.Collectors;
17 18  
18 19 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
19 20 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
20 21  
21   -// TODO 芋艿:convert 后面在梳理下,略微有点乱
22 22 /**
23 23 * CRM 联系人 Convert
24 24 *
... ... @@ -39,64 +39,37 @@ public interface CrmContactConvert {
39 39  
40 40 PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> page);
41 41  
  42 + @Mapping(target = "bizId", source = "reqVO.id")
  43 + CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId);
  44 +
42 45 default PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
43 46 List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
44   - List<CrmContactRespVO> list = converList(pageResult.getList(), userMap, customerList, parentContactList);
45   - return convertPage(pageResult).setList(list);
  47 + PageResult<CrmContactRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmContactRespVO.class);
  48 + // 拼接关联字段
  49 + Map<Long, CrmContactDO> parentContactMap = convertMap(parentContactList, CrmContactDO::getId);
  50 + Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
  51 + voPageResult.getList().forEach(item -> {
  52 + setUserInfo(item, userMap);
  53 + findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
  54 + findAndThen(parentContactMap, item.getParentId(), contactDO -> item.setParentName(contactDO.getName()));
  55 + });
  56 + return voPageResult;
46 57 }
47 58  
48   - List<CrmContactSimpleRespVO> convertAllList(List<CrmContactDO> list);
49   -
50   - @Mapping(target = "bizId", source = "reqVO.id")
51   - CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId);
52   -
53   - /**
54   - * 转换详情信息
55   - *
56   - * @param contactDO 联系人
57   - * @param userMap 用户列表
58   - * @param crmCustomerDOList 客户
59   - * @return ContactRespVO
60   - */
61   - default CrmContactRespVO convert(CrmContactDO contactDO, Map<Long, AdminUserRespDTO> userMap, List<CrmCustomerDO> crmCustomerDOList,
62   - List<CrmContactDO> contactList) {
  59 + default CrmContactRespVO convert(CrmContactDO contactDO, Map<Long, AdminUserRespDTO> userMap,
  60 + List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
63 61 CrmContactRespVO contactVO = convert(contactDO);
64 62 setUserInfo(contactVO, userMap);
65   - Map<Long, CrmCustomerDO> ustomerMap = crmCustomerDOList.stream().collect(Collectors.toMap(CrmCustomerDO::getId, v -> v));
66   - Map<Long, CrmContactDO> contactMap = contactList.stream().collect(Collectors.toMap(CrmContactDO::getId, v -> v));
67   - findAndThen(ustomerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName()));
  63 + Map<Long, CrmCustomerDO> customerMap = CollectionUtils.convertMap(customerList, CrmCustomerDO::getId);
  64 + Map<Long, CrmContactDO> contactMap = CollectionUtils.convertMap(parentContactList, CrmContactDO::getId);
  65 + findAndThen(customerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName()));
68 66 findAndThen(contactMap, contactDO.getParentId(), contact -> contactVO.setParentName(contact.getName()));
69 67 return contactVO;
70 68 }
71 69  
72   - default List<CrmContactRespVO> converList(List<CrmContactDO> contactList, Map<Long, AdminUserRespDTO> userMap,
73   - List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
74   - List<CrmContactRespVO> result = convertList(contactList);
75   - Map<Long, CrmContactDO> parentContactMap = convertMap(parentContactList, CrmContactDO::getId);
76   - Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
77   - result.forEach(item -> {
78   - setUserInfo(item, userMap);
79   - findAndThen(customerMap, item.getCustomerId(), customer -> { // TODO @zyna:这里的 { 可以去掉
80   - item.setCustomerName(customer.getName());
81   - });
82   - findAndThen(parentContactMap, item.getParentId(), contactDO -> { // TODO @zyna:这里的 { 可以去掉
83   - item.setParentName(contactDO.getName());
84   - });
85   - });
86   - return result;
87   - }
88   -
89   - /**
90   - * 设置用户信息
91   - *
92   - * @param contactRespVO 联系人Response VO
93   - * @param userMap 用户信息 map
94   - */
95 70 static void setUserInfo(CrmContactRespVO contactRespVO, Map<Long, AdminUserRespDTO> userMap) {
96 71 contactRespVO.setAreaName(AreaUtils.format(contactRespVO.getAreaId()));
97   - findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> {
98   - contactRespVO.setOwnerUserName(user == null ? "" : user.getNickname());
99   - });
  72 + findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> contactRespVO.setOwnerUserName(user.getNickname()));
100 73 findAndThen(userMap, Long.parseLong(contactRespVO.getCreator()), user -> contactRespVO.setCreatorName(user.getNickname()));
101 74 }
102 75  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contactbusinessslink/CrmContactBusinessLinkConvert.java deleted
1   -package cn.iocoder.yudao.module.crm.convert.contactbusinessslink;
2   -
3   -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
4   -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
5   -import org.mapstruct.Mapper;
6   -import org.mapstruct.factory.Mappers;
7   -
8   -import java.util.List;
9   -
10   -// TODO @zyna:使用 BeanUtils 慢慢替代现有的 mapstruct 哈
11   -@Mapper
12   -public interface CrmContactBusinessLinkConvert {
13   - CrmContactBusinessLinkConvert INSTANCE = Mappers.getMapper(CrmContactBusinessLinkConvert.class);
14   - CrmContactBusinessLinkDO convert(CrmContactBusinessLinkSaveReqVO bean);
15   - List<CrmContactBusinessLinkDO> convert(List<CrmContactBusinessLinkSaveReqVO> bean);
16   -}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
1 1 package cn.iocoder.yudao.module.crm.convert.contract;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 5 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*;
5 6 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
6 7 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
... ... @@ -43,23 +44,15 @@ public interface CrmContractConvert {
43 44  
44 45 default PageResult<ContractRespVO> convertPage(PageResult<CrmContractDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
45 46 List<CrmCustomerDO> customerList) {
46   - return new PageResult<>(converList(pageResult.getList(), userMap, customerList), pageResult.getTotal());
47   - }
48   -
49   - default List<ContractRespVO> converList(List<CrmContractDO> contractList, Map<Long, AdminUserRespDTO> userMap,
50   - List<CrmCustomerDO> customerList) {
51   - List<ContractRespVO> result = convertList(contractList);
  47 + PageResult<ContractRespVO> voPageResult = BeanUtils.toBean(pageResult, ContractRespVO.class);
  48 + // 拼接关联字段
52 49 Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
53   - result.forEach(item -> {
54   - setUserInfo(item, userMap);
55   - findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
  50 + voPageResult.getList().forEach(contract -> {
  51 + findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname()));
  52 + findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname()));
  53 + findAndThen(customerMap, contract.getCustomerId(), customer -> contract.setCustomerName(customer.getName()));
56 54 });
57   - return result;
58   - }
59   -
60   - static void setUserInfo(ContractRespVO contract, Map<Long, AdminUserRespDTO> userMap) {
61   - findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname()));
62   - findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname()));
  55 + return voPageResult;
63 56 }
64 57  
65 58 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java
... ... @@ -12,7 +12,6 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
12 12 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
13 13 import org.mapstruct.Mapper;
14 14 import org.mapstruct.Mapping;
15   -import org.mapstruct.Mappings;
16 15 import org.mapstruct.factory.Mappers;
17 16  
18 17 import java.util.List;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java
1 1 package cn.iocoder.yudao.module.crm.convert.receivable;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
5 6 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableTransferReqVO;
... ... @@ -36,29 +37,19 @@ public interface CrmReceivableConvert {
36 37  
37 38 CrmReceivableRespVO convert(CrmReceivableDO bean);
38 39  
39   - List<CrmReceivableRespVO> convertList(List<CrmReceivableDO> list);
40   -
41 40 default PageResult<CrmReceivableRespVO> convertPage(PageResult<CrmReceivableDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
42 41 List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) {
43   - return new PageResult<>(converList(pageResult.getList(), userMap, customerList, contractList), pageResult.getTotal());
44   - }
45   -
46   - default List<CrmReceivableRespVO> converList(List<CrmReceivableDO> receivableList, Map<Long, AdminUserRespDTO> userMap,
47   - List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) {
48   - List<CrmReceivableRespVO> result = convertList(receivableList);
  42 + PageResult<CrmReceivableRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class);
  43 + // 拼接关联字段
49 44 Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
50 45 Map<Long, CrmContractDO> contractMap = convertMap(contractList, CrmContractDO::getId);
51   - result.forEach(item -> {
52   - setUserInfo(item, userMap);
53   - findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
54   - findAndThen(contractMap, item.getContractId(), contract -> item.setContractNo(contract.getNo()));
  46 + voPageResult.getList().forEach(receivable -> {
  47 + findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname()));
  48 + findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname()));
  49 + findAndThen(customerMap, receivable.getCustomerId(), customer -> receivable.setCustomerName(customer.getName()));
  50 + findAndThen(contractMap, receivable.getContractId(), contract -> receivable.setContractNo(contract.getNo()));
55 51 });
56   - return result;
57   - }
58   -
59   - static void setUserInfo(CrmReceivableRespVO receivable, Map<Long, AdminUserRespDTO> userMap) {
60   - findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname()));
61   - findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname()));
  52 + return voPageResult;
62 53 }
63 54  
64 55 @Mapping(target = "bizId", source = "reqVO.id")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java
1 1 package cn.iocoder.yudao.module.crm.convert.receivable;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
5 6 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanTransferReqVO;
... ... @@ -37,33 +38,22 @@ public interface CrmReceivablePlanConvert {
37 38  
38 39 CrmReceivablePlanRespVO convert(CrmReceivablePlanDO bean);
39 40  
40   - List<CrmReceivablePlanRespVO> convertList(List<CrmReceivablePlanDO> list);
41   -
42 41 default PageResult<CrmReceivablePlanRespVO> convertPage(PageResult<CrmReceivablePlanDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
43 42 List<CrmCustomerDO> customerList, List<CrmContractDO> contractList,
44 43 List<CrmReceivableDO> receivableList) {
45   - return new PageResult<>(converList(pageResult.getList(), userMap, customerList, contractList, receivableList), pageResult.getTotal());
46   - }
47   -
48   - default List<CrmReceivablePlanRespVO> converList(List<CrmReceivablePlanDO> receivablePlanList, Map<Long, AdminUserRespDTO> userMap,
49   - List<CrmCustomerDO> customerList, List<CrmContractDO> contractList,
50   - List<CrmReceivableDO> receivableList) {
51   - List<CrmReceivablePlanRespVO> result = convertList(receivablePlanList);
  44 + PageResult<CrmReceivablePlanRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivablePlanRespVO.class);
  45 + // 拼接关联字段
52 46 Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
53 47 Map<Long, CrmContractDO> contractMap = convertMap(contractList, CrmContractDO::getId);
54 48 Map<Long, CrmReceivableDO> receivableMap = convertMap(receivableList, CrmReceivableDO::getId);
55   - result.forEach(item -> {
56   - setUserInfo(item, userMap);
57   - findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
58   - findAndThen(contractMap, item.getContractId(), contract -> item.setContractNo(contract.getNo()));
59   - findAndThen(receivableMap, item.getReceivableId(), receivable -> item.setReturnType(receivable.getReturnType()));
  49 + voPageResult.getList().forEach(receivablePlan -> {
  50 + findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname()));
  51 + findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname()));
  52 + findAndThen(customerMap, receivablePlan.getCustomerId(), customer -> receivablePlan.setCustomerName(customer.getName()));
  53 + findAndThen(contractMap, receivablePlan.getContractId(), contract -> receivablePlan.setContractNo(contract.getNo()));
  54 + findAndThen(receivableMap, receivablePlan.getReceivableId(), receivable -> receivablePlan.setReturnType(receivable.getReturnType()));
60 55 });
61   - return result;
62   - }
63   -
64   - static void setUserInfo(CrmReceivablePlanRespVO receivablePlan, Map<Long, AdminUserRespDTO> userMap) {
65   - findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname()));
66   - findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname()));
  56 + return voPageResult;
67 57 }
68 58  
69 59 @Mapping(target = "bizId", source = "reqVO.id")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contactbusinesslink/CrmContactBusinessLinkDO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java
1   -package cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink;
  1 +package cn.iocoder.yudao.module.crm.dal.dataobject.contact;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 4 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
5   -import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
6 5 import com.baomidou.mybatisplus.annotation.KeySequence;
7 6 import com.baomidou.mybatisplus.annotation.TableId;
8 7 import com.baomidou.mybatisplus.annotation.TableName;
9 8 import lombok.*;
10 9  
11   -// TODO @zyna:可以放到 contact 包下
12 10 /**
13   - * CRM 联系人商机关联 DO
  11 + * CRM 联系人与商机的关联 DO
14 12 *
15 13 * @author 芋道源码
16 14 */
17   -@TableName("crm_contact_business_link")
18   -@KeySequence("crm_contact_business_link_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  15 +@TableName("crm_contact_business")
  16 +@KeySequence("crm_contact_business_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
19 17 @Data
20 18 @EqualsAndHashCode(callSuper = true)
21 19 @ToString(callSuper = true)
22 20 @Builder
23 21 @NoArgsConstructor
24 22 @AllArgsConstructor
25   -public class CrmContactBusinessLinkDO extends BaseDO {
  23 +public class CrmContactBusinessDO extends BaseDO {
26 24  
27 25 /**
28 26 * 主键
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java
1 1 package cn.iocoder.yudao.module.crm.dal.dataobject.contact;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
4 5 import com.baomidou.mybatisplus.annotation.KeySequence;
5 6 import com.baomidou.mybatisplus.annotation.TableId;
6 7 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -29,9 +30,11 @@ public class CrmContactDO extends BaseDO {
29 30 @TableId
30 31 private Long id;
31 32 /**
32   - * 下次联系时间
  33 + * 客户编号
  34 + *
  35 + * 关联 {@link CrmCustomerDO#getId()}
33 36 */
34   - private LocalDateTime nextTime;
  37 + private Long customerId;
35 38 /**
36 39 * 手机号
37 40 */
... ... @@ -45,21 +48,20 @@ public class CrmContactDO extends BaseDO {
45 48 */
46 49 private String email;
47 50 /**
48   - * 客户编号
  51 + * 所在地
  52 + *
  53 + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
49 54 */
50   - private Long customerId;
  55 + private Integer areaId;
51 56 /**
52   - * 地址
  57 + * 详细地址
53 58 */
54   - private String address;
  59 + private String detailAddress;
55 60 /**
56 61 * 备注
57 62 */
58 63 private String remark;
59   - /**
60   - * 最后跟进时间
61   - */
62   - private LocalDateTime lastTime;
  64 +
63 65 /**
64 66 * 直属上级
65 67 *
... ... @@ -100,10 +102,13 @@ public class CrmContactDO extends BaseDO {
100 102 private Long ownerUserId;
101 103  
102 104 /**
103   - * 所在地
104   - *
105   - * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
  105 + * 最后跟进时间
106 106 */
107   - private Integer areaId;
  107 + private LocalDateTime contactLastTime;
  108 + // TODO @puhui999:增加一个字段 contactLastContent;最后跟进内容
  109 + /**
  110 + * 下次联系时间
  111 + */
  112 + private LocalDateTime contactNextTime;
108 113  
109 114 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
... ... @@ -12,7 +12,7 @@ import java.time.LocalDateTime;
12 12 // TODO 芋艿:调整下字段
13 13  
14 14 /**
15   - * 客户 DO
  15 + * CRM 客户 DO
16 16 *
17 17 * @author Wanwan
18 18 */
... ... @@ -104,17 +104,21 @@ public class CrmCustomerDO extends BaseDO {
104 104 */
105 105 private Long ownerUserId;
106 106 /**
107   - * 地区编号
  107 + * 所在地
  108 + *
  109 + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
108 110 */
109 111 private Integer areaId;
110 112 /**
111 113 * 详细地址
112 114 */
113 115 private String detailAddress;
  116 +
114 117 /**
115 118 * 最后跟进时间
116 119 */
117 120 private LocalDateTime contactLastTime;
  121 + // TODO @puhui999:增加一个字段 contactLastContent;最后跟进内容
118 122 /**
119 123 * 下次联系时间
120 124 */
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.dal.dataobject.followup;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  5 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
  6 +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
  7 +import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableId;
  9 +import com.baomidou.mybatisplus.annotation.TableName;
  10 +import lombok.*;
  11 +
  12 +import java.time.LocalDateTime;
  13 +import java.util.List;
  14 +
  15 +// TODO @puhui999:界面:做成一个 list 列表,字段是 id、跟进人、跟进方式、跟进时间、跟进内容、下次联系时间、关联联系人、关联商机
  16 +// TODO @puhui999:界面:记录时,弹窗,表单字段是跟进方式、跟进内容、下次联系时间、关联联系人、关联商机;其中关联联系人、关联商机,要做成对应的组件列。
  17 +/**
  18 + * 跟进记录 DO
  19 + *
  20 + * 用于记录客户、联系人的每一次跟进
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +@TableName(value = "crm_follow_up_record")
  25 +@KeySequence("crm_follow_up_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  26 +@Data
  27 +@EqualsAndHashCode(callSuper = true)
  28 +@ToString(callSuper = true)
  29 +@Builder
  30 +@NoArgsConstructor
  31 +@AllArgsConstructor
  32 +public class CrmFollowUpRecordDO extends BaseDO {
  33 +
  34 + /**
  35 + * 编号
  36 + */
  37 + @TableId
  38 + private Long id;
  39 +
  40 + /**
  41 + * 数据类型
  42 + *
  43 + * 枚举 {@link CrmBizTypeEnum}
  44 + */
  45 + private Integer bizType;
  46 + /**
  47 + * 数据编号
  48 + *
  49 + * 关联 {@link CrmBizTypeEnum} 对应模块 DO 的 id 字段
  50 + */
  51 + private Long bizId;
  52 +
  53 + /**
  54 + * 跟进类型
  55 + *
  56 + * TODO @puhui999:可以搞个数据字典,打电话、发短信、上门拜访、微信、邮箱、QQ
  57 + */
  58 + private Integer type;
  59 + /**
  60 + * 跟进内容
  61 + */
  62 + private String content;
  63 + /**
  64 + * 下次联系时间
  65 + */
  66 + private LocalDateTime nextTime;
  67 +
  68 + /**
  69 + * 关联的商机编号数组
  70 + *
  71 + * 关联 {@link CrmBusinessDO#getId()}
  72 + */
  73 + private List<Long> businessIds;
  74 + /**
  75 + * 关联的联系人编号数组
  76 + *
  77 + * 关联 {@link CrmContactDO#getId()}
  78 + */
  79 + private List<Long> contactIds;
  80 +
  81 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
... ... @@ -30,7 +30,14 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
30 30  
31 31 default PageResult<CrmBusinessDO> selectPageByCustomerId(CrmBusinessPageReqVO pageReqVO) {
32 32 return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
33   - .eq(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号
  33 + .eq(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号
  34 + .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
  35 + .orderByDesc(CrmBusinessDO::getId));
  36 + }
  37 +
  38 + default PageResult<CrmBusinessDO> selectPageByContactId(CrmBusinessPageReqVO pageReqVO, Collection<Long> businessIds) {
  39 + return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
  40 + .in(CrmBusinessDO::getId, businessIds) // 指定商机编号
34 41 .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
35 42 .orderByDesc(CrmBusinessDO::getId));
36 43 }
... ... @@ -38,11 +45,8 @@ public interface CrmBusinessMapper extends BaseMapperX&lt;CrmBusinessDO&gt; {
38 45 default PageResult<CrmBusinessDO> selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
39 46 MPJLambdaWrapperX<CrmBusinessDO> query = new MPJLambdaWrapperX<>();
40 47 // 拼接数据权限的查询条件
41   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
  48 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
42 49 CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
43   - if (!condition) {
44   - return PageResult.empty();
45   - }
46 50 // 拼接自身的查询条件
47 51 query.selectAll(CrmBusinessDO.class)
48 52 .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
... ... @@ -30,11 +30,8 @@ public interface CrmClueMapper extends BaseMapperX&lt;CrmClueDO&gt; {
30 30 default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO pageReqVO, Long userId) {
31 31 MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
32 32 // 拼接数据权限的查询条件
33   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
  33 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
34 34 CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
35   - if (!condition) {
36   - return PageResult.empty();
37   - }
38 35 // 拼接自身的查询条件
39 36 query.selectAll(CrmClueDO.class)
40 37 .likeIfPresent(CrmClueDO::getName, pageReqVO.getName())
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java
... ... @@ -43,11 +43,8 @@ public interface CrmContactMapper extends BaseMapperX&lt;CrmContactDO&gt; {
43 43 default PageResult<CrmContactDO> selectPage(CrmContactPageReqVO pageReqVO, Long userId) {
44 44 MPJLambdaWrapperX<CrmContactDO> query = new MPJLambdaWrapperX<>();
45 45 // 拼接数据权限的查询条件
46   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
  46 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
47 47 CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
48   - if (!condition) {
49   - return PageResult.empty();
50   - }
51 48 // 拼接自身的查询条件
52 49 query.selectAll(CrmContactDO.class)
53 50 .likeIfPresent(CrmContactDO::getName, pageReqVO.getName())
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java deleted
1   -package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6   -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
7   -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
8   -import org.apache.ibatis.annotations.Mapper;
9   -
10   -/**
11   - * CRM 联系人商机关联 Mapper
12   - *
13   - * @author 芋道源码
14   - */
15   -@Mapper
16   -public interface CrmContactBusinessLinkMapper extends BaseMapperX<CrmContactBusinessLinkDO> {
17   -
18   - default PageResult<CrmContactBusinessLinkDO> selectPage(CrmContactBusinessLinkPageReqVO reqVO) {
19   - return selectPage(reqVO, new LambdaQueryWrapperX<CrmContactBusinessLinkDO>()
20   - .eqIfPresent(CrmContactBusinessLinkDO::getContactId, reqVO.getContactId())
21   - .eqIfPresent(CrmContactBusinessLinkDO::getBusinessId, reqVO.getBusinessId())
22   - .betweenIfPresent(CrmContactBusinessLinkDO::getCreateTime, reqVO.getCreateTime())
23   - .orderByDesc(CrmContactBusinessLinkDO::getId));
24   - } // TODO @zyna:方法和方法之间要有空行
25   - default PageResult<CrmContactBusinessLinkDO> selectPageByContact(CrmContactBusinessLinkPageReqVO reqVO) {
26   - return selectPage(reqVO, new LambdaQueryWrapperX<CrmContactBusinessLinkDO>()
27   - .eqIfPresent(CrmContactBusinessLinkDO::getContactId, reqVO.getContactId())
28   - .orderByDesc(CrmContactBusinessLinkDO::getId));
29   - }
30   -}
31 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
  5 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +import java.util.Collection;
  9 +import java.util.List;
  10 +
  11 +/**
  12 + * CRM 联系人与商机的关联 Mapper
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Mapper
  17 +public interface CrmContactBusinessMapper extends BaseMapperX<CrmContactBusinessDO> {
  18 +
  19 + default CrmContactBusinessDO selectByContactIdAndBusinessId(Long contactId, Long businessId) {
  20 + return selectOne(CrmContactBusinessDO::getContactId, contactId,
  21 + CrmContactBusinessDO::getBusinessId, businessId);
  22 + }
  23 +
  24 + default void deleteByContactIdAndBusinessId(Long contactId, Collection<Long> businessIds) {
  25 + delete(new LambdaQueryWrapper<CrmContactBusinessDO>()
  26 + .eq(CrmContactBusinessDO::getContactId, contactId)
  27 + .in(CrmContactBusinessDO::getBusinessId, businessIds));
  28 + }
  29 +
  30 + default List<CrmContactBusinessDO> selectListByContactId(Long contactId) {
  31 + return selectList(CrmContactBusinessDO::getContactId, contactId);
  32 + }
  33 +
  34 +}
0 35 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
... ... @@ -41,11 +41,8 @@ public interface CrmContractMapper extends BaseMapperX&lt;CrmContractDO&gt; {
41 41 default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId) {
42 42 MPJLambdaWrapperX<CrmContractDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
43 43 // 拼接数据权限的查询条件
44   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
  44 + CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
45 45 CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
46   - if (!condition) {
47   - return PageResult.empty();
48   - }
49 46 // 拼接自身的查询条件
50 47 mpjLambdaWrapperX.selectAll(CrmContractDO.class)
51 48 .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java
... ... @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmC
7 7 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
8 8 import org.apache.ibatis.annotations.Mapper;
9 9  
  10 +import java.util.List;
  11 +
10 12 /**
11 13 * 客户限制配置 Mapper
12 14 *
... ... @@ -21,4 +23,15 @@ public interface CrmCustomerLimitConfigMapper extends BaseMapperX&lt;CrmCustomerLim
21 23 .orderByDesc(CrmCustomerLimitConfigDO::getId));
22 24 }
23 25  
  26 + default List<CrmCustomerLimitConfigDO> selectListByTypeAndUserIdAndDeptId(
  27 + Integer type, Long userId, Long deptId) {
  28 + LambdaQueryWrapperX<CrmCustomerLimitConfigDO> query = new LambdaQueryWrapperX<CrmCustomerLimitConfigDO>()
  29 + .eq(CrmCustomerLimitConfigDO::getType, type);
  30 + query.apply("FIND_IN_SET({0}, user_ids) > 0", userId);
  31 + if (deptId != null) {
  32 + query.apply("FIND_IN_SET({0}, dept_ids) > 0", deptId);
  33 + }
  34 + return selectList(query);
  35 + }
  36 +
24 37 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
6 7 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
7 8 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
... ... @@ -9,6 +10,7 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
9 10 import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
10 11 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
11 12 import org.apache.ibatis.annotations.Mapper;
  13 +import org.springframework.lang.Nullable;
12 14  
13 15 import java.util.Collection;
14 16 import java.util.List;
... ... @@ -21,6 +23,18 @@ import java.util.List;
21 23 @Mapper
22 24 public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
23 25  
  26 + default Long selectCountByLockStatusAndOwnerUserId(Boolean lockStatus, Long ownerUserId) {
  27 + return selectCount(new LambdaUpdateWrapper<CrmCustomerDO>()
  28 + .eq(CrmCustomerDO::getLockStatus, lockStatus)
  29 + .eq(CrmCustomerDO::getOwnerUserId, ownerUserId));
  30 + }
  31 +
  32 + default Long selectCountByDealStatusAndOwnerUserId(@Nullable Boolean dealStatus, Long ownerUserId) {
  33 + return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>()
  34 + .eqIfPresent(CrmCustomerDO::getDealStatus, dealStatus)
  35 + .eq(CrmCustomerDO::getOwnerUserId, ownerUserId));
  36 + }
  37 +
24 38 default int updateOwnerUserIdById(Long id, Long ownerUserId) {
25 39 return update(new LambdaUpdateWrapper<CrmCustomerDO>()
26 40 .eq(CrmCustomerDO::getId, id)
... ... @@ -30,11 +44,8 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; {
30 44 default PageResult<CrmCustomerDO> selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) {
31 45 MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
32 46 // 拼接数据权限的查询条件
33   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
  47 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
34 48 CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
35   - if (!condition) {
36   - return PageResult.empty();
37   - }
38 49 // 拼接自身的查询条件
39 50 query.selectAll(CrmCustomerDO.class)
40 51 .likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName())
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
... ... @@ -39,11 +39,8 @@ public interface CrmReceivableMapper extends BaseMapperX&lt;CrmReceivableDO&gt; {
39 39 default PageResult<CrmReceivableDO> selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) {
40 40 MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
41 41 // 拼接数据权限的查询条件
42   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
  42 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
43 43 CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
44   - if (!condition) {
45   - return PageResult.empty();
46   - }
47 44 // 拼接自身的查询条件
48 45 query.selectAll(CrmReceivableDO.class)
49 46 .eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo())
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
... ... @@ -38,11 +38,8 @@ public interface CrmReceivablePlanMapper extends BaseMapperX&lt;CrmReceivablePlanDO
38 38 default PageResult<CrmReceivablePlanDO> selectPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) {
39 39 MPJLambdaWrapperX<CrmReceivablePlanDO> query = new MPJLambdaWrapperX<>();
40 40 // 拼接数据权限的查询条件
41   - boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
  41 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
42 42 CrmReceivablePlanDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
43   - if (!condition) {
44   - return PageResult.empty();
45   - }
46 43 // 拼接自身的查询条件
47 44 query.selectAll(CrmReceivablePlanDO.class)
48 45 .eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId())
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/package-info.java deleted
1   -package cn.iocoder.yudao.module.crm.framework.bizlog;
2 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/package-info.java deleted
1   -package cn.iocoder.yudao.module.crm.framework.core;
2 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmIndustryParseFunction.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmIndustryParseFunction.java
1   -package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.core;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
... ... @@ -8,9 +8,8 @@ import org.springframework.stereotype.Component;
8 8  
9 9 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
10 10  
11   -// TODO @puhui999:包名使用 operatelog 更合适哈;
12 11 /**
13   - * 自定义函数-通过行业编号获取行业信息
  12 + * 行业的 {@link IParseFunction} 实现类
14 13 *
15 14 * @author HUIHUI
16 15 */
... ... @@ -30,18 +29,10 @@ public class CrmIndustryParseFunction implements IParseFunction {
30 29  
31 30 @Override
32 31 public String apply(Object value) {
33   - if (value == null) {
  32 + if (StrUtil.isEmptyIfStr(value)) {
34 33 return "";
35 34 }
36   - if (StrUtil.isEmpty(value.toString())) {
37   - return "";
38   - }
39   -
40   - // 获取行业信息
41   - try {
42   - return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_INDUSTRY, value.toString());
43   - } catch (Exception ignored) {
44   - }
45   - return "";
  35 + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_INDUSTRY, value.toString());
46 36 }
  37 +
47 38 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmLevelParseFunction.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmLevelParseFunction.java
1   -package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.core;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
... ... @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
9 9 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL;
10 10  
11 11 /**
12   - * 自定义函数-通过客户等级编号获取客户等级信息
  12 + * 客户等级的 {@link IParseFunction} 实现类
13 13 *
14 14 * @author HUIHUI
15 15 */
... ... @@ -29,18 +29,10 @@ public class CrmLevelParseFunction implements IParseFunction {
29 29  
30 30 @Override
31 31 public String apply(Object value) {
32   - if (value == null) {
  32 + if (StrUtil.isEmptyIfStr(value)) {
33 33 return "";
34 34 }
35   - if (StrUtil.isEmpty(value.toString())) {
36   - return "";
37   - }
38   -
39   - // 获取客户等级信息
40   - try {
41   - return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_LEVEL, value.toString());
42   - } catch (Exception ignored) {
43   - }
44   - return "";
  35 + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_LEVEL, value.toString());
45 36 }
  37 +
46 38 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/bizlog/function/CrmSourceParseFunction.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmSourceParseFunction.java
1   -package cn.iocoder.yudao.module.crm.framework.bizlog.function;
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.core;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
... ... @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
9 9 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE;
10 10  
11 11 /**
12   - * 自定义函数-通过客户来源编号获取客户来源信息
  12 + * 客户来源的 {@link IParseFunction} 实现类
13 13 *
14 14 * @author HUIHUI
15 15 */
... ... @@ -29,18 +29,10 @@ public class CrmSourceParseFunction implements IParseFunction {
29 29  
30 30 @Override
31 31 public String apply(Object value) {
32   - if (value == null) {
  32 + if (StrUtil.isEmptyIfStr(value)) {
33 33 return "";
34 34 }
35   - if (StrUtil.isEmpty(value.toString())) {
36   - return "";
37   - }
38   -
39   - // 获取客户来源信息
40   - try {
41   - return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_SOURCE, value.toString());
42   - } catch (Exception ignored) {
43   - }
44   - return "";
  35 + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_SOURCE, value.toString());
45 36 }
  37 +
46 38 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog;
0 2 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/annotations/CrmPermission.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java
1   -package cn.iocoder.yudao.module.crm.framework.core.annotations;
  1 +package cn.iocoder.yudao.module.crm.framework.permission.core.annotations;
2 2  
3 3 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
4 4 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/aop/CrmPermissionAspect.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java
1   -package cn.iocoder.yudao.module.crm.framework.core.aop;
  1 +package cn.iocoder.yudao.module.crm.framework.permission.core.aop;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
... ... @@ -6,27 +6,27 @@ import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
7 7 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
8 8 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
9   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
10 9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 10 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  11 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
  12 +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils;
12 13 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
  14 +import jakarta.annotation.Resource;
13 15 import lombok.extern.slf4j.Slf4j;
14 16 import org.aspectj.lang.JoinPoint;
15 17 import org.aspectj.lang.annotation.Aspect;
16 18 import org.aspectj.lang.annotation.Before;
17 19 import org.springframework.stereotype.Component;
18 20  
19   -import jakarta.annotation.Resource;
20 21 import java.util.ArrayList;
21 22 import java.util.List;
22 23 import java.util.Map;
23 24  
24 25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
25 27 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
26 28 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED;
27   -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_MODEL_NOT_EXISTS;
28 29  
29   -// TODO 这个包,改成 permission,然后搞 config 和 core 包,这个类在 core 包里;目的是:framework 最好分类下
30 30 /**
31 31 * Crm 数据权限校验 AOP 切面
32 32 *
... ... @@ -42,10 +42,6 @@ public class CrmPermissionAspect {
42 42  
43 43 @Before("@annotation(crmPermission)")
44 44 public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) {
45   - // TODO 芋艿:临时,方便大家调试
46   - //if (true) {
47   - // return;
48   - //}
49 45 // 获取相关属性值
50 46 Map<String, Object> expressionValues = parseExpressions(joinPoint, crmPermission);
51 47 Integer bizType = StrUtil.isEmpty(crmPermission.bizTypeValue()) ?
... ... @@ -53,16 +49,28 @@ public class CrmPermissionAspect {
53 49 Long bizId = (Long) expressionValues.get(crmPermission.bizId()); // 模块数据编号
54 50 Integer permissionLevel = crmPermission.level().getLevel(); // 需要的权限级别
55 51  
56   - // TODO 如果是超级管理员则直接通过
57   - //if (superAdmin){
58   - // return;
59   - //}
60   -
61   - // 1. 获取数据权限
  52 + // 1.1 如果是超级管理员则直接通过
  53 + if (CrmPermissionUtils.isCrmAdmin()) {
  54 + return;
  55 + }
  56 + // 1.2 获取数据权限
62 57 List<CrmPermissionDO> bizPermissions = crmPermissionService.getPermissionListByBiz(bizType, bizId);
63   - if (CollUtil.isEmpty(bizPermissions)) { // 数据权限不存存那么数据也不存在
64   - throw exception(CRM_PERMISSION_MODEL_NOT_EXISTS, CrmBizTypeEnum.getNameByType(bizType));
  58 + if (CollUtil.isEmpty(bizPermissions)) { // 没有数据权限的情况
  59 + // 公海数据如果没有团队成员大家也因该有读权限才对
  60 + if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
  61 + return;
  62 + }
  63 +
  64 + // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针
  65 + throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
  66 + } else { // 有数据权限但是没有负责人的情况
  67 + if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))) {
  68 + if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
  69 + return;
  70 + }
  71 + }
65 72 }
  73 +
66 74 // 2.1 情况一:如果自己是负责人,则默认有所有权限
67 75 CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), getUserId()));
68 76 if (userPermission != null) {
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.permission.core;
0 2 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.permission.core.util;
  2 +
  3 +import cn.hutool.extra.spring.SpringUtil;
  4 +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum;
  5 +import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
  6 +
  7 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  8 +
  9 +/**
  10 + * 数据权限工具类
  11 + *
  12 + * @author HUIHUI
  13 + */
  14 +public class CrmPermissionUtils {
  15 +
  16 + /**
  17 + * 校验用户是否是 CRM 管理员
  18 + *
  19 + * @return 是/否
  20 + */
  21 + public static boolean isCrmAdmin() {
  22 + return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode());
  23 + }
  24 +
  25 + /**
  26 + * 静态内部类实现单例获取
  27 + *
  28 + * @author HUIHUI
  29 + */
  30 + private static class SingletonManager {
  31 +
  32 + private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class);
  33 +
  34 + public static PermissionApi getPermissionApi() {
  35 + return PERMISSION_API;
  36 + }
  37 +
  38 + }
  39 +
  40 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.permission;
0 2 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
6 6 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
7 7 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
8 8 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  9 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
9 10 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
10 11 import jakarta.validation.Valid;
11 12  
... ... @@ -75,11 +76,21 @@ public interface CrmBusinessService {
75 76 * 数据权限:基于 {@link CrmCustomerDO} 读取
76 77 *
77 78 * @param pageReqVO 分页查询
78   - * @return 联系人分页
  79 + * @return 商机分页
79 80 */
80 81 PageResult<CrmBusinessDO> getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO);
81 82  
82 83 /**
  84 + * 获得商机分页,基于指定联系人
  85 + *
  86 + * 数据权限:基于 {@link CrmContactDO} 读取
  87 + *
  88 + * @param pageReqVO 分页参数
  89 + * @return 商机分页
  90 + */
  91 + PageResult<CrmBusinessDO> getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO);
  92 +
  93 + /**
83 94 * 商机转移
84 95 *
85 96 * @param reqVO 请求
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
... ... @@ -9,11 +9,12 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
9 9 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
10 10 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
11 11 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  12 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
12 13 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
13 14 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
14 15 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
15   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
16   -import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  16 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
  17 +import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
17 18 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
18 19 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
19 20 import jakarta.annotation.Resource;
... ... @@ -25,6 +26,7 @@ import java.util.Collection;
25 26 import java.util.List;
26 27  
27 28 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
28 30 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
29 31  
30 32 /**
... ... @@ -38,24 +40,26 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
38 40  
39 41 @Resource
40 42 private CrmBusinessMapper businessMapper;
41   - @Resource
42   - private CrmCustomerService customerService;
43 43  
44 44 @Resource
45   - private CrmPermissionService crmPermissionService;
  45 + private CrmPermissionService permissionService;
  46 + @Resource
  47 + private CrmContactBusinessService contactBusinessService;
46 48  
47 49 @Override
48 50 @Transactional(rollbackFor = Exception.class)
  51 + // TODO @商机待定:操作日志;
49 52 public Long createBusiness(CrmBusinessCreateReqVO createReqVO, Long userId) {
50   - // 插入
  53 + // 1. 插入商机
51 54 CrmBusinessDO business = CrmBusinessConvert.INSTANCE.convert(createReqVO);
52 55 businessMapper.insert(business);
  56 + // TODO 商机待定:插入商机与产品的关联表;校验商品存在
53 57  
54   - // 创建数据权限
55   - crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
56   - .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
  58 + // TODO 商机待定:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表
57 59  
58   - // 返回
  60 + // 2. 创建数据权限
  61 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
  62 + .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
59 63 return business.getId();
60 64 }
61 65  
... ... @@ -63,12 +67,17 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
63 67 @Transactional(rollbackFor = Exception.class)
64 68 @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id",
65 69 level = CrmPermissionLevelEnum.WRITE)
  70 + // TODO @商机待定:操作日志;
66 71 public void updateBusiness(CrmBusinessUpdateReqVO updateReqVO) {
67   - // 校验存在
  72 + // 1. 校验存在
68 73 validateBusinessExists(updateReqVO.getId());
69   - // 更新
  74 +
  75 + // 2. 更新商机
70 76 CrmBusinessDO updateObj = CrmBusinessConvert.INSTANCE.convert(updateReqVO);
71 77 businessMapper.updateById(updateObj);
  78 + // TODO 商机待定:插入商机与产品的关联表;校验商品存在
  79 +
  80 + // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表
72 81 }
73 82  
74 83 @Override
... ... @@ -77,10 +86,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
77 86 public void deleteBusiness(Long id) {
78 87 // 校验存在
79 88 validateBusinessExists(id);
  89 + // TODO @商机待定:需要校验有没关联合同。CrmContractDO 的 businessId 字段
  90 +
80 91 // 删除
81 92 businessMapper.deleteById(id);
82 93 // 删除数据权限
83   - crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id);
  94 + permissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id);
84 95 }
85 96  
86 97 private CrmBusinessDO validateBusinessExists(Long id) {
... ... @@ -117,18 +128,31 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
117 128 }
118 129  
119 130 @Override
  131 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#pageReqVO.contactId", level = CrmPermissionLevelEnum.READ)
  132 + public PageResult<CrmBusinessDO> getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO) {
  133 + // 1. 查询关联的商机编号
  134 + List<CrmContactBusinessDO> contactBusinessList = contactBusinessService.getContactBusinessListByContactId(
  135 + pageReqVO.getContactId());
  136 + if (CollUtil.isEmpty(contactBusinessList)) {
  137 + return PageResult.empty();
  138 + }
  139 + // 2. 查询商机分页
  140 + return businessMapper.selectPageByContactId(pageReqVO,
  141 + convertSet(contactBusinessList, CrmContactBusinessDO::getBusinessId));
  142 + }
  143 +
  144 + @Override
120 145 @Transactional(rollbackFor = Exception.class)
  146 + // TODO @puhui999:操作日志
121 147 public void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId) {
122 148 // 1 校验商机是否存在
123 149 validateBusinessExists(reqVO.getId());
124 150  
125 151 // 2.1 数据权限转移
126   - crmPermissionService.transferPermission(
  152 + permissionService.transferPermission(
127 153 CrmBusinessConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()));
128 154 // 2.2 设置新的负责人
129 155 businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
130   -
131   - // 3. TODO 记录转移日志
132 156 }
133 157  
134 158 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java
... ... @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusine
7 7 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
8 8  
9 9 import jakarta.validation.Valid;
  10 +
  11 +import java.util.Collection;
10 12 import java.util.List;
11 13  
12 14 /**
... ... @@ -64,4 +66,12 @@ public interface CrmBusinessStatusService {
64 66 */
65 67 List<CrmBusinessStatusDO> selectList(CrmBusinessStatusQueryVO queryVO);
66 68  
  69 + /**
  70 + * 获得商机状态列表
  71 + *
  72 + * @param ids 编号数组
  73 + * @return 商机状态列表
  74 + */
  75 + List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids);
  76 +
67 77 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java
... ... @@ -12,6 +12,7 @@ import org.springframework.validation.annotation.Validated;
12 12  
13 13 import jakarta.annotation.Resource;
14 14  
  15 +import java.util.Collection;
15 16 import java.util.List;
16 17  
17 18 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -77,4 +78,9 @@ public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService {
77 78 return businessStatusMapper.selectList(queryVO);
78 79 }
79 80  
  81 + @Override
  82 + public List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids) {
  83 + return businessStatusMapper.selectBatchIds(ids);
  84 + }
  85 +
80 86 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java
... ... @@ -5,8 +5,9 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusiness
5 5 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
6 6 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;
7 7 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
8   -
9 8 import jakarta.validation.Valid;
  9 +
  10 +import java.util.Collection;
10 11 import java.util.List;
11 12  
12 13 /**
... ... @@ -63,4 +64,12 @@ public interface CrmBusinessStatusTypeService {
63 64 */
64 65 List<CrmBusinessStatusTypeDO> selectList(CrmBusinessStatusTypeQueryVO queryVO);
65 66  
  67 + /**
  68 + * 获得商机状态类型列表
  69 + *
  70 + * @param ids 编号数组
  71 + * @return 商机状态类型列表
  72 + */
  73 + List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids);
  74 +
66 75 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java
... ... @@ -17,6 +17,8 @@ import org.springframework.transaction.annotation.Transactional;
17 17 import org.springframework.validation.annotation.Validated;
18 18  
19 19 import jakarta.annotation.Resource;
  20 +
  21 +import java.util.Collection;
20 22 import java.util.List;
21 23  
22 24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -118,4 +120,9 @@ public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeSe
118 120 return businessStatusTypeMapper.selectList(queryVO);
119 121 }
120 122  
  123 + @Override
  124 + public List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids) {
  125 + return businessStatusTypeMapper.selectBatchIds(ids);
  126 + }
  127 +
121 128 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/package-info.java deleted
1   -/**
2   - * 商机(销售机会)
3   - */
4   -package cn.iocoder.yudao.module.crm.service.business;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
... ... @@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
12 12 import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
13 13 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
14 14 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
15   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  15 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
16 16 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
17 17 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
18 18 import jakarta.annotation.Resource;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/package-info.java deleted
1   -/**
2   - * 线索
3   - */
4   -package cn.iocoder.yudao.module.crm.service.clue;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.contact;
  2 +
  3 +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
  4 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
  5 +import jakarta.validation.Valid;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * CRM 联系人与商机的关联 Service 接口
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public interface CrmContactBusinessService {
  15 +
  16 + /**
  17 + * 创建联系人与商机的关联
  18 + *
  19 + * @param createReqVO 创建信息
  20 + */
  21 + void createContactBusinessList(@Valid CrmContactBusinessReqVO createReqVO);
  22 +
  23 + /**
  24 + * 删除联系人与商机的关联
  25 + *
  26 + * @param deleteReqVO 删除信息
  27 + */
  28 + void deleteContactBusinessList(@Valid CrmContactBusinessReqVO deleteReqVO);
  29 +
  30 + /**
  31 + * 获得联系人与商机的关联列表,基于联系人编号
  32 + *
  33 + * @param contactId 联系人编号
  34 + * @return 联系人商机关联
  35 + */
  36 + List<CrmContactBusinessDO> getContactBusinessListByContactId(Long contactId);
  37 +
  38 +}
0 39 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.contact;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
  5 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  6 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
  7 +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
  8 +import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper;
  9 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
  10 +import jakarta.annotation.Resource;
  11 +import org.springframework.context.annotation.Lazy;
  12 +import org.springframework.stereotype.Service;
  13 +import org.springframework.validation.annotation.Validated;
  14 +
  15 +import java.util.ArrayList;
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  19 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
  20 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS;
  21 +
  22 +// TODO @puhui999:数据权限的校验;每个操作;
  23 +/**
  24 + * 联系人与商机的关联 Service 实现类
  25 + *
  26 + * @author 芋道源码
  27 + */
  28 +@Service
  29 +@Validated
  30 +public class CrmContactBusinessServiceImpl implements CrmContactBusinessService {
  31 +
  32 + @Resource
  33 + private CrmContactBusinessMapper contactBusinessMapper;
  34 +
  35 + @Resource
  36 + @Lazy // 延迟加载,为了解决延迟加载
  37 + private CrmBusinessService businessService;
  38 + @Resource
  39 + @Lazy // 延迟加载,为了解决延迟加载
  40 + private CrmContactService contactService;
  41 +
  42 + @Override
  43 + public void createContactBusinessList(CrmContactBusinessReqVO createReqVO) {
  44 + CrmContactDO contact = contactService.getContact(createReqVO.getContactId());
  45 + if (contact == null) {
  46 + throw exception(CONTACT_NOT_EXISTS);
  47 + }
  48 + // 遍历处理,考虑到一般数量不会太多,代码处理简单
  49 + List<CrmContactBusinessDO> saveDOList = new ArrayList<>();
  50 + createReqVO.getBusinessIds().forEach(businessId -> {
  51 + CrmBusinessDO business = businessService.getBusiness(businessId);
  52 + if (business == null) {
  53 + throw exception(BUSINESS_NOT_EXISTS);
  54 + }
  55 + // 关联判重
  56 + if (contactBusinessMapper.selectByContactIdAndBusinessId(createReqVO.getContactId(), businessId) != null) {
  57 + return;
  58 + }
  59 + saveDOList.add(new CrmContactBusinessDO(null, createReqVO.getContactId(), businessId));
  60 + });
  61 + // 批量插入
  62 + if (CollUtil.isNotEmpty(saveDOList)) {
  63 + contactBusinessMapper.insertBatch(saveDOList);
  64 + }
  65 + }
  66 +
  67 + @Override
  68 + public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) {
  69 + CrmContactDO contact = contactService.getContact(deleteReqVO.getContactId());
  70 + if (contact == null) {
  71 + throw exception(CONTACT_NOT_EXISTS);
  72 + }
  73 + // 直接删除
  74 + contactBusinessMapper.deleteByContactIdAndBusinessId(
  75 + deleteReqVO.getContactId(), deleteReqVO.getBusinessIds());
  76 + }
  77 +
  78 + @Override
  79 + public List<CrmContactBusinessDO> getContactBusinessListByContactId(Long contactId) {
  80 + return contactBusinessMapper.selectListByContactId(contactId);
  81 + }
  82 +
  83 +}
0 84 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
... ... @@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
9 9 import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
10 10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 11 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
12   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  12 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
13 13 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
14 14 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
15 15 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
... ... @@ -43,33 +43,38 @@ public class CrmContactServiceImpl implements CrmContactService {
43 43 private CrmCustomerService customerService;
44 44 @Resource
45 45 private CrmPermissionService crmPermissionService;
46   -
47 46 @Resource
48 47 private AdminUserApi adminUserApi;
49 48  
50 49 @Override
51 50 @Transactional(rollbackFor = Exception.class)
  51 + // TODO @zyna:增加操作日志,可以参考 CustomerService;内容是 新建了联系人【名字】
52 52 public Long createContact(CrmContactCreateReqVO createReqVO, Long userId) {
53   - // 1.1 校验
  53 + // 1. 校验
54 54 validateRelationDataExists(createReqVO);
55   - // 1.2 插入
  55 +
  56 + // 2. 插入联系人
56 57 CrmContactDO contact = CrmContactConvert.INSTANCE.convert(createReqVO);
57 58 contactMapper.insert(contact);
58 59  
59   - // 2. 创建数据权限
  60 + // 3. 创建数据权限
60 61 crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId)
61 62 .setBizType(CrmBizTypeEnum.CRM_CONTACT.getType()).setBizId(contact.getId())
62 63 .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
  64 +
  65 + // TODO @zyna:特殊逻辑:如果在【商机】详情那,点击【新增联系人】时,可以自动绑定商机
63 66 return contact.getId();
64 67 }
65 68  
66 69 @Override
67 70 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
  71 + // TODO @zyna:增加操作日志,可以参考 CustomerService;需要 diff 出字段
68 72 public void updateContact(CrmContactUpdateReqVO updateReqVO) {
69 73 // 1. 校验存在
70 74 validateContactExists(updateReqVO.getId());
71 75 validateRelationDataExists(updateReqVO);
72   - // 2. 更新
  76 +
  77 + // 2. 更新联系人
73 78 CrmContactDO updateObj = CrmContactConvert.INSTANCE.convert(updateReqVO);
74 79 contactMapper.updateById(updateObj);
75 80 }
... ... @@ -99,10 +104,15 @@ public class CrmContactServiceImpl implements CrmContactService {
99 104 public void deleteContact(Long id) {
100 105 // 校验存在
101 106 validateContactExists(id);
  107 + // TODO @zyna:如果有关联的合同,不允许删除;Contract.contactId
  108 +
102 109 // 删除
103 110 contactMapper.deleteById(id);
104 111 // 删除数据权限
105 112 crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
  113 + // TODO @zyna:删除商机联系人关联
  114 +
  115 + // TODO @puhui999:删除跟进记录
106 116 }
107 117  
108 118 private void validateContactExists(Long id) {
... ... @@ -137,6 +147,8 @@ public class CrmContactServiceImpl implements CrmContactService {
137 147 }
138 148  
139 149 @Override
  150 + // TODO @puhui999:权限校验
  151 + // TODO @puhui999:记录操作日志;将联系人【名字】转移给【新负责人】
140 152 public void transferContact(CrmContactTransferReqVO reqVO, Long userId) {
141 153 // 1 校验联系人是否存在
142 154 validateContactExists(reqVO.getId());
... ... @@ -150,4 +162,4 @@ public class CrmContactServiceImpl implements CrmContactService {
150 162 // 3. TODO 记录转移日志
151 163 }
152 164  
153 165 -}
  166 +}
154 167 \ No newline at end of file
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkService.java deleted
1   -package cn.iocoder.yudao.module.crm.service.contactbusinesslink;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
5   -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
6   -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
7   -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
8   -
9   -import jakarta.validation.Valid;
10   -import java.util.List;
11   -
12   -/**
13   - * CRM 联系人商机关联 Service 接口
14   - *
15   - * @author 芋道源码
16   - */
17   -public interface CrmContactBusinessLinkService {
18   -
19   - /**
20   - * 创建联系人商机关联
21   - *
22   - * @param createReqVO 创建信息
23   - * @return 编号
24   - */
25   - Long createContactBusinessLink(@Valid CrmContactBusinessLinkSaveReqVO createReqVO);
26   -
27   - /**
28   - * 创建联系人商机关联
29   - *
30   - * @param createReqVO 创建信息
31   - */
32   - void createContactBusinessLinkBatch(@Valid List<CrmContactBusinessLinkSaveReqVO> createReqVO);
33   -
34   - /**
35   - * 更新联系人商机关联
36   - *
37   - * @param updateReqVO 更新信息
38   - */
39   - void updateContactBusinessLink(@Valid CrmContactBusinessLinkSaveReqVO updateReqVO);
40   -
41   - /**
42   - * 删除联系人商机关联
43   - *
44   - * @param createReqVO 删除列表
45   - */
46   - void deleteContactBusinessLink(@Valid List<CrmContactBusinessLinkSaveReqVO> createReqVO);
47   -
48   - /**
49   - * 获得联系人商机关联
50   - *
51   - * @param id 编号
52   - * @return 联系人商机关联
53   - */
54   - CrmContactBusinessLinkDO getContactBusinessLink(Long id);
55   -
56   - /**
57   - * 获得联系人商机关联分页
58   - *
59   - * @param pageReqVO 编号
60   - * @return 联系人商机关联
61   - */
62   - PageResult<CrmBusinessRespVO> getContactBusinessLinkPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO);
63   -
64   - /**
65   - * 获得联系人商机关联分页
66   - *
67   - * @param pageReqVO 分页查询
68   - * @return 联系人商机关联分页
69   - */
70   - PageResult<CrmContactBusinessLinkDO> getContactBusinessLinkPage(CrmContactBusinessLinkPageReqVO pageReqVO);
71   -
72   -}
73 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.crm.service.contactbusinesslink;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7   -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
8   -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
9   -import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
10   -import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
11   -import cn.iocoder.yudao.module.crm.convert.contactbusinessslink.CrmContactBusinessLinkConvert;
12   -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
13   -import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
14   -import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessLinkMapper;
15   -import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
16   -import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
17   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
18   -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
19   -import jakarta.annotation.Resource;
20   -import org.springframework.stereotype.Service;
21   -import org.springframework.validation.annotation.Validated;
22   -
23   -import java.util.List;
24   -
25   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
26   -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
27   -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_BUSINESS_LINK_NOT_EXISTS;
28   -
29   -// TODO @puhui999:数据权限的校验;每个操作;
30   -/**
31   - * 联系人商机关联 Service 实现类
32   - *
33   - * @author 芋道源码
34   - */
35   -@Service
36   -@Validated
37   -public class CrmContactBusinessLinkServiceImpl implements CrmContactBusinessLinkService {
38   -
39   - @Resource
40   - private CrmContactBusinessLinkMapper contactBusinessLinkMapper;
41   - @Resource
42   - private CrmBusinessService crmBusinessService;
43   -
44   - @Override
45   - public Long createContactBusinessLink(CrmContactBusinessLinkSaveReqVO createReqVO) {
46   - CrmContactBusinessLinkDO contactBusinessLink = BeanUtils.toBean(createReqVO, CrmContactBusinessLinkDO.class);
47   - contactBusinessLinkMapper.insert(contactBusinessLink);
48   - return contactBusinessLink.getId();
49   - }
50   -
51   - @Override
52   - public void createContactBusinessLinkBatch(List<CrmContactBusinessLinkSaveReqVO> createReqVOList) {
53   - // 插入
54   - // TODO @zyna:如果已经关联过,不用重复插入;
55   - // TODO @zyna:contact 和 business 存在校验,挪到这里,Controller 不用 @Transactional 注解,添加到这里哈。尽量业务都在 Service;
56   - List<CrmContactBusinessLinkDO> saveDoList = CrmContactBusinessLinkConvert.INSTANCE.convert(createReqVOList);
57   - contactBusinessLinkMapper.insertBatch(saveDoList);
58   - }
59   -
60   - @Override
61   - @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
62   - public void updateContactBusinessLink(CrmContactBusinessLinkSaveReqVO updateReqVO) {
63   - // 校验存在
64   - validateContactBusinessLinkExists(updateReqVO.getId());
65   - // 更新
66   - CrmContactBusinessLinkDO updateObj = BeanUtils.toBean(updateReqVO, CrmContactBusinessLinkDO.class);
67   - contactBusinessLinkMapper.updateById(updateObj);
68   - }
69   -
70   - @Override
71   - public void deleteContactBusinessLink(List<CrmContactBusinessLinkSaveReqVO> createReqVO) {
72   - // 删除
73   - createReqVO.forEach(item -> {
74   - contactBusinessLinkMapper.delete(new LambdaQueryWrapperX<CrmContactBusinessLinkDO>()
75   - .eq(CrmContactBusinessLinkDO::getBusinessId,item.getBusinessId())
76   - .eq(CrmContactBusinessLinkDO::getContactId,item.getContactId())
77   - .eq(CrmContactBusinessLinkDO::getDeleted,0));
78   - });
79   - }
80   -
81   - private void validateContactBusinessLinkExists(Long id) {
82   - if (contactBusinessLinkMapper.selectById(id) == null) {
83   - throw exception(CONTACT_BUSINESS_LINK_NOT_EXISTS);
84   - }
85   - }
86   -
87   - @Override
88   - @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.READ)
89   - public CrmContactBusinessLinkDO getContactBusinessLink(Long id) {
90   - return contactBusinessLinkMapper.selectById(id);
91   - }
92   -
93   - @Override
94   - public PageResult<CrmBusinessRespVO> getContactBusinessLinkPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO) {
95   - CrmContactBusinessLinkPageReqVO crmContactBusinessLinkPageReqVO = new CrmContactBusinessLinkPageReqVO();
96   - crmContactBusinessLinkPageReqVO.setContactId(pageReqVO.getContactId());
97   - PageResult<CrmContactBusinessLinkDO> businessLinkDOS = contactBusinessLinkMapper.selectPageByContact(crmContactBusinessLinkPageReqVO);
98   - List<CrmBusinessDO> businessDOS = crmBusinessService.getBusinessList(CollectionUtils.convertList(businessLinkDOS.getList(),
99   - CrmContactBusinessLinkDO::getBusinessId), getLoginUserId());
100   - PageResult<CrmBusinessRespVO> pageResult = new PageResult<CrmBusinessRespVO>();
101   - pageResult.setList(CrmBusinessConvert.INSTANCE.convert(businessDOS));
102   - pageResult.setTotal(businessLinkDOS.getTotal());
103   - return pageResult;
104   -
105   - }
106   -
107   - @Override
108   - public PageResult<CrmContactBusinessLinkDO> getContactBusinessLinkPage(CrmContactBusinessLinkPageReqVO pageReqVO) {
109   - return contactBusinessLinkMapper.selectPage(pageReqVO);
110   - }
111   -
112   -}
113 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
... ... @@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
12 12 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
13 13 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
14 14 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
15   -import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
  15 +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
16 16 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
17 17 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
18 18 import jakarta.annotation.Resource;
... ... @@ -42,8 +42,10 @@ public class CrmContractServiceImpl implements CrmContractService {
42 42 private CrmPermissionService crmPermissionService;
43 43  
44 44 @Override
  45 + // TODO @puhui999:添加操作日志
45 46 public Long createContract(CrmContractCreateReqVO createReqVO, Long userId) {
46   - // 插入
  47 + // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO
  48 + // 插入合同
47 49 CrmContractDO contract = CrmContractConvert.INSTANCE.convert(createReqVO);
48 50 contractMapper.insert(contract);
49 51  
... ... @@ -57,18 +59,26 @@ public class CrmContractServiceImpl implements CrmContractService {
57 59 @Override
58 60 @Transactional(rollbackFor = Exception.class)
59 61 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
  62 + // TODO @puhui999:添加操作日志
60 63 public void updateContract(CrmContractUpdateReqVO updateReqVO) {
  64 + // TODO @合同待定:只有草稿、审批中,可以编辑;
61 65 // 校验存在
62 66 validateContractExists(updateReqVO.getId());
63   - // 更新
  67 + // 更新合同
64 68 CrmContractDO updateObj = CrmContractConvert.INSTANCE.convert(updateReqVO);
65 69 contractMapper.updateById(updateObj);
  70 + // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO
66 71 }
67 72  
  73 + // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
  74 +
  75 + // TODO @合同待定:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum
  76 +
68 77 @Override
69 78 @Transactional(rollbackFor = Exception.class)
70 79 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
71 80 public void deleteContract(Long id) {
  81 + // TODO @合同待定:如果被 CrmReceivableDO 所使用,则不允许删除
72 82 // 校验存在
73 83 validateContractExists(id);
74 84 // 删除
... ... @@ -112,6 +122,8 @@ public class CrmContractServiceImpl implements CrmContractService {
112 122  
113 123 @Override
114 124 @Transactional(rollbackFor = Exception.class)
  125 + // 3. TODO @puhui999:记录转移日志
  126 + // TODO @puhui999:权限校验,这里要搞哇?
115 127 public void transferContract(CrmContractTransferReqVO reqVO, Long userId) {
116 128 // 1. 校验合同是否存在
117 129 validateContractExists(reqVO.getId());
... ... @@ -121,9 +133,7 @@ public class CrmContractServiceImpl implements CrmContractService {
121 133 CrmContractConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()));
122 134 // 2.2 设置负责人
123 135 contractMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
124   -
125   - // 3. TODO 记录转移日志
126   -
127 136 }
128 137  
  138 + // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒;
129 139 }
... ...