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
This commit is contained in:
owen 2024-01-05 15:40:29 +08:00
commit 9203f485e9
206 changed files with 2100 additions and 1493 deletions

View File

@ -31,7 +31,7 @@
![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
* Java 后端:`master` 分支为 JDK 21 + Spring Boot 3.2.0`master-jdk8` 分支为 JDK8 + Spring Boot 2.7.18
* Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7.18`master-jdk21` 分支为 JDK21 + Spring Boot 3.2.0
* 管理后台的电脑端Vue3 提供 `element-plus`、`vben(ant-design-vue)` 两个版本Vue2 提供 `element-ui` 版本
* 管理后台的移动端:采用 `uni-app` 方案,一份代码多终端适配,同时支持 APP、小程序、H5
* 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
@ -78,15 +78,21 @@
【完整版】包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM 等功能
* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro>`master` 分支
* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro>`master-jdk8` 分支
* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro>`master` 分支
* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro>`master-jdk21` 分支
两个分支的功能是一致的,可以放心使用!
### ➡️️ 精简版
【精简版】只包括系统功能、基础设施功能不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM 等功能
* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/yudaocode/yudao-boot-mini>`master` 分支
* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/yudaocode/yudao-boot-mini>`master-jdk8` 分支
* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/yudaocode/yudao-boot-mini>`master` 分支
* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/yudaocode/yudao-boot-mini>`master-jdk21` 分支
如果你想把【完整版】的功能,迁移到【精简版】,可以参考 [《迁移功能到精简版》](https://doc.iocoder.cn/migrate-module/) 文档。
如果你想把【完整版】的功能,迁移到【精简版】,可以参考 [《迁移功能到精简版》](https://doc.iocoder.cn/migrate-module/) 文档。
## 😎 开源协议

View File

@ -1,6 +1,6 @@
-- `ruoyi-vue-pro`.crm_contact_business_link definition
CREATE TABLE `crm_contact_business_link` (
CREATE TABLE `crm_contact_business` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`contact_id` int(11) DEFAULT NULL COMMENT '联系人id',
`business_id` int(11) DEFAULT NULL COMMENT '商机id',
@ -10,6 +10,5 @@ CREATE TABLE `crm_contact_business_link` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '租户编号',
PRIMARY KEY (`id`),
UNIQUE KEY `crm_contact_business_link_un` (`contact_id`,`business_id`,`deleted`,`tenant_id`)
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='联系人商机关联表';

View File

@ -26,7 +26,7 @@
<mybatis-plus.version>3.5.4.1</mybatis-plus.version>
<mybatis-plus-generator.version>3.5.4.1</mybatis-plus-generator.version>
<dynamic-datasource.version>4.2.0</dynamic-datasource.version>
<mybatis-plus-join.version>1.4.7.2</mybatis-plus-join.version>
<mybatis-plus-join.version>1.4.8.1</mybatis-plus-join.version>
<redisson.version>3.25.0</redisson.version>
<dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
<!-- 消息队列 -->
@ -51,7 +51,7 @@
<mapstruct.version>1.5.5.Final</mapstruct.version>
<hutool-5.version>5.8.23</hutool-5.version>
<hutool-6.version>6.0.0-M8</hutool-6.version>
<easyexcel.verion>3.3.2</easyexcel.verion>
<easyexcel.verion>3.3.3</easyexcel.verion>
<velocity.version>2.3</velocity.version>
<screw.version>1.0.5</screw.version>
<fastjson.version>1.2.83</fastjson.version>
@ -72,7 +72,7 @@
<aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
<tencentcloud-sdk-java.version>3.1.880</tencentcloud-sdk-java.version>
<justauth.version>2.0.5</justauth.version>
<jimureport.version>1.6.1</jimureport.version>
<jimureport.version>1.6.6-beta2</jimureport.version>
<xercesImpl.version>2.12.2</xercesImpl.version>
<weixin-java.version>4.5.7.B</weixin-java.version>
<ureport2.version>2.2.9</ureport2.version>
@ -651,7 +651,7 @@
<!-- 积木报表-->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot-starter</artifactId>
<artifactId>jimureport-spring-boot3-starter</artifactId>
<version>${jimureport.version}</version>
<exclusions>
<exclusion>

View File

@ -15,6 +15,7 @@ import java.util.Arrays;
@Getter
public enum TerminalEnum implements IntArrayValuable {
UNKNOWN(0, "未知"), // 目的在无法解析到 terminal 使用它
WECHAT_MINI_PROGRAM(10, "微信小程序"),
WECHAT_WAP(11, "微信公众号"),
H5(20, "H5 网页"),

View File

@ -1,10 +1,12 @@
package cn.iocoder.yudao.framework.common.util.cache;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.time.Duration;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
@ -15,11 +17,13 @@ import java.util.concurrent.Executors;
public class CacheUtils {
public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
Executor executor = Executors.newCachedThreadPool( // TODO 芋艿可能要思考下未来要不要做成可配置
TtlExecutors.getDefaultDisableInheritableThreadFactory()); // TTL 保证 ThreadLocal 可以透传
return CacheBuilder.newBuilder()
// 只阻塞当前数据加载线程其他线程返回旧值
.refreshAfterWrite(duration)
// 通过 asyncReloading 实现全异步加载包括 refreshAfterWrite 被阻塞的加载线程
.build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO 芋艿可能要思考下未来要不要做成可配置
.build(CacheLoader.asyncReloading(loader, executor));
}
}

View File

@ -88,8 +88,6 @@ public class ServletUtils {
return JakartaServletUtil.getClientIP(request);
}
// TODO @疯狂terminal 还是从 ServletUtils 里拿更容易全局治理
public static boolean isJsonRequest(ServletRequest request) {
return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);
}

View File

@ -490,6 +490,37 @@ id,name,type,parentId
441700,阳江市,3,440000
441800,清远市,3,440000
441900,东莞市,3,440000
441901,莞城区,4,441900
441902,南城区,4,441900
441904,万江区,4,441900
441905,石碣镇,4,441900
441906,石龙镇,4,441900
441907,茶山镇,4,441900
441908,石排镇,4,441900
441909,企石镇,4,441900
441910,横沥镇,4,441900
441911,桥头镇,4,441900
441912,谢岗镇,4,441900
441913,东坑镇,4,441900
441914,常平镇,4,441900
441915,寮步镇,4,441900
441916,大朗镇,4,441900
441917,麻涌镇,4,441900
441918,中堂镇,4,441900
441919,高埗镇,4,441900
441920,樟木头镇,4,441900
441921,大岭山镇,4,441900
441922,望牛墩镇,4,441900
441923,黄江镇,4,441900
441924,洪梅镇,4,441900
441925,清溪镇,4,441900
441926,沙田镇,4,441900
441927,道滘镇,4,441900
441928,塘厦镇,4,441900
441929,虎门镇,4,441900
441930,厚街镇,4,441900
441931,凤岗镇,4,441900
441932,长安镇,4,441900
442000,中山市,3,440000
445100,潮州市,3,440000
445200,揭阳市,3,440000
@ -3605,4 +3636,4 @@ id,name,type,parentId
659008,可克达拉市,4,659000
659009,昆玉市,4,659000
659010,胡杨河市,4,659000
659011,新星市,4,659000
659011,新星市,4,659000
1 id name type parentId
490 441700 阳江市 3 440000
491 441800 清远市 3 440000
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
524 442000 中山市 3 440000
525 445100 潮州市 3 440000
526 445200 揭阳市 3 440000
3636 659008 可克达拉市 4 659000
3637 659009 昆玉市 4 659000
3638 659010 胡杨河市 4 659000
3639 659011 新星市 4 659000

View File

@ -46,6 +46,7 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -71,6 +71,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
}
// payConfig.setCertSerialNo();
// 创建 client 客户端
client = new WxPayServiceImpl();

View File

@ -41,6 +41,7 @@ public abstract class AbstractSmsClient implements SmsClient {
return;
}
log.info("[refresh][配置({})发生变化,重新初始化]", properties);
this.properties = properties;
// 初始化
this.init();
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.tenant.core.context;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
import com.alibaba.ttl.TransmittableThreadLocal;
@ -21,7 +22,7 @@ public class TenantContextHolder {
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
/**
* 获得租户编号
* 获得租户编号
*
* @return 租户编号
*/
@ -29,6 +30,16 @@ public class TenantContextHolder {
return TENANT_ID.get();
}
/**
* 获得租户编号 String
*
* @return 租户编号
*/
public static String getTenantIdStr() {
Long tenantId = getTenantId();
return StrUtil.toStringOrNull(tenantId);
}
/**
* 获得租户编号如果不存在则抛出 NullPointerException 异常
*

View File

@ -1,12 +1,14 @@
package cn.iocoder.yudao.framework.excel.core.util;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
@ -33,9 +35,10 @@ public class ExcelUtils {
EasyExcel.write(response.getOutputStream(), head)
.autoCloseStream(false) // 不要自动关闭交给 Servlet 自己处理
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度自动适配最大 255 宽度
.registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
.sheet(sheetName).doWrite(data);
// 设置 header contentType写在最后的原因是避免报错时响应 contentType 已经被修改了
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
}

View File

@ -81,7 +81,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
}
// 腾讯云必须有 region否则会报错
if (config.getEndpoint().contains(ENDPOINT_TENCENT)) {
return StrUtil.subAfter(config.getEndpoint(), ".cos.", false)
return StrUtil.subAfter(config.getEndpoint(), "cos.", false)
.replaceAll("." + ENDPOINT_TENCENT, ""); // 去除 Endpoint
}
return null;

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.flowable.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.flowable.core.web.FlowableWebFilter;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.AsyncListenableTaskExecutor;
@ -17,6 +18,7 @@ public class YudaoFlowableConfiguration {
* 如果不创建会导致项目启动时Flowable 报错的问题
*/
@Bean
@ConditionalOnMissingBean
public AsyncListenableTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
@ -40,4 +42,5 @@ public class YudaoFlowableConfiguration {
registrationBean.setOrder(WebFilterOrderEnum.FLOWABLE_FILTER);
return registrationBean;
}
}

View File

@ -65,6 +65,12 @@
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>

View File

@ -146,8 +146,8 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
*
* @param entities 实体们
*/
default void insertBatch(Collection<T> entities) {
Db.saveBatch(entities);
default Boolean insertBatch(Collection<T> entities) {
return Db.saveBatch(entities);
}
/**
@ -156,28 +156,28 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
* @param entities 实体们
* @param size 插入数量 Db.saveBatch 默认为 1000
*/
default void insertBatch(Collection<T> entities, int size) {
Db.saveBatch(entities, size);
default Boolean insertBatch(Collection<T> entities, int size) {
return Db.saveBatch(entities, size);
}
default void updateBatch(T update) {
update(update, new QueryWrapper<>());
default int updateBatch(T update) {
return update(update, new QueryWrapper<>());
}
default void updateBatch(Collection<T> entities) {
Db.updateBatchById(entities);
default Boolean updateBatch(Collection<T> entities) {
return Db.updateBatchById(entities);
}
default void updateBatch(Collection<T> entities, int size) {
Db.updateBatchById(entities, size);
default Boolean updateBatch(Collection<T> entities, int size) {
return Db.updateBatchById(entities, size);
}
default void insertOrUpdate(T entity) {
Db.saveOrUpdate(entity);
default Boolean insertOrUpdate(T entity) {
return Db.saveOrUpdate(entity);
}
default void insertOrUpdateBatch(Collection<T> collection) {
Db.saveOrUpdateBatch(collection);
default Boolean insertOrUpdateBatch(Collection<T> collection) {
return Db.saveOrUpdateBatch(collection);
}
default int delete(String field, String value) {

View File

@ -12,7 +12,10 @@
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>用户的认证、权限的校验</description>
<description>
1. security用户的认证、权限的校验实现「谁」可以做「什么事」
2. operatelog操作日志实现「谁」在「什么时间」对「什么」做了「什么事」
</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
@ -50,6 +53,13 @@
<artifactId>guava</artifactId>
</dependency>
<dependency>
<!-- Spring Boot 通用操作日志组件,基于注解实现 -->
<!-- 此组件解决的问题是:「谁」在「什么时间」对「什么」做了「什么事」 -->
<groupId>io.github.mouzt</groupId>
<artifactId>bizlog-sdk</artifactId>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.framework.operatelog.config;
import cn.iocoder.yudao.framework.operatelog.core.service.ILogRecordServiceImpl;
import com.mzt.logapi.service.ILogRecordService;
import com.mzt.logapi.starter.annotation.EnableLogRecord;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* 操作日志配置类
*
* @author HUIHUI
*/
@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
@AutoConfiguration
@Slf4j
public class YudaoOperateLogV2Configuration {
@Bean
@Primary
public ILogRecordService iLogRecordServiceImpl() {
return new ILogRecordServiceImpl();
}
}

View File

@ -0,0 +1,4 @@
/**
* 占位无特殊作用
*/
package cn.iocoder.yudao.framework.operatelog.core;

View File

@ -0,0 +1,85 @@
package cn.iocoder.yudao.framework.operatelog.core.service;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2CreateReqDTO;
import com.mzt.logapi.beans.LogRecord;
import com.mzt.logapi.service.ILogRecordService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
// TODO @puhui999LogRecordServiceImpl 改成这个名字哈
/**
* 操作日志 ILogRecordService 实现类
*
* 基于 {@link OperateLogApi} 实现记录操作日志
*
* @author HUIHUI
*/
@Slf4j
public class ILogRecordServiceImpl implements ILogRecordService {
@Resource
private OperateLogApi operateLogApi;
@Override
public void record(LogRecord logRecord) {
// 1. 补全通用字段
OperateLogV2CreateReqDTO reqDTO = new OperateLogV2CreateReqDTO();
reqDTO.setTraceId(TracerUtils.getTraceId());
// 补充用户信息
fillUserFields(reqDTO);
// 补全模块信息
fillModuleFields(reqDTO, logRecord);
// 补全请求信息
fillRequestFields(reqDTO);
// 2. 异步记录日志
operateLogApi.createOperateLogV2(reqDTO);
// TODO 测试结束删除或搞个开关
log.info("操作日志 ===> {}", reqDTO);
}
private static void fillUserFields(OperateLogV2CreateReqDTO reqDTO) {
// TODO @puhui999使用 SecurityFrameworkUtils因为要考虑rpcmqjob它其实不是 web
reqDTO.setUserId(WebFrameworkUtils.getLoginUserId());
reqDTO.setUserType(WebFrameworkUtils.getLoginUserType());
}
public static void fillModuleFields(OperateLogV2CreateReqDTO reqDTO, LogRecord logRecord) {
reqDTO.setType(logRecord.getType()); // 大模块类型例如CRM 客户
reqDTO.setSubType(logRecord.getSubType());// 操作名称例如转移客户
reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 业务编号例如客户编号
reqDTO.setAction(logRecord.getAction());// 操作内容例如修改编号为 1 的用户信息将性别从男改成女将姓名从芋道改成源码
reqDTO.setExtra(logRecord.getExtra()); // 拓展字段有些复杂的业务需要记录一些字段 ( JSON 格式 )例如说记录订单编号{ orderId: "1"}
}
private static void fillRequestFields(OperateLogV2CreateReqDTO reqDTO) {
// 获得 Request 对象
HttpServletRequest request = ServletUtils.getRequest();
if (request == null) {
return;
}
// 补全请求信息
reqDTO.setRequestMethod(request.getMethod());
reqDTO.setRequestUrl(request.getRequestURI());
reqDTO.setUserIp(ServletUtils.getClientIP(request));
reqDTO.setUserAgent(ServletUtils.getUserAgent(request));
}
@Override
public List<LogRecord> queryLog(String bizNo, String type) {
throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询");
}
@Override
public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
throw new UnsupportedOperationException("使用 OperateLogApi 进行操作日志的查询");
}
}

View File

@ -0,0 +1,7 @@
/**
* 基于 mzt-log 框架
* 实现操作日志功能
*
* @author HUIHUI
*/
package cn.iocoder.yudao.framework.operatelog;

View File

@ -1,2 +1,3 @@
cn.iocoder.yudao.framework.security.config.YudaoSecurityAutoConfiguration
cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter
cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter
cn.iocoder.yudao.framework.operatelog.config.YudaoOperateLogV2Configuration

View File

@ -1,8 +1,11 @@
package cn.iocoder.yudao.framework.web.core.util;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
@ -25,6 +28,13 @@ public class WebFrameworkUtils {
public static final String HEADER_TENANT_ID = "tenant-id";
/**
* 终端的 Header
*
* @see cn.iocoder.yudao.framework.common.enums.TerminalEnum
*/
public static final String HEADER_TERMINAL = "terminal";
private static WebProperties properties;
public WebFrameworkUtils(WebProperties webProperties) {
@ -107,6 +117,15 @@ public class WebFrameworkUtils {
return getLoginUserId(request);
}
public static Integer getTerminal() {
HttpServletRequest request = getRequest();
if (request == null) {
return TerminalEnum.UNKNOWN.getTerminal();
}
String terminalValue = request.getHeader(HEADER_TERMINAL);
return NumberUtil.parseInt(terminalValue, TerminalEnum.UNKNOWN.getTerminal());
}
public static void setCommonResult(ServletRequest request, CommonResult<?> result) {
request.setAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT, result);
}

View File

@ -38,17 +38,6 @@
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<!-- 为什么是 websocket 依赖 security 呢?而不是 security 拓展 websocket 呢?
因为 websocket 和 LoginUser 当前登录的用户有一定的相关性,具体可见 WebSocketSessionManagerImpl 逻辑。
如果让 security 拓展 websocket 的话,会导致 websocket 组件的封装很散,进而增大理解成本。
-->
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
<scope>provided</scope>
</dependency>
<!-- 消息队列相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>

View File

@ -66,7 +66,7 @@ public class WebSocketSessionManagerImpl implements WebSocketSessionManager {
@Override
public void removeSession(WebSocketSession session) {
// 移除从 idSessions
idSessions.remove(session.getId(), session);
idSessions.remove(session.getId());
// 移除从 idSessions
LoginUser user = WebSocketFrameworkUtils.getLoginUser(session);
if (user == null) {

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
@ -71,7 +72,8 @@ public class BpmModelServiceImpl implements BpmModelService {
modelQuery.modelCategory(pageVO.getCategory());
}
// 执行查询
List<Model> models = modelQuery.orderByCreateTime().desc()
List<Model> models = modelQuery.modelTenantId(TenantContextHolder.getTenantIdStr())
.orderByCreateTime().desc()
.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
// 获得 Form Map
@ -107,6 +109,7 @@ public class BpmModelServiceImpl implements BpmModelService {
// 创建流程定义
Model model = repositoryService.newModel();
BpmModelConvert.INSTANCE.copy(model, createReqVO);
model.setTenantId(TenantContextHolder.getTenantIdStr());
// 保存流程定义
repositoryService.saveModel(model);
// 保存 BPMN XML

View File

@ -6,6 +6,8 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
@ -124,6 +126,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
Deployment deploy = repositoryService.createDeployment()
.key(createReqDTO.getKey()).name(createReqDTO.getName()).category(createReqDTO.getCategory())
.addBytes(createReqDTO.getKey() + BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes())
.tenantId(TenantContextHolder.getTenantIdStr())
.deploy();
// 设置 ProcessDefinition category 分类
@ -234,6 +237,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
definitionQuery.active();
}
// 执行查询
definitionQuery.processDefinitionTenantId(TenantContextHolder.getTenantIdStr());
List<ProcessDefinition> processDefinitions = definitionQuery.list();
if (CollUtil.isEmpty(processDefinitions)) {
return Collections.emptyList();

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;

View File

@ -39,6 +39,10 @@ public interface ErrorCodeConstants {
ErrorCode CUSTOMER_IN_POOL = new ErrorCode(1_020_006_004, "客户【{}】放入公海失败,原因:已经是公海客户");
ErrorCode CUSTOMER_LOCKED_PUT_POOL_FAIL = new ErrorCode(1_020_006_005, "客户【{}】放入公海失败,原因:客户已锁定");
ErrorCode CUSTOMER_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_006_006, "更新客户【{}】负责人失败, 原因:系统异常");
ErrorCode CUSTOMER_LOCK_FAIL_IS_LOCK = new ErrorCode(1_020_006_007, "锁定客户失败,它已经处于锁定状态");
ErrorCode CUSTOMER_UNLOCK_FAIL_IS_UNLOCK = new ErrorCode(1_020_006_008, "锁定客户失败,它已经处于未锁定状态");
ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限");
ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限");
// ========== 权限管理 1_020_007_000 ==========
ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.crm.enums;
// TODO 芋艿操作日志看看这个类怎么搞个好点的规范
/**
* CRM 操作日志枚举
*
@ -22,6 +21,13 @@ public interface LogRecordConstants {
//======================= 客户转移操作日志 =======================
String TRANSFER_CUSTOMER_LOG_SUCCESS = "把客户【{{#crmCustomer.name}}】的负责人从【{getAdminUserById{#crmCustomer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String TRANSFER_CUSTOMER_LOG_FAIL = ""; // TODO @puhui999这个可以删除哈一般不搞失败的日志
// TODO @puhui999这里格式是不是可以这样;目的是统一管理也减少 Service 里各种复杂字符串
// ======================= Customer 客户 =======================
String CUSTOMER_TYPE = "CRM 客户";
String CUSTOMER_CREATE_SUB_TYPE = "创建客户";
String CUSTOMER_CREATE_SUCCESS = "更新了客户{_DIFF{#updateReqVO}}";
String CUSTOMER_UPDATE_SUB_TYPE = "更新客户";
}

View File

@ -18,19 +18,19 @@ public enum CrmCustomerLimitConfigTypeEnum implements IntArrayValuable {
/**
* 拥有客户数限制
*/
CUSTOMER_QUANTITY_LIMIT(1, "拥有客户数限制"),
CUSTOMER_OWNER_LIMIT(1, "拥有客户数限制"),
/**
* 锁定客户数限制
*/
CUSTOMER_LOCK_LIMIT(2, "锁定客户数限制"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLimitConfigTypeEnum::getCode).toArray();
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLimitConfigTypeEnum::getType).toArray();
/**
* 状态
*/
private final Integer code;
private final Integer type;
/**
* 状态名
*/

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.crm.enums.permission;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Crm 数据权限角色枚举
*
* @author HUIHUI
*/
@Getter
@AllArgsConstructor
public enum CrmPermissionRoleCodeEnum {
CRM_ADMIN("crm_admin", "CRM 管理员");
/**
* 角色标识
*/
private String code;
/**
* 角色名称
*/
private String name;
}

View File

@ -7,8 +7,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
@ -30,12 +28,10 @@ import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@ -47,16 +43,14 @@ public class CrmBusinessController {
@Resource
private CrmBusinessService businessService;
@Resource
private CrmCustomerService customerService;
@Resource
private CrmBusinessStatusTypeService businessStatusTypeService;
@Resource
private CrmBusinessStatusService businessStatusService;
// TODO @商机待定CrmBusinessCreateReqVOCrmBusinessUpdateReqVOCrmBusinessRespVO 按照新的 VO 规范
@PostMapping("/create")
@Operation(summary = "创建商机")
@PreAuthorize("@ss.hasPermission('crm:business:create')")
@ -95,27 +89,7 @@ public class CrmBusinessController {
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) {
PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId());
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
}
// 处理客户名称回显
// TODO @ljlleo可以使用 CollectionUtils.convertSet 替代常用的 stream 操作更简洁一点下面几个也是哈
Set<Long> customerIds = pageResult.getList().stream()
.map(CrmBusinessDO::getCustomerId).filter(Objects::nonNull).collect(Collectors.toSet());
List<CrmCustomerDO> customerList = customerService.getCustomerList(customerIds, getLoginUserId());
// 处理商机状态类型名称回显
Set<Long> statusTypeIds = pageResult.getList().stream()
.map(CrmBusinessDO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet());
CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO();
queryStatusTypeVO.setIdList(statusTypeIds);
List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO);
// 处理商机状态名称回显
Set<Long> statusIds = pageResult.getList().stream()
.map(CrmBusinessDO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet());
CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
queryVO.setIdList(statusIds);
List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);
return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList));
return success(buildBusinessDetailPageResult(pageResult));
}
@GetMapping("/page-by-customer")
@ -123,24 +97,15 @@ public class CrmBusinessController {
public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByCustomer(@Valid CrmBusinessPageReqVO pageReqVO) {
Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO);
// 处理客户名称回显
// TODO @ljlleo可以使用 CollectionUtils.convertSet 替代常用的 stream 操作更简洁一点下面几个也是哈
Set<Long> customerIds = pageResult.getList().stream()
.map(CrmBusinessDO::getCustomerId).filter(Objects::nonNull).collect(Collectors.toSet());
List<CrmCustomerDO> customerList = customerService.getCustomerList(customerIds, getLoginUserId());
// 处理商机状态类型名称回显
Set<Long> statusTypeIds = pageResult.getList().stream()
.map(CrmBusinessDO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet());
CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO();
queryStatusTypeVO.setIdList(statusTypeIds);
List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO);
// 处理商机状态名称回显
Set<Long> statusIds = pageResult.getList().stream()
.map(CrmBusinessDO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet());
CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
queryVO.setIdList(statusIds);
List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);
return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList));
return success(buildBusinessDetailPageResult(pageResult));
}
@GetMapping("/page-by-contact")
@Operation(summary = "获得联系人的商机分页")
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) {
PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByContact(pageReqVO);
return success(buildBusinessDetailPageResult(pageResult));
}
@GetMapping("/export-excel")
@ -152,8 +117,27 @@ public class CrmBusinessController {
exportReqVO.setPageSize(PAGE_SIZE_NONE);
PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId());
// 导出 Excel
ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessExcelVO.class,
CrmBusinessConvert.INSTANCE.convertList02(pageResult.getList()));
ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class,
buildBusinessDetailPageResult(pageResult).getList());
}
/**
* 构建详细的商机分页结果
*
* @param pageResult 简单的商机分页结果
* @return 详细的商机分页结果
*/
private PageResult<CrmBusinessRespVO> buildBusinessDetailPageResult(PageResult<CrmBusinessDO> pageResult) {
if (CollUtil.isEmpty(pageResult.getList())) {
return PageResult.empty(pageResult.getTotal());
}
List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.getBusinessStatusTypeList(
convertSet(pageResult.getList(), CrmBusinessDO::getStatusTypeId));
List<CrmBusinessStatusDO> statusList = businessStatusService.getBusinessStatusList(
convertSet(pageResult.getList(), CrmBusinessDO::getStatusId));
List<CrmCustomerDO> customerList = customerService.getCustomerList(
convertSet(pageResult.getList(), CrmBusinessDO::getCustomerId));
return CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList);
}
@PutMapping("/transfer")

View File

@ -20,6 +20,9 @@ public class CrmBusinessPageReqVO extends PageParam {
@Schema(description = "客户编号", example = "10795")
private Long customerId;
@Schema(description = "联系人编号", example = "10795")
private Long contactId;
@Schema(description = "场景类型", example = "1")
@InEnum(CrmSceneTypeEnum.class)
private Integer sceneType; // 场景类型 null 时则表示全部

View File

@ -6,6 +6,7 @@ import cn.hutool.core.util.NumberUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@ -54,10 +56,13 @@ public class CrmContactController {
private CrmContactService contactService;
@Resource
private CrmCustomerService customerService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private CrmContactBusinessService contactBusinessLinkService;
// TODO @zynaCrmContactCreateReqVOCrmContactUpdateReqVOCrmContactRespVO 按照新的 VO 规范搞哈可以参考 dept 模块
@PostMapping("/create")
@Operation(summary = "创建联系人")
@PreAuthorize("@ss.hasPermission('crm:contact:create')")
@ -96,7 +101,7 @@ public class CrmContactController {
NumberUtil.parseLong(contact.getCreator()), contact.getOwnerUserId())));
// 2. 获取客户信息
List<CrmCustomerDO> customerList = customerService.getCustomerList(
Collections.singletonList(contact.getCustomerId()), getLoginUserId());
Collections.singletonList(contact.getCustomerId()));
// 3. 直属上级
List<CrmContactDO> parentContactList = contactService.getContactList(
Collections.singletonList(contact.getParentId()), getLoginUserId());
@ -107,10 +112,11 @@ public class CrmContactController {
@Operation(summary = "获得联系人列表")
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<List<CrmContactSimpleRespVO>> getSimpleContactList() {
// TODO @zyna建议 contactService 单独搞个 list 接口哈
CrmContactPageReqVO pageReqVO = new CrmContactPageReqVO();
pageReqVO.setPageSize(PAGE_SIZE_NONE);
List<CrmContactDO> list = contactService.getContactPage(pageReqVO, getLoginUserId()).getList();
return success(CrmContactConvert.INSTANCE.convertAllList(list));
return success(BeanUtils.toBean(list, CrmContactSimpleRespVO.class));
}
@GetMapping("/page")
@ -118,7 +124,7 @@ public class CrmContactController {
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<PageResult<CrmContactRespVO>> getContactPage(@Valid CrmContactPageReqVO pageVO) {
PageResult<CrmContactDO> pageResult = contactService.getContactPage(pageVO, getLoginUserId());
return success(convertDetailContactPage(pageResult));
return success(buildContactDetailPage(pageResult));
}
@GetMapping("/page-by-customer")
@ -126,7 +132,7 @@ public class CrmContactController {
public CommonResult<PageResult<CrmContactRespVO>> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) {
Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
PageResult<CrmContactDO> pageResult = contactService.getContactPageByCustomerId(pageVO);
return success(convertDetailContactPage(pageResult));
return success(buildContactDetailPage(pageResult));
}
@GetMapping("/export-excel")
@ -138,23 +144,23 @@ public class CrmContactController {
exportReqVO.setPageNo(PAGE_SIZE_NONE);
PageResult<CrmContactDO> pageResult = contactService.getContactPage(exportReqVO, getLoginUserId());
ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class,
convertDetailContactPage(pageResult).getList());
buildContactDetailPage(pageResult).getList());
}
/**
* 转换成详细的联系人分页即读取关联信息
* 构建详细的联系人分页结果
*
* @param pageResult 联系人分页
* @return 详细的联系人分页
* @param pageResult 简单的联系人分页结果
* @return 详细的联系人分页结果
*/
private PageResult<CrmContactRespVO> convertDetailContactPage(PageResult<CrmContactDO> pageResult) {
private PageResult<CrmContactRespVO> buildContactDetailPage(PageResult<CrmContactDO> pageResult) {
List<CrmContactDO> contactList = pageResult.getList();
if (CollUtil.isEmpty(contactList)) {
return PageResult.empty(pageResult.getTotal());
}
// 1. 获取客户列表
List<CrmCustomerDO> crmCustomerDOList = customerService.getCustomerList(
convertSet(contactList, CrmContactDO::getCustomerId), getLoginUserId());
convertSet(contactList, CrmContactDO::getCustomerId));
// 2. 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
@ -172,4 +178,22 @@ public class CrmContactController {
return success(true);
}
// ================== 关联/取关联系人 ===================
@PostMapping("/create-business-list")
@Operation(summary = "创建联系人与联系人的关联")
@PreAuthorize("@ss.hasPermission('crm:contact:create-business')")
public CommonResult<Boolean> createContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO createReqVO) {
contactBusinessLinkService.createContactBusinessList(createReqVO);
return success(true);
}
@DeleteMapping("/delete-business-list")
@Operation(summary = "删除联系人与联系人的关联")
@PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
public CommonResult<Boolean> deleteContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO deleteReqVO) {
contactBusinessLinkService.deleteContactBusinessList(deleteReqVO);
return success(true);
}
}

View File

@ -8,11 +8,11 @@ import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@ -75,29 +75,29 @@ public class CrmContactBaseVO {
@ExcelProperty(value = "邮箱",order = 4)
private String email;
@Schema(description = "地区编号", example = "20158")
private Integer areaId;
@ExcelProperty(value = "地址",order = 5)
@Schema(description = "地址")
private String address;
@Schema(description = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
@ExcelProperty(value = "下次联系时间",order = 6)
private LocalDateTime nextTime;
private String detailAddress;
@Schema(description = "备注", example = "你说的对")
@ExcelProperty(value = "备注",order = 6)
private String remark;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ExcelProperty(value = "最后跟进时间",order = 6)
private LocalDateTime lastTime;
@Schema(description = "负责人用户编号", example = "14334")
@NotNull(message = "负责人不能为空")
private Long ownerUserId;
@Schema(description = "地区编号", example = "20158")
private Integer areaId;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ExcelProperty(value = "最后跟进时间",order = 6)
private LocalDateTime contactLastTime;
@Schema(description = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
@ExcelProperty(value = "下次联系时间",order = 6)
private LocalDateTime contactNextTime;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 用于关联取消关联的操作
@Data
public class CrmContactBusinessReqVO {
@Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
@NotNull(message="联系人不能为空")
private Long contactId;
@Schema(description = "商机编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
@NotEmpty(message="商机不能为空")
private List<Long> businessIds;
}

View File

@ -1,115 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contactbusinesslink.CrmContactBusinessLinkService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
@Tag(name = "管理后台 - CRM 联系人商机关联")
@RestController
@RequestMapping("/crm/contact-business-link")
@Validated
public class CrmContactBusinessLinkController {
@Resource
private CrmContactBusinessLinkService contactBusinessLinkService;
@Resource
private CrmBusinessService crmBusinessService;
// TODO @zynacreateContactBusinessLink createContactBusinessLinkBatch 是不是合并成一个接口contactIdList<businessId>
@PostMapping("/create")
@Operation(summary = "创建联系人商机关联")
@PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')")
public CommonResult<Long> createContactBusinessLink(@Valid @RequestBody CrmContactBusinessLinkSaveReqVO createReqVO) {
return success(contactBusinessLinkService.createContactBusinessLink(createReqVO));
}
@PostMapping("/create-batch")
@Operation(summary = "创建联系人商机关联")
@PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')")
@Transactional(rollbackFor = Exception.class)
public CommonResult<Boolean> createContactBusinessLinkBatch(
@Valid @RequestBody List<CrmContactBusinessLinkSaveReqVO> createReqVO) {
createReqVO.stream().forEach(item -> {
CrmBusinessDO crmBusinessDO = crmBusinessService.getBusiness(item.getBusinessId());
if(crmBusinessDO == null){
throw exception(BUSINESS_NOT_EXISTS);
}
});
contactBusinessLinkService.createContactBusinessLinkBatch(createReqVO);
return success(true);
}
// TODO @zyna这个接口是不是可以删除掉了哈应该不存在更新
@PutMapping("/update")
@Operation(summary = "更新联系人商机关联")
@PreAuthorize("@ss.hasPermission('crm:contact-business-link:update')")
public CommonResult<Boolean> updateContactBusinessLink(@Valid @RequestBody CrmContactBusinessLinkSaveReqVO updateReqVO) {
contactBusinessLinkService.updateContactBusinessLink(updateReqVO);
return success(true);
}
// TODO @zyna删除是不是传递 ids
@DeleteMapping("/delete-batch")
@Operation(summary = "批量删除联系人商机关联")
@PreAuthorize("@ss.hasPermission('crm:contact-business-link:delete')")
public CommonResult<Boolean> deleteContactBusinessLinkBatch(@Valid @RequestBody List<CrmContactBusinessLinkSaveReqVO> deleteList) {
contactBusinessLinkService.deleteContactBusinessLink(deleteList);
return success(true);
}
// TODO @zyna这个接口是不是可以删除掉了哈应该不存在单个读取
@GetMapping("/get")
@Operation(summary = "获得联系人商机关联")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')")
public CommonResult<CrmContactBusinessLinkRespVO> getContactBusinessLink(@RequestParam("id") Long id) {
CrmContactBusinessLinkDO contactBusinessLink = contactBusinessLinkService.getContactBusinessLink(id);
return success(BeanUtils.toBean(contactBusinessLink, CrmContactBusinessLinkRespVO.class));
}
// TODO @zyna这个可以转化下使用客户编号去查询就是使用 CrmBusinessController getBusinessPageByCustomer 接口目的是复用
@GetMapping("/page-by-contact")
@Operation(summary = "获得联系人商机关联")
@PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')")
public CommonResult<PageResult<CrmBusinessRespVO>> getContactBusinessLinkByContact(
@Valid CrmContactBusinessLinkPageReqVO pageReqVO) {
PageResult<CrmBusinessRespVO> contactBusinessLink = contactBusinessLinkService.getContactBusinessLinkPageByContact(pageReqVO);
return success(contactBusinessLink);
}
// TODO @zyna这个优化下搞到 CrmBusinessController 里去加一个 CrmBusinessController getBusinessPageByContact 接口目的是
@GetMapping("/page")
@Operation(summary = "获得联系人商机关联分页")
@PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')")
public CommonResult<PageResult<CrmContactBusinessLinkRespVO>> getContactBusinessLinkPage(
@Valid CrmContactBusinessLinkPageReqVO pageReqVO) {
PageResult<CrmContactBusinessLinkDO> pageResult = contactBusinessLinkService.getContactBusinessLinkPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, CrmContactBusinessLinkRespVO.class));
}
// TODO @zyna最终梳理完后应该就 2 个接口要不直接合并到 CrmContactController 不作为独立模块就关联接触关联其实和 user 设置它有哪些岗位部门是类似的
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 联系人商机关联分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmContactBusinessLinkPageReqVO extends PageParam {
@Schema(description = "联系人编号", example = "20878")
private Long contactId;
@Schema(description = "商机编号", example = "7638")
private Long businessId;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - CRM 联系人商机关联 Response VO")
@Data
public class CrmContactBusinessLinkRespVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "17220")
@ExcelProperty("主键")
private Long id;
@Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
private Long contactId;
@Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
private Long businessId;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -1,23 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
@Schema(description = "管理后台 - CRM 联系人商机关联新增/修改 Request VO")
@Data
public class CrmContactBusinessLinkSaveReqVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "17220")
private Long id;
@Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
@NotNull(message="联系人不能为空")
private Long contactId;
@Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
@NotNull(message="商机不能为空")
private Long businessId;
}

View File

@ -88,7 +88,7 @@ public class CrmContractController {
@PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<PageResult<ContractRespVO>> getContractPage(@Valid CrmContractPageReqVO pageVO) {
PageResult<CrmContractDO> pageResult = contractService.getContractPage(pageVO, getLoginUserId());
return success(convertDetailContractPage(pageResult));
return success(buildContractDetailPage(pageResult));
}
@GetMapping("/page-by-customer")
@ -96,7 +96,7 @@ public class CrmContractController {
public CommonResult<PageResult<ContractRespVO>> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) {
Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageVO);
return success(convertDetailContractPage(pageResult));
return success(buildContractDetailPage(pageResult));
}
@GetMapping("/export-excel")
@ -112,19 +112,19 @@ public class CrmContractController {
}
/**
* 转换成详细的合同分页即读取关联信息
* 构建详细的合同分页结果
*
* @param pageResult 合同分页
* @return 详细的合同分页
* @param pageResult 简单的合同分页结果
* @return 详细的合同分页结果
*/
private PageResult<ContractRespVO> convertDetailContractPage(PageResult<CrmContractDO> pageResult) {
private PageResult<ContractRespVO> buildContractDetailPage(PageResult<CrmContractDO> pageResult) {
List<CrmContractDO> contactList = pageResult.getList();
if (CollUtil.isEmpty(contactList)) {
return PageResult.empty(pageResult.getTotal());
}
// 1. 获取客户列表
List<CrmCustomerDO> customerList = customerService.getCustomerList(
convertSet(contactList, CrmContractDO::getCustomerId), getLoginUserId());
convertSet(contactList, CrmContractDO::getCustomerId));
// 2. 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));

View File

@ -1,8 +1,8 @@
### 请求 /transfer
PUT {{baseUrl}}/crm/customer/transfer
Content-Type: application/json
Content-Type: application/-id: {{adminTenentId}}json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
tenant
{
"id": 10,

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
@ -12,6 +13,7 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@ -56,15 +58,18 @@ public class CrmCustomerController {
@Resource
private OperateLogApi operateLogApi;
// TODO @puhui999 CrmCustomerCreateReqVOCrmCustomerUpdateReqVOCrmCustomerRespVO 按照新的规范搞一下哈
@PostMapping("/create")
@Operation(summary = "创建客户")
@OperateLog(enable = false) // TODO 关闭原有日志记录@puhui999注解都先删除先记录没关系我们下个迭代就都删除掉操作日志了
@PreAuthorize("@ss.hasPermission('crm:customer:create')")
public CommonResult<Long> createCustomer(@Valid @RequestBody CrmCustomerCreateReqVO createReqVO) {
return success(customerService.createCustomer(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
//@Operation(summary = "更新客户")
@Operation(summary = "更新客户")
@OperateLog(enable = false) // TODO 关闭原有日志记录
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
customerService.updateCustomer(updateReqVO);
@ -73,6 +78,7 @@ public class CrmCustomerController {
@DeleteMapping("/delete")
@Operation(summary = "删除客户")
@OperateLog(enable = false) // TODO 关闭原有日志记录
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:customer:delete')")
public CommonResult<Boolean> deleteCustomer(@RequestParam("id") Long id) {
@ -97,6 +103,7 @@ public class CrmCustomerController {
return success(CrmCustomerConvert.INSTANCE.convert(customer, userMap, deptMap));
}
// TODO @puhui999这个查询会查出多个微信发你图了
@GetMapping("/page")
@Operation(summary = "获得客户分页")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
@ -108,6 +115,7 @@ public class CrmCustomerController {
}
// 2. 拼接数据
// TODO @puhui999距离进入公海的时间
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
@ -128,38 +136,30 @@ public class CrmCustomerController {
}
@PutMapping("/transfer")
//@Operation(summary = "客户转移")
@Operation(summary = "转移客户")
@OperateLog(enable = false) // TODO 关闭原有日志记录
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
customerService.transferCustomer(reqVO, getLoginUserId());
return success(true);
}
// TODO @puhui999operate-log-list 或者 operate-log-page 如果分页
@GetMapping("/operate-log")
// TODO @puhui999是不是接口只要传递 bizId Controller 自己组装出 OperateLogV2PageReqDTO
@GetMapping("/operate-log-page")
@Operation(summary = "获得客户操作日志")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
// TODO @puhui999最好有读权限方法名改成 getCustomerOperateLog
public CommonResult<List<OperateLogV2RespDTO>> getOperateLog(@RequestParam("id") Long id) {
// 1. 获取客户
// TODO @puhui999这个校验可以去掉哈
CrmCustomerDO customer = customerService.getCustomer(id);
if (customer == null) {
return success(null);
}
// 2. 获取操作日志
// TODO @puhui999操作日志返回可能要分页哈
return success(operateLogApi.getOperateLogByModuleAndBizId(CRM_CUSTOMER, id));
public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(CrmCustomerOperateLogPageReqVO reqVO) {
reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
reqVO.setBizType(CRM_CUSTOMER);
return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class)));
}
// TODO @Joey单独建一个属于自己业务的 ReqVO因为前端如果模拟请求是不是可以更新其它字段了
@PutMapping("/lock")
@Operation(summary = "锁定/解锁客户")
@OperateLog(enable = false) // TODO 关闭原有日志记录
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
customerService.lockCustomer(updateReqVO);
public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerLockReqVO lockReqVO) {
customerService.lockCustomer(lockReqVO, getLoginUserId());
return success(true);
}
@ -167,6 +167,7 @@ public class CrmCustomerController {
@PutMapping("/put-pool")
@Operation(summary = "数据放入公海")
@OperateLog(enable = false) // TODO 关闭原有日志记录
@Parameter(name = "id", description = "客户编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> putCustomerPool(@RequestParam("id") Long id) {
@ -183,6 +184,7 @@ public class CrmCustomerController {
return success(true);
}
// TODO @puhui999需要搞个 VO
@PutMapping("/distribute")
@Operation(summary = "分配公海给对应负责人")
@Parameters({
@ -192,7 +194,6 @@ public class CrmCustomerController {
@PreAuthorize("@ss.hasPermission('crm:customer:distribute')")
public CommonResult<Boolean> distributeCustomer(@RequestParam(value = "ids") List<Long> ids,
@RequestParam(value = "ownerUserId") Long ownerUserId) {
// 领取公海数据
customerService.receiveCustomer(ids, ownerUserId);
return success(true);
}

View File

@ -43,6 +43,7 @@ public class CrmCustomerLimitConfigController {
@Resource
private AdminUserApi adminUserApi;
// TODO @puhui999可以把 vo 改下哈
@PostMapping("/create")
@Operation(summary = "创建客户限制配置")
@PreAuthorize("@ss.hasPermission('crm:customer-limit-config:create')")

View File

@ -26,6 +26,7 @@ public class CrmCustomerPoolConfigController {
@Resource
private CrmCustomerPoolConfigService customerPoolConfigService;
// TODO @puhui999可以把 vo 改下哈
@GetMapping("/get")
@Operation(summary = "获取客户公海规则设置")
@PreAuthorize("@ss.hasPermission('crm:customer-pool-config:query')")

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 客户锁定/解锁 Request VO")
@Data
public class CrmCustomerLockReqVO {
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
private Long id;
@Schema(description = "客户锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Boolean lockStatus;
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - crm 客户操作日志分页 Request VO")
@Data
public class CrmCustomerOperateLogPageReqVO extends PageParam {
@Schema(description = "模块数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long bizId;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long userId;
@Schema(description = "模块类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String bizType;
}

View File

@ -28,4 +28,6 @@ public class CrmCustomerTransferReqVO {
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
// TODO @puhui999联系人商机合同的转移
}

View File

@ -16,9 +16,9 @@ import java.util.Objects;
@ToString(callSuper = true)
public class CrmCustomerPoolConfigSaveReqVO extends CrmCustomerPoolConfigBaseVO {
// TODO @wanwanAssertTrue 必须 is 开头哈注意需要 json 忽略下避免被序列化
// TODO @puhui999AssertTrue 必须 is 开头哈注意需要 json 忽略下避免被序列化
@AssertTrue(message = "客户公海规则设置不正确")
// TODO @wanwan这个方法是不是拆成 2 一个校验 contactExpireDays一个校验 dealExpireDays
// TODO @puhui999这个方法是不是拆成 2 一个校验 contactExpireDays一个校验 dealExpireDays
public boolean poolEnableValid() {
if (!BooleanUtil.isTrue(getEnabled())) {
return true;
@ -27,7 +27,7 @@ public class CrmCustomerPoolConfigSaveReqVO extends CrmCustomerPoolConfigBaseVO
}
@AssertTrue(message = "客户公海规则设置不正确")
// TODO @wanwan这个方法是不是改成 isNotifyDaysValid() 更好点本质校验的是 notifyDays 是否为空
// TODO @puhui999这个方法是不是改成 isNotifyDaysValid() 更好点本质校验的是 notifyDays 是否为空
public boolean notifyEnableValid() {
if (!BooleanUtil.isTrue(getNotifyEnabled())) {
return true;

View File

@ -8,8 +8,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionR
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
@ -21,12 +21,12 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -103,6 +103,7 @@ public class CrmPermissionController {
// 拼接数据
List<AdminUserRespDTO> userList = adminUserApi.getUserList(convertSet(permission, CrmPermissionDO::getUserId));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId));
// TODO @puhui999可能 postIds 为空的时候会导致报错看看怎么 fix
Set<Long> postIds = CollectionUtils.convertSetByFlatMap(userList, AdminUserRespDTO::getPostIds, Collection::stream);
Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
return success(CrmPermissionConvert.INSTANCE.convert(permission, userList, deptMap, postMap));

View File

@ -93,7 +93,7 @@ public class CrmReceivableController {
@PreAuthorize("@ss.hasPermission('crm:receivable:query')")
public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) {
PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId());
return success(convertDetailReceivablePage(pageResult));
return success(buildReceivableDetailPage(pageResult));
}
@GetMapping("/page-by-customer")
@ -101,7 +101,7 @@ public class CrmReceivableController {
public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) {
Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO);
return success(convertDetailReceivablePage(pageResult));
return success(buildReceivableDetailPage(pageResult));
}
// TODO 芋艿后面在优化导出
@ -115,23 +115,23 @@ public class CrmReceivableController {
PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId());
// 导出 Excel
ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class,
convertDetailReceivablePage(pageResult).getList());
buildReceivableDetailPage(pageResult).getList());
}
/**
* 转换成详细的回款分页即读取关联信息
* 构建详细的回款分页结果
*
* @param pageResult 回款分页
* @return 详细的回款分页
* @param pageResult 简单的回款分页结果
* @return 详细的回款分页结果
*/
private PageResult<CrmReceivableRespVO> convertDetailReceivablePage(PageResult<CrmReceivableDO> pageResult) {
private PageResult<CrmReceivableRespVO> buildReceivableDetailPage(PageResult<CrmReceivableDO> pageResult) {
List<CrmReceivableDO> receivableList = pageResult.getList();
if (CollUtil.isEmpty(receivableList)) {
return PageResult.empty(pageResult.getTotal());
}
// 1. 获取客户列表
List<CrmCustomerDO> customerList = customerService.getCustomerList(
convertSet(receivableList, CrmReceivableDO::getCustomerId), getLoginUserId());
convertSet(receivableList, CrmReceivableDO::getCustomerId));
// 2. 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));

View File

@ -123,10 +123,10 @@ public class CrmReceivablePlanController {
}
/**
* 转换成详细的回款计划分页即读取关联信息
* 构建详细的回款计划分页结果
*
* @param pageResult 回款计划分页
* @return 详细的回款计划分页
* @param pageResult 简单的回款计划分页结果
* @return 详细的回款计划分页结果
*/
private PageResult<CrmReceivablePlanRespVO> convertDetailReceivablePlanPage(PageResult<CrmReceivablePlanDO> pageResult) {
List<CrmReceivablePlanDO> receivablePlanList = pageResult.getList();
@ -135,7 +135,7 @@ public class CrmReceivablePlanController {
}
// 1. 获取客户列表
List<CrmCustomerDO> customerList = customerService.getCustomerList(
convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId), getLoginUserId());
convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId));
// 2. 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.convert.business;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
@ -9,7 +10,6 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@ -34,25 +34,23 @@ public interface CrmBusinessConvert {
CrmBusinessRespVO convert(CrmBusinessDO bean);
List<CrmBusinessRespVO> convert(List<CrmBusinessDO> bean);
PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> page);
List<CrmBusinessExcelVO> convertList02(List<CrmBusinessDO> list);
@Mapping(target = "bizId", source = "reqVO.id")
CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId);
default PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> page, List<CrmCustomerDO> customerList,
default PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> pageResult, List<CrmCustomerDO> customerList,
List<CrmBusinessStatusTypeDO> statusTypeList, List<CrmBusinessStatusDO> statusList) {
PageResult<CrmBusinessRespVO> result = convertPage(page);
PageResult<CrmBusinessRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmBusinessRespVO.class);
// 拼接关联字段
Map<Long, String> customerMap = convertMap(customerList, CrmCustomerDO::getId, CrmCustomerDO::getName);
Map<Long, String> statusTypeMap = convertMap(statusTypeList, CrmBusinessStatusTypeDO::getId, CrmBusinessStatusTypeDO::getName);
Map<Long, String> statusMap = convertMap(statusList, CrmBusinessStatusDO::getId, CrmBusinessStatusDO::getName);
result.getList().forEach(type -> type
voPageResult.getList().forEach(type -> type
.setCustomerName(customerMap.get(type.getCustomerId()))
.setStatusTypeName(statusTypeMap.get(type.getStatusTypeId()))
.setStatusName(statusMap.get(type.getStatusId())));
return result;
return voPageResult;
}
}

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.crm.convert.contact;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
@ -13,12 +15,10 @@ import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
// TODO 芋艿convert 后面在梳理下略微有点乱
/**
* CRM 联系人 Convert
*
@ -39,64 +39,37 @@ public interface CrmContactConvert {
PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> page);
default PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
List<CrmContactRespVO> list = converList(pageResult.getList(), userMap, customerList, parentContactList);
return convertPage(pageResult).setList(list);
}
List<CrmContactSimpleRespVO> convertAllList(List<CrmContactDO> list);
@Mapping(target = "bizId", source = "reqVO.id")
CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId);
/**
* 转换详情信息
*
* @param contactDO 联系人
* @param userMap 用户列表
* @param crmCustomerDOList 客户
* @return ContactRespVO
*/
default CrmContactRespVO convert(CrmContactDO contactDO, Map<Long, AdminUserRespDTO> userMap, List<CrmCustomerDO> crmCustomerDOList,
List<CrmContactDO> contactList) {
default PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
PageResult<CrmContactRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmContactRespVO.class);
// 拼接关联字段
Map<Long, CrmContactDO> parentContactMap = convertMap(parentContactList, CrmContactDO::getId);
Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
voPageResult.getList().forEach(item -> {
setUserInfo(item, userMap);
findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
findAndThen(parentContactMap, item.getParentId(), contactDO -> item.setParentName(contactDO.getName()));
});
return voPageResult;
}
default CrmContactRespVO convert(CrmContactDO contactDO, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
CrmContactRespVO contactVO = convert(contactDO);
setUserInfo(contactVO, userMap);
Map<Long, CrmCustomerDO> ustomerMap = crmCustomerDOList.stream().collect(Collectors.toMap(CrmCustomerDO::getId, v -> v));
Map<Long, CrmContactDO> contactMap = contactList.stream().collect(Collectors.toMap(CrmContactDO::getId, v -> v));
findAndThen(ustomerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName()));
Map<Long, CrmCustomerDO> customerMap = CollectionUtils.convertMap(customerList, CrmCustomerDO::getId);
Map<Long, CrmContactDO> contactMap = CollectionUtils.convertMap(parentContactList, CrmContactDO::getId);
findAndThen(customerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName()));
findAndThen(contactMap, contactDO.getParentId(), contact -> contactVO.setParentName(contact.getName()));
return contactVO;
}
default List<CrmContactRespVO> converList(List<CrmContactDO> contactList, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
List<CrmContactRespVO> result = convertList(contactList);
Map<Long, CrmContactDO> parentContactMap = convertMap(parentContactList, CrmContactDO::getId);
Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
result.forEach(item -> {
setUserInfo(item, userMap);
findAndThen(customerMap, item.getCustomerId(), customer -> { // TODO @zyna这里的 { 可以去掉
item.setCustomerName(customer.getName());
});
findAndThen(parentContactMap, item.getParentId(), contactDO -> { // TODO @zyna这里的 { 可以去掉
item.setParentName(contactDO.getName());
});
});
return result;
}
/**
* 设置用户信息
*
* @param contactRespVO 联系人Response VO
* @param userMap 用户信息 map
*/
static void setUserInfo(CrmContactRespVO contactRespVO, Map<Long, AdminUserRespDTO> userMap) {
contactRespVO.setAreaName(AreaUtils.format(contactRespVO.getAreaId()));
findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> {
contactRespVO.setOwnerUserName(user == null ? "" : user.getNickname());
});
findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> contactRespVO.setOwnerUserName(user.getNickname()));
findAndThen(userMap, Long.parseLong(contactRespVO.getCreator()), user -> contactRespVO.setCreatorName(user.getNickname()));
}

View File

@ -1,16 +0,0 @@
package cn.iocoder.yudao.module.crm.convert.contactbusinessslink;
import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
// TODO @zyna使用 BeanUtils 慢慢替代现有的 mapstruct
@Mapper
public interface CrmContactBusinessLinkConvert {
CrmContactBusinessLinkConvert INSTANCE = Mappers.getMapper(CrmContactBusinessLinkConvert.class);
CrmContactBusinessLinkDO convert(CrmContactBusinessLinkSaveReqVO bean);
List<CrmContactBusinessLinkDO> convert(List<CrmContactBusinessLinkSaveReqVO> bean);
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.convert.contract;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@ -43,23 +44,15 @@ public interface CrmContractConvert {
default PageResult<ContractRespVO> convertPage(PageResult<CrmContractDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList) {
return new PageResult<>(converList(pageResult.getList(), userMap, customerList), pageResult.getTotal());
}
default List<ContractRespVO> converList(List<CrmContractDO> contractList, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList) {
List<ContractRespVO> result = convertList(contractList);
PageResult<ContractRespVO> voPageResult = BeanUtils.toBean(pageResult, ContractRespVO.class);
// 拼接关联字段
Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
result.forEach(item -> {
setUserInfo(item, userMap);
findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
voPageResult.getList().forEach(contract -> {
findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname()));
findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname()));
findAndThen(customerMap, contract.getCustomerId(), customer -> contract.setCustomerName(customer.getName()));
});
return result;
}
static void setUserInfo(ContractRespVO contract, Map<Long, AdminUserRespDTO> userMap) {
findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname()));
findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname()));
return voPageResult;
}
}

View File

@ -12,7 +12,6 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.convert.receivable;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableTransferReqVO;
@ -36,29 +37,19 @@ public interface CrmReceivableConvert {
CrmReceivableRespVO convert(CrmReceivableDO bean);
List<CrmReceivableRespVO> convertList(List<CrmReceivableDO> list);
default PageResult<CrmReceivableRespVO> convertPage(PageResult<CrmReceivableDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) {
return new PageResult<>(converList(pageResult.getList(), userMap, customerList, contractList), pageResult.getTotal());
}
default List<CrmReceivableRespVO> converList(List<CrmReceivableDO> receivableList, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) {
List<CrmReceivableRespVO> result = convertList(receivableList);
PageResult<CrmReceivableRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class);
// 拼接关联字段
Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
Map<Long, CrmContractDO> contractMap = convertMap(contractList, CrmContractDO::getId);
result.forEach(item -> {
setUserInfo(item, userMap);
findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
findAndThen(contractMap, item.getContractId(), contract -> item.setContractNo(contract.getNo()));
voPageResult.getList().forEach(receivable -> {
findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname()));
findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname()));
findAndThen(customerMap, receivable.getCustomerId(), customer -> receivable.setCustomerName(customer.getName()));
findAndThen(contractMap, receivable.getContractId(), contract -> receivable.setContractNo(contract.getNo()));
});
return result;
}
static void setUserInfo(CrmReceivableRespVO receivable, Map<Long, AdminUserRespDTO> userMap) {
findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname()));
findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname()));
return voPageResult;
}
@Mapping(target = "bizId", source = "reqVO.id")

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.convert.receivable;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanTransferReqVO;
@ -37,33 +38,22 @@ public interface CrmReceivablePlanConvert {
CrmReceivablePlanRespVO convert(CrmReceivablePlanDO bean);
List<CrmReceivablePlanRespVO> convertList(List<CrmReceivablePlanDO> list);
default PageResult<CrmReceivablePlanRespVO> convertPage(PageResult<CrmReceivablePlanDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList, List<CrmContractDO> contractList,
List<CrmReceivableDO> receivableList) {
return new PageResult<>(converList(pageResult.getList(), userMap, customerList, contractList, receivableList), pageResult.getTotal());
}
default List<CrmReceivablePlanRespVO> converList(List<CrmReceivablePlanDO> receivablePlanList, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList, List<CrmContractDO> contractList,
List<CrmReceivableDO> receivableList) {
List<CrmReceivablePlanRespVO> result = convertList(receivablePlanList);
PageResult<CrmReceivablePlanRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivablePlanRespVO.class);
// 拼接关联字段
Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
Map<Long, CrmContractDO> contractMap = convertMap(contractList, CrmContractDO::getId);
Map<Long, CrmReceivableDO> receivableMap = convertMap(receivableList, CrmReceivableDO::getId);
result.forEach(item -> {
setUserInfo(item, userMap);
findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
findAndThen(contractMap, item.getContractId(), contract -> item.setContractNo(contract.getNo()));
findAndThen(receivableMap, item.getReceivableId(), receivable -> item.setReturnType(receivable.getReturnType()));
voPageResult.getList().forEach(receivablePlan -> {
findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname()));
findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname()));
findAndThen(customerMap, receivablePlan.getCustomerId(), customer -> receivablePlan.setCustomerName(customer.getName()));
findAndThen(contractMap, receivablePlan.getContractId(), contract -> receivablePlan.setContractNo(contract.getNo()));
findAndThen(receivableMap, receivablePlan.getReceivableId(), receivable -> receivablePlan.setReturnType(receivable.getReturnType()));
});
return result;
}
static void setUserInfo(CrmReceivablePlanRespVO receivablePlan, Map<Long, AdminUserRespDTO> userMap) {
findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname()));
findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname()));
return voPageResult;
}
@Mapping(target = "bizId", source = "reqVO.id")

View File

@ -1,4 +0,0 @@
/**
* 商机销售机会
*/
package cn.iocoder.yudao.module.crm.dal.dataobject.business;

View File

@ -1,28 +1,26 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink;
package cn.iocoder.yudao.module.crm.dal.dataobject.contact;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
// TODO @zyna可以放到 contact 包下
/**
* CRM 联系人商机关联 DO
* CRM 联系人商机关联 DO
*
* @author 芋道源码
*/
@TableName("crm_contact_business_link")
@KeySequence("crm_contact_business_link_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@TableName("crm_contact_business")
@KeySequence("crm_contact_business_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CrmContactBusinessLinkDO extends BaseDO {
public class CrmContactBusinessDO extends BaseDO {
/**
* 主键

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.contact;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@ -29,9 +30,11 @@ public class CrmContactDO extends BaseDO {
@TableId
private Long id;
/**
* 下次联系时间
* 客户编号
*
* 关联 {@link CrmCustomerDO#getId()}
*/
private LocalDateTime nextTime;
private Long customerId;
/**
* 手机号
*/
@ -45,21 +48,20 @@ public class CrmContactDO extends BaseDO {
*/
private String email;
/**
* 客户编号
* 所在地
*
* 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
*/
private Long customerId;
private Integer areaId;
/**
* 地址
* 详细地址
*/
private String address;
private String detailAddress;
/**
* 备注
*/
private String remark;
/**
* 最后跟进时间
*/
private LocalDateTime lastTime;
/**
* 直属上级
*
@ -100,10 +102,13 @@ public class CrmContactDO extends BaseDO {
private Long ownerUserId;
/**
* 所在地
*
* 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
* 最后跟进时间
*/
private Integer areaId;
private LocalDateTime contactLastTime;
// TODO @puhui999增加一个字段 contactLastContent最后跟进内容
/**
* 下次联系时间
*/
private LocalDateTime contactNextTime;
}

View File

@ -12,7 +12,7 @@ import java.time.LocalDateTime;
// TODO 芋艿调整下字段
/**
* 客户 DO
* CRM 客户 DO
*
* @author Wanwan
*/
@ -104,17 +104,21 @@ public class CrmCustomerDO extends BaseDO {
*/
private Long ownerUserId;
/**
* 地区编号
* 所在地
*
* 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
*/
private Integer areaId;
/**
* 详细地址
*/
private String detailAddress;
/**
* 最后跟进时间
*/
private LocalDateTime contactLastTime;
// TODO @puhui999增加一个字段 contactLastContent最后跟进内容
/**
* 下次联系时间
*/

View File

@ -0,0 +1,81 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.followup;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
// TODO @puhui999界面做成一个 list 列表字段是 id跟进人跟进方式跟进时间跟进内容下次联系时间关联联系人关联商机
// TODO @puhui999界面记录时弹窗表单字段是跟进方式跟进内容下次联系时间关联联系人关联商机其中关联联系人关联商机要做成对应的组件列
/**
* 跟进记录 DO
*
* 用于记录客户联系人的每一次跟进
*
* @author 芋道源码
*/
@TableName(value = "crm_follow_up_record")
@KeySequence("crm_follow_up_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CrmFollowUpRecordDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 数据类型
*
* 枚举 {@link CrmBizTypeEnum}
*/
private Integer bizType;
/**
* 数据编号
*
* 关联 {@link CrmBizTypeEnum} 对应模块 DO id 字段
*/
private Long bizId;
/**
* 跟进类型
*
* TODO @puhui999可以搞个数据字典打电话发短信上门拜访微信邮箱QQ
*/
private Integer type;
/**
* 跟进内容
*/
private String content;
/**
* 下次联系时间
*/
private LocalDateTime nextTime;
/**
* 关联的商机编号数组
*
* 关联 {@link CrmBusinessDO#getId()}
*/
private List<Long> businessIds;
/**
* 关联的联系人编号数组
*
* 关联 {@link CrmContactDO#getId()}
*/
private List<Long> contactIds;
}

View File

@ -30,7 +30,14 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
default PageResult<CrmBusinessDO> selectPageByCustomerId(CrmBusinessPageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
.eq(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号
.eq(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号
.likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
.orderByDesc(CrmBusinessDO::getId));
}
default PageResult<CrmBusinessDO> selectPageByContactId(CrmBusinessPageReqVO pageReqVO, Collection<Long> businessIds) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
.in(CrmBusinessDO::getId, businessIds) // 指定商机编号
.likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
.orderByDesc(CrmBusinessDO::getId));
}
@ -38,11 +45,8 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
default PageResult<CrmBusinessDO> selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmBusinessDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmBusinessDO.class)
.likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())

View File

@ -30,11 +30,8 @@ public interface CrmClueMapper extends BaseMapperX<CrmClueDO> {
default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmClueDO.class)
.likeIfPresent(CrmClueDO::getName, pageReqVO.getName())

View File

@ -43,11 +43,8 @@ public interface CrmContactMapper extends BaseMapperX<CrmContactDO> {
default PageResult<CrmContactDO> selectPage(CrmContactPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmContactDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmContactDO.class)
.likeIfPresent(CrmContactDO::getName, pageReqVO.getName())

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
import org.apache.ibatis.annotations.Mapper;
/**
* CRM 联系人商机关联 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface CrmContactBusinessLinkMapper extends BaseMapperX<CrmContactBusinessLinkDO> {
default PageResult<CrmContactBusinessLinkDO> selectPage(CrmContactBusinessLinkPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<CrmContactBusinessLinkDO>()
.eqIfPresent(CrmContactBusinessLinkDO::getContactId, reqVO.getContactId())
.eqIfPresent(CrmContactBusinessLinkDO::getBusinessId, reqVO.getBusinessId())
.betweenIfPresent(CrmContactBusinessLinkDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(CrmContactBusinessLinkDO::getId));
} // TODO @zyna方法和方法之间要有空行
default PageResult<CrmContactBusinessLinkDO> selectPageByContact(CrmContactBusinessLinkPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<CrmContactBusinessLinkDO>()
.eqIfPresent(CrmContactBusinessLinkDO::getContactId, reqVO.getContactId())
.orderByDesc(CrmContactBusinessLinkDO::getId));
}
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* CRM 联系人与商机的关联 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface CrmContactBusinessMapper extends BaseMapperX<CrmContactBusinessDO> {
default CrmContactBusinessDO selectByContactIdAndBusinessId(Long contactId, Long businessId) {
return selectOne(CrmContactBusinessDO::getContactId, contactId,
CrmContactBusinessDO::getBusinessId, businessId);
}
default void deleteByContactIdAndBusinessId(Long contactId, Collection<Long> businessIds) {
delete(new LambdaQueryWrapper<CrmContactBusinessDO>()
.eq(CrmContactBusinessDO::getContactId, contactId)
.in(CrmContactBusinessDO::getBusinessId, businessIds));
}
default List<CrmContactBusinessDO> selectListByContactId(Long contactId) {
return selectList(CrmContactBusinessDO::getContactId, contactId);
}
}

View File

@ -41,11 +41,8 @@ public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmContractDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
mpjLambdaWrapperX.selectAll(CrmContractDO.class)
.likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())

View File

@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmC
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 客户限制配置 Mapper
*
@ -21,4 +23,15 @@ public interface CrmCustomerLimitConfigMapper extends BaseMapperX<CrmCustomerLim
.orderByDesc(CrmCustomerLimitConfigDO::getId));
}
default List<CrmCustomerLimitConfigDO> selectListByTypeAndUserIdAndDeptId(
Integer type, Long userId, Long deptId) {
LambdaQueryWrapperX<CrmCustomerLimitConfigDO> query = new LambdaQueryWrapperX<CrmCustomerLimitConfigDO>()
.eq(CrmCustomerLimitConfigDO::getType, type);
query.apply("FIND_IN_SET({0}, user_ids) > 0", userId);
if (deptId != null) {
query.apply("FIND_IN_SET({0}, dept_ids) > 0", deptId);
}
return selectList(query);
}
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@ -9,6 +10,7 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.lang.Nullable;
import java.util.Collection;
import java.util.List;
@ -21,6 +23,18 @@ import java.util.List;
@Mapper
public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
default Long selectCountByLockStatusAndOwnerUserId(Boolean lockStatus, Long ownerUserId) {
return selectCount(new LambdaUpdateWrapper<CrmCustomerDO>()
.eq(CrmCustomerDO::getLockStatus, lockStatus)
.eq(CrmCustomerDO::getOwnerUserId, ownerUserId));
}
default Long selectCountByDealStatusAndOwnerUserId(@Nullable Boolean dealStatus, Long ownerUserId) {
return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>()
.eqIfPresent(CrmCustomerDO::getDealStatus, dealStatus)
.eq(CrmCustomerDO::getOwnerUserId, ownerUserId));
}
default int updateOwnerUserIdById(Long id, Long ownerUserId) {
return update(new LambdaUpdateWrapper<CrmCustomerDO>()
.eq(CrmCustomerDO::getId, id)
@ -30,11 +44,8 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
default PageResult<CrmCustomerDO> selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmCustomerDO.class)
.likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName())

View File

@ -39,11 +39,8 @@ public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> {
default PageResult<CrmReceivableDO> selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmReceivableDO.class)
.eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo())

View File

@ -38,11 +38,8 @@ public interface CrmReceivablePlanMapper extends BaseMapperX<CrmReceivablePlanDO
default PageResult<CrmReceivablePlanDO> selectPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmReceivablePlanDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
boolean condition = CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(),
CrmReceivablePlanDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
if (!condition) {
return PageResult.empty();
}
// 拼接自身的查询条件
query.selectAll(CrmReceivablePlanDO.class)
.eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId())

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.crm.framework.bizlog;

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.crm.framework.core;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.crm.framework.bizlog.function;
package cn.iocoder.yudao.module.crm.framework.operatelog.core;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
@ -8,9 +8,8 @@ import org.springframework.stereotype.Component;
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
// TODO @puhui999包名使用 operatelog 更合适哈
/**
* 自定义函数-通过行业编号获取行业信息
* 行业的 {@link IParseFunction} 实现类
*
* @author HUIHUI
*/
@ -30,18 +29,10 @@ public class CrmIndustryParseFunction implements IParseFunction {
@Override
public String apply(Object value) {
if (value == null) {
if (StrUtil.isEmptyIfStr(value)) {
return "";
}
if (StrUtil.isEmpty(value.toString())) {
return "";
}
// 获取行业信息
try {
return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_INDUSTRY, value.toString());
} catch (Exception ignored) {
}
return "";
return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_INDUSTRY, value.toString());
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.crm.framework.bizlog.function;
package cn.iocoder.yudao.module.crm.framework.operatelog.core;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL;
/**
* 自定义函数-通过客户等级编号获取客户等级信息
* 客户等级的 {@link IParseFunction} 实现类
*
* @author HUIHUI
*/
@ -29,18 +29,10 @@ public class CrmLevelParseFunction implements IParseFunction {
@Override
public String apply(Object value) {
if (value == null) {
if (StrUtil.isEmptyIfStr(value)) {
return "";
}
if (StrUtil.isEmpty(value.toString())) {
return "";
}
// 获取客户等级信息
try {
return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_LEVEL, value.toString());
} catch (Exception ignored) {
}
return "";
return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_LEVEL, value.toString());
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.crm.framework.bizlog.function;
package cn.iocoder.yudao.module.crm.framework.operatelog.core;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE;
/**
* 自定义函数-通过客户来源编号获取客户来源信息
* 客户来源的 {@link IParseFunction} 实现类
*
* @author HUIHUI
*/
@ -29,18 +29,10 @@ public class CrmSourceParseFunction implements IParseFunction {
@Override
public String apply(Object value) {
if (value == null) {
if (StrUtil.isEmptyIfStr(value)) {
return "";
}
if (StrUtil.isEmpty(value.toString())) {
return "";
}
// 获取客户来源信息
try {
return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_SOURCE, value.toString());
} catch (Exception ignored) {
}
return "";
return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_SOURCE, value.toString());
}
}

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.crm.framework.operatelog;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.crm.framework.core.annotations;
package cn.iocoder.yudao.module.crm.framework.permission.core.annotations;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.crm.framework.core.aop;
package cn.iocoder.yudao.module.crm.framework.permission.core.aop;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
@ -6,27 +6,27 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_MODEL_NOT_EXISTS;
// TODO 这个包改成 permission然后搞 config core 这个类在 core 包里目的是framework 最好分类下
/**
* Crm 数据权限校验 AOP 切面
*
@ -42,10 +42,6 @@ public class CrmPermissionAspect {
@Before("@annotation(crmPermission)")
public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) {
// TODO 芋艿临时方便大家调试
//if (true) {
// return;
//}
// 获取相关属性值
Map<String, Object> expressionValues = parseExpressions(joinPoint, crmPermission);
Integer bizType = StrUtil.isEmpty(crmPermission.bizTypeValue()) ?
@ -53,16 +49,28 @@ public class CrmPermissionAspect {
Long bizId = (Long) expressionValues.get(crmPermission.bizId()); // 模块数据编号
Integer permissionLevel = crmPermission.level().getLevel(); // 需要的权限级别
// TODO 如果是超级管理员则直接通过
//if (superAdmin){
// return;
//}
// 1. 获取数据权限
List<CrmPermissionDO> bizPermissions = crmPermissionService.getPermissionListByBiz(bizType, bizId);
if (CollUtil.isEmpty(bizPermissions)) { // 数据权限不存存那么数据也不存在
throw exception(CRM_PERMISSION_MODEL_NOT_EXISTS, CrmBizTypeEnum.getNameByType(bizType));
// 1.1 如果是超级管理员则直接通过
if (CrmPermissionUtils.isCrmAdmin()) {
return;
}
// 1.2 获取数据权限
List<CrmPermissionDO> bizPermissions = crmPermissionService.getPermissionListByBiz(bizType, bizId);
if (CollUtil.isEmpty(bizPermissions)) { // 没有数据权限的情况
// 公海数据如果没有团队成员大家也因该有读权限才对
if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
return;
}
// 没有数据权限的情况下超出了读权限直接报错避免后面校验空指针
throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType));
} else { // 有数据权限但是没有负责人的情况
if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))) {
if (CrmPermissionLevelEnum.isRead(permissionLevel)) {
return;
}
}
}
// 2.1 情况一如果自己是负责人则默认有所有权限
CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), getUserId()));
if (userPermission != null) {

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.crm.framework.permission.core;

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.crm.framework.permission.core.util;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
/**
* 数据权限工具类
*
* @author HUIHUI
*/
public class CrmPermissionUtils {
/**
* 校验用户是否是 CRM 管理员
*
* @return /
*/
public static boolean isCrmAdmin() {
return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode());
}
/**
* 静态内部类实现单例获取
*
* @author HUIHUI
*/
private static class SingletonManager {
private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class);
public static PermissionApi getPermissionApi() {
return PERMISSION_API;
}
}
}

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.crm.framework.permission;

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import jakarta.validation.Valid;
@ -75,10 +76,20 @@ public interface CrmBusinessService {
* 数据权限基于 {@link CrmCustomerDO} 读取
*
* @param pageReqVO 分页查询
* @return 联系人分页
* @return 商机分页
*/
PageResult<CrmBusinessDO> getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO);
/**
* 获得商机分页基于指定联系人
*
* 数据权限基于 {@link CrmContactDO} 读取
*
* @param pageReqVO 分页参数
* @return 商机分页
*/
PageResult<CrmBusinessDO> getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO);
/**
* 商机转移
*

View File

@ -9,11 +9,12 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import jakarta.annotation.Resource;
@ -25,6 +26,7 @@ import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
/**
@ -38,24 +40,26 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
@Resource
private CrmBusinessMapper businessMapper;
@Resource
private CrmCustomerService customerService;
@Resource
private CrmPermissionService crmPermissionService;
private CrmPermissionService permissionService;
@Resource
private CrmContactBusinessService contactBusinessService;
@Override
@Transactional(rollbackFor = Exception.class)
// TODO @商机待定操作日志
public Long createBusiness(CrmBusinessCreateReqVO createReqVO, Long userId) {
// 插入
// 1. 插入商机
CrmBusinessDO business = CrmBusinessConvert.INSTANCE.convert(createReqVO);
businessMapper.insert(business);
// TODO 商机待定插入商机与产品的关联表校验商品存在
// 创建数据权限
crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
// TODO 商机待定在联系人的详情页如果直接新建商机则需要关联下这里要搞个 CrmContactBusinessDO
// 2. 创建数据权限
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
.setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
// 返回
return business.getId();
}
@ -63,12 +67,17 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
@Transactional(rollbackFor = Exception.class)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id",
level = CrmPermissionLevelEnum.WRITE)
// TODO @商机待定操作日志
public void updateBusiness(CrmBusinessUpdateReqVO updateReqVO) {
// 校验存在
// 1. 校验存在
validateBusinessExists(updateReqVO.getId());
// 更新
// 2. 更新商机
CrmBusinessDO updateObj = CrmBusinessConvert.INSTANCE.convert(updateReqVO);
businessMapper.updateById(updateObj);
// TODO 商机待定插入商机与产品的关联表校验商品存在
// TODO @商机待定如果状态发生变化插入商机状态变更记录表
}
@Override
@ -77,10 +86,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
public void deleteBusiness(Long id) {
// 校验存在
validateBusinessExists(id);
// TODO @商机待定需要校验有没关联合同CrmContractDO businessId 字段
// 删除
businessMapper.deleteById(id);
// 删除数据权限
crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id);
permissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id);
}
private CrmBusinessDO validateBusinessExists(Long id) {
@ -116,19 +127,32 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
return businessMapper.selectPageByCustomerId(pageReqVO);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#pageReqVO.contactId", level = CrmPermissionLevelEnum.READ)
public PageResult<CrmBusinessDO> getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO) {
// 1. 查询关联的商机编号
List<CrmContactBusinessDO> contactBusinessList = contactBusinessService.getContactBusinessListByContactId(
pageReqVO.getContactId());
if (CollUtil.isEmpty(contactBusinessList)) {
return PageResult.empty();
}
// 2. 查询商机分页
return businessMapper.selectPageByContactId(pageReqVO,
convertSet(contactBusinessList, CrmContactBusinessDO::getBusinessId));
}
@Override
@Transactional(rollbackFor = Exception.class)
// TODO @puhui999操作日志
public void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId) {
// 1 校验商机是否存在
validateBusinessExists(reqVO.getId());
// 2.1 数据权限转移
crmPermissionService.transferPermission(
permissionService.transferPermission(
CrmBusinessConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()));
// 2.2 设置新的负责人
businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
// 3. TODO 记录转移日志
}
}

View File

@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusine
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
@ -64,4 +66,12 @@ public interface CrmBusinessStatusService {
*/
List<CrmBusinessStatusDO> selectList(CrmBusinessStatusQueryVO queryVO);
/**
* 获得商机状态列表
*
* @param ids 编号数组
* @return 商机状态列表
*/
List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids);
}

View File

@ -12,6 +12,7 @@ import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -77,4 +78,9 @@ public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService {
return businessStatusMapper.selectList(queryVO);
}
@Override
public List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids) {
return businessStatusMapper.selectBatchIds(ids);
}
}

View File

@ -5,8 +5,9 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusiness
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
@ -63,4 +64,12 @@ public interface CrmBusinessStatusTypeService {
*/
List<CrmBusinessStatusTypeDO> selectList(CrmBusinessStatusTypeQueryVO queryVO);
/**
* 获得商机状态类型列表
*
* @param ids 编号数组
* @return 商机状态类型列表
*/
List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids);
}

View File

@ -17,6 +17,8 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -118,4 +120,9 @@ public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeSe
return businessStatusTypeMapper.selectList(queryVO);
}
@Override
public List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids) {
return businessStatusTypeMapper.selectBatchIds(ids);
}
}

View File

@ -1,4 +0,0 @@
/**
* 商机销售机会
*/
package cn.iocoder.yudao.module.crm.service.business;

View File

@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import jakarta.annotation.Resource;

View File

@ -1,4 +0,0 @@
/**
* 线索
*/
package cn.iocoder.yudao.module.crm.service.clue;

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.crm.service.contact;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
import jakarta.validation.Valid;
import java.util.List;
/**
* CRM 联系人与商机的关联 Service 接口
*
* @author 芋道源码
*/
public interface CrmContactBusinessService {
/**
* 创建联系人与商机的关联
*
* @param createReqVO 创建信息
*/
void createContactBusinessList(@Valid CrmContactBusinessReqVO createReqVO);
/**
* 删除联系人与商机的关联
*
* @param deleteReqVO 删除信息
*/
void deleteContactBusinessList(@Valid CrmContactBusinessReqVO deleteReqVO);
/**
* 获得联系人与商机的关联列表基于联系人编号
*
* @param contactId 联系人编号
* @return 联系人商机关联
*/
List<CrmContactBusinessDO> getContactBusinessListByContactId(Long contactId);
}

View File

@ -0,0 +1,83 @@
package cn.iocoder.yudao.module.crm.service.contact;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS;
// TODO @puhui999数据权限的校验每个操作
/**
* 联系人与商机的关联 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class CrmContactBusinessServiceImpl implements CrmContactBusinessService {
@Resource
private CrmContactBusinessMapper contactBusinessMapper;
@Resource
@Lazy // 延迟加载为了解决延迟加载
private CrmBusinessService businessService;
@Resource
@Lazy // 延迟加载为了解决延迟加载
private CrmContactService contactService;
@Override
public void createContactBusinessList(CrmContactBusinessReqVO createReqVO) {
CrmContactDO contact = contactService.getContact(createReqVO.getContactId());
if (contact == null) {
throw exception(CONTACT_NOT_EXISTS);
}
// 遍历处理考虑到一般数量不会太多代码处理简单
List<CrmContactBusinessDO> saveDOList = new ArrayList<>();
createReqVO.getBusinessIds().forEach(businessId -> {
CrmBusinessDO business = businessService.getBusiness(businessId);
if (business == null) {
throw exception(BUSINESS_NOT_EXISTS);
}
// 关联判重
if (contactBusinessMapper.selectByContactIdAndBusinessId(createReqVO.getContactId(), businessId) != null) {
return;
}
saveDOList.add(new CrmContactBusinessDO(null, createReqVO.getContactId(), businessId));
});
// 批量插入
if (CollUtil.isNotEmpty(saveDOList)) {
contactBusinessMapper.insertBatch(saveDOList);
}
}
@Override
public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) {
CrmContactDO contact = contactService.getContact(deleteReqVO.getContactId());
if (contact == null) {
throw exception(CONTACT_NOT_EXISTS);
}
// 直接删除
contactBusinessMapper.deleteByContactIdAndBusinessId(
deleteReqVO.getContactId(), deleteReqVO.getBusinessIds());
}
@Override
public List<CrmContactBusinessDO> getContactBusinessListByContactId(Long contactId) {
return contactBusinessMapper.selectListByContactId(contactId);
}
}

View File

@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
@ -43,33 +43,38 @@ public class CrmContactServiceImpl implements CrmContactService {
private CrmCustomerService customerService;
@Resource
private CrmPermissionService crmPermissionService;
@Resource
private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
// TODO @zyna增加操作日志可以参考 CustomerService内容是 新建了联系人名字
public Long createContact(CrmContactCreateReqVO createReqVO, Long userId) {
// 1.1 校验
// 1. 校验
validateRelationDataExists(createReqVO);
// 1.2 插入
// 2. 插入联系人
CrmContactDO contact = CrmContactConvert.INSTANCE.convert(createReqVO);
contactMapper.insert(contact);
// 2. 创建数据权限
// 3. 创建数据权限
crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId)
.setBizType(CrmBizTypeEnum.CRM_CONTACT.getType()).setBizId(contact.getId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
// TODO @zyna特殊逻辑如果在商机详情那点击新增联系人可以自动绑定商机
return contact.getId();
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
// TODO @zyna增加操作日志可以参考 CustomerService需要 diff 出字段
public void updateContact(CrmContactUpdateReqVO updateReqVO) {
// 1. 校验存在
validateContactExists(updateReqVO.getId());
validateRelationDataExists(updateReqVO);
// 2. 更新
// 2. 更新联系人
CrmContactDO updateObj = CrmContactConvert.INSTANCE.convert(updateReqVO);
contactMapper.updateById(updateObj);
}
@ -99,10 +104,15 @@ public class CrmContactServiceImpl implements CrmContactService {
public void deleteContact(Long id) {
// 校验存在
validateContactExists(id);
// TODO @zyna如果有关联的合同不允许删除Contract.contactId
// 删除
contactMapper.deleteById(id);
// 删除数据权限
crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
// TODO @zyna删除商机联系人关联
// TODO @puhui999删除跟进记录
}
private void validateContactExists(Long id) {
@ -137,6 +147,8 @@ public class CrmContactServiceImpl implements CrmContactService {
}
@Override
// TODO @puhui999权限校验
// TODO @puhui999记录操作日志将联系人名字转移给新负责人
public void transferContact(CrmContactTransferReqVO reqVO, Long userId) {
// 1 校验联系人是否存在
validateContactExists(reqVO.getId());
@ -150,4 +162,4 @@ public class CrmContactServiceImpl implements CrmContactService {
// 3. TODO 记录转移日志
}
}
}

View File

@ -1,72 +0,0 @@
package cn.iocoder.yudao.module.crm.service.contactbusinesslink;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
import jakarta.validation.Valid;
import java.util.List;
/**
* CRM 联系人商机关联 Service 接口
*
* @author 芋道源码
*/
public interface CrmContactBusinessLinkService {
/**
* 创建联系人商机关联
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createContactBusinessLink(@Valid CrmContactBusinessLinkSaveReqVO createReqVO);
/**
* 创建联系人商机关联
*
* @param createReqVO 创建信息
*/
void createContactBusinessLinkBatch(@Valid List<CrmContactBusinessLinkSaveReqVO> createReqVO);
/**
* 更新联系人商机关联
*
* @param updateReqVO 更新信息
*/
void updateContactBusinessLink(@Valid CrmContactBusinessLinkSaveReqVO updateReqVO);
/**
* 删除联系人商机关联
*
* @param createReqVO 删除列表
*/
void deleteContactBusinessLink(@Valid List<CrmContactBusinessLinkSaveReqVO> createReqVO);
/**
* 获得联系人商机关联
*
* @param id 编号
* @return 联系人商机关联
*/
CrmContactBusinessLinkDO getContactBusinessLink(Long id);
/**
* 获得联系人商机关联分页
*
* @param pageReqVO 编号
* @return 联系人商机关联
*/
PageResult<CrmBusinessRespVO> getContactBusinessLinkPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO);
/**
* 获得联系人商机关联分页
*
* @param pageReqVO 分页查询
* @return 联系人商机关联分页
*/
PageResult<CrmContactBusinessLinkDO> getContactBusinessLinkPage(CrmContactBusinessLinkPageReqVO pageReqVO);
}

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