同步最新代码到简化分支

This commit is contained in:
YunaiV 2024-04-24 22:29:27 +08:00
parent 8252abe3ef
commit c2c7eb0084
149 changed files with 0 additions and 18033 deletions

View File

@ -1,64 +0,0 @@
package cn.iocoder.yudao.framework.common.util.collection;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link CollectionUtils} 的单元测试
*/
public class CollectionUtilsTest {
@Data
@AllArgsConstructor
private static class Dog {
private Integer id;
private String name;
private String code;
}
@Test
public void testDiffList() {
// 准备参数
Collection<Dog> oldList = Arrays.asList(
new Dog(1, "花花", "hh"),
new Dog(2, "旺财", "wc")
);
Collection<Dog> newList = Arrays.asList(
new Dog(null, "花花2", "hh"),
new Dog(null, "小白", "xb")
);
BiFunction<Dog, Dog, Boolean> sameFunc = (oldObj, newObj) -> {
boolean same = oldObj.getCode().equals(newObj.getCode());
// 如果相等的情况下需要设置下 id后续好更新
if (same) {
newObj.setId(oldObj.getId());
}
return same;
};
// 调用
List<List<Dog>> result = CollectionUtils.diffList(oldList, newList, sameFunc);
// 断言
assertEquals(result.size(), 3);
// 断言 create
assertEquals(result.get(0).size(), 1);
assertEquals(result.get(0).get(0), new Dog(null, "小白", "xb"));
// 断言 update
assertEquals(result.get(1).size(), 1);
assertEquals(result.get(1).get(0), new Dog(1, "花花2", "hh"));
// 断言 delete
assertEquals(result.get(2).size(), 1);
assertEquals(result.get(2).get(0), new Dog(2, "旺财", "wc"));
}
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.hutool.core.convert.Convert;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 流程表达式 {@link BpmTaskCandidateStrategy} 实现类
*
* @author 芋道源码
*/
@Component
public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy {
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.EXPRESSION;
}
@Override
public void validateParam(String param) {
// do nothing 因为它基本做不了校验
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
Object result = FlowableUtils.getExpressionValue(execution, param);
return Convert.toSet(Long.class, result);
}
}

View File

@ -1,42 +0,0 @@
spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
mybatis-plus:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
--- #################### 监控相关配置 ####################
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
base-package: cn.iocoder.yudao.module

View File

@ -1,108 +0,0 @@
package cn.iocoder.yudao.module.crm.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* CRM 错误码枚举类
* <p>
* crm 系统使用 1-020-000-000
*/
public interface ErrorCodeConstants {
// ========== 合同管理 1-020-000-000 ==========
ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在");
ErrorCode CONTRACT_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_001, "合同更新失败,原因:合同不是草稿状态");
ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态");
ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态");
ErrorCode CONTRACT_NO_EXISTS = new ErrorCode(1_020_000_004, "生成合同序列号重复,请重试");
ErrorCode CONTRACT_DELETE_FAIL = new ErrorCode(1_020_000_005, "删除合同失败,原因:有被回款所使用");
// ========== 线索管理 1-020-001-000 ==========
ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
ErrorCode CLUE_TRANSFORM_FAIL_ALREADY = new ErrorCode(1_020_001_001, "线索已经转化过了,请勿重复转化");
// ========== 商机管理 1-020-002-000 ==========
ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在");
ErrorCode BUSINESS_DELETE_FAIL_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除");
ErrorCode BUSINESS_UPDATE_STATUS_FAIL_END_STATUS = new ErrorCode(1_020_002_002, "更新商机状态失败,原因:已经是结束状态");
ErrorCode BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS = new ErrorCode(1_020_002_003, "更新商机状态失败,原因:已经是该状态");
// ========== 联系人管理 1-020-003-000 ==========
ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode(1_020_003_002, "联系人已关联合同,不能删除");
ErrorCode CONTACT_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_003_003, "更新联系人负责人失败");
// ========== 回款 1-020-004-000 ==========
ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在");
ErrorCode RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_004_001, "更新回款失败,原因:禁止编辑");
ErrorCode RECEIVABLE_DELETE_FAIL = new ErrorCode(1_020_004_002, "删除回款失败,原因: 被回款计划所使用,不允许删除");
ErrorCode RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_004_003, "回款提交审核失败,原因:回款没处在未提交状态");
ErrorCode RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_004_004, "更新回款审核状态失败,原因:回款不是审核中状态");
ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试");
ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态");
ErrorCode RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_004_007, "创建回款失败,原因:回款金额超出合同金额,目前剩余可退:{} 元");
ErrorCode RECEIVABLE_DELETE_FAIL_IS_APPROVE = new ErrorCode(1_020_004_008, "删除回款失败,原因:回款审批已通过");
// ========== 回款计划 1-020-005-000 ==========
ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
ErrorCode RECEIVABLE_PLAN_UPDATE_FAIL = new ErrorCode(1_020_006_000, "更想回款计划失败,原因:已经有对应的还款");
ErrorCode RECEIVABLE_PLAN_EXISTS_RECEIVABLE = new ErrorCode(1_020_006_001, "回款计划已经有对应的回款,不能使用");
// ========== 客户管理 1_020_006_000 ==========
ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在");
ErrorCode CUSTOMER_OWNER_EXISTS = new ErrorCode(1_020_006_001, "客户【{}】已存在所属负责人");
ErrorCode CUSTOMER_LOCKED = new ErrorCode(1_020_006_002, "客户【{}】状态已锁定");
ErrorCode CUSTOMER_ALREADY_DEAL = new ErrorCode(1_020_006_003, "客户已交易");
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, "操作失败,超出客户数拥有上限");
ErrorCode CUSTOMER_DELETE_FAIL_HAVE_REFERENCE = new ErrorCode(1_020_006_011, "删除客户失败,有关联{}");
ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!");
ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!");
ErrorCode CUSTOMER_NAME_EXISTS = new ErrorCode(1_020_006_014, "已存在名为【{}】的客户!");
ErrorCode CUSTOMER_UPDATE_DEAL_STATUS_FAIL = new ErrorCode(1_020_006_015, "更新客户的成交状态失败,原因:已经是该状态,无需更新");
// ========== 权限管理 1_020_007_000 ==========
ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
ErrorCode CRM_PERMISSION_DENIED = new ErrorCode(1_020_007_001, "{}操作失败,原因:没有权限");
ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS = new ErrorCode(1_020_007_003, "{}操作失败,原因:转移对象已经是该负责人");
ErrorCode CRM_PERMISSION_DELETE_FAIL = new ErrorCode(1_020_007_004, "删除数据权限失败,原因:批量删除权限的时候,只能属于同一个 bizId 下");
ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限");
ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人");
ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限");
ErrorCode CRM_PERMISSION_CREATE_FAIL_EXISTS = new ErrorCode(1_020_007_009, "同时添加数据权限失败,原因:用户【{}】已有模块【{}】数据【{}】的【{}】权限");
// ========== 产品 1_020_008_000 ==========
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
ErrorCode PRODUCT_NO_EXISTS = new ErrorCode(1_020_008_001, "产品编号已存在");
ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_020_008_002, "产品【{}】已禁用");
// ========== 产品分类 1_020_009_000 ==========
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_009_000, "产品分类不存在");
ErrorCode PRODUCT_CATEGORY_EXISTS = new ErrorCode(1_020_009_001, "产品分类已存在");
ErrorCode PRODUCT_CATEGORY_USED = new ErrorCode(1_020_009_002, "产品分类已关联产品");
ErrorCode PRODUCT_CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1_020_009_003, "父分类不存在");
ErrorCode PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_020_009_004, "父分类不能是二级分类");
ErrorCode PRODUCT_CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_020_009_005, "存在子分类,无法删除");
// ========== 商机状态 1_020_010_000 ==========
ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_010_000, "商机状态组不存在");
ErrorCode BUSINESS_STATUS_TYPE_NAME_EXISTS = new ErrorCode(1_020_010_001, "商机状态组的名称已存在");
ErrorCode BUSINESS_STATUS_UPDATE_FAIL_USED = new ErrorCode(1_020_010_002, "已经被使用的商机状态组,无法进行更新");
ErrorCode BUSINESS_STATUS_DELETE_FAIL_USED = new ErrorCode(1_020_010_002, "已经被使用的商机状态组,无法进行删除");
ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_010_003, "商机状态不存在");
// ========== 客户公海规则设置 1_020_012_000 ==========
ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_012_001, "客户限制配置不存在");
// ========== 跟进记录 1_020_013_000 ==========
ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限");
// ========== 数据统计 1_020_014_000 ==========
}

View File

@ -1,163 +0,0 @@
package cn.iocoder.yudao.module.crm.enums;
/**
* CRM 操作日志枚举
* 目的统一管理也减少 Service 里各种复杂字符串
*
* @author HUIHUI
*/
public interface LogRecordConstants {
// ======================= CRM_CLUE 线索 =======================
String CRM_CLUE_TYPE = "CRM 线索";
String CRM_CLUE_CREATE_SUB_TYPE = "创建线索";
String CRM_CLUE_CREATE_SUCCESS = "创建了线索{{#clue.name}}";
String CRM_CLUE_UPDATE_SUB_TYPE = "更新线索";
String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}";
String CRM_CLUE_DELETE_SUB_TYPE = "删除线索";
String CRM_CLUE_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";
String CRM_CLUE_TRANSFER_SUB_TYPE = "转移线索";
String CRM_CLUE_TRANSFER_SUCCESS = "将线索【{{#clue.name}}】的负责人从【{getAdminUserById{#clue.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String CRM_CLUE_TRANSLATE_SUB_TYPE = "线索转化为客户";
String CRM_CLUE_TRANSLATE_SUCCESS = "将线索【{{#clueName}}】转化为客户";
String CRM_CLUE_FOLLOW_UP_SUB_TYPE = "线索跟进";
String CRM_CLUE_FOLLOW_UP_SUCCESS = "线索跟进【{{#clueName}}】";
// ======================= CRM_CUSTOMER 客户 =======================
String CRM_CUSTOMER_TYPE = "CRM 客户";
String CRM_CUSTOMER_CREATE_SUB_TYPE = "创建客户";
String CRM_CUSTOMER_CREATE_SUCCESS = "创建了客户{{#customer.name}}";
String CRM_CUSTOMER_UPDATE_SUB_TYPE = "更新客户";
String CRM_CUSTOMER_UPDATE_SUCCESS = "更新了客户【{{#customerName}}】: {_DIFF{#updateReqVO}}";
String CRM_CUSTOMER_DELETE_SUB_TYPE = "删除客户";
String CRM_CUSTOMER_DELETE_SUCCESS = "删除了客户【{{#customerName}}】";
String CRM_CUSTOMER_TRANSFER_SUB_TYPE = "转移客户";
String CRM_CUSTOMER_TRANSFER_SUCCESS = "将客户【{{#customer.name}}】的负责人从【{getAdminUserById{#customer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String CRM_CUSTOMER_LOCK_SUB_TYPE = "{{#customer.lockStatus ? '解锁客户' : '锁定客户'}}";
String CRM_CUSTOMER_LOCK_SUCCESS = "{{#customer.lockStatus ? '将客户【' + #customer.name + '】解锁' : '将客户【' + #customer.name + '】锁定'}}";
String CRM_CUSTOMER_POOL_SUB_TYPE = "客户放入公海";
String CRM_CUSTOMER_POOL_SUCCESS = "将客户【{{#customerName}}】放入了公海";
String CRM_CUSTOMER_RECEIVE_SUB_TYPE = "{{#ownerUserName != null ? '分配客户' : '领取客户'}}";
String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}";
String CRM_CUSTOMER_IMPORT_SUB_TYPE = "{{#isUpdate ? '导入并更新客户' : '导入客户'}}";
String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}";
String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE = "更新客户成交状态";
String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS = "更新了客户【{{#customerName}}】的成交状态为【{{#dealStatus ? '已成交' : '未成交'}}】";
String CRM_CUSTOMER_FOLLOW_UP_SUB_TYPE = "客户跟进";
String CRM_CUSTOMER_FOLLOW_UP_SUCCESS = "客户跟进【{{#customerName}}】";
// ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 =======================
String CRM_CUSTOMER_LIMIT_CONFIG_TYPE = "CRM 客户限制配置";
String CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUB_TYPE = "创建客户限制配置";
String CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUCCESS = "创建了【{{#limitType}}】类型的客户限制配置";
String CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUB_TYPE = "更新客户限制配置";
String CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUCCESS = "更新了客户限制配置: {_DIFF{#updateReqVO}}";
String CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUB_TYPE = "删除客户限制配置";
String CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUCCESS = "删除了【{{#limitType}}】类型的客户限制配置";
// ======================= CRM_CUSTOMER_POOL_CONFIG 客户公海规则 =======================
String CRM_CUSTOMER_POOL_CONFIG_TYPE = "CRM 客户公海规则";
String CRM_CUSTOMER_POOL_CONFIG_SUB_TYPE = "{{#isPoolConfigUpdate ? '更新客户公海规则' : '创建客户公海规则'}}";
String CRM_CUSTOMER_POOL_CONFIG_SUCCESS = "{{#isPoolConfigUpdate ? '更新了客户公海规则' : '创建了客户公海规则'}}";
// ======================= CRM_CONTACT 联系人 =======================
String CRM_CONTACT_TYPE = "CRM 联系人";
String CRM_CONTACT_CREATE_SUB_TYPE = "创建联系人";
String CRM_CONTACT_CREATE_SUCCESS = "创建了联系人{{#contact.name}}";
String CRM_CONTACT_UPDATE_SUB_TYPE = "更新联系人";
String CRM_CONTACT_UPDATE_SUCCESS = "更新了联系人【{{#contactName}}】: {_DIFF{#updateReqVO}}";
String CRM_CONTACT_DELETE_SUB_TYPE = "删除联系人";
String CRM_CONTACT_DELETE_SUCCESS = "删除了联系人【{{#contactName}}】";
String CRM_CONTACT_TRANSFER_SUB_TYPE = "转移联系人";
String CRM_CONTACT_TRANSFER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String CRM_CONTACT_FOLLOW_UP_SUB_TYPE = "联系人跟进";
String CRM_CONTACT_FOLLOW_UP_SUCCESS = "联系人跟进【{{#contactName}}】";
String CRM_CONTACT_UPDATE_OWNER_USER_SUB_TYPE = "更新联系人负责人";
String CRM_CONTACT_UPDATE_OWNER_USER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#ownerUserId}}】";
// ======================= CRM_BUSINESS 商机 =======================
String CRM_BUSINESS_TYPE = "CRM 商机";
String CRM_BUSINESS_CREATE_SUB_TYPE = "创建商机";
String CRM_BUSINESS_CREATE_SUCCESS = "创建了商机{{#business.name}}";
String CRM_BUSINESS_UPDATE_SUB_TYPE = "更新商机";
String CRM_BUSINESS_UPDATE_SUCCESS = "更新了商机【{{#businessName}}】: {_DIFF{#updateReqVO}}";
String CRM_BUSINESS_DELETE_SUB_TYPE = "删除商机";
String CRM_BUSINESS_DELETE_SUCCESS = "删除了商机【{{#businessName}}】";
String CRM_BUSINESS_TRANSFER_SUB_TYPE = "转移商机";
String CRM_BUSINESS_TRANSFER_SUCCESS = "将商机【{{#business.name}}】的负责人从【{getAdminUserById{#business.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String CRM_BUSINESS_FOLLOW_UP_SUB_TYPE = "商机跟进";
String CRM_BUSINESS_FOLLOW_UP_SUCCESS = "商机跟进【{{#businessName}}】";
String CRM_BUSINESS_UPDATE_STATUS_SUB_TYPE = "更新商机状态";
String CRM_BUSINESS_UPDATE_STATUS_SUCCESS = "更新了商机【{{#businessName}}】的状态从【{{#oldStatusName}}】变更为了【{{#newStatusName}}】";
// ======================= CRM_CONTRACT_CONFIG 合同配置 =======================
String CRM_CONTRACT_CONFIG_TYPE = "CRM 合同配置";
String CRM_CONTRACT_CONFIG_SUB_TYPE = "{{#isPoolConfigUpdate ? '更新合同配置' : '创建合同配置'}}";
String CRM_CONTRACT_CONFIG_SUCCESS = "{{#isPoolConfigUpdate ? '更新了合同配置' : '创建了合同配置'}}";
// ======================= CRM_CONTRACT 合同 =======================
String CRM_CONTRACT_TYPE = "CRM 合同";
String CRM_CONTRACT_CREATE_SUB_TYPE = "创建合同";
String CRM_CONTRACT_CREATE_SUCCESS = "创建了合同{{#contract.name}}";
String CRM_CONTRACT_UPDATE_SUB_TYPE = "更新合同";
String CRM_CONTRACT_UPDATE_SUCCESS = "更新了合同【{{#contractName}}】: {_DIFF{#updateReqVO}}";
String CRM_CONTRACT_DELETE_SUB_TYPE = "删除合同";
String CRM_CONTRACT_DELETE_SUCCESS = "删除了合同【{{#contractName}}】";
String CRM_CONTRACT_TRANSFER_SUB_TYPE = "转移合同";
String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String CRM_CONTRACT_SUBMIT_SUB_TYPE = "提交合同审批";
String CRM_CONTRACT_SUBMIT_SUCCESS = "提交合同【{{#contractName}}】审批成功";
String CRM_CONTRACT_FOLLOW_UP_SUB_TYPE = "合同跟进";
String CRM_CONTRACT_FOLLOW_UP_SUCCESS = "合同跟进【{{#contractName}}】";
// ======================= CRM_PRODUCT 产品 =======================
String CRM_PRODUCT_TYPE = "CRM 产品";
String CRM_PRODUCT_CREATE_SUB_TYPE = "创建产品";
String CRM_PRODUCT_CREATE_SUCCESS = "创建了产品【{{#createReqVO.name}}】";
String CRM_PRODUCT_UPDATE_SUB_TYPE = "更新产品";
String CRM_PRODUCT_UPDATE_SUCCESS = "更新了产品【{{#updateReqVO.name}}】: {_DIFF{#updateReqVO}}";
String CRM_PRODUCT_DELETE_SUB_TYPE = "删除产品";
String CRM_PRODUCT_DELETE_SUCCESS = "删除了产品【{{#product.name}}】";
// ======================= CRM_PRODUCT_CATEGORY 产品分类 =======================
String CRM_PRODUCT_CATEGORY_TYPE = "CRM 产品分类";
String CRM_PRODUCT_CATEGORY_CREATE_SUB_TYPE = "创建产品分类";
String CRM_PRODUCT_CATEGORY_CREATE_SUCCESS = "创建了产品分类【{{#createReqVO.name}}】";
String CRM_PRODUCT_CATEGORY_UPDATE_SUB_TYPE = "更新产品分类";
String CRM_PRODUCT_CATEGORY_UPDATE_SUCCESS = "更新了产品分类【{{#updateReqVO.name}}】: {_DIFF{#updateReqVO}}";
String CRM_PRODUCT_CATEGORY_DELETE_SUB_TYPE = "删除产品分类";
String CRM_PRODUCT_CATEGORY_DELETE_SUCCESS = "删除了产品分类【{{#productCategory.name}}】";
// ======================= CRM_RECEIVABLE 回款 =======================
String CRM_RECEIVABLE_TYPE = "CRM 回款";
String CRM_RECEIVABLE_CREATE_SUB_TYPE = "创建回款";
String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款";
String CRM_RECEIVABLE_UPDATE_SUB_TYPE = "更新回款";
String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款: {_DIFF{#updateReqVO}}";
String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款";
String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款";
String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批";
String CRM_RECEIVABLE_SUBMIT_SUCCESS = "提交编号为【{{#receivableNo}}】的回款审批成功";
// ======================= CRM_RECEIVABLE_PLAN 回款计划 =======================
String CRM_RECEIVABLE_PLAN_TYPE = "CRM 回款计划";
String CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE = "创建回款计划";
String CRM_RECEIVABLE_PLAN_CREATE_SUCCESS = "创建了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划";
String CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE = "更新回款计划";
String CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划: {_DIFF{#updateReqVO}}";
String CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE = "删除回款计划";
String CRM_RECEIVABLE_PLAN_DELETE_SUCCESS = "删除了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划";
}

View File

@ -1,60 +0,0 @@
package cn.iocoder.yudao.module.crm.enums.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* CRM 数据权限级别枚举
*
* OWNER > WRITE > READ
*
* @author HUIHUI
*/
@Getter
@AllArgsConstructor
public enum CrmPermissionLevelEnum implements IntArrayValuable {
OWNER(1, "负责人"),
READ(2, "只读"),
WRITE(3, "读写");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmPermissionLevelEnum::getLevel).toArray();
/**
* 级别
*/
private final Integer level;
/**
* 级别名称
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
public static boolean isOwner(Integer level) {
return ObjUtil.equal(OWNER.level, level);
}
public static boolean isRead(Integer level) {
return ObjUtil.equal(READ.level, level);
}
public static boolean isWrite(Integer level) {
return ObjUtil.equal(WRITE.level, level);
}
public static String getNameByLevel(Integer level) {
CrmPermissionLevelEnum typeEnum = CollUtil.findOne(CollUtil.newArrayList(CrmPermissionLevelEnum.values()),
item -> ObjUtil.equal(item.level, level));
return typeEnum == null ? null : typeEnum.getName();
}
}

View File

@ -1,222 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.business;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.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.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
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.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
@Tag(name = "管理后台 - CRM 商机")
@RestController
@RequestMapping("/crm/business")
@Validated
public class CrmBusinessController {
@Resource
private CrmBusinessService businessService;
@Resource
private CrmCustomerService customerService;
@Resource
private CrmBusinessStatusService businessStatusTypeService;
@Resource
private CrmBusinessStatusService businessStatusService;
@Resource
private CrmProductService productService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@PostMapping("/create")
@Operation(summary = "创建商机")
@PreAuthorize("@ss.hasPermission('crm:business:create')")
public CommonResult<Long> createBusiness(@Valid @RequestBody CrmBusinessSaveReqVO createReqVO) {
return success(businessService.createBusiness(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新商机")
@PreAuthorize("@ss.hasPermission('crm:business:update')")
public CommonResult<Boolean> updateBusiness(@Valid @RequestBody CrmBusinessSaveReqVO updateReqVO) {
businessService.updateBusiness(updateReqVO);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新商机状态")
@PreAuthorize("@ss.hasPermission('crm:business:update')")
public CommonResult<Boolean> updateBusinessStatus(@Valid @RequestBody CrmBusinessUpdateStatusReqVO updateStatusReqVO) {
businessService.updateBusinessStatus(updateStatusReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除商机")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:business:delete')")
public CommonResult<Boolean> deleteBusiness(@RequestParam("id") Long id) {
businessService.deleteBusiness(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得商机")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<CrmBusinessRespVO> getBusiness(@RequestParam("id") Long id) {
CrmBusinessDO business = businessService.getBusiness(id);
return success(buildBusinessDetail(business));
}
private CrmBusinessRespVO buildBusinessDetail(CrmBusinessDO business) {
if (business == null) {
return null;
}
CrmBusinessRespVO businessVO = buildBusinessDetailList(Collections.singletonList(business)).get(0);
// 拼接产品项
List<CrmBusinessProductDO> businessProducts = businessService.getBusinessProductListByBusinessId(businessVO.getId());
Map<Long, CrmProductDO> productMap = productService.getProductMap(
convertSet(businessProducts, CrmBusinessProductDO::getProductId));
businessVO.setProducts(BeanUtils.toBean(businessProducts, CrmBusinessRespVO.Product.class, businessProductVO ->
MapUtils.findAndThen(productMap, businessProductVO.getProductId(),
product -> businessProductVO.setProductName(product.getName())
.setProductNo(product.getNo()).setProductUnit(product.getUnit()))));
return businessVO;
}
@GetMapping("/simple-all-list")
@Operation(summary = "获得联系人的精简列表")
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<List<CrmBusinessRespVO>> getSimpleContactList() {
CrmBusinessPageReqVO reqVO = new CrmBusinessPageReqVO();
reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(reqVO, getLoginUserId());
return success(convertList(pageResult.getList(), business -> // 只返回 idname 字段
new CrmBusinessRespVO().setId(business.getId()).setName(business.getName())
.setCustomerId(business.getCustomerId())));
}
@GetMapping("/page")
@Operation(summary = "获得商机分页")
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) {
PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId());
return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/page-by-customer")
@Operation(summary = "获得商机分页,基于指定客户")
public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByCustomer(@Valid CrmBusinessPageReqVO pageReqVO) {
if (pageReqVO.getCustomerId() == null) {
throw exception(CUSTOMER_NOT_EXISTS);
}
PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO);
return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
}
@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(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/export-excel")
@Operation(summary = "导出商机 Excel")
@PreAuthorize("@ss.hasPermission('crm:business:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportBusinessExcel(@Valid CrmBusinessPageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
exportReqVO.setPageSize(PAGE_SIZE_NONE);
List<CrmBusinessDO> list = businessService.getBusinessPage(exportReqVO, getLoginUserId()).getList();
// 导出 Excel
ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class,
buildBusinessDetailList(list));
}
private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
if (CollUtil.isEmpty(list)) {
return Collections.emptyList();
}
// 1.1 获取客户列表
Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
convertSet(list, CrmBusinessDO::getCustomerId));
// 1.2 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 1.3 获得商机状态组
Map<Long, CrmBusinessStatusTypeDO> statusTypeMap = businessStatusTypeService.getBusinessStatusTypeMap(
convertSet(list, CrmBusinessDO::getStatusTypeId));
Map<Long, CrmBusinessStatusDO> statusMap = businessStatusService.getBusinessStatusMap(
convertSet(list, CrmBusinessDO::getStatusId));
// 2. 拼接数据
return BeanUtils.toBean(list, CrmBusinessRespVO.class, businessVO -> {
// 2.1 设置客户名称
MapUtils.findAndThen(customerMap, businessVO.getCustomerId(), customer -> businessVO.setCustomerName(customer.getName()));
// 2.2 设置创建人负责人名称
MapUtils.findAndThen(userMap, NumberUtils.parseLong(businessVO.getCreator()),
user -> businessVO.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, businessVO.getOwnerUserId(), user -> {
businessVO.setOwnerUserName(user.getNickname());
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> businessVO.setOwnerUserDeptName(dept.getName()));
});
// 2.3 设置商机状态
MapUtils.findAndThen(statusTypeMap, businessVO.getStatusTypeId(), statusType -> businessVO.setStatusTypeName(statusType.getName()));
MapUtils.findAndThen(statusMap, businessVO.getStatusId(), status -> businessVO.setStatusName(
businessService.getBusinessStatusName(businessVO.getEndStatus(), status)));
});
}
@PutMapping("/transfer")
@Operation(summary = "商机转移")
@PreAuthorize("@ss.hasPermission('crm:business:update')")
public CommonResult<Boolean> transferBusiness(@Valid @RequestBody CrmBusinessTransferReqVO reqVO) {
businessService.transferBusiness(reqVO, getLoginUserId());
return success(true);
}
}

View File

@ -1,95 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction;
import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction;
import com.mzt.logapi.starter.annotation.DiffLogField;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 商机创建/更新 Request VO")
@Data
public class CrmBusinessSaveReqVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
private Long id;
@Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@DiffLogField(name = "商机名称")
@NotNull(message = "商机名称不能为空")
private String name;
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
@DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
@NotNull(message = "客户不能为空")
private Long customerId;
@Schema(description = "下次联系时间")
@DiffLogField(name = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime contactNextTime;
@Schema(description = "负责人用户编号", example = "14334")
@NotNull(message = "负责人不能为空")
@DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
private Long ownerUserId;
@Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
@DiffLogField(name = "商机状态组")
@NotNull(message = "商机状态组不能为空")
private Long statusTypeId;
@Schema(description = "预计成交日期")
@DiffLogField(name = "预计成交日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime dealTime;
@Schema(description = "整单折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "55.00")
@DiffLogField(name = "整单折扣")
@NotNull(message = "整单折扣不能为空")
private BigDecimal discountPercent;
@Schema(description = "备注", example = "随便")
@DiffLogField(name = "备注")
private String remark;
@Schema(description = "联系人编号", example = "110")
private Long contactId; // 使用场景联系人详情添加商机时如果需要关联两者需要传递 contactId 字段
@Schema(description = "产品列表")
private List<BusinessProduct> businessProducts;
@Schema(description = "产品列表")
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class BusinessProduct {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
@NotNull(message = "产品编号不能为空")
private Long productId;
@Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
@NotNull(message = "产品单价不能为空")
private BigDecimal productPrice;
@Schema(description = "商机价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
@NotNull(message = "商机价格不能为空")
private BigDecimal businessPrice;
@Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
@NotNull(message = "产品数量不能为空")
private Integer count;
}
}

View File

@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 商机转移 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CrmBusinessTransferReqVO {
@Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "商机编号不能为空")
private Long id;
/**
* 新负责人的用户编号
*/
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
/**
* 老负责人加入团队后的权限级别如果 null 说明移除
*
* 关联 {@link CrmPermissionLevelEnum}
*/
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
}

View File

@ -1,173 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.clue;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.service.clue.CrmClueService;
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.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
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.convertListByFlatMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static java.util.Collections.singletonList;
@Tag(name = "管理后台 - 线索")
@RestController
@RequestMapping("/crm/clue")
@Validated
public class CrmClueController {
@Resource
private CrmClueService clueService;
@Resource
private CrmCustomerService customerService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@PostMapping("/create")
@Operation(summary = "创建线索")
@PreAuthorize("@ss.hasPermission('crm:clue:create')")
public CommonResult<Long> createClue(@Valid @RequestBody CrmClueSaveReqVO createReqVO) {
return success(clueService.createClue(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新线索")
@PreAuthorize("@ss.hasPermission('crm:clue:update')")
public CommonResult<Boolean> updateClue(@Valid @RequestBody CrmClueSaveReqVO updateReqVO) {
clueService.updateClue(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除线索")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:clue:delete')")
public CommonResult<Boolean> deleteClue(@RequestParam("id") Long id) {
clueService.deleteClue(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得线索")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:clue:query')")
public CommonResult<CrmClueRespVO> getClue(@RequestParam("id") Long id) {
CrmClueDO clue = clueService.getClue(id);
return success(buildClueDetail(clue));
}
private CrmClueRespVO buildClueDetail(CrmClueDO clue) {
if (clue == null) {
return null;
}
return buildClueDetailList(singletonList(clue)).get(0);
}
@GetMapping("/page")
@Operation(summary = "获得线索分页")
@PreAuthorize("@ss.hasPermission('crm:clue:query')")
public CommonResult<PageResult<CrmClueRespVO>> getCluePage(@Valid CrmCluePageReqVO pageVO) {
PageResult<CrmClueDO> pageResult = clueService.getCluePage(pageVO, getLoginUserId());
return success(new PageResult<>(buildClueDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/export-excel")
@Operation(summary = "导出线索 Excel")
@PreAuthorize("@ss.hasPermission('crm:clue:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportClueExcel(@Valid CrmCluePageReqVO pageReqVO, HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PAGE_SIZE_NONE);
List<CrmClueDO> list = clueService.getCluePage(pageReqVO, getLoginUserId()).getList();
// 导出 Excel
ExcelUtils.write(response, "线索.xls", "数据", CrmClueRespVO.class, buildClueDetailList(list));
}
private List<CrmClueRespVO> buildClueDetailList(List<CrmClueDO> list) {
if (CollUtil.isEmpty(list)) {
return Collections.emptyList();
}
// 1.1 获取客户列表
Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
convertSet(list, CrmClueDO::getCustomerId));
// 1.2 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 2. 转换成 VO
return BeanUtils.toBean(list, CrmClueRespVO.class, clueVO -> {
clueVO.setAreaName(AreaUtils.format(clueVO.getAreaId()));
// 2.1 设置客户名称
MapUtils.findAndThen(customerMap, clueVO.getCustomerId(), customer -> clueVO.setCustomerName(customer.getName()));
// 2.2 设置创建人负责人名称
MapUtils.findAndThen(userMap, NumberUtils.parseLong(clueVO.getCreator()),
user -> clueVO.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, clueVO.getOwnerUserId(), user -> {
clueVO.setOwnerUserName(user.getNickname());
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> clueVO.setOwnerUserDeptName(dept.getName()));
});
});
}
@PutMapping("/transfer")
@Operation(summary = "线索转移")
@PreAuthorize("@ss.hasPermission('crm:clue:update')")
public CommonResult<Boolean> transferClue(@Valid @RequestBody CrmClueTransferReqVO reqVO) {
clueService.transferClue(reqVO, getLoginUserId());
return success(true);
}
@PutMapping("/transform")
@Operation(summary = "线索转化为客户")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:clue:update')")
public CommonResult<Boolean> transformClue(@RequestParam("id") Long id) {
clueService.transformClue(id, getLoginUserId());
return success(Boolean.TRUE);
}
@GetMapping("/follow-count")
@Operation(summary = "获得分配给我的、待跟进的线索数量")
@PreAuthorize("@ss.hasPermission('crm:clue:query')")
public CommonResult<Long> getFollowClueCount() {
return success(clueService.getFollowClueCount(getLoginUserId()));
}
}

View File

@ -1,226 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
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.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.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static java.util.Collections.singletonList;
@Tag(name = "管理后台 - CRM 联系人")
@RestController
@RequestMapping("/crm/contact")
@Validated
@Slf4j
public class CrmContactController {
@Resource
private CrmContactService contactService;
@Resource
private CrmCustomerService customerService;
@Resource
private CrmContactBusinessService contactBusinessLinkService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@PostMapping("/create")
@Operation(summary = "创建联系人")
@PreAuthorize("@ss.hasPermission('crm:contact:create')")
public CommonResult<Long> createContact(@Valid @RequestBody CrmContactSaveReqVO createReqVO) {
return success(contactService.createContact(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新联系人")
@PreAuthorize("@ss.hasPermission('crm:contact:update')")
public CommonResult<Boolean> updateContact(@Valid @RequestBody CrmContactSaveReqVO updateReqVO) {
contactService.updateContact(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除联系人")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:contact:delete')")
public CommonResult<Boolean> deleteContact(@RequestParam("id") Long id) {
contactService.deleteContact(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得联系人")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<CrmContactRespVO> getContact(@RequestParam("id") Long id) {
CrmContactDO contact = contactService.getContact(id);
return success(buildContactDetail(contact));
}
private CrmContactRespVO buildContactDetail(CrmContactDO contact) {
if (contact == null) {
return null;
}
return buildContactDetailList(singletonList(contact)).get(0);
}
@GetMapping("/simple-all-list")
@Operation(summary = "获得联系人的精简列表")
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<List<CrmContactRespVO>> getSimpleContactList() {
List<CrmContactDO> list = contactService.getContactList(getLoginUserId());
return success(convertList(list, contact -> // 只返回 idname 字段
new CrmContactRespVO().setId(contact.getId()).setName(contact.getName())
.setCustomerId(contact.getCustomerId())));
}
@GetMapping("/page")
@Operation(summary = "获得联系人分页")
@PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<PageResult<CrmContactRespVO>> getContactPage(@Valid CrmContactPageReqVO pageVO) {
PageResult<CrmContactDO> pageResult = contactService.getContactPage(pageVO, getLoginUserId());
return success(new PageResult<>(buildContactDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/page-by-customer")
@Operation(summary = "获得联系人分页,基于指定客户")
public CommonResult<PageResult<CrmContactRespVO>> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) {
Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
PageResult<CrmContactDO> pageResult = contactService.getContactPageByCustomerId(pageVO);
return success(new PageResult<>(buildContactDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/page-by-business")
@Operation(summary = "获得联系人分页,基于指定商机")
public CommonResult<PageResult<CrmContactRespVO>> getContactPageByBusiness(@Valid CrmContactPageReqVO pageVO) {
Assert.notNull(pageVO.getBusinessId(), "商机编号不能为空");
PageResult<CrmContactDO> pageResult = contactService.getContactPageByBusinessId(pageVO);
return success(new PageResult<>(buildContactDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/export-excel")
@Operation(summary = "导出联系人 Excel")
@PreAuthorize("@ss.hasPermission('crm:contact:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportContactExcel(@Valid CrmContactPageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
exportReqVO.setPageNo(PAGE_SIZE_NONE);
List<CrmContactDO> list = contactService.getContactPage(exportReqVO, getLoginUserId()).getList();
ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class, buildContactDetailList(list));
}
private List<CrmContactRespVO> buildContactDetailList(List<CrmContactDO> contactList) {
if (CollUtil.isEmpty(contactList)) {
return Collections.emptyList();
}
// 1.1 获取客户列表
Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
convertSet(contactList, CrmContactDO::getCustomerId));
// 1.2 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 1.3 直属上级 Map
Map<Long, CrmContactDO> parentContactMap = contactService.getContactMap(
convertSet(contactList, CrmContactDO::getParentId));
// 2. 转换成 VO
return BeanUtils.toBean(contactList, CrmContactRespVO.class, contactVO -> {
contactVO.setAreaName(AreaUtils.format(contactVO.getAreaId()));
// 2.1 设置客户名称
MapUtils.findAndThen(customerMap, contactVO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName()));
// 2.2 设置创建人负责人名称
MapUtils.findAndThen(userMap, NumberUtils.parseLong(contactVO.getCreator()),
user -> contactVO.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, contactVO.getOwnerUserId(), user -> {
contactVO.setOwnerUserName(user.getNickname());
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> contactVO.setOwnerUserDeptName(dept.getName()));
});
// 2.3 设置直属上级名称
findAndThen(parentContactMap, contactVO.getParentId(), contact -> contactVO.setParentName(contact.getName()));
});
}
@PutMapping("/transfer")
@Operation(summary = "联系人转移")
@PreAuthorize("@ss.hasPermission('crm:contact:update')")
public CommonResult<Boolean> transferContact(@Valid @RequestBody CrmContactTransferReqVO reqVO) {
contactService.transferContact(reqVO, getLoginUserId());
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);
}
@PostMapping("/create-business-list2")
@Operation(summary = "创建联系人与商机的关联")
@PreAuthorize("@ss.hasPermission('crm:contact:create-business')")
public CommonResult<Boolean> createContactBusinessList2(@Valid @RequestBody CrmContactBusiness2ReqVO createReqVO) {
contactBusinessLinkService.createContactBusinessList2(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);
}
@DeleteMapping("/delete-business-list2")
@Operation(summary = "删除联系人与联系人的关联")
@PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
public CommonResult<Boolean> deleteContactBusinessList(@Valid @RequestBody CrmContactBusiness2ReqVO deleteReqVO) {
contactBusinessLinkService.deleteContactBusinessList2(deleteReqVO);
return success(true);
}
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - CRM 联系人转移 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CrmContactTransferReqVO {
@Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "联系人编号不能为空")
private Long id;
/**
* 新负责人的用户编号
*/
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
/**
* 老负责人加入团队后的权限级别如果 null 说明移除
*
* 关联 {@link CrmPermissionLevelEnum}
*/
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
}

View File

@ -1,256 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static java.util.Collections.singletonList;
@Tag(name = "管理后台 - CRM 合同")
@RestController
@RequestMapping("/crm/contract")
@Validated
public class CrmContractController {
@Resource
private CrmContractService contractService;
@Resource
private CrmCustomerService customerService;
@Resource
private CrmContactService contactService;
@Resource
private CrmBusinessService businessService;
@Resource
private CrmProductService productService;
@Resource
private CrmReceivableService receivableService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@PostMapping("/create")
@Operation(summary = "创建合同")
@PreAuthorize("@ss.hasPermission('crm:contract:create')")
public CommonResult<Long> createContract(@Valid @RequestBody CrmContractSaveReqVO createReqVO) {
return success(contractService.createContract(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新合同")
@PreAuthorize("@ss.hasPermission('crm:contract:update')")
public CommonResult<Boolean> updateContract(@Valid @RequestBody CrmContractSaveReqVO updateReqVO) {
contractService.updateContract(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除合同")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:contract:delete')")
public CommonResult<Boolean> deleteContract(@RequestParam("id") Long id) {
contractService.deleteContract(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得合同")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<CrmContractRespVO> getContract(@RequestParam("id") Long id) {
CrmContractDO contract = contractService.getContract(id);
return success(buildContractDetail(contract));
}
private CrmContractRespVO buildContractDetail(CrmContractDO contract) {
if (contract == null) {
return null;
}
CrmContractRespVO contractVO = buildContractDetailList(singletonList(contract)).get(0);
// 拼接产品项
List<CrmContractProductDO> businessProducts = contractService.getContractProductListByContractId(contractVO.getId());
Map<Long, CrmProductDO> productMap = productService.getProductMap(
convertSet(businessProducts, CrmContractProductDO::getProductId));
contractVO.setProducts(BeanUtils.toBean(businessProducts, CrmContractRespVO.Product.class, businessProductVO ->
MapUtils.findAndThen(productMap, businessProductVO.getProductId(),
product -> businessProductVO.setProductName(product.getName())
.setProductNo(product.getNo()).setProductUnit(product.getUnit()))));
return contractVO;
}
@GetMapping("/page")
@Operation(summary = "获得合同分页")
@PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<PageResult<CrmContractRespVO>> getContractPage(@Valid CrmContractPageReqVO pageVO) {
PageResult<CrmContractDO> pageResult = contractService.getContractPage(pageVO, getLoginUserId());
return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList())));
}
@GetMapping("/page-by-customer")
@Operation(summary = "获得合同分页,基于指定客户")
public CommonResult<PageResult<CrmContractRespVO>> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) {
Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageVO);
return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList())));
}
@GetMapping("/page-by-business")
@Operation(summary = "获得合同分页,基于指定商机")
public CommonResult<PageResult<CrmContractRespVO>> getContractPageByBusiness(@Valid CrmContractPageReqVO pageVO) {
Assert.notNull(pageVO.getBusinessId(), "商机编号不能为空");
PageResult<CrmContractDO> pageResult = contractService.getContractPageByBusinessId(pageVO);
return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList())));
}
@GetMapping("/export-excel")
@Operation(summary = "导出合同 Excel")
@PreAuthorize("@ss.hasPermission('crm:contract:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportContractExcel(@Valid CrmContractPageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
PageResult<CrmContractDO> pageResult = contractService.getContractPage(exportReqVO, getLoginUserId());
// 导出 Excel
ExcelUtils.write(response, "合同.xls", "数据", CrmContractRespVO.class,
BeanUtils.toBean(pageResult.getList(), CrmContractRespVO.class));
}
@PutMapping("/transfer")
@Operation(summary = "合同转移")
@PreAuthorize("@ss.hasPermission('crm:contract:update')")
public CommonResult<Boolean> transferContract(@Valid @RequestBody CrmContractTransferReqVO reqVO) {
contractService.transferContract(reqVO, getLoginUserId());
return success(true);
}
@PutMapping("/submit")
@Operation(summary = "提交合同审批")
@PreAuthorize("@ss.hasPermission('crm:contract:update')")
public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {
contractService.submitContract(id, getLoginUserId());
return success(true);
}
private List<CrmContractRespVO> buildContractDetailList(List<CrmContractDO> contractList) {
if (CollUtil.isEmpty(contractList)) {
return Collections.emptyList();
}
// 1.1 获取客户列表
Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
convertSet(contractList, CrmContractDO::getCustomerId));
// 1.2 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contractList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 1.3 获取联系人
Map<Long, CrmContactDO> contactMap = convertMap(contactService.getContactList(convertSet(contractList,
CrmContractDO::getSignContactId)), CrmContactDO::getId);
// 1.4 获取商机
Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap(
convertSet(contractList, CrmContractDO::getBusinessId));
// 1.5 获得已回款金额
Map<Long, BigDecimal> receivablePriceMap = receivableService.getReceivablePriceMapByContractId(
convertSet(contractList, CrmContractDO::getId));
// 2. 拼接数据
return BeanUtils.toBean(contractList, CrmContractRespVO.class, contractVO -> {
// 2.1 设置客户信息
findAndThen(customerMap, contractVO.getCustomerId(), customer -> contractVO.setCustomerName(customer.getName()));
// 2.2 设置用户信息
findAndThen(userMap, Long.parseLong(contractVO.getCreator()), user -> contractVO.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, contractVO.getOwnerUserId(), user -> {
contractVO.setOwnerUserName(user.getNickname());
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> contractVO.setOwnerUserDeptName(dept.getName()));
});
findAndThen(userMap, contractVO.getSignUserId(), user -> contractVO.setSignUserName(user.getNickname()));
// 2.3 设置联系人信息
findAndThen(contactMap, contractVO.getSignContactId(), contact -> contractVO.setSignContactName(contact.getName()));
// 2.4 设置商机信息
findAndThen(businessMap, contractVO.getBusinessId(), business -> contractVO.setBusinessName(business.getName()));
// 2.5 设置已回款金额
contractVO.setTotalReceivablePrice(receivablePriceMap.getOrDefault(contractVO.getId(), BigDecimal.ZERO));
});
}
@GetMapping("/audit-count")
@Operation(summary = "获得待审核合同数量")
@PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<Long> getAuditContractCount() {
return success(contractService.getAuditContractCount(getLoginUserId()));
}
@GetMapping("/remind-count")
@Operation(summary = "获得即将到期(提醒)的合同数量")
@PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<Long> getRemindContractCount() {
return success(contractService.getRemindContractCount(getLoginUserId()));
}
@GetMapping("/simple-list")
@Operation(summary = "获得合同精简列表", description = "只包含的合同,主要用于前端的下拉选项")
@Parameter(name = "customerId", description = "客户编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<List<CrmContractRespVO>> getContractSimpleList(@RequestParam("customerId") Long customerId) {
CrmContractPageReqVO pageReqVO = new CrmContractPageReqVO().setCustomerId(customerId);
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); // 不分页
PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(Collections.emptyList());
}
// 拼接数据
Map<Long, BigDecimal> receivablePriceMap = receivableService.getReceivablePriceMapByContractId(
convertSet(pageResult.getList(), CrmContractDO::getId));
return success(convertList(pageResult.getList(), contract -> new CrmContractRespVO() // 只返回 idname 等精简字段
.setId(contract.getId()).setName(contract.getName()).setAuditStatus(contract.getAuditStatus())
.setTotalPrice(contract.getTotalPrice())
.setTotalReceivablePrice(receivablePriceMap.getOrDefault(contract.getId(), BigDecimal.ZERO))));
}
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - CRM 合同转移 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CrmContractTransferReqVO {
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "联系人编号不能为空")
private Long id;
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(value = CrmPermissionLevelEnum.class)
private Integer oldOwnerPermissionLevel;
}

View File

@ -1,316 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
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.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static java.util.Collections.singletonList;
@Tag(name = "管理后台 - CRM 客户")
@RestController
@RequestMapping("/crm/customer")
@Validated
public class CrmCustomerController {
@Resource
private CrmCustomerService customerService;
@Resource
private CrmCustomerPoolConfigService customerPoolConfigService;
@Resource
private DeptApi deptApi;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建客户")
@PreAuthorize("@ss.hasPermission('crm:customer:create')")
public CommonResult<Long> createCustomer(@Valid @RequestBody CrmCustomerSaveReqVO createReqVO) {
return success(customerService.createCustomer(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新客户")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> updateCustomer(@Valid @RequestBody CrmCustomerSaveReqVO updateReqVO) {
customerService.updateCustomer(updateReqVO);
return success(true);
}
@PutMapping("/update-deal-status")
@Operation(summary = "更新客户的成交状态")
@Parameters({
@Parameter(name = "id", description = "客户编号", required = true),
@Parameter(name = "dealStatus", description = "成交状态", required = true)
})
public CommonResult<Boolean> updateCustomerDealStatus(@RequestParam("id") Long id,
@RequestParam("dealStatus") Boolean dealStatus) {
customerService.updateCustomerDealStatus(id, dealStatus);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除客户")
@Parameter(name = "id", description = "客户编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:customer:delete')")
public CommonResult<Boolean> deleteCustomer(@RequestParam("id") Long id) {
customerService.deleteCustomer(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得客户")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<CrmCustomerRespVO> getCustomer(@RequestParam("id") Long id) {
// 1. 获取客户
CrmCustomerDO customer = customerService.getCustomer(id);
// 2. 拼接数据
return success(buildCustomerDetail(customer));
}
public CrmCustomerRespVO buildCustomerDetail(CrmCustomerDO customer) {
if (customer == null) {
return null;
}
return buildCustomerDetailList(singletonList(customer)).get(0);
}
@GetMapping("/page")
@Operation(summary = "获得客户分页")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
// 1. 查询客户分页
PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId());
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
}
// 2. 拼接数据
return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal()));
}
public List<CrmCustomerRespVO> buildCustomerDetailList(List<CrmCustomerDO> list) {
if (CollUtil.isEmpty(list)) {
return java.util.Collections.emptyList();
}
// 1.1 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(list,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 1.2 获取距离进入公海的时间
Map<Long, Long> poolDayMap = getPoolDayMap(list);
// 2. 转换成 VO
return BeanUtils.toBean(list, CrmCustomerRespVO.class, customerVO -> {
customerVO.setAreaName(AreaUtils.format(customerVO.getAreaId()));
// 2.1 设置创建人负责人名称
MapUtils.findAndThen(userMap, NumberUtils.parseLong(customerVO.getCreator()),
user -> customerVO.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, customerVO.getOwnerUserId(), user -> {
customerVO.setOwnerUserName(user.getNickname());
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> customerVO.setOwnerUserDeptName(dept.getName()));
});
// 2.2 设置距离进入公海的时间
if (customerVO.getOwnerUserId() != null) {
customerVO.setPoolDay(poolDayMap.get(customerVO.getId()));
}
});
}
@GetMapping("/put-pool-remind-page")
@Operation(summary = "获得待进入公海客户分页")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<PageResult<CrmCustomerRespVO>> getPutPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
// 1. 查询客户分页
PageResult<CrmCustomerDO> pageResult = customerService.getPutPoolRemindCustomerPage(pageVO, getLoginUserId());
// 2. 拼接数据
return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/put-pool-remind-count")
@Operation(summary = "获得待进入公海客户数量")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<Long> getPutPoolRemindCustomerCount() {
return success(customerService.getPutPoolRemindCustomerCount(getLoginUserId()));
}
@GetMapping("/today-contact-count")
@Operation(summary = "获得今日需联系客户数量")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<Long> getTodayContactCustomerCount() {
return success(customerService.getTodayContactCustomerCount(getLoginUserId()));
}
@GetMapping("/follow-count")
@Operation(summary = "获得分配给我、待跟进的线索数量的客户数量")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<Long> getFollowCustomerCount() {
return success(customerService.getFollowCustomerCount(getLoginUserId()));
}
/**
* 获取距离进入公海的时间 Map
*
* @param list 客户列表
* @return key 客户编号, value 距离进入公海的时间
*/
private Map<Long, Long> getPoolDayMap(List<CrmCustomerDO> list) {
CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
if (poolConfig == null || !poolConfig.getEnabled()) {
return MapUtil.empty();
}
list = CollectionUtils.filterList(list, customer -> {
// 特殊如果没负责人则说明已经在公海不用计算
if (customer.getOwnerUserId() == null) {
return false;
}
// 已成交 or 已锁定不进入公海
return !customer.getDealStatus() && !customer.getLockStatus();
});
return convertMap(list, CrmCustomerDO::getId, customer -> {
// 1.1 未成交放入公海天数
long dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getOwnerTime());
// 1.2 未跟进放入公海天数
LocalDateTime lastTime = customer.getOwnerTime();
if (customer.getContactLastTime() != null && customer.getContactLastTime().isAfter(lastTime)) {
lastTime = customer.getContactLastTime();
}
long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime);
// 2. 返回最小的天数
long poolDay = Math.min(dealExpireDay, contactExpireDay);
return poolDay > 0 ? poolDay : 0;
});
}
@GetMapping(value = "/simple-list")
@Operation(summary = "获取客户精简信息列表", description = "只包含有读权限的客户,主要用于前端的下拉选项")
public CommonResult<List<CrmCustomerRespVO>> getCustomerSimpleList() {
CrmCustomerPageReqVO reqVO = new CrmCustomerPageReqVO();
reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
List<CrmCustomerDO> list = customerService.getCustomerPage(reqVO, getLoginUserId()).getList();
return success(convertList(list, customer -> // 只返回 idname 精简字段
new CrmCustomerRespVO().setId(customer.getId()).setName(customer.getName())));
}
@GetMapping("/export-excel")
@Operation(summary = "导出客户 Excel")
@PreAuthorize("@ss.hasPermission('crm:customer:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportCustomerExcel(@Valid CrmCustomerPageReqVO pageVO,
HttpServletResponse response) throws IOException {
pageVO.setPageSize(PAGE_SIZE_NONE); // 不分页
List<CrmCustomerDO> list = customerService.getCustomerPage(pageVO, getLoginUserId()).getList();
// 导出 Excel
ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerRespVO.class,
buildCustomerDetailList(list));
}
@GetMapping("/get-import-template")
@Operation(summary = "获得导入客户模板")
public void importTemplate(HttpServletResponse response) throws IOException {
// 手动创建导出 demo
List<CrmCustomerImportExcelVO> list = Arrays.asList(
CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1)
.mobile("15601691300").telephone("").qq("").wechat("").email("yunai@iocoder.cn")
.areaId(null).detailAddress("").remark("").build(),
CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1)
.mobile("15601691300").telephone("").qq("").wechat("").email("yunai@iocoder.cn")
.areaId(null).detailAddress("").remark("").build()
);
// 输出
ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list);
}
@PostMapping("/import")
@Operation(summary = "导入客户")
@PreAuthorize("@ss.hasPermission('crm:customer:import')")
public CommonResult<CrmCustomerImportRespVO> importExcel(@Valid CrmCustomerImportReqVO importReqVO)
throws Exception {
List<CrmCustomerImportExcelVO> list = ExcelUtils.read(importReqVO.getFile(), CrmCustomerImportExcelVO.class);
return success(customerService.importCustomerList(list, importReqVO));
}
@PutMapping("/transfer")
@Operation(summary = "转移客户")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> transferCustomer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
customerService.transferCustomer(reqVO, getLoginUserId());
return success(true);
}
@PutMapping("/lock")
@Operation(summary = "锁定/解锁客户")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerLockReqVO lockReqVO) {
customerService.lockCustomer(lockReqVO, getLoginUserId());
return success(true);
}
// ==================== 公海相关操作 ====================
@PutMapping("/put-pool")
@Operation(summary = "数据放入公海")
@Parameter(name = "id", description = "客户编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> putCustomerPool(@RequestParam("id") Long id) {
customerService.putCustomerPool(id);
return success(true);
}
@PutMapping("/receive")
@Operation(summary = "领取公海客户")
@Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3")
@PreAuthorize("@ss.hasPermission('crm:customer:receive')")
public CommonResult<Boolean> receiveCustomer(@RequestParam(value = "ids") List<Long> ids) {
customerService.receiveCustomer(ids, getLoginUserId(), Boolean.TRUE);
return success(true);
}
@PutMapping("/distribute")
@Operation(summary = "分配公海给对应负责人")
@PreAuthorize("@ss.hasPermission('crm:customer:distribute')")
public CommonResult<Boolean> distributeCustomer(@Valid @RequestBody CrmCustomerDistributeReqVO distributeReqVO) {
customerService.receiveCustomer(distributeReqVO.getIds(), distributeReqVO.getOwnerUserId(), Boolean.FALSE);
return success(true);
}
}

View File

@ -1,70 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunction;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
/**
* 客户 Excel 导入 VO
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false) // 设置 chain = false避免用户导入有问题
public class CrmCustomerImportExcelVO {
@ExcelProperty("客户名称")
private String name;
@ExcelProperty("手机")
private String mobile;
@ExcelProperty("电话")
private String telephone;
@ExcelProperty("QQ")
private String qq;
@ExcelProperty("微信")
private String wechat;
@ExcelProperty("邮箱")
private String email;
@ExcelProperty(value = "地区", converter = AreaConvert.class)
@ExcelColumnSelect(functionName = AreaExcelColumnSelectFunction.NAME)
private Integer areaId;
@ExcelProperty("详细地址")
private String detailAddress;
@ExcelProperty(value = "所属行业", converter = DictConvert.class)
@DictFormat(CRM_CUSTOMER_INDUSTRY)
@ExcelColumnSelect(dictType = CRM_CUSTOMER_INDUSTRY)
private Integer industryId;
@ExcelProperty(value = "客户等级", converter = DictConvert.class)
@DictFormat(CRM_CUSTOMER_LEVEL)
@ExcelColumnSelect(dictType = CRM_CUSTOMER_LEVEL)
private Integer level;
@ExcelProperty(value = "客户来源", converter = DictConvert.class)
@DictFormat(CRM_CUSTOMER_SOURCE)
@ExcelColumnSelect(dictType = CRM_CUSTOMER_SOURCE)
private Integer source;
@ExcelProperty("备注")
private String remark;
}

View File

@ -1,39 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - CRM 客户转移 Request VO")
@Data
public class CrmCustomerTransferReqVO {
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "客户编号不能为空")
private Long id;
/**
* 新负责人的用户编号
*/
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
/**
* 老负责人加入团队后的权限级别如果 null 说明移除
*
* 关联 {@link CrmPermissionLevelEnum}
*/
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer oldOwnerPermissionLevel;
/**
* 转移客户时需要额外有联系人商机合同 checkbox 选择选中时也一起转移
*/
@Schema(description = "同时转移", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
private List<Integer> toBizTypes;
}

View File

@ -1,100 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.followup;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO;
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.followup.CrmFollowUpRecordDO;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 跟进记录")
@RestController
@RequestMapping("/crm/follow-up-record")
@Validated
public class CrmFollowUpRecordController {
@Resource
private CrmFollowUpRecordService followUpRecordService;
@Resource
private CrmContactService contactService;
@Resource
private CrmBusinessService businessService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建跟进记录")
public CommonResult<Long> createFollowUpRecord(@Valid @RequestBody CrmFollowUpRecordSaveReqVO createReqVO) {
return success(followUpRecordService.createFollowUpRecord(createReqVO));
}
@DeleteMapping("/delete")
@Operation(summary = "删除跟进记录")
@Parameter(name = "id", description = "编号", required = true)
public CommonResult<Boolean> deleteFollowUpRecord(@RequestParam("id") Long id) {
followUpRecordService.deleteFollowUpRecord(id, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得跟进记录")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<CrmFollowUpRecordRespVO> getFollowUpRecord(@RequestParam("id") Long id) {
CrmFollowUpRecordDO followUpRecord = followUpRecordService.getFollowUpRecord(id);
return success(BeanUtils.toBean(followUpRecord, CrmFollowUpRecordRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得跟进记录分页")
public CommonResult<PageResult<CrmFollowUpRecordRespVO>> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) {
PageResult<CrmFollowUpRecordDO> pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO);
// 1.1 查询联系人和商机
Map<Long, CrmContactDO> contactMap = contactService.getContactMap(
convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream()));
Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap(
convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream()));
// 1.2 查询用户
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(pageResult.getList(), item -> Long.valueOf(item.getCreator())));
// 2. 拼接数据
PageResult<CrmFollowUpRecordRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmFollowUpRecordRespVO.class, record -> {
// 2.1 设置联系人和商机信息
record.setBusinesses(new ArrayList<>()).setContacts(new ArrayList<>());
record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id, contact ->
record.getContacts().add(new CrmBusinessRespVO().setId(contact.getId()).setName(contact.getName()))));
record.getBusinessIds().forEach(id -> MapUtils.findAndThen(businessMap, id, business ->
record.getBusinesses().add(new CrmBusinessRespVO().setId(business.getId()).setName(business.getName()))));
// 2.2 设置用户信息
MapUtils.findAndThen(userMap, Long.valueOf(record.getCreator()), user -> record.setCreatorName(user.getNickname()));
});
return success(voPageResult);
}
}

View File

@ -1,63 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.operatelog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogRespVO;
import cn.iocoder.yudao.module.crm.enums.LogRecordConstants;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogPageReqDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
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.module.crm.enums.LogRecordConstants.*;
@Tag(name = "管理后台 - CRM 操作日志")
@RestController
@RequestMapping("/crm/operate-log")
@Validated
public class CrmOperateLogController {
@Resource
private OperateLogApi operateLogApi;
/**
* {@link CrmBizTypeEnum} {@link LogRecordConstants} 的映射关系
*/
private static final Map<Integer, String> BIZ_TYPE_MAP = new HashMap<>();
static {
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CLUE.getType(), CRM_CLUE_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CUSTOMER.getType(), CRM_CUSTOMER_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTACT.getType(), CRM_CONTACT_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_BUSINESS.getType(), CRM_BUSINESS_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTRACT.getType(), CRM_CONTRACT_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_PRODUCT.getType(), CRM_PRODUCT_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), CRM_RECEIVABLE_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), CRM_RECEIVABLE_PLAN_TYPE);
}
@GetMapping("/page")
@Operation(summary = "获得操作日志")
public CommonResult<PageResult<CrmOperateLogRespVO>> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
OperateLogPageReqDTO reqDTO = new OperateLogPageReqDTO();
reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页需要分页需注释
reqDTO.setType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId());
return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogRespVO.class));
}
}

View File

@ -1,126 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.permission;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.collect.Multimaps;
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.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - CRM 数据权限")
@RestController
@RequestMapping("/crm/permission")
@Validated
public class CrmPermissionController {
@Resource
private CrmPermissionService permissionService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Resource
private PostApi postApi;
@PostMapping("/create")
@Operation(summary = "创建数据权限")
public CommonResult<Boolean> create(@Valid @RequestBody CrmPermissionSaveReqVO reqVO) {
permissionService.createPermission(reqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update")
@Operation(summary = "编辑数据权限")
@CrmPermission(bizTypeValue = "#updateReqVO.bizType", bizId = "#updateReqVO.bizId"
, level = CrmPermissionLevelEnum.OWNER)
public CommonResult<Boolean> updatePermission(@Valid @RequestBody CrmPermissionUpdateReqVO updateReqVO) {
permissionService.updatePermission(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除数据权限")
@Parameter(name = "ids", description = "数据权限编号", required = true, example = "1024")
public CommonResult<Boolean> deletePermission(@RequestParam("ids") Collection<Long> ids) {
permissionService.deletePermissionBatch(ids, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete-self")
@Operation(summary = "删除自己的数据权限")
@Parameter(name = "id", description = "数据权限编号", required = true, example = "1024")
public CommonResult<Boolean> deleteSelfPermission(@RequestParam("id") Long id) {
permissionService.deleteSelfPermission(id, getLoginUserId());
return success(true);
}
@GetMapping("/list")
@Operation(summary = "获得数据权限列表")
@Parameters({
@Parameter(name = "bizType", description = "CRM 类型", required = true, example = "2"),
@Parameter(name = "bizId", description = "CRM 类型数据编号", required = true, example = "1024")
})
public CommonResult<List<CrmPermissionRespVO>> getPermissionList(@RequestParam("bizType") Integer bizType,
@RequestParam("bizId") Long bizId) {
List<CrmPermissionDO> permissions = permissionService.getPermissionListByBiz(bizType, bizId);
if (CollUtil.isEmpty(permissions)) {
return success(Collections.emptyList());
}
// 查询相关数据
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(permissions, CrmPermissionDO::getUserId));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
Map<Long, PostRespDTO> postMap = postApi.getPostMap(
convertSetByFlatMap(userMap.values(), AdminUserRespDTO::getPostIds,
item -> item != null ? item.stream() : Stream.empty()));
// 拼接数据
return success(CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> {
findAndThen(userMap, item.getUserId(), user -> {
item.setNickname(user.getNickname());
findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName()));
if (CollUtil.isEmpty(user.getPostIds())) {
item.setPostNames(Collections.emptySet());
return;
}
List<PostRespDTO> postList = MapUtils.getList(Multimaps.forMap(postMap), user.getPostIds());
item.setPostNames(CollectionUtils.convertSet(postList, PostRespDTO::getName));
});
return item;
}));
}
}

View File

@ -1,50 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.permission.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Set;
@Schema(description = "管理后台 - CRM 数据权限 Response VO")
@Data
public class CrmPermissionRespVO {
@Schema(description = "数据权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
private Long id;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "CRM 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(CrmBizTypeEnum.class)
@NotNull(message = "CRM 类型不能为空")
private Integer bizType;
@Schema(description = "CRM 类型数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "CRM 类型数据编号不能为空")
private Long bizId;
@Schema(description = "权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(CrmPermissionLevelEnum.class)
@NotNull(message = "权限级别不能为空")
private Integer level;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String nickname;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
private String deptName;
@Schema(description = "岗位名称数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[BOOS,经理]")
private Set<String> postNames;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-01-01 00:00:00")
private LocalDateTime createTime;
}

View File

@ -1,42 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.permission.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - CRM 数据权限创建/更新 Request VO")
@Data
public class CrmPermissionSaveReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "CRM 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(CrmBizTypeEnum.class)
@NotNull(message = "CRM 类型不能为空")
private Integer bizType;
@Schema(description = "CRM 类型数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "CRM 类型数据编号不能为空")
private Long bizId;
@Schema(description = "权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(CrmPermissionLevelEnum.class)
@NotNull(message = "权限级别不能为空")
private Integer level;
/**
* 添加客户团队成员时需要额外有联系人商机合同 checkbox 选择
* 选中时同时添加对应的权限
*/
@Schema(description = "同时添加", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
private List<Integer> toBizTypes;
}

View File

@ -1,107 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.product;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.translate.core.TranslateUtils;
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
import cn.iocoder.yudao.module.crm.enums.product.CrmProductStatusEnum;
import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - CRM 产品")
@RestController
@RequestMapping("/crm/product")
@Validated
public class CrmProductController {
@Resource
private CrmProductService productService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建产品")
@PreAuthorize("@ss.hasPermission('crm:product:create')")
public CommonResult<Long> createProduct(@Valid @RequestBody CrmProductSaveReqVO createReqVO) {
return success(productService.createProduct(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新产品")
@PreAuthorize("@ss.hasPermission('crm:product:update')")
public CommonResult<Boolean> updateProduct(@Valid @RequestBody CrmProductSaveReqVO updateReqVO) {
productService.updateProduct(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除产品")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:product:delete')")
public CommonResult<Boolean> deleteProduct(@RequestParam("id") Long id) {
productService.deleteProduct(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得产品")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:product:query')")
public CommonResult<CrmProductRespVO> getProduct(@RequestParam("id") Long id) {
CrmProductDO product = productService.getProduct(id);
return success(BeanUtils.toBean(product, CrmProductRespVO.class));
}
@GetMapping("/simple-list")
@Operation(summary = "获得产品精简列表", description = "只包含被开启的产品,主要用于前端的下拉选项")
public CommonResult<List<CrmProductRespVO>> getProductSimpleList() {
List<CrmProductDO> list = productService.getProductListByStatus(CrmProductStatusEnum.ENABLE.getStatus());
return success(convertList(list, product -> new CrmProductRespVO().setId(product.getId()).setName(product.getName())
.setUnit(product.getUnit()).setNo(product.getNo()).setPrice(product.getPrice())));
}
@GetMapping("/page")
@Operation(summary = "获得产品分页")
@PreAuthorize("@ss.hasPermission('crm:product:query')")
public CommonResult<PageResult<CrmProductRespVO>> getProductPage(@Valid CrmProductPageReqVO pageVO) {
PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO);
return success(BeanUtils.toBean(pageResult, CrmProductRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出产品 Excel")
@PreAuthorize("@ss.hasPermission('crm:product:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportProductExcel(@Valid CrmProductPageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<CrmProductDO> list = productService.getProductPage(exportReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "产品.xls", "数据", CrmProductRespVO.class,
TranslateUtils.translate(BeanUtils.toBean(list, CrmProductRespVO.class)));
}
}

View File

@ -1,183 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.receivable;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
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.convertListByFlatMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - CRM 回款")
@RestController
@RequestMapping("/crm/receivable")
@Validated
public class CrmReceivableController {
@Resource
private CrmReceivableService receivableService;
@Resource
private CrmContractService contractService;
@Resource
private CrmCustomerService customerService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@PostMapping("/create")
@Operation(summary = "创建回款")
@PreAuthorize("@ss.hasPermission('crm:receivable:create')")
public CommonResult<Long> createReceivable(@Valid @RequestBody CrmReceivableSaveReqVO createReqVO) {
return success(receivableService.createReceivable(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新回款")
@PreAuthorize("@ss.hasPermission('crm:receivable:update')")
public CommonResult<Boolean> updateReceivable(@Valid @RequestBody CrmReceivableSaveReqVO updateReqVO) {
receivableService.updateReceivable(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除回款")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:receivable:delete')")
public CommonResult<Boolean> deleteReceivable(@RequestParam("id") Long id) {
receivableService.deleteReceivable(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得回款")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:receivable:query')")
public CommonResult<CrmReceivableRespVO> getReceivable(@RequestParam("id") Long id) {
CrmReceivableDO receivable = receivableService.getReceivable(id);
return success(buildReceivableDetail(receivable));
}
private CrmReceivableRespVO buildReceivableDetail(CrmReceivableDO receivable) {
if (receivable == null) {
return null;
}
return buildReceivableDetailList(Collections.singletonList(receivable)).get(0);
}
@GetMapping("/page")
@Operation(summary = "获得回款分页")
@PreAuthorize("@ss.hasPermission('crm:receivable:query')")
public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) {
PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId());
return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/page-by-customer")
@Operation(summary = "获得回款分页,基于指定客户")
public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) {
Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO);
return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/export-excel")
@Operation(summary = "导出回款 Excel")
@PreAuthorize("@ss.hasPermission('crm:receivable:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportReceivableExcel(@Valid CrmReceivablePageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
exportReqVO.setPageSize(PAGE_SIZE_NONE);
List<CrmReceivableDO> list = receivableService.getReceivablePage(exportReqVO, getLoginUserId()).getList();
// 导出 Excel
ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class,
buildReceivableDetailList(list));
}
private List<CrmReceivableRespVO> buildReceivableDetailList(List<CrmReceivableDO> receivableList) {
if (CollUtil.isEmpty(receivableList)) {
return Collections.emptyList();
}
// 1.1 获取客户列表
Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
convertSet(receivableList, CrmReceivableDO::getCustomerId));
// 1.2 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 1.3 获得合同列表
Map<Long, CrmContractDO> contractMap = contractService.getContractMap(
convertSet(receivableList, CrmReceivableDO::getContractId));
// 2. 拼接结果
return BeanUtils.toBean(receivableList, CrmReceivableRespVO.class, (receivableVO) -> {
// 2.1 拼接客户名称
findAndThen(customerMap, receivableVO.getCustomerId(), customer -> receivableVO.setCustomerName(customer.getName()));
// 2.2 拼接负责人创建人名称
MapUtils.findAndThen(userMap, NumberUtils.parseLong(receivableVO.getCreator()),
user -> receivableVO.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, receivableVO.getOwnerUserId(), user -> {
receivableVO.setOwnerUserName(user.getNickname());
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> receivableVO.setOwnerUserDeptName(dept.getName()));
});
// 2.3 拼接合同信息
findAndThen(contractMap, receivableVO.getContractId(), contract ->
receivableVO.setContract(BeanUtils.toBean(contract, CrmContractRespVO.class)));
});
}
@PutMapping("/submit")
@Operation(summary = "提交回款审批")
@PreAuthorize("@ss.hasPermission('crm:receivable:update')")
public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {
receivableService.submitReceivable(id, getLoginUserId());
return success(true);
}
@GetMapping("/audit-count")
@Operation(summary = "获得待审核回款数量")
@PreAuthorize("@ss.hasPermission('crm:receivable:query')")
public CommonResult<Long> getAuditReceivableCount() {
return success(receivableService.getAuditReceivableCount(getLoginUserId()));
}
}

View File

@ -1,190 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.receivable;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
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.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivablePlanService;
import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
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.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - CRM 回款计划")
@RestController
@RequestMapping("/crm/receivable-plan")
@Validated
public class CrmReceivablePlanController {
@Resource
private CrmReceivablePlanService receivablePlanService;
@Resource
private CrmReceivableService receivableService;
@Resource
private CrmContractService contractService;
@Resource
private CrmCustomerService customerService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建回款计划")
@PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')")
public CommonResult<Long> createReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO createReqVO) {
return success(receivablePlanService.createReceivablePlan(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新回款计划")
@PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')")
public CommonResult<Boolean> updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO updateReqVO) {
receivablePlanService.updateReceivablePlan(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除回款计划")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:receivable-plan:delete')")
public CommonResult<Boolean> deleteReceivablePlan(@RequestParam("id") Long id) {
receivablePlanService.deleteReceivablePlan(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得回款计划")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
public CommonResult<CrmReceivablePlanRespVO> getReceivablePlan(@RequestParam("id") Long id) {
CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id);
return success(buildReceivablePlanDetail(receivablePlan));
}
private CrmReceivablePlanRespVO buildReceivablePlanDetail(CrmReceivablePlanDO receivablePlan) {
if (receivablePlan == null) {
return null;
}
return buildReceivableDetailList(Collections.singletonList(receivablePlan)).get(0);
}
@GetMapping("/page")
@Operation(summary = "获得回款计划分页")
@PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPage(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO, getLoginUserId());
return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/page-by-customer")
@Operation(summary = "获得回款计划分页,基于指定客户")
public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPageByCustomer(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO);
return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/export-excel")
@Operation(summary = "导出回款计划 Excel")
@PreAuthorize("@ss.hasPermission('crm:receivable-plan:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportReceivablePlanExcel(@Valid CrmReceivablePlanPageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
exportReqVO.setPageSize(PAGE_SIZE_NONE);
List<CrmReceivablePlanDO> list = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId()).getList();
// 导出 Excel
ExcelUtils.write(response, "回款计划.xls", "数据", CrmReceivablePlanRespVO.class,
buildReceivableDetailList(list));
}
private List<CrmReceivablePlanRespVO> buildReceivableDetailList(List<CrmReceivablePlanDO> receivablePlanList) {
if (CollUtil.isEmpty(receivablePlanList)) {
return Collections.emptyList();
}
// 1.1 获取客户 Map
Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId));
// 1.2 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
// 1.3 获得合同 Map
Map<Long, CrmContractDO> contractMap = contractService.getContractMap(
convertSet(receivablePlanList, CrmReceivablePlanDO::getContractId));
// 1.4 获得回款 Map
Map<Long, CrmReceivableDO> receivableMap = receivableService.getReceivableMap(
convertSet(receivablePlanList, CrmReceivablePlanDO::getReceivableId));
// 2. 拼接数据
return BeanUtils.toBean(receivablePlanList, CrmReceivablePlanRespVO.class, (receivablePlanVO) -> {
// 2.1 拼接客户信息
findAndThen(customerMap, receivablePlanVO.getCustomerId(), customer -> receivablePlanVO.setCustomerName(customer.getName()));
// 2.2 拼接用户信息
findAndThen(userMap, receivablePlanVO.getOwnerUserId(), user -> receivablePlanVO.setOwnerUserName(user.getNickname()));
findAndThen(userMap, Long.parseLong(receivablePlanVO.getCreator()), user -> receivablePlanVO.setCreatorName(user.getNickname()));
// 2.3 拼接合同信息
findAndThen(contractMap, receivablePlanVO.getContractId(), contract -> receivablePlanVO.setContractNo(contract.getNo()));
// 2.4 拼接回款信息
receivablePlanVO.setReceivable(BeanUtils.toBean(receivableMap.get(receivablePlanVO.getReceivableId()), CrmReceivableRespVO.class));
});
}
@GetMapping("/simple-list")
@Operation(summary = "获得回款计划精简列表", description = "获得回款计划精简列表,主要用于前端的下拉选项")
@Parameters({
@Parameter(name = "customerId", description = "客户编号", required = true),
@Parameter(name = "contractId", description = "合同编号", required = true)
})
@PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
public CommonResult<List<CrmReceivablePlanRespVO>> getReceivablePlanSimpleList(@RequestParam("customerId") Long customerId,
@RequestParam("contractId") Long contractId) {
CrmReceivablePlanPageReqVO pageReqVO = new CrmReceivablePlanPageReqVO().setCustomerId(customerId).setContractId(contractId);
pageReqVO.setPageNo(PAGE_SIZE_NONE);
PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO);
return success(convertList(pageResult.getList(), receivablePlan -> new CrmReceivablePlanRespVO() // 只返回 idperiod 等信息
.setId(receivablePlan.getId()).setPeriod(receivablePlan.getPeriod()).setReceivableId(receivablePlan.getReceivableId())
.setPrice(receivablePlan.getPrice()).setReturnType(receivablePlan.getReturnType())));
}
@GetMapping("/remind-count")
@Operation(summary = "获得待回款提醒数量")
@PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
public CommonResult<Long> getReceivablePlanRemindCount() {
return success(receivablePlanService.getReceivablePlanRemindCount(getLoginUserId()));
}
}

View File

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

View File

@ -1,92 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - CRM 回款 Response VO")
@Data
@ExcelIgnoreUnannotated
public class CrmReceivableRespVO {
@Schema(description = "编号", example = "25787")
@ExcelProperty("编号")
private Long id;
@Schema(description = "回款编号", example = "31177")
@ExcelProperty("回款编号")
private String no;
@Schema(description = "回款计划编号", example = "1024")
@ExcelProperty("回款计划编号")
private Long planId;
@Schema(description = "回款方式", example = "2")
@ExcelProperty("回款方式")
private Integer returnType;
@Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
@ExcelProperty("回款金额")
private BigDecimal price;
@Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
@ExcelProperty("计划回款日期")
private LocalDateTime returnTime;
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("客户编号")
private Long customerId;
@Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
@ExcelProperty("客户名字")
private String customerName;
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("合同编号")
private Long contractId;
@Schema(description = "合同信息")
@ExcelProperty("合同信息")
private CrmContractRespVO contract;
@Schema(description = "负责人的用户编号", example = "25682")
@ExcelProperty("负责人的用户编号")
private Long ownerUserId;
@Schema(description = "负责人名字", example = "25682")
@ExcelProperty("负责人名字")
private String ownerUserName;
@Schema(description = "负责人部门")
@ExcelProperty("负责人部门")
private String ownerUserDeptName;
@Schema(description = "工作流编号", example = "1043")
@ExcelProperty("工作流编号")
private String processInstanceId;
@Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@ExcelProperty("审批状态")
private Integer auditStatus;
@Schema(description = "工作流编号", example = "备注")
@ExcelProperty("工作流编号")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("更新时间")
private LocalDateTime updateTime;
@Schema(description = "创建人", example = "25682")
@ExcelProperty("创建人")
private String creator;
@Schema(description = "创建人名字", example = "test")
@ExcelProperty("创建人名字")
private String creatorName;
}

View File

@ -1,55 +0,0 @@
# == 1. 客户总量分析 ==
### 1.1 客户总量分析(按日期)
GET {{baseUrl}}/crm/statistics-customer/get-customer-summary-by-date?deptId=100&interval=2&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 1.2 客户总量统计(按用户)
GET {{baseUrl}}/crm/statistics-customer/get-customer-summary-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 2. 客户跟进次数分析 ==
### 2.1 客户跟进次数分析(按日期)
GET {{baseUrl}}/crm/statistics-customer/get-follow-up-summary-by-date?deptId=100&interval=2&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 2.2 客户总量统计(按用户)
GET {{baseUrl}}/crm/statistics-customer/get-follow-up-summary-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 3. 客户跟进方式分析 ==
### 3.1 客户跟进方式分析
GET {{baseUrl}}/crm/statistics-customer/get-follow-up-summary-by-type?deptId=100&interval=2&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 4. 客户成交周期 ==
### 4.1 合同摘要信息(客户转化率页面)
GET {{baseUrl}}/crm/statistics-customer/get-contract-summary?deptId=100&interval=2&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 5. 客户成交周期 ==
### 5.1 获取客户公海分析(按日期)
GET {{baseUrl}}/crm/statistics-customer/get-pool-summary-by-date?deptId=100&interval=2&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 5.2 获取客户公海分析(按用户)
GET {{baseUrl}}/crm/statistics-customer/get-pool-summary-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
# == 6. 客户成交周期 ==
### 6.1 客户成交周期(按日期)
GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-date?deptId=100&interval=2&times[0]=2024-01-01 00:00:00&times[1]=2024-01-29 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 6.2 获取客户成交周期(按用户)
GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 用户客户统计响应 Base Response VO
*
* 目的可以统一拼接子 VO ownerUserIdownerUserName 属性
*/
@Data
public class CrmStatisticsCustomerByUserBaseRespVO {
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long ownerUserId;
@Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
private String ownerUserName;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,42 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 客户画像 Request VO")
@Data
public class CrmStatisticsPortraitReqVO {
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "部门 id 不能为空")
private Long deptId;
/**
* 负责人用户 id, 当用户为空, 则计算部门下用户
*/
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Long userId;
/**
* userIds 目前不用前端传递目前是方便后端通过 deptId 读取编号后设置回来
* 后续可能会支持选择部分用户进行查询
*/
@Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
private List<Long> userIds;
/**
* 前端如果选择自定义时间, 那么前端传递起始-终止时间, 如果选择其他时间间隔类型, 则由后台计算起始-终止时间
* 并作为参数传递给Mapper
*/
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] times;
}

View File

@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - CRM 排行榜统计 Request VO")
@Data
public class CrmStatisticsRankReqVO {
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "部门 id 不能为空")
private Long deptId;
/**
* userIds 目前不用前端传递目前是方便后端通过 deptId 读取编号后设置回来
* <p>
* 后续可能会支持选择部分用户进行查询
*/
@Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
private List<Long> userIds;
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@NotEmpty(message = "时间范围不能为空")
private LocalDateTime[] times;
}

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 排行榜统计 Response VO")
@Data
public class CrmStatisticsRankRespVO {
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long ownerUserId;
@Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String nickname;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String deptName;
/**
* 数量是个特别抽象的概念在不同排行下代表不同含义
*
* 1. 金额合同金额排行回款金额排行
* 2. 个数签约合同排行产品销量排行产品销量排行新增客户数排行新增联系人排行跟进次数排行跟进客户数排行
*
* 为什么使用 BigDecimal 的原因
*/
@Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private BigDecimal count;
}

View File

@ -1,68 +0,0 @@
package cn.iocoder.yudao.module.crm.dal.mysql.business;
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.business.vo.business.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* 商机 Mapper
*
* @author ljlleo
*/
@Mapper
public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
default int updateOwnerUserIdById(Long id, Long ownerUserId) {
return update(new LambdaUpdateWrapper<CrmBusinessDO>()
.eq(CrmBusinessDO::getId, id)
.set(CrmBusinessDO::getOwnerUserId, ownerUserId));
}
default PageResult<CrmBusinessDO> selectPageByCustomerId(CrmBusinessPageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
.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));
}
default PageResult<CrmBusinessDO> selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmBusinessDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(),
CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
// 拼接自身的查询条件
query.selectAll(CrmBusinessDO.class)
.likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
.orderByDesc(CrmBusinessDO::getId);
return selectJoinPage(pageReqVO, CrmBusinessDO.class, query);
}
default Long selectCountByStatusTypeId(Long statusTypeId) {
return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId);
}
default List<CrmBusinessDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId){
return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
.eq(CrmBusinessDO::getCustomerId, customerId)
.eq(CrmBusinessDO::getOwnerUserId, ownerUserId));
}
}

View File

@ -1,81 +0,0 @@
package cn.iocoder.yudao.module.crm.dal.mysql.contact;
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.contact.vo.CrmContactPageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* CRM 联系人 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface CrmContactMapper extends BaseMapperX<CrmContactDO> {
default int updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId) {
return update(new LambdaUpdateWrapper<CrmContactDO>()
.eq(CrmContactDO::getCustomerId, customerId)
.set(CrmContactDO::getOwnerUserId, ownerUserId));
}
default PageResult<CrmContactDO> selectPageByCustomerId(CrmContactPageReqVO pageVO) {
return selectPage(pageVO, new LambdaQueryWrapperX<CrmContactDO>()
.eq(CrmContactDO::getCustomerId, pageVO.getCustomerId()) // 指定客户编号
.likeIfPresent(CrmContactDO::getName, pageVO.getName())
.eqIfPresent(CrmContactDO::getMobile, pageVO.getMobile())
.eqIfPresent(CrmContactDO::getTelephone, pageVO.getTelephone())
.eqIfPresent(CrmContactDO::getEmail, pageVO.getEmail())
.eqIfPresent(CrmContactDO::getQq, pageVO.getQq())
.eqIfPresent(CrmContactDO::getWechat, pageVO.getWechat())
.orderByDesc(CrmContactDO::getId));
}
default PageResult<CrmContactDO> selectPageByBusinessId(CrmContactPageReqVO pageVO, Collection<Long> ids) {
return selectPage(pageVO, new LambdaQueryWrapperX<CrmContactDO>()
.in(CrmContactDO::getId, ids) // 指定联系人编号
.likeIfPresent(CrmContactDO::getName, pageVO.getName())
.eqIfPresent(CrmContactDO::getMobile, pageVO.getMobile())
.eqIfPresent(CrmContactDO::getTelephone, pageVO.getTelephone())
.eqIfPresent(CrmContactDO::getEmail, pageVO.getEmail())
.eqIfPresent(CrmContactDO::getQq, pageVO.getQq())
.eqIfPresent(CrmContactDO::getWechat, pageVO.getWechat())
.orderByDesc(CrmContactDO::getId));
}
default PageResult<CrmContactDO> selectPage(CrmContactPageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmContactDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(),
CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
// 拼接自身的查询条件
query.selectAll(CrmContactDO.class)
.likeIfPresent(CrmContactDO::getName, pageReqVO.getName())
.eqIfPresent(CrmContactDO::getMobile, pageReqVO.getMobile())
.eqIfPresent(CrmContactDO::getTelephone, pageReqVO.getTelephone())
.eqIfPresent(CrmContactDO::getEmail, pageReqVO.getEmail())
.eqIfPresent(CrmContactDO::getQq, pageReqVO.getQq())
.eqIfPresent(CrmContactDO::getWechat, pageReqVO.getWechat())
.orderByDesc(CrmContactDO::getId);
return selectJoinPage(pageReqVO, CrmContactDO.class, query);
}
default List<CrmContactDO> selectListByCustomerId(Long customerId) {
return selectList(CrmContactDO::getCustomerId, customerId);
}
default List<CrmContactDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return selectList(CrmContactDO::getCustomerId, customerId,
CrmContactDO::getOwnerUserId, ownerUserId);
}
}

View File

@ -1,126 +0,0 @@
package cn.iocoder.yudao.module.crm.dal.mysql.contract;
import cn.hutool.core.date.LocalDateTimeUtil;
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.contract.vo.contract.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
/**
* CRM 合同 Mapper
*
* @author dhb52
*/
@Mapper
public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
default CrmContractDO selectByNo(String no) {
return selectOne(CrmContractDO::getNo, no);
}
default PageResult<CrmContractDO> selectPageByCustomerId(CrmContractPageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmContractDO>()
.eq(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
.likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())
.likeIfPresent(CrmContractDO::getName, pageReqVO.getName())
.eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
.eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId())
.orderByDesc(CrmContractDO::getId));
}
default PageResult<CrmContractDO> selectPageByBusinessId(CrmContractPageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmContractDO>()
.eq(CrmContractDO::getBusinessId, pageReqVO.getBusinessId())
.likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())
.likeIfPresent(CrmContractDO::getName, pageReqVO.getName())
.eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
.eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId())
.orderByDesc(CrmContractDO::getId));
}
default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId, CrmContractConfigDO config) {
MPJLambdaWrapperX<CrmContractDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(),
CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
// 拼接自身的查询条件
query.selectAll(CrmContractDO.class)
.likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())
.likeIfPresent(CrmContractDO::getName, pageReqVO.getName())
.eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
.eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId())
.eqIfPresent(CrmContractDO::getAuditStatus, pageReqVO.getAuditStatus())
.orderByDesc(CrmContractDO::getId);
// Backlog: 即将到期的合同
LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) { // 即将到期
query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus())
.between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(config.getNotifyDays()));
} else if (CrmContractPageReqVO.EXPIRY_TYPE_EXPIRED.equals(pageReqVO.getExpiryType())) { // 已到期
query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus())
.lt(CrmContractDO::getEndTime, endOfToday);
}
return selectJoinPage(pageReqVO, CrmContractDO.class, query);
}
default List<CrmContractDO> selectBatchIds(Collection<Long> ids, Long userId) {
MPJLambdaWrapperX<CrmContractDO> query = new MPJLambdaWrapperX<>();
// 构建数据权限连表条件
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), ids, userId);
// 拼接自身的查询条件
query.selectAll(CrmContractDO.class).in(CrmContractDO::getId, ids).orderByDesc(CrmContractDO::getId);
return selectJoinList(CrmContractDO.class, query);
}
default Long selectCountByContactId(Long contactId) {
return selectCount(CrmContractDO::getSignContactId, contactId);
}
default Long selectCountByBusinessId(Long businessId) {
return selectCount(CrmContractDO::getBusinessId, businessId);
}
default Long selectCountByAudit(Long userId) {
MPJLambdaWrapperX<CrmContractDO> query = new MPJLambdaWrapperX<>();
// 我负责的 + 非公海
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(),
CrmContractDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE);
// 未审核
query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.PROCESS.getStatus());
return selectCount(query);
}
default Long selectCountByRemind(Long userId, CrmContractConfigDO config) {
MPJLambdaWrapperX<CrmContractDO> query = new MPJLambdaWrapperX<>();
// 我负责的 + 非公海
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(),
CrmContractDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE);
// 即将到期
LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus()) // 必须审批通过
.between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(config.getNotifyDays()));
return selectCount(query);
}
default List<CrmContractDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return selectList(new LambdaQueryWrapperX<CrmContractDO>()
.eq(CrmContractDO::getCustomerId, customerId)
.eq(CrmContractDO::getOwnerUserId, ownerUserId));
}
}

View File

@ -1,76 +0,0 @@
package cn.iocoder.yudao.module.crm.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* crm 数据权限 mapper
*
* @author HUIHUI
*/
@Mapper
public interface CrmPermissionMapper extends BaseMapperX<CrmPermissionDO> {
default CrmPermissionDO selectByBizTypeAndBizIdByUserId(Integer bizType, Long bizId, Long userId) {
return selectOne(new LambdaQueryWrapperX<CrmPermissionDO>()
.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId)
.eq(CrmPermissionDO::getUserId, userId));
}
default List<CrmPermissionDO> selectByBizTypeAndBizId(Integer bizType, Long bizId) {
return selectList(new LambdaQueryWrapperX<CrmPermissionDO>()
.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId));
}
default List<CrmPermissionDO> selectByBizTypeAndBizIds(Integer bizType, Collection<Long> bizIds) {
return selectList(new LambdaQueryWrapperX<CrmPermissionDO>()
.eq(CrmPermissionDO::getBizType, bizType)
.in(CrmPermissionDO::getBizId, bizIds));
}
default List<CrmPermissionDO> selectListByBizTypeAndUserId(Integer bizType, Long userId) {
return selectList(new LambdaQueryWrapperX<CrmPermissionDO>()
.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getUserId, userId));
}
default List<CrmPermissionDO> selectListByBizTypeAndBizIdAndLevel(Integer bizType, Long bizId, Integer level) {
return selectList(new LambdaQueryWrapperX<CrmPermissionDO>()
.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId)
.eq(CrmPermissionDO::getLevel, level));
}
default CrmPermissionDO selectByIdAndUserId(Long id, Long userId) {
return selectOne(CrmPermissionDO::getId, id,
CrmPermissionDO::getUserId, userId);
}
default CrmPermissionDO selectByBizAndUserId(Integer bizType, Long bizId, Long userId) {
return selectOne(new LambdaQueryWrapperX<CrmPermissionDO>()
.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId)
.eq(CrmPermissionDO::getUserId, userId));
}
default int deletePermission(Integer bizType, Long bizId) {
return delete(new LambdaQueryWrapperX<CrmPermissionDO>()
.eq(CrmPermissionDO::getBizType, bizType)
.eq(CrmPermissionDO::getBizId, bizId));
}
default Long selectListByBiz(Collection<Integer> bizTypes, Collection<Long> bizIds, Collection<Long> userIds) {
return selectCount(new LambdaQueryWrapperX<CrmPermissionDO>()
.in(CrmPermissionDO::getBizType, bizTypes)
.in(CrmPermissionDO::getBizId, bizIds)
.in(CrmPermissionDO::getUserId, userIds));
}
}

View File

@ -1,106 +0,0 @@
package cn.iocoder.yudao.module.crm.dal.mysql.receivable;
import cn.hutool.core.collection.CollUtil;
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.receivable.vo.receivable.CrmReceivablePageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 回款 Mapper
*
* @author 赤焰
*/
@Mapper
public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> {
default CrmReceivableDO selectByNo(String no) {
return selectOne(CrmReceivableDO::getNo, no);
}
default PageResult<CrmReceivableDO> selectPageByCustomerId(CrmReceivablePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivableDO>()
.eq(CrmReceivableDO::getCustomerId, reqVO.getCustomerId()) // 必须传递
.eqIfPresent(CrmReceivableDO::getNo, reqVO.getNo())
.eqIfPresent(CrmReceivableDO::getContractId, reqVO.getContractId())
.eqIfPresent(CrmReceivableDO::getPlanId, reqVO.getPlanId())
.orderByDesc(CrmReceivableDO::getId));
}
default PageResult<CrmReceivableDO> selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) {
MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
// 拼接自身的查询条件
query.selectAll(CrmReceivableDO.class)
.eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo())
.eqIfPresent(CrmReceivableDO::getPlanId, pageReqVO.getPlanId())
.eqIfPresent(CrmReceivableDO::getContractId, pageReqVO.getContractId())
.eqIfPresent(CrmReceivableDO::getAuditStatus, pageReqVO.getAuditStatus())
.orderByDesc(CrmReceivableDO::getId);
return selectJoinPage(pageReqVO, CrmReceivableDO.class, query);
}
default List<CrmReceivableDO> selectBatchIds(Collection<Long> ids, Long userId) {
MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), ids, userId);
// 拼接自身的查询条件
query.selectAll(CrmReceivableDO.class).in(CrmReceivableDO::getId, ids).orderByDesc(CrmReceivableDO::getId);
return selectJoinList(CrmReceivableDO.class, query);
}
default Long selectCountByAudit(Long userId) {
MPJLambdaWrapperX<CrmReceivableDO> query = new MPJLambdaWrapperX<>();
// 我负责的 + 非公海
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(),
CrmReceivableDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE);
// 未审核
query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.PROCESS.getStatus());
return selectCount(query);
}
default List<CrmReceivableDO> selectListByContractIdAndStatus(Long contractId, Collection<Integer> auditStatuses) {
return selectList(new LambdaQueryWrapperX<CrmReceivableDO>()
.eq(CrmReceivableDO::getContractId, contractId)
.in(CrmReceivableDO::getAuditStatus, auditStatuses));
}
default Map<Long, BigDecimal> selectReceivablePriceMapByContractId(Collection<Long> contractIds) {
if (CollUtil.isEmpty(contractIds)) {
return Collections.emptyMap();
}
// SQL sum 查询
List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmReceivableDO>()
.select("contract_id, SUM(price) AS total_price")
.in("audit_status", CrmAuditStatusEnum.DRAFT.getStatus(), // 草稿 + 审批中 + 审批通过
CrmAuditStatusEnum.PROCESS, CrmAuditStatusEnum.APPROVE.getStatus())
.groupBy("contract_id")
.in("contract_id", contractIds));
// 获得金额
return convertMap(result, obj -> (Long) obj.get("contract_id"), obj -> (BigDecimal) obj.get("total_price"));
}
default Long selectCountByContractId(Long contractId) {
return selectCount(CrmReceivableDO::getContractId, contractId);
}
}

View File

@ -1,194 +0,0 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* CRM 客户分析 Mapper
*
* @author dhb52
*/
@Mapper
public interface CrmStatisticsCustomerMapper {
/**
* 新建客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerCreateCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 成交客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerSummaryByDateRespVO> selectCustomerDealCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 新建客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerCreateCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 成交客户数(按用户)
*
* @param reqVO 请求参数@param reqVO 请求参数@param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> selectCustomerDealCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 合同总金额(按用户)
* @return 统计数据@return 统计数据@param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> selectContractPriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 合同回款金额(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> selectReceivablePriceGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 跟进次数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsFollowUpSummaryByDateRespVO> selectFollowUpRecordCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 跟进客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsFollowUpSummaryByDateRespVO> selectFollowUpCustomerCountGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 跟进次数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsFollowUpSummaryByUserRespVO> selectFollowUpRecordCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 跟进客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsFollowUpSummaryByUserRespVO> selectFollowUpCustomerCountGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 首次合同回款信息(用于客户转化率页面)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerContractSummaryRespVO> selectContractSummary(CrmStatisticsCustomerReqVO reqVO);
/**
* 跟进次数(按类型)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsFollowUpSummaryByTypeRespVO> selectFollowUpRecordCountGroupByType(CrmStatisticsCustomerReqVO reqVO);
/**
* 进入公海客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record
default List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO) {
LocalDateTime currrentDate = LocalDateTimeUtil.beginOfDay(reqVO.getTimes()[0]);
LocalDateTime endDate = LocalDateTimeUtil.endOfDay(reqVO.getTimes()[1]);
List<CrmStatisticsPoolSummaryByDateRespVO> voList = new ArrayList<>();
while (currrentDate.isBefore(endDate)) {
voList.add(new CrmStatisticsPoolSummaryByDateRespVO()
.setTime(LocalDateTimeUtil.format(currrentDate, "yyyy-MM-dd"))
.setCustomerPutCount(RandomUtil.randomInt(0, 10))
.setCustomerTakeCount(RandomUtil.randomInt(0, 10)));
currrentDate = currrentDate.plusDays(1);
}
return voList;
}
/**
* 公海领取客户数(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record
default List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO) {
return selectPoolCustomerPutCountByDate(reqVO);
}
/**
* 进入公海客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record
default List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO) {
return convertList(reqVO.getUserIds(), userId ->
(CrmStatisticsPoolSummaryByUserRespVO) new CrmStatisticsPoolSummaryByUserRespVO()
.setCustomerPutCount(RandomUtil.randomInt(0, 10))
.setCustomerTakeCount(RandomUtil.randomInt(0, 10))
.setOwnerUserId(userId));
}
/**
* 公海领取客户数(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
// TODO: @芋艿 模拟数据, 需要增加 crm_owner_record
default List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO) {
return selectPoolCustomerPutCountByUser(reqVO);
}
/**
* 客户成交周期(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerDealCycleByDateRespVO> selectCustomerDealCycleGroupByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 客户成交周期(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerDealCycleByUserRespVO> selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO);
}

View File

@ -1,41 +0,0 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* CRM 员工业绩分析 Mapper
*
* @author scholar
*/
@Mapper
public interface CrmStatisticsPerformanceMapper {
/**
* 员工签约合同数量
*
* @param performanceReqVO 参数
* @return 员工签约合同数量
*/
List<CrmStatisticsPerformanceRespVO> selectContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
* 员工签约合同金额
*
* @param performanceReqVO 参数
* @return 员工签约合同金额
*/
List<CrmStatisticsPerformanceRespVO> selectContractPricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
* 员工回款金额
*
* @param performanceReqVO 参数
* @return 员工回款金额
*/
List<CrmStatisticsPerformanceRespVO> selectReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
}

View File

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

View File

@ -1,81 +0,0 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* CRM 排行榜统计 Mapper
*
* @author anhaohao
*/
@Mapper
public interface CrmStatisticsRankMapper {
/**
* 查询合同金额排行榜
*
* @param rankReqVO 参数
* @return 合同金额排行榜
*/
List<CrmStatisticsRankRespVO> selectContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询回款金额排行榜
*
* @param rankReqVO 参数
* @return 回款金额排行榜
*/
List<CrmStatisticsRankRespVO> selectReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询签约合同数量排行榜
*
* @param rankReqVO 参数
* @return 签约合同数量排行榜
*/
List<CrmStatisticsRankRespVO> selectContractCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询产品销量排行榜
*
* @param rankReqVO 参数
* @return 产品销量排行榜
*/
List<CrmStatisticsRankRespVO> selectProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询新增客户数排行榜
*
* @param rankReqVO 参数
* @return 新增客户数排行榜
*/
List<CrmStatisticsRankRespVO> selectCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询联系人数量排行榜
*
* @param rankReqVO 参数
* @return 联系人数量排行榜
*/
List<CrmStatisticsRankRespVO> selectContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询跟进次数排行榜
*
* @param rankReqVO 参数
* @return 跟进次数排行榜
*/
List<CrmStatisticsRankRespVO> selectFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 查询跟进客户数排行榜
*
* @param rankReqVO 参数
* @return 跟进客户数排行榜
*/
List<CrmStatisticsRankRespVO> selectFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
}

View File

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

View File

@ -1,4 +0,0 @@
/**
* crm 模块的 excel 拓展封装
*/
package cn.iocoder.yudao.module.crm.framework.excel;

View File

@ -1,4 +0,0 @@
/**
* crm 模块的 operatelog 拓展封装
*/
package cn.iocoder.yudao.module.crm.framework.operatelog;

View File

@ -1,4 +0,0 @@
/**
* crm 模块的 permission 拓展封装
*/
package cn.iocoder.yudao.module.crm.framework.permission;

View File

@ -1,4 +0,0 @@
/**
* crm 模块的 web 拓展封装
*/
package cn.iocoder.yudao.module.crm.framework.web;

View File

@ -1,197 +0,0 @@
package cn.iocoder.yudao.module.crm.service.business;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
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.business.CrmBusinessEndStatusEnum;
import jakarta.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 商机 Service 接口
*
* @author ljlleo
*/
public interface CrmBusinessService {
/**
* 创建商机
*
* @param createReqVO 创建信息
* @param userId 用户编号
* @return 编号
*/
Long createBusiness(@Valid CrmBusinessSaveReqVO createReqVO, Long userId);
/**
* 更新商机
*
* @param updateReqVO 更新信息
*/
void updateBusiness(@Valid CrmBusinessSaveReqVO updateReqVO);
/**
* 更新商机相关跟进信息
*
* @param id 编号
* @param contactNextTime 下次联系时间
* @param contactLastContent 最后联系内容
*/
void updateBusinessFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
/**
* 更新商机的下次联系时间
*
* @param ids 编号数组
* @param contactNextTime 下次联系时间
*/
void updateBusinessContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime);
/**
* 更新商机的状态
*
* @param reqVO 更新请求
*/
void updateBusinessStatus(CrmBusinessUpdateStatusReqVO reqVO);
/**
* 删除商机
*
* @param id 编号
*/
void deleteBusiness(Long id);
/**
* 商机转移
*
* @param reqVO 请求
* @param userId 用户编号
*/
void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId);
/**
* 获得商机
*
* @param id 编号
* @return 商机
*/
CrmBusinessDO getBusiness(Long id);
/**
* 校验商机是否有效
*
* @param id 编号
* @return 商机
*/
CrmBusinessDO validateBusiness(Long id);
/**
* 获得商机列表
*
* @param ids 编号
* @return 商机列表
*/
List<CrmBusinessDO> getBusinessList(Collection<Long> ids);
/**
* 获得商机 Map
*
* @param ids 编号
* @return 商机 Map
*/
default Map<Long, CrmBusinessDO> getBusinessMap(Collection<Long> ids) {
return convertMap(getBusinessList(ids), CrmBusinessDO::getId);
}
/**
* 获得指定商机编号的产品列表
*
* @param businessId 商机编号
* @return 商机产品列表
*/
List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId);
/**
* 获得商机分页
*
* 数据权限基于 {@link CrmBusinessDO}
*
* @param pageReqVO 分页查询
* @param userId 用户编号
* @return 商机分页
*/
PageResult<CrmBusinessDO> getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId);
/**
* 获得商机分页基于指定客户
*
* 数据权限基于 {@link CrmCustomerDO} 读取
*
* @param pageReqVO 分页查询
* @return 商机分页
*/
PageResult<CrmBusinessDO> getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO);
/**
* 获得商机分页基于指定联系人
*
* 数据权限基于 {@link CrmContactDO} 读取
*
* @param pageReqVO 分页参数
* @return 商机分页
*/
PageResult<CrmBusinessDO> getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO);
/**
* 获取关联客户的商机数量
*
* @param customerId 客户编号
* @return 数量
*/
Long getBusinessCountByCustomerId(Long customerId);
/**
* 获得使用指定商机状态组的商机数量
*
* @param statusTypeId 商机状态组编号
* @return 数量
*/
Long getBusinessCountByStatusTypeId(Long statusTypeId);
/**
* 获得商机状态名称
*
* @param endStatus 结束状态
* @param status 商机状态
* @return 商机状态名称
*/
default String getBusinessStatusName(Integer endStatus, CrmBusinessStatusDO status) {
if (endStatus != null) {
return CrmBusinessEndStatusEnum.fromStatus(endStatus).getName();
}
return status.getName();
}
/**
* 获得商机列表
*
* @param customerId 客户编号
* @param ownerUserId 负责人编号
* @return 商机列表
*/
List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
}

View File

@ -1,378 +0,0 @@
package cn.iocoder.yudao.module.crm.service.business;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO;
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.business.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
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.dal.mysql.business.CrmBusinessProductMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
/**
* 商机 Service 实现类
*
* @author ljlleo
*/
@Service
@Validated
public class CrmBusinessServiceImpl implements CrmBusinessService {
@Resource
private CrmBusinessMapper businessMapper;
@Resource
private CrmBusinessProductMapper businessProductMapper;
@Resource
private CrmBusinessStatusService businessStatusService;
@Resource
@Lazy // 延迟加载避免循环依赖
private CrmContractService contractService;
@Resource
private CrmCustomerService customerService;
@Resource
@Lazy // 延迟加载避免循环依赖
private CrmContactService contactService;
@Resource
private CrmPermissionService permissionService;
@Resource
private CrmContactBusinessService contactBusinessService;
@Resource
private CrmProductService productService;
@Resource
private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE, bizNo = "{{#business.id}}",
success = CRM_BUSINESS_CREATE_SUCCESS)
public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
// 1.1 校验产品项的有效性
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getBusinessProducts());
// 1.2 校验关联字段
validateRelationDataExists(createReqVO);
// 2.1 插入商机
CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class);
business.setStatusId(businessStatusService.getBusinessStatusListByTypeId(createReqVO.getStatusTypeId()).get(0).getId()); // 默认状态
calculateTotalPrice(business, businessProducts);
businessMapper.insert(business);
// 2.2 插入商机关联商品
if (CollUtil.isNotEmpty(businessProducts)) {
businessProducts.forEach(item -> item.setBusinessId(business.getId()));
businessProductMapper.insertBatch(businessProducts);
}
// 3. 创建数据权限
permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(business.getOwnerUserId())
.setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()).setBizId(business.getId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
// 4. 在联系人的详情页如果直接新建商机则需要关联下
if (createReqVO.getContactId() != null) {
contactBusinessService.createContactBusinessList(new CrmContactBusinessReqVO().setContactId(createReqVO.getContactId())
.setBusinessIds(Collections.singletonList(business.getId())));
}
// 5. 记录操作日志上下文
LogRecordContext.putVariable("business", business);
return business.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = CRM_BUSINESS_UPDATE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateBusiness(CrmBusinessSaveReqVO updateReqVO) {
updateReqVO.setOwnerUserId(null).setStatusTypeId(null); // 不允许更新的字段
// 1.1 校验存在
CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
// 1.2 校验产品项的有效性
List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getBusinessProducts());
// 1.3 校验关联字段
validateRelationDataExists(updateReqVO);
// 2.1 更新商机
CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class);
calculateTotalPrice(updateObj, businessProducts);
businessMapper.updateById(updateObj);
// 2.2 更新商机关联商品
updateBusinessProduct(updateObj.getId(), businessProducts);
// 3. 记录操作日志上下文
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
LogRecordContext.putVariable("businessName", oldBusiness.getName());
}
@Override
@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}",
success = CRM_BUSINESS_FOLLOW_UP_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
public void updateBusinessFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) {
// 1. 校验存在
CrmBusinessDO business = validateBusinessExists(id);
// 2. 更新联系人的跟进信息
businessMapper.updateById(new CrmBusinessDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime)
.setContactLastTime(LocalDateTime.now()));
// 3. 记录操作日志上下文
LogRecordContext.putVariable("businessName", business.getName());
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#ids", level = CrmPermissionLevelEnum.WRITE)
public void updateBusinessContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime) {
businessMapper.updateBatch(convertList(ids, id -> new CrmBusinessDO().setId(id).setContactNextTime(contactNextTime)));
}
private void updateBusinessProduct(Long id, List<CrmBusinessProductDO> newList) {
List<CrmBusinessProductDO> oldList = businessProductMapper.selectListByBusinessId(id);
List<List<CrmBusinessProductDO>> diffList = diffList(oldList, newList, // id 不同就认为是不同的记录
(oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
if (CollUtil.isNotEmpty(diffList.get(0))) {
diffList.get(0).forEach(o -> o.setBusinessId(id));
businessProductMapper.insertBatch(diffList.get(0));
}
if (CollUtil.isNotEmpty(diffList.get(1))) {
businessProductMapper.updateBatch(diffList.get(1));
}
if (CollUtil.isNotEmpty(diffList.get(2))) {
businessProductMapper.deleteBatchIds(convertSet(diffList.get(2), CrmBusinessProductDO::getId));
}
}
private void validateRelationDataExists(CrmBusinessSaveReqVO saveReqVO) {
// 校验商机状态
if (saveReqVO.getStatusTypeId() != null) {
businessStatusService.validateBusinessStatusType(saveReqVO.getStatusTypeId());
}
// 校验客户
if (saveReqVO.getCustomerId() != null) {
customerService.validateCustomer(saveReqVO.getCustomerId());
}
// 校验联系人
if (saveReqVO.getContactId() != null) {
contactService.validateContact(saveReqVO.getContactId());
}
// 校验负责人
if (saveReqVO.getOwnerUserId() != null) {
adminUserApi.validateUser(saveReqVO.getOwnerUserId());
}
}
private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.BusinessProduct> list) {
// 1. 校验产品存在
productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.BusinessProduct::getProductId));
// 2. 转化为 CrmBusinessProductDO 列表
return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class,
item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount()))));
}
private void calculateTotalPrice(CrmBusinessDO business, List<CrmBusinessProductDO> businessProducts) {
business.setTotalProductPrice(getSumValue(businessProducts, CrmBusinessProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(business.getTotalProductPrice(), business.getDiscountPercent());
business.setTotalPrice(business.getTotalProductPrice().subtract(discountPrice));
}
@Override
@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_STATUS_SUB_TYPE, bizNo = "{{#reqVO.id}}",
success = CRM_BUSINESS_UPDATE_STATUS_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateBusinessStatus(CrmBusinessUpdateStatusReqVO reqVO) {
// 1.1 校验存在
CrmBusinessDO business = validateBusinessExists(reqVO.getId());
// 1.2 校验商机未结束
if (business.getEndStatus() != null) {
throw exception(BUSINESS_UPDATE_STATUS_FAIL_END_STATUS);
}
// 1.3 校验商机状态
CrmBusinessStatusDO status = null;
if (reqVO.getStatusId() != null) {
status = businessStatusService.validateBusinessStatus(business.getStatusTypeId(), reqVO.getStatusId());
}
// 1.4 校验是不是状态没变更
if ((reqVO.getStatusId() != null && reqVO.getStatusId().equals(business.getStatusId()))
|| (reqVO.getEndStatus() != null && reqVO.getEndStatus().equals(business.getEndStatus()))) {
throw exception(BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS);
}
// 2. 更新商机状态
businessMapper.updateById(new CrmBusinessDO().setId(reqVO.getId()).setStatusId(reqVO.getStatusId())
.setEndStatus(reqVO.getEndStatus()));
// 3. 记录操作日志上下文
LogRecordContext.putVariable("businessName", business.getName());
LogRecordContext.putVariable("oldStatusName", getBusinessStatusName(business.getEndStatus(),
businessStatusService.getBusinessStatus(business.getStatusId())));
LogRecordContext.putVariable("newStatusName", getBusinessStatusName(reqVO.getEndStatus(), status));
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_DELETE_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_BUSINESS_DELETE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void deleteBusiness(Long id) {
// 1.1 校验存在
CrmBusinessDO business = validateBusinessExists(id);
// 1.2 校验是否关联合同
validateContractExists(id);
// 删除商机
businessMapper.deleteById(id);
// 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id);
// 记录操作日志上下文
LogRecordContext.putVariable("businessName", business.getName());
}
/**
* 删除校验合同是关联合同
*
* @param businessId 商机id
* @author lzxhqs
*/
private void validateContractExists(Long businessId) {
if (contractService.getContractCountByBusinessId(businessId) > 0) {
throw exception(BUSINESS_DELETE_FAIL_CONTRACT_EXISTS);
}
}
private CrmBusinessDO validateBusinessExists(Long id) {
CrmBusinessDO crmBusiness = businessMapper.selectById(id);
if (crmBusiness == null) {
throw exception(BUSINESS_NOT_EXISTS);
}
return crmBusiness;
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
success = CRM_BUSINESS_TRANSFER_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
public void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId) {
// 1 校验商机是否存在
CrmBusinessDO business = validateBusinessExists(reqVO.getId());
// 2.1 数据权限转移
permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_BUSINESS.getType(),
reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
// 2.2 设置新的负责人
businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
// 记录操作日志上下文
LogRecordContext.putVariable("business", business);
}
//======================= 查询相关 =======================
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.READ)
public CrmBusinessDO getBusiness(Long id) {
return businessMapper.selectById(id);
}
@Override
public CrmBusinessDO validateBusiness(Long id) {
return validateBusinessExists(id);
}
@Override
public List<CrmBusinessDO> getBusinessList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return ListUtil.empty();
}
return businessMapper.selectBatchIds(ids);
}
@Override
public List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId) {
return businessProductMapper.selectListByBusinessId(businessId);
}
@Override
public PageResult<CrmBusinessDO> getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
return businessMapper.selectPage(pageReqVO, userId);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
public PageResult<CrmBusinessDO> getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO) {
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
public Long getBusinessCountByCustomerId(Long customerId) {
return businessMapper.selectCount(CrmBusinessDO::getCustomerId, customerId);
}
@Override
public Long getBusinessCountByStatusTypeId(Long statusTypeId) {
return businessMapper.selectCountByStatusTypeId(statusTypeId);
}
@Override
public List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
}
}

View File

@ -1,172 +0,0 @@
package cn.iocoder.yudao.module.crm.service.contact;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
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;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* CRM 联系人 Service 接口
*
* @author 芋道源码
*/
public interface CrmContactService {
/**
* 创建联系人
*
* @param createReqVO 创建信息
* @param userId 用户编号
* @return 编号
*/
Long createContact(@Valid CrmContactSaveReqVO createReqVO, Long userId);
/**
* 更新联系人
*
* @param updateReqVO 更新信息
*/
void updateContact(@Valid CrmContactSaveReqVO updateReqVO);
/**
* 删除联系人
*
* @param id 编号
*/
void deleteContact(Long id);
/**
* 联系人转移
*
* @param reqVO 请求
* @param userId 用户编号
*/
void transferContact(CrmContactTransferReqVO reqVO, Long userId);
/**
* 更新指定客户的联系人的负责人
* 数据权限基于 客户
*
* @param customerId 客户编号
* @param ownerUserId 用户编号
*/
void updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId);
/**
* 更新联系人相关跟进信息
*
* @param id 编号
* @param contactNextTime 下次联系时间
* @param contactLastContent 最后联系内容
*/
void updateContactFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
/**
* 更新联系人的下次联系时间
*
* @param ids 编号数组
* @param contactNextTime 下次联系时间
*/
void updateContactContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime);
/**
* 获得联系人
*
* @param id 编号
* @return 联系人
*/
CrmContactDO getContact(Long id);
/**
* 校验联系人
*
* @param id 编号
*/
void validateContact(Long id);
/**
* 获得联系人列表
*
* @param ids 编号
* @return 联系人列表
*/
List<CrmContactDO> getContactList(Collection<Long> ids);
/**
* 获得联系人 Map
*
* @param ids 编号
* @return 联系人 Map
*/
default Map<Long, CrmContactDO> getContactMap(Collection<Long> ids) {
return convertMap(getContactList(ids), CrmContactDO::getId);
}
/**
* 获取联系人列表校验权限
*
* @param userId 用户编号
* @return 联系人列表
*/
List<CrmContactDO> getContactList(Long userId);
/**
* 获得联系人分页
*
* 数据权限基于 {@link CrmContactDO}
*
* @param pageReqVO 分页查询
* @param userId 用户编号
* @return 联系人分页
*/
PageResult<CrmContactDO> getContactPage(CrmContactPageReqVO pageReqVO, Long userId);
/**
* 获得联系人分页
*
* 数据权限基于 {@link CrmCustomerDO}
*
* @param pageVO 分页查询
* @return 联系人分页
*/
PageResult<CrmContactDO> getContactPageByCustomerId(CrmContactPageReqVO pageVO);
/**
* 获得联系人分页
*
* 数据权限基于 {@link CrmBusinessDO}
*
* @param pageVO 分页查询
* @return 联系人分页
*/
PageResult<CrmContactDO> getContactPageByBusinessId(CrmContactPageReqVO pageVO);
/**
* 获取关联客户的联系人数量
*
* @param customerId 客户编号
* @return 数量
*/
Long getContactCountByCustomerId(Long customerId);
/**
* 获得联系人列表
*
* @param customerId 客户编号
* @param ownerUserId 负责人编号
* @return 联系人列表
*/
List<CrmContactDO> getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
}

View File

@ -1,306 +0,0 @@
package cn.iocoder.yudao.module.crm.service.contact;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
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.contact.vo.CrmContactBusinessReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
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.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.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
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;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
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.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
import static java.util.Collections.singletonList;
/**
* CRM 联系人 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class CrmContactServiceImpl implements CrmContactService {
@Resource
private CrmContactMapper contactMapper;
@Resource
private CrmCustomerService customerService;
@Resource
private CrmPermissionService permissionService;
@Resource
@Lazy
private CrmContractService contractService;
@Resource
private CrmContactBusinessService contactBusinessService;
@Resource
private CrmBusinessService businessService;
@Resource
private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_CREATE_SUB_TYPE, bizNo = "{{#contact.id}}",
success = CRM_CONTACT_CREATE_SUCCESS)
public Long createContact(CrmContactSaveReqVO createReqVO, Long userId) {
createReqVO.setId(null);
// 1. 校验关联数据
validateRelationDataExists(createReqVO);
// 2. 插入联系人
CrmContactDO contact = BeanUtils.toBean(createReqVO, CrmContactDO.class);
contactMapper.insert(contact);
// 3. 创建数据权限
permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId)
.setBizType(CrmBizTypeEnum.CRM_CONTACT.getType()).setBizId(contact.getId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
// 4. 如果有关联商机则需要创建关联
if (createReqVO.getBusinessId() != null) {
contactBusinessService.createContactBusinessList(new CrmContactBusinessReqVO()
.setContactId(contact.getId()).setBusinessIds(singletonList(createReqVO.getBusinessId())));
}
// 5. 记录操作日志
LogRecordContext.putVariable("contact", contact);
return contact.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = CRM_CONTACT_UPDATE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateContact(CrmContactSaveReqVO updateReqVO) {
// 1.1 校验存在
CrmContactDO oldContact = validateContactExists(updateReqVO.getId());
// 1.2 校验关联数据
validateRelationDataExists(updateReqVO);
// 2. 更新联系人
CrmContactDO updateObj = BeanUtils.toBean(updateReqVO, CrmContactDO.class);
contactMapper.updateById(updateObj);
// 3. 记录操作日志
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContact, CrmContactSaveReqVO.class));
LogRecordContext.putVariable("contactName", oldContact.getName());
}
/**
* 校验关联的数据都存在
*
* @param saveReqVO 新增/修改请求 VO
*/
private void validateRelationDataExists(CrmContactSaveReqVO saveReqVO) {
// 1. 校验客户
if (saveReqVO.getCustomerId() != null && customerService.getCustomer(saveReqVO.getCustomerId()) == null) {
customerService.validateCustomer(saveReqVO.getCustomerId());
}
// 2. 校验负责人
if (saveReqVO.getOwnerUserId() != null) {
adminUserApi.validateUser(saveReqVO.getOwnerUserId());
}
// 3. 直属上级
if (saveReqVO.getParentId() != null) {
validateContactExists(saveReqVO.getParentId());
}
// 4. 如果有关联商机则需要校验存在
if (saveReqVO.getBusinessId() != null && businessService.getBusiness(saveReqVO.getBusinessId()) == null) {
throw exception(BUSINESS_NOT_EXISTS);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_DELETE_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_CONTACT_DELETE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void deleteContact(Long id) {
// 1.1 校验存在
CrmContactDO contact = validateContactExists(id);
// 1.2 校验是否关联合同
if (contractService.getContractCountByContactId(id) > 0) {
throw exception(CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS);
}
// 2. 删除联系人
contactMapper.deleteById(id);
// 4.1 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
// 4.2 删除商机关联
contactBusinessService.deleteContactBusinessByContactId(id);
// 记录操作日志上下文
LogRecordContext.putVariable("contactName", contact.getName());
}
private CrmContactDO validateContactExists(Long id) {
CrmContactDO contact = contactMapper.selectById(id);
if (contact == null) {
throw exception(CONTACT_NOT_EXISTS);
}
return contact;
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
success = CRM_CONTACT_TRANSFER_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
public void transferContact(CrmContactTransferReqVO reqVO, Long userId) {
// 1 校验联系人是否存在
CrmContactDO contact = validateContactExists(reqVO.getId());
// 2.1 数据权限转移
permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CONTACT.getType(),
reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
// 2.2 设置新的负责人
contactMapper.updateById(new CrmContactDO().setId(reqVO.getId()).setOwnerUserId(reqVO.getNewOwnerUserId()));
// 3. 记录转移日志
LogRecordContext.putVariable("contact", contact);
}
@Override
@Transactional(rollbackFor = Exception.class)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#customerId", level = CrmPermissionLevelEnum.OWNER)
public void updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId) {
// 1. 校验存在
List<CrmContactDO> contacts = contactMapper.selectListByCustomerId(customerId);
if (CollUtil.isEmpty(contacts)) {
return;
}
int count = contactMapper.updateOwnerUserIdByCustomerId(customerId, ownerUserId);
if (count == 0) {
throw exception(CONTACT_UPDATE_OWNER_USER_FAIL);
}
// 2. 记录操作日志
for (CrmContactDO contact : contacts) {
receiveContactLog(contact, ownerUserId);
}
}
@LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_UPDATE_OWNER_USER_SUB_TYPE, bizNo = "{{#contact.id}",
success = CRM_CONTACT_UPDATE_OWNER_USER_SUCCESS)
public void receiveContactLog(CrmContactDO contact, Long ownerUserId) {
// 记录操作日志上下文
LogRecordContext.putVariable("contact", contact);
LogRecordContext.putVariable("ownerUserId", ownerUserId);
}
@Override
@LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}",
success = CRM_CONTACT_FOLLOW_UP_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
public void updateContactFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) {
// 1. 校验存在
CrmContactDO contact = validateContactExists(id);
// 2. 更新联系人的跟进信息
contactMapper.updateById(new CrmContactDO().setId(id).setContactNextTime(contactNextTime)
.setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent));
// 3. 记录操作日志上下文
LogRecordContext.putVariable("contactName", contact.getName());
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#ids", level = CrmPermissionLevelEnum.WRITE)
public void updateContactContactNextTime(Collection<Long> ids, LocalDateTime contactNextTime) {
contactMapper.updateBatch(convertList(ids, id -> new CrmContactDO().setId(id).setContactNextTime(contactNextTime)));
}
//======================= 查询相关 =======================
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.READ)
public CrmContactDO getContact(Long id) {
return contactMapper.selectById(id);
}
@Override
public void validateContact(Long id) {
validateContactExists(id);
}
@Override
public List<CrmContactDO> getContactList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return ListUtil.empty();
}
return contactMapper.selectBatchIds(ids);
}
@Override
public List<CrmContactDO> getContactList(Long userId) {
CrmContactPageReqVO reqVO = new CrmContactPageReqVO();
reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
return contactMapper.selectPage(reqVO, userId).getList();
}
@Override
public PageResult<CrmContactDO> getContactPage(CrmContactPageReqVO pageReqVO, Long userId) {
return contactMapper.selectPage(pageReqVO, userId);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageVO.customerId", level = CrmPermissionLevelEnum.READ)
public PageResult<CrmContactDO> getContactPageByCustomerId(CrmContactPageReqVO pageVO) {
return contactMapper.selectPageByCustomerId(pageVO);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#pageVO.businessId", level = CrmPermissionLevelEnum.READ)
public PageResult<CrmContactDO> getContactPageByBusinessId(CrmContactPageReqVO pageVO) {
List<CrmContactBusinessDO> contactBusinessList = contactBusinessService.getContactBusinessListByBusinessId(pageVO.getBusinessId());
if (CollUtil.isEmpty(contactBusinessList)) {
return PageResult.empty();
}
return contactMapper.selectPageByBusinessId(pageVO, convertSet(contactBusinessList, CrmContactBusinessDO::getContactId));
}
@Override
public Long getContactCountByCustomerId(Long customerId) {
return contactMapper.selectCount(CrmContactDO::getCustomerId, customerId);
}
@Override
public List<CrmContactDO> getContactListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return contactMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
}
}

View File

@ -1,205 +0,0 @@
package cn.iocoder.yudao.module.crm.service.contract;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import jakarta.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* CRM 合同 Service 接口
*
* @author dhb52
*/
public interface CrmContractService {
/**
* 创建合同
*
* @param createReqVO 创建信息
* @param userId 用户编号
* @return 编号
*/
Long createContract(@Valid CrmContractSaveReqVO createReqVO, Long userId);
/**
* 更新合同
*
* @param updateReqVO 更新信息
*/
void updateContract(@Valid CrmContractSaveReqVO updateReqVO);
/**
* 删除合同
*
* @param id 编号
*/
void deleteContract(Long id);
/**
* 合同转移
*
* @param reqVO 请求
* @param userId 用户编号
*/
void transferContract(CrmContractTransferReqVO reqVO, Long userId);
/**
* 更新合同相关的更进信息
*
* @param id 合同编号
* @param contactNextTime 下次联系时间
* @param contactLastContent 最后联系内容
*/
void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
/**
* 发起合同审批流程
*
* @param id 合同编号
* @param userId 用户编号
*/
void submitContract(Long id, Long userId);
/**
* 更新合同流程审批结果
*
* @param id 合同编号
* @param bpmResult BPM 审批结果
*/
void updateContractAuditStatus(Long id, Integer bpmResult);
/**
* 获得合同
*
* @param id 编号
* @return 合同
*/
CrmContractDO getContract(Long id);
/**
* 校验合同是否合法
*
* @param id 编号
* @return 合同
*/
CrmContractDO validateContract(Long id);
/**
* 获得合同列表
*
* @param ids 编号
* @return 合同列表
*/
List<CrmContractDO> getContractList(Collection<Long> ids);
/**
* 获得合同 Map
*
* @param ids 编号
* @return 合同 Map
*/
default Map<Long, CrmContractDO> getContractMap(Collection<Long> ids) {
return convertMap(getContractList(ids), CrmContractDO::getId);
}
/**
* 获得合同分页
*
* 数据权限基于 {@link CrmContractDO} 读取
*
* @param pageReqVO 分页查询
* @param userId 用户编号
* @return 合同分页
*/
PageResult<CrmContractDO> getContractPage(CrmContractPageReqVO pageReqVO, Long userId);
/**
* 获得合同分页基于指定客户
*
* 数据权限基于 {@link CrmCustomerDO} 读取
*
* @param pageReqVO 分页查询
* @return 合同分页
*/
PageResult<CrmContractDO> getContractPageByCustomerId(CrmContractPageReqVO pageReqVO);
/**
* 获得合同分页基于指定商机
*
* 数据权限基于 {@link CrmBusinessDO} 读取
*
* @param pageReqVO 分页查询
* @return 合同分页
*/
PageResult<CrmContractDO> getContractPageByBusinessId(CrmContractPageReqVO pageReqVO);
/**
* 查询属于某个联系人的合同数量
*
* @param contactId 联系人ID
* @return 合同
*/
Long getContractCountByContactId(Long contactId);
/**
* 获取关联客户的合同数量
*
* @param customerId 客户编号
* @return 数量
*/
Long getContractCountByCustomerId(Long customerId);
/**
* 根据商机编号获取关联客户的合同数量
*
* @param businessId 商机编号
* @return 数量
*/
Long getContractCountByBusinessId(Long businessId);
/**
* 根据合同编号获得合同的产品列表
*
* @param contactId 合同编号
* @return 产品列表
*/
List<CrmContractProductDO> getContractProductListByContractId(Long contactId);
/**
* 获得待审核合同数量
*
* @param userId 用户编号
* @return 提醒数量
*/
Long getAuditContractCount(Long userId);
/**
* 获得即将到期提醒的合同数量
*
* @param userId 用户编号
* @return 提醒数量
*/
Long getRemindContractCount(Long userId);
/**
* 获得合同列表
*
* @param customerId 客户编号
* @param ownerUserId 负责人编号
* @return 合同列表
*/
List<CrmContractDO> getContractListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
}

View File

@ -1,415 +0,0 @@
package cn.iocoder.yudao.module.crm.service.contract;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractProductMapper;
import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO;
import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
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.*;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
/**
* CRM 合同 Service 实现类
*
* @author dhb52
*/
@Service
@Validated
@Slf4j
public class CrmContractServiceImpl implements CrmContractService {
/**
* BPM 合同审批流程标识
*/
public static final String BPM_PROCESS_DEFINITION_KEY = "crm-contract-audit";
@Resource
private CrmContractMapper contractMapper;
@Resource
private CrmContractProductMapper contractProductMapper;
@Resource
private CrmNoRedisDAO noRedisDAO;
@Resource
private CrmPermissionService crmPermissionService;
@Resource
private CrmProductService productService;
@Resource
private CrmCustomerService customerService;
@Resource
private CrmBusinessService businessService;
@Resource
private CrmContactService contactService;
@Resource
private CrmContractConfigService contractConfigService;
@Resource
@Lazy // 延迟加载避免循环依赖
private CrmReceivableService receivableService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private BpmProcessInstanceApi bpmProcessInstanceApi;
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_CREATE_SUB_TYPE, bizNo = "{{#contract.id}}",
success = CRM_CONTRACT_CREATE_SUCCESS)
public Long createContract(CrmContractSaveReqVO createReqVO, Long userId) {
// 1.1 校验产品项的有效性
List<CrmContractProductDO> contractProducts = validateContractProducts(createReqVO.getProducts());
// 1.2 校验关联字段
validateRelationDataExists(createReqVO);
// 1.3 生成序号
String no = noRedisDAO.generate(CrmNoRedisDAO.CONTRACT_NO_PREFIX);
if (contractMapper.selectByNo(no) != null) {
throw exception(CONTRACT_NO_EXISTS);
}
// 2.1 插入合同
CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setNo(no);
calculateTotalPrice(contract, contractProducts);
contractMapper.insert(contract);
// 2.2 插入合同关联商品
if (CollUtil.isNotEmpty(contractProducts)) {
contractProducts.forEach(item -> item.setContractId(contract.getId()));
contractProductMapper.insertBatch(contractProducts);
}
// 3. 创建数据权限
crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(contract.getOwnerUserId())
.setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()).setBizId(contract.getId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
// 4. 记录操作日志上下文
LogRecordContext.putVariable("contract", contract);
return contract.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = CRM_CONTRACT_UPDATE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateContract(CrmContractSaveReqVO updateReqVO) {
Assert.notNull(updateReqVO.getId(), "合同编号不能为空");
updateReqVO.setOwnerUserId(null); // 不允许更新的字段
// 1.1 校验存在
CrmContractDO contract = validateContractExists(updateReqVO.getId());
// 1.2 只有草稿审批中可以编辑
if (!ObjectUtils.equalsAny(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
CrmAuditStatusEnum.PROCESS.getStatus())) {
throw exception(CONTRACT_UPDATE_FAIL_NOT_DRAFT);
}
// 1.3 校验产品项的有效性
List<CrmContractProductDO> contractProducts = validateContractProducts(updateReqVO.getProducts());
// 1.4 校验关联字段
validateRelationDataExists(updateReqVO);
// 2.1 更新合同
CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class);
calculateTotalPrice(updateObj, contractProducts);
contractMapper.updateById(updateObj);
// 2.2 更新合同关联商品
updateContractProduct(updateReqVO.getId(), contractProducts);
// 3. 记录操作日志上下文
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(contract, CrmContractSaveReqVO.class));
LogRecordContext.putVariable("contractName", contract.getName());
}
private void updateContractProduct(Long id, List<CrmContractProductDO> newList) {
List<CrmContractProductDO> oldList = contractProductMapper.selectListByContractId(id);
List<List<CrmContractProductDO>> diffList = diffList(oldList, newList, // id 不同就认为是不同的记录
(oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
if (CollUtil.isNotEmpty(diffList.get(0))) {
diffList.get(0).forEach(o -> o.setContractId(id));
contractProductMapper.insertBatch(diffList.get(0));
}
if (CollUtil.isNotEmpty(diffList.get(1))) {
contractProductMapper.updateBatch(diffList.get(1));
}
if (CollUtil.isNotEmpty(diffList.get(2))) {
contractProductMapper.deleteBatchIds(convertSet(diffList.get(2), CrmContractProductDO::getId));
}
}
/**
* 校验关联数据是否存在
*
* @param reqVO 请求
*/
private void validateRelationDataExists(CrmContractSaveReqVO reqVO) {
// 1. 校验客户
if (reqVO.getCustomerId() != null) {
customerService.validateCustomer(reqVO.getCustomerId());
}
// 2. 校验负责人
if (reqVO.getOwnerUserId() != null) {
adminUserApi.validateUser(reqVO.getOwnerUserId());
}
// 3. 如果有关联商机则需要校验存在
if (reqVO.getBusinessId() != null) {
businessService.validateBusiness(reqVO.getBusinessId());
}
// 4. 校验签约相关字段
if (reqVO.getSignContactId() != null) {
contactService.validateContact(reqVO.getSignContactId());
}
if (reqVO.getSignUserId() != null) {
adminUserApi.validateUser(reqVO.getSignUserId());
}
}
private List<CrmContractProductDO> validateContractProducts(List<CrmContractSaveReqVO.Product> list) {
// 1. 校验产品存在
productService.validProductList(convertSet(list, CrmContractSaveReqVO.Product::getProductId));
// 2. 转化为 CrmContractProductDO 列表
return convertList(list, o -> BeanUtils.toBean(o, CrmContractProductDO.class,
item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getContractPrice(), item.getCount()))));
}
private void calculateTotalPrice(CrmContractDO contract, List<CrmContractProductDO> contractProducts) {
contract.setTotalProductPrice(getSumValue(contractProducts, CrmContractProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(contract.getTotalProductPrice(), contract.getDiscountPercent());
contract.setTotalPrice(contract.getTotalProductPrice().subtract(discountPrice));
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_DELETE_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_CONTRACT_DELETE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void deleteContract(Long id) {
// 1.1 校验存在
CrmContractDO contract = validateContractExists(id);
// 1.2 如果被 CrmReceivableDO 所使用则不允许删除
if (receivableService.getReceivableCountByContractId(contract.getId()) > 0) {
throw exception(CONTRACT_DELETE_FAIL);
}
// 2.1 删除合同
contractMapper.deleteById(id);
// 2.2 删除数据权限
crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTRACT.getType(), id);
// 3. 记录操作日志上下文
LogRecordContext.putVariable("contractName", contract.getName());
}
private CrmContractDO validateContractExists(Long id) {
CrmContractDO contract = contractMapper.selectById(id);
if (contract == null) {
throw exception(CONTRACT_NOT_EXISTS);
}
return contract;
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
success = CRM_CONTRACT_TRANSFER_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
public void transferContract(CrmContractTransferReqVO reqVO, Long userId) {
// 1. 校验合同是否存在
CrmContractDO contract = validateContractExists(reqVO.getId());
// 2.1 数据权限转移
crmPermissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CONTRACT.getType(),
reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
// 2.2 设置负责人
contractMapper.updateById(new CrmContractDO().setId(reqVO.getId()).setOwnerUserId(reqVO.getNewOwnerUserId()));
// 3. 记录转移日志
LogRecordContext.putVariable("contract", contract);
}
@Override
@LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}",
success = CRM_CONTRACT_FOLLOW_UP_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
public void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) {
// 1. 校验存在
CrmContractDO contract = validateContractExists(id);
// 2. 更新联系人的跟进信息
contractMapper.updateById(new CrmContractDO().setId(id).setContactLastTime(LocalDateTime.now()));
// 3. 记录操作日志上下文
LogRecordContext.putVariable("contractName", contract.getName());
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_SUBMIT_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_CONTRACT_SUBMIT_SUCCESS)
public void submitContract(Long id, Long userId) {
// 1. 校验合同是否在审批
CrmContractDO contract = validateContractExists(id);
if (ObjUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) {
throw exception(CONTRACT_SUBMIT_FAIL_NOT_DRAFT);
}
// 2. 创建合同审批流程实例
String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
.setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY).setBusinessKey(String.valueOf(id)));
// 3. 更新合同工作流编号
contractMapper.updateById(new CrmContractDO().setId(id).setProcessInstanceId(processInstanceId)
.setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
// 3. 记录日志
LogRecordContext.putVariable("contractName", contract.getName());
}
@Override
public void updateContractAuditStatus(Long id, Integer bpmResult) {
// 1.1 校验合同是否存在
CrmContractDO contract = validateContractExists(id);
// 1.2 只有审批中可以更新审批结果
if (ObjUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
log.error("[updateContractAuditStatus][contract({}) 不处于审批中,无法更新审批结果({})]",
contract.getId(), bpmResult);
throw exception(CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
}
// 2. 更新合同审批结果
Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
contractMapper.updateById(new CrmContractDO().setId(id).setAuditStatus(auditStatus));
}
// ======================= 查询相关 =======================
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.READ)
public CrmContractDO getContract(Long id) {
return contractMapper.selectById(id);
}
@Override
public CrmContractDO validateContract(Long id) {
return validateContractExists(id);
}
@Override
public List<CrmContractDO> getContractList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return ListUtil.empty();
}
return contractMapper.selectBatchIds(ids);
}
@Override
public PageResult<CrmContractDO> getContractPage(CrmContractPageReqVO pageReqVO, Long userId) {
// 1. 即将到期需要查询合同配置
CrmContractConfigDO config = null;
if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) {
config = contractConfigService.getContractConfig();
if (config != null && Boolean.FALSE.equals(config.getNotifyEnabled())) {
config = null;
}
if (config == null) {
return PageResult.empty();
}
}
// 2. 查询分页
return contractMapper.selectPage(pageReqVO, userId, config);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
public PageResult<CrmContractDO> getContractPageByCustomerId(CrmContractPageReqVO pageReqVO) {
return contractMapper.selectPageByCustomerId(pageReqVO);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#pageReqVO.businessId", level = CrmPermissionLevelEnum.READ)
public PageResult<CrmContractDO> getContractPageByBusinessId(CrmContractPageReqVO pageReqVO) {
return contractMapper.selectPageByBusinessId(pageReqVO);
}
@Override
public Long getContractCountByContactId(Long contactId) {
return contractMapper.selectCountByContactId(contactId);
}
@Override
public Long getContractCountByCustomerId(Long customerId) {
return contractMapper.selectCount(CrmContractDO::getCustomerId, customerId);
}
@Override
public Long getContractCountByBusinessId(Long businessId) {
return contractMapper.selectCountByBusinessId(businessId);
}
@Override
public List<CrmContractProductDO> getContractProductListByContractId(Long contactId) {
return contractProductMapper.selectListByContractId(contactId);
}
@Override
public Long getAuditContractCount(Long userId) {
return contractMapper.selectCountByAudit(userId);
}
@Override
public Long getRemindContractCount(Long userId) {
CrmContractConfigDO config = contractConfigService.getContractConfig();
if (config == null || Boolean.FALSE.equals(config.getNotifyEnabled())) {
return 0L;
}
return contractMapper.selectCountByRemind(userId, config);
}
@Override
public List<CrmContractDO> getContractListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return contractMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
}
}

View File

@ -1,662 +0,0 @@
package cn.iocoder.yudao.module.crm.service.customer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT;
import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_OWNER_LIMIT;
import static java.util.Collections.singletonList;
/**
* 客户 Service 实现类
*
* @author Wanwan
*/
@Service
@Slf4j
@Validated
public class CrmCustomerServiceImpl implements CrmCustomerService {
@Resource
private CrmCustomerMapper customerMapper;
@Resource
private CrmPermissionService permissionService;
@Resource
private CrmCustomerLimitConfigService customerLimitConfigService;
@Resource
@Lazy
private CrmCustomerPoolConfigService customerPoolConfigService;
@Resource
@Lazy
private CrmContactService contactService;
@Resource
@Lazy
private CrmBusinessService businessService;
@Resource
@Lazy
private CrmContractService contractService;
@Resource
private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}",
success = CRM_CUSTOMER_CREATE_SUCCESS)
public Long createCustomer(CrmCustomerSaveReqVO createReqVO, Long userId) {
createReqVO.setId(null);
// 1. 校验拥有客户是否到达上限
validateCustomerExceedOwnerLimit(createReqVO.getOwnerUserId(), 1);
// 2. 插入客户
CrmCustomerDO customer = initCustomer(createReqVO, userId);
customerMapper.insert(customer);
// 3. 创建数据权限
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
// 4. 记录操作日志上下文
LogRecordContext.putVariable("customer", customer);
return customer.getId();
}
/**
* 初始化客户的通用字段
*
* @param customer 客户信息
* @param ownerUserId 负责人编号
* @return 客户信息 DO
*/
private static CrmCustomerDO initCustomer(Object customer, Long ownerUserId) {
return BeanUtils.toBean(customer, CrmCustomerDO.class).setOwnerUserId(ownerUserId)
.setOwnerTime(LocalDateTime.now());
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = CRM_CUSTOMER_UPDATE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateCustomer(CrmCustomerSaveReqVO updateReqVO) {
Assert.notNull(updateReqVO.getId(), "客户编号不能为空");
updateReqVO.setOwnerUserId(null); // 更新的时候要把 updateReqVO 负责人设置为空避免修改
// 1. 校验存在
CrmCustomerDO oldCustomer = validateCustomerExists(updateReqVO.getId());
// 2. 更新客户
CrmCustomerDO updateObj = BeanUtils.toBean(updateReqVO, CrmCustomerDO.class);
customerMapper.updateById(updateObj);
// 3. 记录操作日志上下文
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerSaveReqVO.class));
LogRecordContext.putVariable("customerName", oldCustomer.getName());
}
@Override
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
public void updateCustomerDealStatus(Long id, Boolean dealStatus) {
// 1.1 校验存在
CrmCustomerDO customer = validateCustomerExists(id);
// 1.2 校验是否重复操作
if (Objects.equals(customer.getDealStatus(), dealStatus)) {
throw exception(CUSTOMER_UPDATE_DEAL_STATUS_FAIL);
}
// 2. 更新客户的成交状态
customerMapper.updateById(new CrmCustomerDO().setId(id).setDealStatus(dealStatus));
// 3. 记录操作日志上下文
LogRecordContext.putVariable("customerName", customer.getName());
LogRecordContext.putVariable("dealStatus", dealStatus);
}
@Override
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}",
success = CRM_CUSTOMER_FOLLOW_UP_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
public void updateCustomerFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) {
// 1.1 校验存在
CrmCustomerDO customer = validateCustomerExists(id);
// 2. 更新客户的跟进信息
customerMapper.updateById(new CrmCustomerDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime)
.setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent));
// 3. 记录操作日志上下文
LogRecordContext.putVariable("customerName", customer.getName());
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_CUSTOMER_DELETE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void deleteCustomer(Long id) {
// 1.1 校验存在
CrmCustomerDO customer = validateCustomerExists(id);
// 1.2 检查引用
validateCustomerReference(id);
// 2. 删除客户
customerMapper.deleteById(id);
// 3. 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
// 4. 记录操作日志上下文
LogRecordContext.putVariable("customerName", customer.getName());
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
success = CRM_CUSTOMER_TRANSFER_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) {
// 1.1 校验客户是否存在
CrmCustomerDO customer = validateCustomerExists(reqVO.getId());
// 1.2 校验拥有客户是否到达上限
validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1);
// 2.1 数据权限转移
permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
// 2.2 转移后重新设置负责人
customerMapper.updateById(new CrmCustomerDO().setId(reqVO.getId())
.setOwnerUserId(reqVO.getNewOwnerUserId()).setOwnerTime(LocalDateTime.now()));
// 2.3 同时转移
if (CollUtil.isNotEmpty(reqVO.getToBizTypes())) {
transfer(reqVO, userId);
}
// 3. 记录转移日志
LogRecordContext.putVariable("customer", customer);
}
/**
* 转移客户时需要额外有联系人商机合同
*
* @param reqVO 请求
* @param userId 用户编号
*/
private void transfer(CrmCustomerTransferReqVO reqVO, Long userId) {
if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTACT.getType())) {
List<CrmContactDO> contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getId(), userId);
contactList.forEach(item -> {
contactService.transferContact(new CrmContactTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
reqVO.getOldOwnerPermissionLevel()), userId);
});
}
if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_BUSINESS.getType())) {
List<CrmBusinessDO> businessList = businessService.getBusinessListByCustomerIdOwnerUserId(reqVO.getId(), userId);
businessList.forEach(item -> {
businessService.transferBusiness(new CrmBusinessTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
reqVO.getOldOwnerPermissionLevel()), userId);
});
}
if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTRACT.getType())) {
List<CrmContractDO> contractList = contractService.getContractListByCustomerIdOwnerUserId(reqVO.getId(), userId);
contractList.forEach(item -> {
contractService.transferContract(new CrmContractTransferReqVO(item.getId(), reqVO.getNewOwnerUserId(),
reqVO.getOldOwnerPermissionLevel()), userId);
});
}
}
@Override
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_LOCK_SUB_TYPE, bizNo = "{{#lockReqVO.id}}",
success = CRM_CUSTOMER_LOCK_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#lockReqVO.id", level = CrmPermissionLevelEnum.OWNER)
public void lockCustomer(CrmCustomerLockReqVO lockReqVO, Long userId) {
// 1.1 校验当前客户是否存在
CrmCustomerDO customer = validateCustomerExists(lockReqVO.getId());
// 1.2 校验当前是否重复操作锁定/解锁状态
if (customer.getLockStatus().equals(lockReqVO.getLockStatus())) {
throw exception(customer.getLockStatus() ? CUSTOMER_LOCK_FAIL_IS_LOCK : CUSTOMER_UNLOCK_FAIL_IS_UNLOCK);
}
// 1.3 校验锁定上限
if (lockReqVO.getLockStatus()) {
validateCustomerExceedLockLimit(userId);
}
// 2. 更新锁定状态
customerMapper.updateById(BeanUtils.toBean(lockReqVO, CrmCustomerDO.class));
// 3. 记录操作日志上下文
// tips: 因为这里使用的是老的状态所以记录时反着记录也就是 lockStatus true 那么就是解锁反之为锁定
LogRecordContext.putVariable("customer", customer);
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}",
success = CRM_CUSTOMER_CREATE_SUCCESS)
public Long createCustomer(CrmCustomerCreateReqBO createReqBO, Long userId) {
// 1. 插入客户
CrmCustomerDO customer = initCustomer(createReqBO, userId);
customerMapper.insert(customer);
// 2. 创建数据权限
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
// 3. 记录操作日志上下文
LogRecordContext.putVariable("customer", customer);
return customer.getId();
}
@Override
public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers,
CrmCustomerImportReqVO importReqVO) {
// 校验非空
importCustomers = filterList(importCustomers, item -> Objects.nonNull(item.getName()));
if (CollUtil.isEmpty(importCustomers)) {
throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY);
}
// 逐条处理
CrmCustomerImportRespVO respVO = CrmCustomerImportRespVO.builder().createCustomerNames(new ArrayList<>())
.updateCustomerNames(new ArrayList<>()).failureCustomerNames(new LinkedHashMap<>()).build();
importCustomers.forEach(importCustomer -> {
// 校验判断是否有不符合的原因
try {
validateCustomerForCreate(importCustomer);
} catch (ServiceException ex) {
respVO.getFailureCustomerNames().put(importCustomer.getName(), ex.getMessage());
return;
}
// 情况一判断如果不存在在进行插入
CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName());
if (existCustomer == null) {
// 1.1 插入客户信息
CrmCustomerDO customer = initCustomer(importCustomer, importReqVO.getOwnerUserId());
customerMapper.insert(customer);
respVO.getCreateCustomerNames().add(importCustomer.getName());
// 1.2 创建数据权限
if (importReqVO.getOwnerUserId() != null) {
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setUserId(importReqVO.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
}
// 1.3 记录操作日志
getSelf().importCustomerLog(customer, false);
return;
}
// 情况二如果存在判断是否允许更新
if (!importReqVO.getUpdateSupport()) {
respVO.getFailureCustomerNames().put(importCustomer.getName(),
StrUtil.format(CUSTOMER_NAME_EXISTS.getMsg(), importCustomer.getName()));
return;
}
// 2.1 更新客户信息
CrmCustomerDO updateCustomer = BeanUtils.toBean(importCustomer, CrmCustomerDO.class)
.setId(existCustomer.getId());
customerMapper.updateById(updateCustomer);
respVO.getUpdateCustomerNames().add(importCustomer.getName());
// 2.2 记录操作日志
getSelf().importCustomerLog(updateCustomer, true);
});
return respVO;
}
/**
* 记录导入客户时的操作日志
*
* @param customer 客户信息
* @param isUpdate 是否更新true - 更新false - 新增
*/
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_IMPORT_SUB_TYPE, bizNo = "{{#customer.id}}",
success = CRM_CUSTOMER_IMPORT_SUCCESS)
public void importCustomerLog(CrmCustomerDO customer, boolean isUpdate) {
LogRecordContext.putVariable("customer", customer);
LogRecordContext.putVariable("isUpdate", isUpdate);
}
// ==================== 公海相关操作 ====================
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_POOL_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_CUSTOMER_POOL_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void putCustomerPool(Long id) {
// 1. 校验存在
CrmCustomerDO customer = customerMapper.selectById(id);
if (customer == null) {
throw exception(CUSTOMER_NOT_EXISTS);
}
// 1.2. 校验是否为公海数据
validateCustomerOwnerExists(customer, true);
// 1.3. 校验客户是否锁定
validateCustomerIsLocked(customer, true);
// 2. 客户放入公海
putCustomerPool(customer);
// 记录操作日志上下文
LogRecordContext.putVariable("customerName", customer.getName());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void receiveCustomer(List<Long> ids, Long ownerUserId, Boolean isReceive) {
// 1.1 校验存在
List<CrmCustomerDO> customers = customerMapper.selectBatchIds(ids);
if (customers.size() != ids.size()) {
throw exception(CUSTOMER_NOT_EXISTS);
}
// 1.2 校验负责人是否存在
adminUserApi.validateUserList(singletonList(ownerUserId));
// 1.3 校验状态
customers.forEach(customer -> {
// 校验是否已有负责人
validateCustomerOwnerExists(customer, false);
// 校验是否锁定
validateCustomerIsLocked(customer, false);
// 校验成交状态
validateCustomerDeal(customer);
});
// 1.4 校验负责人是否到达上限
validateCustomerExceedOwnerLimit(ownerUserId, customers.size());
// 2. 领取公海数据
List<CrmCustomerDO> updateCustomers = new ArrayList<>();
List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();
customers.forEach(customer -> {
// 2.1. 设置负责人
updateCustomers.add(new CrmCustomerDO().setId(customer.getId())
.setOwnerUserId(ownerUserId).setOwnerTime(LocalDateTime.now()));
// 2.2. 创建负责人数据权限
createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
});
// 2.2 更新客户负责人
customerMapper.updateBatch(updateCustomers);
// 2.3 创建负责人数据权限
permissionService.createPermissionBatch(createPermissions);
// TODO @芋艿要不要处理关联的联系人
// 3. 记录操作日志
AdminUserRespDTO user = null;
if (!isReceive) {
user = adminUserApi.getUser(ownerUserId);
}
for (CrmCustomerDO customer : customers) {
getSelf().receiveCustomerLog(customer, user == null ? null : user.getNickname());
}
}
@Override
public int autoPutCustomerPool() {
CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
if (poolConfig == null || !poolConfig.getEnabled()) {
return 0;
}
// 1. 获得需要放到的客户列表
List<CrmCustomerDO> customerList = customerMapper.selectListByAutoPool(poolConfig);
// 2. 逐个放入公海
int count = 0;
for (CrmCustomerDO customer : customerList) {
try {
getSelf().putCustomerPool(customer);
count++;
} catch (Throwable e) {
log.error("[autoPutCustomerPool][客户({}) 放入公海异常]", customer.getId(), e);
}
}
return count;
}
@Transactional(rollbackFor = Exception.class) // 需要 protected 修饰因为需要在事务中调用
protected void putCustomerPool(CrmCustomerDO customer) {
// 1. 设置负责人为 NULL
int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null);
if (updateOwnerUserIncr == 0) {
throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL);
}
// 2. 联系人的负责人也要设置为 null因为因为领取后负责人也要关联过来这块和 receiveCustomer 是对应的
contactService.updateOwnerUserIdByCustomerId(customer.getId(), null);
// 3. 删除负责人数据权限
// 注意需要放在 contactService 后面不然客户数据权限已经被删除无法操作
permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(),
CrmPermissionLevelEnum.OWNER.getLevel());
}
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_RECEIVE_SUB_TYPE, bizNo = "{{#customer.id}}",
success = CRM_CUSTOMER_RECEIVE_SUCCESS)
public void receiveCustomerLog(CrmCustomerDO customer, String ownerUserName) {
// 记录操作日志上下文
LogRecordContext.putVariable("customer", customer);
LogRecordContext.putVariable("ownerUserName", ownerUserName);
}
//======================= 查询相关 =======================
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.READ)
public CrmCustomerDO getCustomer(Long id) {
return customerMapper.selectById(id);
}
@Override
public List<CrmCustomerDO> getCustomerList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return customerMapper.selectBatchIds(ids);
}
@Override
public PageResult<CrmCustomerDO> getCustomerPage(CrmCustomerPageReqVO pageReqVO, Long userId) {
return customerMapper.selectPage(pageReqVO, userId);
}
@Override
public PageResult<CrmCustomerDO> getPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, Long userId) {
CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
if (ObjUtil.isNull(poolConfig)
|| Boolean.FALSE.equals(poolConfig.getEnabled())
|| Boolean.FALSE.equals(poolConfig.getNotifyEnabled())) {
return PageResult.empty();
}
return customerMapper.selectPutPoolRemindCustomerPage(pageVO, poolConfig, userId);
}
@Override
public Long getPutPoolRemindCustomerCount(Long userId) {
CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
if (ObjUtil.isNull(poolConfig)
|| Boolean.FALSE.equals(poolConfig.getEnabled())
|| Boolean.FALSE.equals(poolConfig.getNotifyEnabled())) {
return 0L;
}
CrmCustomerPageReqVO pageVO = new CrmCustomerPageReqVO()
.setPool(null)
.setContactStatus(CrmCustomerPageReqVO.CONTACT_TODAY)
.setSceneType(CrmSceneTypeEnum.OWNER.getType());
return customerMapper.selectPutPoolRemindCustomerCount(pageVO, poolConfig, userId);
}
@Override
public Long getTodayContactCustomerCount(Long userId) {
return customerMapper.selectCountByTodayContact(userId);
}
@Override
public Long getFollowCustomerCount(Long userId) {
return customerMapper.selectCountByFollow(userId);
}
// ======================= 校验相关 =======================
private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) {
// 校验客户名称不能为空
if (StrUtil.isEmptyIfStr(importCustomer.getName())) {
throw exception(CUSTOMER_CREATE_NAME_NOT_NULL);
}
}
/**
* 校验客户是否被引用
*
* @param id 客户编号
*/
private void validateCustomerReference(Long id) {
if (contactService.getContactCountByCustomerId(id) > 0) {
throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTACT.getName());
}
if (businessService.getBusinessCountByCustomerId(id) > 0) {
throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_BUSINESS.getName());
}
if (contractService.getContractCountByCustomerId(id) > 0) {
throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTRACT.getName());
}
}
/**
* 校验客户是否存在
*
* @param id 客户 id
*/
@Override
public void validateCustomer(Long id) {
validateCustomerExists(id);
}
private void validateCustomerOwnerExists(CrmCustomerDO customer, Boolean pool) {
if (customer == null) { // 防御一下
throw exception(CUSTOMER_NOT_EXISTS);
}
// 校验是否为公海数据
if (pool && customer.getOwnerUserId() == null) {
throw exception(CUSTOMER_IN_POOL, customer.getName());
}
// 负责人已存在
if (!pool && customer.getOwnerUserId() != null) {
throw exception(CUSTOMER_OWNER_EXISTS, customer.getName());
}
}
private CrmCustomerDO validateCustomerExists(Long id) {
CrmCustomerDO customerDO = customerMapper.selectById(id);
if (customerDO == null) {
throw exception(CUSTOMER_NOT_EXISTS);
}
return customerDO;
}
private void validateCustomerIsLocked(CrmCustomerDO customer, Boolean pool) {
if (customer.getLockStatus()) {
throw exception(pool ? CUSTOMER_LOCKED_PUT_POOL_FAIL : CUSTOMER_LOCKED, customer.getName());
}
}
private void validateCustomerDeal(CrmCustomerDO customer) {
if (customer.getDealStatus()) {
throw exception(CUSTOMER_ALREADY_DEAL);
}
}
/**
* 校验用户拥有的客户数量是否到达上限
*
* @param userId 用户编号
* @param newCount 附加数量
*/
private void validateCustomerExceedOwnerLimit(Long userId, int newCount) {
List<CrmCustomerLimitConfigDO> limitConfigs = customerLimitConfigService.getCustomerLimitConfigListByUserId(
CUSTOMER_OWNER_LIMIT.getType(), userId);
if (CollUtil.isEmpty(limitConfigs)) {
return;
}
Long ownerCount = customerMapper.selectCountByDealStatusAndOwnerUserId(null, userId);
Long dealOwnerCount = customerMapper.selectCountByDealStatusAndOwnerUserId(true, userId);
limitConfigs.forEach(limitConfig -> {
long nowCount = limitConfig.getDealCountEnabled() ? ownerCount : ownerCount - dealOwnerCount;
if (nowCount + newCount > limitConfig.getMaxCount()) {
throw exception(CUSTOMER_OWNER_EXCEED_LIMIT);
}
});
}
/**
* 校验用户锁定的客户数量是否到达上限
*
* @param userId 用户编号
*/
private void validateCustomerExceedLockLimit(Long userId) {
List<CrmCustomerLimitConfigDO> limitConfigs = customerLimitConfigService.getCustomerLimitConfigListByUserId(
CUSTOMER_LOCK_LIMIT.getType(), userId);
if (CollUtil.isEmpty(limitConfigs)) {
return;
}
Long lockCount = customerMapper.selectCountByLockStatusAndOwnerUserId(true, userId);
Integer maxCount = CollectionUtils.getMaxValue(limitConfigs, CrmCustomerLimitConfigDO::getMaxCount);
assert maxCount != null;
if (lockCount >= maxCount) {
throw exception(CUSTOMER_LOCK_EXCEED_LIMIT);
}
}
/**
* 获得自身的代理对象解决 AOP 生效问题
*
* @return 自己
*/
private CrmCustomerServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
}

View File

@ -1,131 +0,0 @@
package cn.iocoder.yudao.module.crm.service.permission;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* crm 数据权限 Service 接口
*
* @author HUIHUI
*/
public interface CrmPermissionService {
/**
* 创建数据权限
*
* @param reqVO 创建信息
* @param userId 用户编号
*/
void createPermission(CrmPermissionSaveReqVO reqVO, Long userId);
/**
* 创建数据权限
*
* @param createReqBO 创建信息
* @return 编号
*/
Long createPermission(@Valid CrmPermissionCreateReqBO createReqBO);
/**
* 创建数据权限
*
* @param createReqBOs 创建信息
*/
void createPermissionBatch(@Valid List<CrmPermissionCreateReqBO> createReqBOs);
/**
* 更新数据权限
*
* @param updateReqVO 更新信息
*/
void updatePermission(CrmPermissionUpdateReqVO updateReqVO);
/**
* 数据权限转移
*
* @param crmPermissionTransferReqBO 数据权限转移请求
*/
void transferPermission(@Valid CrmPermissionTransferReqBO crmPermissionTransferReqBO);
/**
* 删除数据权限
*
* @param bizType 数据类型关联 {@link CrmBizTypeEnum}
* @param bizId 数据编号关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
* @param level 数据权限级别关联 {@link CrmPermissionLevelEnum}
*/
void deletePermission(Integer bizType, Long bizId, Integer level);
/**
* 删除数据权限
*
* @param bizType 数据类型关联 {@link CrmBizTypeEnum}
* @param bizId 数据编号关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
*/
void deletePermission(Integer bizType, Long bizId);
/**
* 批量删除数据权限
*
* @param ids 权限编号
* @param userId 用户编号
*/
void deletePermissionBatch(Collection<Long> ids, Long userId);
/**
* 删除指定用户数据权限
*
* @param id 权限编号
* @param userId 用户编号
*/
void deleteSelfPermission(Long id, Long userId);
/**
* 获取数据权限列表通过 数据类型 x 某个数据
*
* @param bizType 数据类型关联 {@link CrmBizTypeEnum}
* @param bizId 数据编号关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
* @return Crm 数据权限列表
*/
List<CrmPermissionDO> getPermissionListByBiz(Integer bizType, Long bizId);
/**
* 获取数据权限列表通过 数据类型 x 某个数据
*
* @param bizType 数据类型关联 {@link CrmBizTypeEnum}
* @param bizIds 数据编号关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
* @return Crm 数据权限列表
*/
List<CrmPermissionDO> getPermissionListByBiz(Integer bizType, Collection<Long> bizIds);
/**
* 获取用户参与的模块数据列表
*
* @param bizType 模块类型
* @param userId 用户编号
* @return 模块数据列表
*/
List<CrmPermissionDO> getPermissionListByBizTypeAndUserId(Integer bizType, Long userId);
/**
* 校验是否有指定数据的操作权限
*
* @param bizType 数据类型关联 {@link CrmBizTypeEnum}
* @param bizId 数据编号关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
* @param userId 用户编号
* @param level 权限级别
* @return 是否有权限
*/
boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum level);
}

View File

@ -1,335 +0,0 @@
package cn.iocoder.yudao.module.crm.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner;
/**
* CRM 数据权限 Service 接口实现类
*
* @author HUIHUI
*/
@Service
@Validated
public class CrmPermissionServiceImpl implements CrmPermissionService {
@Resource
private CrmPermissionMapper permissionMapper;
@Resource
@Lazy // 解决依赖循环
private CrmContactService contactService;
@Resource
@Lazy // 解决依赖循环
private CrmBusinessService businessService;
@Resource
@Lazy // 解决依赖循环
private CrmContractService contractService;
@Resource
private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
@CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER)
public void createPermission(CrmPermissionSaveReqVO reqVO, Long userId) {
// 1. 创建数据权限
createPermission0(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class));
// 2. 处理同时添加至的权限
if (CollUtil.isEmpty(reqVO.getToBizTypes())) {
return;
}
List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();
buildContactPermissions(reqVO, userId, createPermissions);
buildBusinessPermissions(reqVO, userId, createPermissions);
buildContractPermissions(reqVO, userId, createPermissions);
if (CollUtil.isEmpty(createPermissions)) {
return;
}
createPermissionBatch(createPermissions);
}
/**
* 处理同时添加至联系人
*
* @param reqVO 请求
* @param userId 操作人
* @param createPermissions 待添加权限列表
*/
private void buildContactPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List<CrmPermissionCreateReqBO> createPermissions) {
// 1. 校验是否被同时添加
Integer type = CrmBizTypeEnum.CRM_CONTACT.getType();
if (!reqVO.getToBizTypes().contains(type)) {
return;
}
// 2. 添加数据权限
List<CrmContactDO> contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
contactList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
}
/**
* 处理同时添加至商机
*
* @param reqVO 请求
* @param userId 操作人
* @param createPermissions 待添加权限列表
*/
private void buildBusinessPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List<CrmPermissionCreateReqBO> createPermissions) {
// 1. 校验是否被同时添加
Integer type = CrmBizTypeEnum.CRM_BUSINESS.getType();
if (!reqVO.getToBizTypes().contains(type)) {
return;
}
// 2. 添加数据权限
List<CrmBusinessDO> businessList = businessService.getBusinessListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
businessList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
}
/**
* 处理同时添加至合同
*
* @param reqVO 请求
* @param userId 操作人
* @param createPermissions 待添加权限列表
*/
private void buildContractPermissions(CrmPermissionSaveReqVO reqVO, Long userId, List<CrmPermissionCreateReqBO> createPermissions) {
// 1. 校验是否被同时添加
Integer type = CrmBizTypeEnum.CRM_CONTRACT.getType();
if (!reqVO.getToBizTypes().contains(type)) {
return;
}
// 2. 添加数据权限
List<CrmContractDO> contractList = contractService.getContractListByCustomerIdOwnerUserId(reqVO.getBizId(), userId);
contractList.forEach(item -> createBizTypePermissions(reqVO, type, item.getId(), item.getName(), createPermissions));
}
private void createBizTypePermissions(CrmPermissionSaveReqVO reqVO, Integer type, Long bizId, String name,
List<CrmPermissionCreateReqBO> createPermissions) {
AdminUserRespDTO user = adminUserApi.getUser(reqVO.getUserId());
// 1. 需要考虑被添加人是不是应该有对应的权限了
CrmPermissionDO permission = hasAnyPermission(type, bizId, reqVO.getUserId());
if (ObjUtil.isNotNull(permission)) {
throw exception(CRM_PERMISSION_CREATE_FAIL_EXISTS, user.getNickname(), CrmBizTypeEnum.getNameByType(type),
name, CrmPermissionLevelEnum.getNameByLevel(permission.getLevel()));
}
// 2. 添加数据权限
createPermissions.add(new CrmPermissionCreateReqBO().setBizType(type)
.setBizId(bizId).setUserId(reqVO.getUserId()).setLevel(reqVO.getLevel()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createPermission(CrmPermissionCreateReqBO createReqBO) {
return createPermission0(createReqBO);
}
private Long createPermission0(CrmPermissionCreateReqBO createReqBO) {
validatePermissionNotExists(Collections.singletonList(createReqBO));
// 1. 校验用户是否存在
adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId()));
// 2. 插入权限
CrmPermissionDO permission = BeanUtils.toBean(createReqBO, CrmPermissionDO.class);
permissionMapper.insert(permission);
return permission.getId();
}
@Override
public void createPermissionBatch(List<CrmPermissionCreateReqBO> createReqBOs) {
validatePermissionNotExists(createReqBOs);
// 1. 校验用户是否存在
adminUserApi.validateUserList(convertSet(createReqBOs, CrmPermissionCreateReqBO::getUserId));
// 2. 创建
List<CrmPermissionDO> permissions = BeanUtils.toBean(createReqBOs, CrmPermissionDO.class);
permissionMapper.insertBatch(permissions);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updatePermission(CrmPermissionUpdateReqVO updateReqVO) {
// 1. 校验存在
validatePermissionExists(updateReqVO.getIds());
// 2. 更新
List<CrmPermissionDO> updateList = CollectionUtils.convertList(updateReqVO.getIds(),
id -> new CrmPermissionDO().setId(id).setLevel(updateReqVO.getLevel()));
permissionMapper.updateBatch(updateList);
}
private void validatePermissionExists(Collection<Long> ids) {
List<CrmPermissionDO> permissionList = permissionMapper.selectBatchIds(ids);
if (ObjUtil.notEqual(permissionList.size(), ids.size())) {
throw exception(CRM_PERMISSION_NOT_EXISTS);
}
}
private void validatePermissionNotExists(Collection<CrmPermissionCreateReqBO> createReqBOs) {
Set<Integer> bizTypes = convertSet(createReqBOs, CrmPermissionCreateReqBO::getBizType);
Set<Long> bizIds = convertSet(createReqBOs, CrmPermissionCreateReqBO::getBizId);
Set<Long> userIds = convertSet(createReqBOs, CrmPermissionCreateReqBO::getUserId);
Long count = permissionMapper.selectListByBiz(bizTypes, bizIds, userIds);
if (count > 0) {
throw exception(CRM_PERMISSION_CREATE_FAIL);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void transferPermission(CrmPermissionTransferReqBO transferReqBO) {
// 1. 校验数据权限是否是负责人只有负责人才可以转移
CrmPermissionDO oldPermission = permissionMapper.selectByBizTypeAndBizIdByUserId(
transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId());
String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType());
if (oldPermission == null // 不是拥有者并且不是超管
|| (!isOwner(oldPermission.getLevel()) && !CrmPermissionUtils.isCrmAdmin())) {
throw exception(CRM_PERMISSION_DENIED, bizTypeName);
}
// 1.1 校验转移对象是否已经是该负责人
if (ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldPermission.getUserId())) {
throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName);
}
// 1.2 校验新负责人是否存在
adminUserApi.validateUserList(Collections.singletonList(transferReqBO.getNewOwnerUserId()));
// 2. 修改新负责人的权限
List<CrmPermissionDO> permissions = permissionMapper.selectByBizTypeAndBizId(
transferReqBO.getBizType(), transferReqBO.getBizId()); // 获得所有数据权限
CrmPermissionDO permission = CollUtil.findOne(permissions,
item -> ObjUtil.equal(item.getUserId(), transferReqBO.getNewOwnerUserId()));
if (permission == null) {
permissionMapper.insert(new CrmPermissionDO().setBizType(transferReqBO.getBizType())
.setBizId(transferReqBO.getBizId()).setUserId(transferReqBO.getNewOwnerUserId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
} else {
permissionMapper.updateById(new CrmPermissionDO().setId(permission.getId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
}
// 3. 修改老负责人的权限
if (transferReqBO.getOldOwnerPermissionLevel() != null) {
permissionMapper.updateById(new CrmPermissionDO().setId(oldPermission.getId())
.setLevel(transferReqBO.getOldOwnerPermissionLevel()));
} else {
permissionMapper.deleteById(oldPermission.getId());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deletePermission(Integer bizType, Long bizId, Integer level) {
// 校验存在
List<CrmPermissionDO> permissions = permissionMapper.selectListByBizTypeAndBizIdAndLevel(
bizType, bizId, level);
if (CollUtil.isEmpty(permissions)) {
throw exception(CRM_PERMISSION_NOT_EXISTS);
}
// 删除数据权限
permissionMapper.deleteBatchIds(convertSet(permissions, CrmPermissionDO::getId));
}
@Override
public void deletePermission(Integer bizType, Long bizId) {
int deletedCount = permissionMapper.deletePermission(bizType, bizId);
if (deletedCount == 0) {
throw exception(CRM_PERMISSION_NOT_EXISTS);
}
}
@Override
public void deletePermissionBatch(Collection<Long> ids, Long userId) {
List<CrmPermissionDO> permissions = permissionMapper.selectBatchIds(ids);
if (CollUtil.isEmpty(permissions)) {
throw exception(CRM_PERMISSION_NOT_EXISTS);
}
// 校验数据权限的模块数据编号是一致的不可能存在两个
if (convertSet(permissions, CrmPermissionDO::getBizId).size() > 1) {
throw exception(CRM_PERMISSION_DELETE_FAIL);
}
// 校验操作人是否为负责人
CrmPermissionDO permission = permissionMapper.selectByBizAndUserId(permissions.get(0).getBizType(), permissions.get(0).getBizId(), userId);
if (permission == null) {
throw exception(CRM_PERMISSION_DELETE_DENIED);
}
if (!CrmPermissionLevelEnum.isOwner(permission.getLevel())) {
throw exception(CRM_PERMISSION_DELETE_DENIED);
}
// 删除数据权限
permissionMapper.deleteBatchIds(ids);
}
@Override
public void deleteSelfPermission(Long id, Long userId) {
// 校验数据存在且是自己
CrmPermissionDO permission = permissionMapper.selectByIdAndUserId(id, userId);
if (permission == null) {
throw exception(CRM_PERMISSION_NOT_EXISTS);
}
// 校验是否是负责人
if (CrmPermissionLevelEnum.isOwner(permission.getLevel())) {
throw exception(CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER);
}
// 删除
permissionMapper.deleteById(id);
}
@Override
public List<CrmPermissionDO> getPermissionListByBiz(Integer bizType, Long bizId) {
return permissionMapper.selectByBizTypeAndBizId(bizType, bizId);
}
@Override
public List<CrmPermissionDO> getPermissionListByBiz(Integer bizType, Collection<Long> bizIds) {
return permissionMapper.selectByBizTypeAndBizIds(bizType, bizIds);
}
@Override
public List<CrmPermissionDO> getPermissionListByBizTypeAndUserId(Integer bizType, Long userId) {
return permissionMapper.selectListByBizTypeAndUserId(bizType, userId);
}
@Override
public boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum level) {
List<CrmPermissionDO> permissionList = permissionMapper.selectByBizTypeAndBizId(bizType, bizId);
return anyMatch(permissionList, permission ->
ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), level.getLevel()));
}
public CrmPermissionDO hasAnyPermission(Integer bizType, Long bizId, Long userId) {
List<CrmPermissionDO> permissionList = permissionMapper.selectByBizTypeAndBizId(bizType, bizId);
return findFirst(permissionList, permission -> ObjUtil.equal(permission.getUserId(), userId));
}
}

View File

@ -1,138 +0,0 @@
package cn.iocoder.yudao.module.crm.service.product;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryCreateReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
import cn.iocoder.yudao.module.crm.dal.mysql.product.CrmProductCategoryMapper;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO.PARENT_ID_NULL;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
/**
* CRM 产品分类 Service 实现类
*
* @author ZanGe
*/
@Service
@Validated
public class CrmProductCategoryServiceImpl implements CrmProductCategoryService {
@Resource(name = "crmProductCategoryMapper")
private CrmProductCategoryMapper productCategoryMapper;
@Resource
@Lazy // 延迟加载解决循环依赖问题
private CrmProductService crmProductService;
@Override
@LogRecord(type = CRM_PRODUCT_CATEGORY_TYPE, subType = CRM_PRODUCT_CATEGORY_CREATE_SUB_TYPE, bizNo = "{{#productCategoryId}}",
success = CRM_PRODUCT_CATEGORY_CREATE_SUCCESS)
public Long createProductCategory(CrmProductCategoryCreateReqVO createReqVO) {
// 1.1 校验父分类存在
validateParentProductCategory(createReqVO.getParentId());
// 1.2 分类名称是否存在
validateProductNameExists(null, createReqVO.getParentId(), createReqVO.getName());
// 2. 插入分类
CrmProductCategoryDO category = BeanUtils.toBean(createReqVO, CrmProductCategoryDO.class);
productCategoryMapper.insert(category);
// 3. 记录操作日志上下文
LogRecordContext.putVariable("productCategoryId", category.getId());
return category.getId();
}
@Override
@LogRecord(type = CRM_PRODUCT_CATEGORY_TYPE, subType = CRM_PRODUCT_CATEGORY_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = CRM_PRODUCT_CATEGORY_UPDATE_SUCCESS)
public void updateProductCategory(CrmProductCategoryCreateReqVO updateReqVO) {
// 1.1 校验存在
validateProductCategoryExists(updateReqVO.getId());
// 1.2 校验父分类存在
validateParentProductCategory(updateReqVO.getParentId());
// 1.3 分类名称是否存在
validateProductNameExists(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
// 2. 更新分类
CrmProductCategoryDO updateObj = BeanUtils.toBean(updateReqVO, CrmProductCategoryDO.class);
productCategoryMapper.updateById(updateObj);
}
private void validateProductCategoryExists(Long id) {
if (productCategoryMapper.selectById(id) == null) {
throw exception(PRODUCT_CATEGORY_NOT_EXISTS);
}
}
private void validateParentProductCategory(Long id) {
// 如果是根分类无需验证
if (Objects.equals(id, PARENT_ID_NULL)) {
return;
}
// 父分类不存在
CrmProductCategoryDO category = productCategoryMapper.selectById(id);
if (category == null) {
throw exception(PRODUCT_CATEGORY_PARENT_NOT_EXISTS);
}
// 父分类不能是二级分类
if (!Objects.equals(category.getParentId(), PARENT_ID_NULL)) {
throw exception(PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL);
}
}
private void validateProductNameExists(Long id, Long parentId, String name) {
CrmProductCategoryDO category = productCategoryMapper.selectByParentIdAndName(parentId, name);
if (category == null
|| category.getId().equals(id)) {
return;
}
throw exception(PRODUCT_CATEGORY_EXISTS);
}
@Override
@LogRecord(type = CRM_PRODUCT_CATEGORY_TYPE, subType = CRM_PRODUCT_CATEGORY_DELETE_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_PRODUCT_CATEGORY_DELETE_SUCCESS)
public void deleteProductCategory(Long id) {
// 1.1 校验存在
validateProductCategoryExists(id);
// 1.2 校验是否还有子分类
if (productCategoryMapper.selectCountByParentId(id) > 0) {
throw exception(PRODUCT_CATEGORY_EXISTS_CHILDREN);
}
// 1.3 校验是否被产品使用
if (crmProductService.getProductByCategoryId(id) > 0) {
throw exception(PRODUCT_CATEGORY_USED);
}
// 2. 删除
productCategoryMapper.deleteById(id);
}
@Override
public CrmProductCategoryDO getProductCategory(Long id) {
return productCategoryMapper.selectById(id);
}
@Override
public List<CrmProductCategoryDO> getProductCategoryList(CrmProductCategoryListReqVO listReqVO) {
return productCategoryMapper.selectList(listReqVO);
}
@Override
public List<CrmProductCategoryDO> getProductCategoryList(Collection<Long> ids) {
return productCategoryMapper.selectBatchIds(ids);
}
}

View File

@ -1,187 +0,0 @@
package cn.iocoder.yudao.module.crm.service.receivable;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
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.CrmReceivablePlanPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_PLAN_NOT_EXISTS;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_PLAN_UPDATE_FAIL;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
/**
* 回款计划 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
@Resource
private CrmReceivablePlanMapper receivablePlanMapper;
@Resource
private CrmContractService contractService;
@Resource
private CrmPermissionService permissionService;
@Resource
private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE, bizNo = "{{#receivablePlan.id}}",
success = CRM_RECEIVABLE_PLAN_CREATE_SUCCESS)
public Long createReceivablePlan(CrmReceivablePlanSaveReqVO createReqVO) {
// 1. 校验关联数据是否存在
validateRelationDataExists(createReqVO);
// 2. 插入回款计划
CrmReceivablePlanDO maxPeriodReceivablePlan = receivablePlanMapper.selectMaxPeriodByContractId(createReqVO.getContractId());
int period = maxPeriodReceivablePlan == null ? 1 : maxPeriodReceivablePlan.getPeriod() + 1;
CrmReceivablePlanDO receivablePlan = BeanUtils.toBean(createReqVO, CrmReceivablePlanDO.class).setPeriod(period);
if (createReqVO.getReturnTime() != null && createReqVO.getRemindDays() != null) {
receivablePlan.setRemindTime(createReqVO.getReturnTime().minusDays(createReqVO.getRemindDays()));
}
receivablePlanMapper.insert(receivablePlan);
// 3. 创建数据权限
permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(createReqVO.getOwnerUserId())
.setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
// 4. 记录操作日志上下文
LogRecordContext.putVariable("receivablePlan", receivablePlan);
return receivablePlan.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateReceivablePlan(CrmReceivablePlanSaveReqVO updateReqVO) {
updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null); // 防止修改这些字段
// 1.1 校验存在
validateRelationDataExists(updateReqVO);
// 1.2 校验关联数据是否存在
CrmReceivablePlanDO oldReceivablePlan = validateReceivablePlanExists(updateReqVO.getId());
// 1.3 如果已经有对应的回款则不允许编辑
if (Objects.nonNull(oldReceivablePlan.getReceivableId())) {
throw exception(RECEIVABLE_PLAN_UPDATE_FAIL);
}
// 2. 更新回款计划
CrmReceivablePlanDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivablePlanDO.class);
if (updateReqVO.getReturnTime() != null && updateReqVO.getRemindDays() != null) {
updateObj.setRemindTime(updateReqVO.getReturnTime().minusDays(updateReqVO.getRemindDays()));
}
receivablePlanMapper.updateById(updateObj);
// 3. 记录操作日志上下文
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanSaveReqVO.class));
LogRecordContext.putVariable("receivablePlan", oldReceivablePlan);
}
private void validateRelationDataExists(CrmReceivablePlanSaveReqVO reqVO) {
// 校验负责人存在
if (reqVO.getOwnerUserId() != null) {
adminUserApi.validateUser(reqVO.getOwnerUserId());
}
// 校验合同存在
if (reqVO.getContractId() != null) {
CrmContractDO contract = contractService.getContract(reqVO.getContractId());
reqVO.setCustomerId(contract.getCustomerId());
}
}
@Override
public void updateReceivablePlanReceivableId(Long id, Long receivableId) {
// 校验存在
validateReceivablePlanExists(id);
// 更新回款计划
receivablePlanMapper.updateById(new CrmReceivablePlanDO().setId(id).setReceivableId(receivableId));
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_RECEIVABLE_PLAN_DELETE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void deleteReceivablePlan(Long id) {
// 1. 校验存在
CrmReceivablePlanDO receivablePlan = validateReceivablePlanExists(id);
// 2. 删除
receivablePlanMapper.deleteById(id);
// 3. 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), id);
// 4. 记录操作日志上下文
LogRecordContext.putVariable("receivablePlan", receivablePlan);
}
private CrmReceivablePlanDO validateReceivablePlanExists(Long id) {
CrmReceivablePlanDO receivablePlan = receivablePlanMapper.selectById(id);
if (receivablePlan == null) {
throw exception(RECEIVABLE_PLAN_NOT_EXISTS);
}
return receivablePlan;
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.READ)
public CrmReceivablePlanDO getReceivablePlan(Long id) {
return receivablePlanMapper.selectById(id);
}
@Override
public List<CrmReceivablePlanDO> getReceivablePlanList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return ListUtil.empty();
}
return receivablePlanMapper.selectBatchIds(ids);
}
@Override
public PageResult<CrmReceivablePlanDO> getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) {
return receivablePlanMapper.selectPage(pageReqVO, userId);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
public PageResult<CrmReceivablePlanDO> getReceivablePlanPageByCustomerId(CrmReceivablePlanPageReqVO pageReqVO) {
return receivablePlanMapper.selectPageByCustomerId(pageReqVO);
}
@Override
public Long getReceivablePlanRemindCount(Long userId) {
return receivablePlanMapper.selectReceivablePlanCountByRemind(userId);
}
}

View File

@ -1,133 +0,0 @@
package cn.iocoder.yudao.module.crm.service.receivable;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
import jakarta.validation.Valid;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* CRM 回款 Service 接口
*
* @author 赤焰
*/
public interface CrmReceivableService {
/**
* 创建回款
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createReceivable(@Valid CrmReceivableSaveReqVO createReqVO);
/**
* 更新回款
*
* @param updateReqVO 更新信息
*/
void updateReceivable(@Valid CrmReceivableSaveReqVO updateReqVO);
/**
* 更新回款流程审批结果
*
* @param id 回款编号
* @param bpmResult BPM 审批结果
*/
void updateReceivableAuditStatus(Long id, Integer bpmResult);
/**
* 删除回款
*
* @param id 编号
*/
void deleteReceivable(Long id);
/**
* 发起回款审批流程
*
* @param id 回款编号
* @param userId 用户编号
*/
void submitReceivable(Long id, Long userId);
/**
* 获得回款
*
* @param id 编号
* @return 回款
*/
CrmReceivableDO getReceivable(Long id);
/**
* 获得回款列表
*
* @param ids 编号
* @return 回款列表
*/
List<CrmReceivableDO> getReceivableList(Collection<Long> ids);
/**
* 获得回款 Map
*
* @param ids 编号
* @return 回款 Map
*/
default Map<Long, CrmReceivableDO> getReceivableMap(Collection<Long> ids) {
return convertMap(getReceivableList(ids), CrmReceivableDO::getId);
}
/**
* 获得回款分页
*
* 数据权限基于 {@link CrmReceivableDO} 读取
*
* @param pageReqVO 分页查询
* @param userId 用户编号
* @return 回款分页
*/
PageResult<CrmReceivableDO> getReceivablePage(CrmReceivablePageReqVO pageReqVO, Long userId);
/**
* 获得回款分页基于指定客户
*
* 数据权限基于 {@link CrmCustomerDO} 读取
*
* @param pageReqVO 分页查询
* @return 回款分页
*/
PageResult<CrmReceivableDO> getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO);
/**
* 获得待审核回款数量
*
* @param userId 用户编号
* @return 待审批数量
*/
Long getAuditReceivableCount(Long userId);
/**
* 获得合同已回款金额 Map
*
* @param contractIds 合同编号
* @return 回款金额 Map
*/
Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds);
/**
* 根据合同编号查询回款数量
*
* @param contractId 合同编号
* @return 回款数量
*/
Long getReceivableCountByContractId(Long contractId);
}

View File

@ -1,309 +0,0 @@
package cn.iocoder.yudao.module.crm.service.receivable;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.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.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper;
import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO;
import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
/**
* CRM 回款 Service 实现类
*
* @author 赤焰
*/
@Service
@Validated
@Slf4j
public class CrmReceivableServiceImpl implements CrmReceivableService {
/**
* BPM 合同审批流程标识
*/
public static final String BPM_PROCESS_DEFINITION_KEY = "crm-receivable-audit";
@Resource
private CrmReceivableMapper receivableMapper;
@Resource
private CrmNoRedisDAO noRedisDAO;
@Resource
private CrmContractService contractService;
@Resource
@Lazy // 延迟加载避免循环依赖
private CrmReceivablePlanService receivablePlanService;
@Resource
private CrmPermissionService permissionService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private BpmProcessInstanceApi bpmProcessInstanceApi;
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}",
success = CRM_RECEIVABLE_CREATE_SUCCESS)
public Long createReceivable(CrmReceivableSaveReqVO createReqVO) {
// 1.1 校验可回款金额超过上限
validateReceivablePriceExceedsLimit(createReqVO);
// 1.2 校验关联数据存在
validateRelationDataExists(createReqVO);
// 1.3 生成回款编号
String no = noRedisDAO.generate(CrmNoRedisDAO.RECEIVABLE_PREFIX);
if (receivableMapper.selectByNo(no) != null) {
throw exception(RECEIVABLE_NO_EXISTS);
}
// 2.1 插入回款
CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class)
.setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
receivableMapper.insert(receivable);
// 2.2
// 3. 创建数据权限
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType())
.setBizId(receivable.getId()).setUserId(createReqVO.getOwnerUserId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
// 4. 更新关联的回款计划
if (createReqVO.getPlanId() != null) {
receivablePlanService.updateReceivablePlanReceivableId(receivable.getPlanId(), receivable.getId());
}
// 5. 记录操作日志上下文
LogRecordContext.putVariable("receivable", receivable);
LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
return receivable.getId();
}
private void validateReceivablePriceExceedsLimit(CrmReceivableSaveReqVO reqVO) {
// 1. 计算剩余可退款金额不包括 reqVO 自身
CrmContractDO contract = contractService.validateContract(reqVO.getContractId());
List<CrmReceivableDO> receivables = receivableMapper.selectListByContractIdAndStatus(reqVO.getContractId(),
Arrays.asList(CrmAuditStatusEnum.APPROVE.getStatus(), CrmAuditStatusEnum.PROCESS.getStatus()));
if (reqVO.getId() != null) {
receivables.removeIf(receivable -> ObjectUtil.equal(receivable.getId(), reqVO.getId()));
}
BigDecimal notReceivablePrice = contract.getTotalPrice().subtract(
CollectionUtils.getSumValue(receivables, CrmReceivableDO::getPrice, BigDecimal::add, BigDecimal.ZERO));
// 2. 校验金额是否超过
if (reqVO.getPrice().compareTo(notReceivablePrice) > 0) {
throw exception(RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT, notReceivablePrice);
}
}
private void validateRelationDataExists(CrmReceivableSaveReqVO reqVO) {
if (reqVO.getOwnerUserId() != null) {
adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在
}
if (reqVO.getContractId() != null) {
CrmContractDO contract = contractService.validateContract(reqVO.getContractId());
if (ObjectUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
throw exception(RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE);
}
reqVO.setCustomerId(contract.getCustomerId()); // 设置客户编号
}
if (reqVO.getPlanId() != null) {
CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(reqVO.getPlanId());
if (receivablePlan == null) {
throw exception(RECEIVABLE_PLAN_NOT_EXISTS);
}
if (receivablePlan.getReceivableId() != null) {
throw exception(RECEIVABLE_PLAN_EXISTS_RECEIVABLE);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = CRM_RECEIVABLE_UPDATE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) {
Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
// 1.1 校验存在
CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());
updateReqVO.setOwnerUserId(receivable.getOwnerUserId()).setCustomerId(receivable.getCustomerId())
.setContractId(receivable.getContractId()).setPlanId(receivable.getPlanId()); // 设置已存在的值
// 1.2 校验可回款金额超过上限
validateReceivablePriceExceedsLimit(updateReqVO);
// 1.3 只有草稿审批中可以编辑
if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
CrmAuditStatusEnum.PROCESS.getStatus())) {
throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
}
// 2. 更新回款
CrmReceivableDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivableDO.class);
receivableMapper.updateById(updateObj);
// 3. 记录操作日志上下文
LogRecordContext.putVariable("receivable", receivable);
LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
}
private Integer getReceivablePeriod(Long planId) {
if (Objects.isNull(planId)) {
return null;
}
CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(planId);
return receivablePlan.getPeriod();
}
@Override
public void updateReceivableAuditStatus(Long id, Integer bpmResult) {
// 1.1 校验存在
CrmReceivableDO receivable = validateReceivableExists(id);
// 1.2 只有审批中可以更新审批结果
if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
log.error("[updateReceivableAuditStatus][receivable({}) 不处于审批中,无法更新审批结果({})]",
receivable.getId(), bpmResult);
throw exception(RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
}
// 2. 更新回款审批状态
Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
receivableMapper.updateById(new CrmReceivableDO().setId(id).setAuditStatus(auditStatus));
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_RECEIVABLE_DELETE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void deleteReceivable(Long id) {
// 1.1 校验存在
CrmReceivableDO receivable = validateReceivableExists(id);
// 1.2 如果被 CrmReceivablePlanDO 所使用则不允许删除
if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
throw exception(RECEIVABLE_DELETE_FAIL);
}
// 1.3 审批通过时不允许删除
if (ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
throw exception(RECEIVABLE_DELETE_FAIL_IS_APPROVE);
}
// 2.1 删除回款
receivableMapper.deleteById(id);
// 2.2 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id);
// 3. 记录操作日志上下文
LogRecordContext.putVariable("receivable", receivable);
LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_SUBMIT_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_RECEIVABLE_SUBMIT_SUCCESS)
public void submitReceivable(Long id, Long userId) {
// 1. 校验回款是否在审批
CrmReceivableDO receivable = validateReceivableExists(id);
if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) {
throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT);
}
// 2. 创建回款审批流程实例
String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
.setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY).setBusinessKey(String.valueOf(id)));
// 3. 更新回款工作流编号
receivableMapper.updateById(new CrmReceivableDO().setId(id).setProcessInstanceId(processInstanceId)
.setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
// 4. 记录日志
LogRecordContext.putVariable("receivableNo", receivable.getNo());
}
private CrmReceivableDO validateReceivableExists(Long id) {
CrmReceivableDO receivable = receivableMapper.selectById(id);
if (receivable == null) {
throw exception(RECEIVABLE_NOT_EXISTS);
}
return receivable;
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.READ)
public CrmReceivableDO getReceivable(Long id) {
return receivableMapper.selectById(id);
}
@Override
public List<CrmReceivableDO> getReceivableList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return ListUtil.empty();
}
return receivableMapper.selectBatchIds(ids);
}
@Override
public PageResult<CrmReceivableDO> getReceivablePage(CrmReceivablePageReqVO pageReqVO, Long userId) {
return receivableMapper.selectPage(pageReqVO, userId);
}
@Override
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ)
public PageResult<CrmReceivableDO> getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO) {
return receivableMapper.selectPageByCustomerId(pageReqVO);
}
@Override
public Long getAuditReceivableCount(Long userId) {
return receivableMapper.selectCountByAudit(userId);
}
@Override
public Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds) {
return receivableMapper.selectReceivablePriceMapByContractId(contractIds);
}
@Override
public Long getReceivableCountByContractId(Long contractId) {
return receivableMapper.selectCountByContractId(contractId);
}
}

View File

@ -1,96 +0,0 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
import java.util.List;
/**
* CRM 客户分析 Service 接口
*
* @author dhb52
*/
public interface CrmStatisticsCustomerService {
/**
* 总量分析(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerSummaryByDateRespVO> getCustomerSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 总量分析(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerSummaryByUserRespVO> getCustomerSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 跟进次数分析(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsFollowUpSummaryByDateRespVO> getFollowUpSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 跟进次数分析(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsFollowUpSummaryByUserRespVO> getFollowUpSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 客户跟进次数分析(按类型)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsFollowUpSummaryByTypeRespVO> getFollowUpSummaryByType(CrmStatisticsCustomerReqVO reqVO);
/**
* 获取客户的首次合同回款信息列表用于客户转化率页面
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerContractSummaryRespVO> getContractSummary(CrmStatisticsCustomerReqVO reqVO);
/**
* 公海客户分析(按日期)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByDateRespVO> getPoolSummaryByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 公海客户分析(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsPoolSummaryByUserRespVO> getPoolSummaryByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* 客户成交周期(按日期)
*
* 成交周期的定义客户 customer 在创建出来到合同 contract 第一次成交的时间差
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerDealCycleByDateRespVO> getCustomerDealCycleByDate(CrmStatisticsCustomerReqVO reqVO);
/**
* 客户成交周期(按用户)
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO);
}

View File

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

View File

@ -1,42 +0,0 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
import java.util.List;
/**
* CRM 员工绩效统计 Service 接口
*
* @author scholar
*/
public interface CrmStatisticsPerformanceService {
/**
* 员工签约合同数量分析
*
* @param performanceReqVO 排行参数
* @return 员工签约合同数量排行分析
*/
List<CrmStatisticsPerformanceRespVO> getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
* 员工签约合同金额分析
*
* @param performanceReqVO 排行参数
* @return 员工签约合同金额分析
*/
List<CrmStatisticsPerformanceRespVO> getContractPricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
/**
* 员工获得回款金额分析
*
* @param performanceReqVO 排行参数
* @return 员工获得回款金额分析
*/
List<CrmStatisticsPerformanceRespVO> getReceivablePricePerformance(CrmStatisticsPerformanceReqVO performanceReqVO);
}

View File

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

View File

@ -1,46 +0,0 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.*;
import java.util.List;
/**
* CRM 客户画像 Service 接口
*
* @author HUIHUI
*/
public interface CrmStatisticsPortraitService {
/**
* 获取客户地区统计数据
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticCustomerAreaRespVO> getCustomerSummaryByArea(CrmStatisticsPortraitReqVO reqVO);
/**
* 获取客户行业统计数据
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticCustomerIndustryRespVO> getCustomerSummaryByIndustry(CrmStatisticsPortraitReqVO reqVO);
/**
* 获取客户级别统计数据
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticCustomerLevelRespVO> getCustomerSummaryByLevel(CrmStatisticsPortraitReqVO reqVO);
/**
* 获取客户来源统计数据
*
* @param reqVO 请求参数
* @return 统计数据
*/
List<CrmStatisticCustomerSourceRespVO> getCustomerSummaryBySource(CrmStatisticsPortraitReqVO reqVO);
}

View File

@ -1,128 +0,0 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.*;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPortraitMapper;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
/**
* CRM 客户画像 Service 实现类
*
* @author HUIHUI
*/
@Service
public class CrmStatisticsPortraitServiceImpl implements CrmStatisticsPortraitService {
@Resource
private CrmStatisticsPortraitMapper portraitMapper;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Override
public List<CrmStatisticCustomerAreaRespVO> getCustomerSummaryByArea(CrmStatisticsPortraitReqVO reqVO) {
// 1. 获得用户编号数组
List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
reqVO.setUserIds(userIds);
// 2. 获取客户地区统计数据
List<CrmStatisticCustomerAreaRespVO> list = portraitMapper.selectSummaryListGroupByAreaId(reqVO);
if (CollUtil.isEmpty(list)) {
return Collections.emptyList();
}
// 3. 拼接数据
List<Area> areaList = AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area);
areaList.add(new Area().setId(null).setName("未知")); // TODO @puhui999是不是 65 find 的逻辑改下不用 findAndThen直接从 areaMap 拿到就设置不拿到就设置 null 未知这样58 本行可以删除掉完事了这样代码更简单和一致
Map<Integer, Area> areaMap = convertMap(areaList, Area::getId);
return convertList(list, item -> {
Integer parentId = AreaUtils.getParentIdByType(item.getAreaId(), AreaTypeEnum.PROVINCE);
if (parentId == null) { // 找不到归到未知
return item.setAreaId(null).setAreaName("未知");
}
findAndThen(areaMap, parentId, area -> item.setAreaId(parentId).setAreaName(area.getName()));
return item;
});
}
@Override
public List<CrmStatisticCustomerIndustryRespVO> getCustomerSummaryByIndustry(CrmStatisticsPortraitReqVO reqVO) {
// 1. 获得用户编号数组
List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
reqVO.setUserIds(userIds);
// 2. 获取客户行业统计数据
return portraitMapper.selectCustomerIndustryListGroupByIndustryId(reqVO);
}
@Override
public List<CrmStatisticCustomerSourceRespVO> getCustomerSummaryBySource(CrmStatisticsPortraitReqVO reqVO) {
// 1. 获得用户编号数组
List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
reqVO.setUserIds(userIds);
// 2. 获取客户行业统计数据
return portraitMapper.selectCustomerSourceListGroupBySource(reqVO);
}
@Override
public List<CrmStatisticCustomerLevelRespVO> getCustomerSummaryByLevel(CrmStatisticsPortraitReqVO reqVO) {
// 1. 获得用户编号数组
List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
reqVO.setUserIds(userIds);
// 2. 获取客户级别统计数据
return portraitMapper.selectCustomerLevelListGroupByLevel(reqVO);
}
/**
* 获取用户编号数组如果用户编号为空, 则获得部门下的用户编号数组包括子部门的所有用户编号
*
* @param reqVO 请求参数
* @return 用户编号数组
*/
private List<Long> getUserIds(CrmStatisticsPortraitReqVO reqVO) {
// 情况一选中某个用户
if (ObjUtil.isNotNull(reqVO.getUserId())) {
return List.of(reqVO.getUserId());
}
// 情况二选中某个部门
// 2.1 获得部门列表
List<Long> deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId);
deptIds.add(reqVO.getDeptId());
// 2.2 获得用户编号
return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
}
}

View File

@ -1,80 +0,0 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
import java.util.List;
/**
* CRM 排行榜统计 Service 接口
*
* @author anhaohao
*/
public interface CrmStatisticsRankService {
/**
* 获得合同金额排行榜
*
* @param rankReqVO 排行参数
* @return 合同金额排行榜
*/
List<CrmStatisticsRankRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 获得回款金额排行榜
*
* @param rankReqVO 排行参数
* @return 回款金额排行榜
*/
List<CrmStatisticsRankRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 获得签约合同数量排行榜
*
* @param rankReqVO 排行参数
* @return 签约合同数量排行榜
*/
List<CrmStatisticsRankRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 获得产品销量排行榜
*
* @param rankReqVO 排行参数
* @return 产品销量排行榜
*/
List<CrmStatisticsRankRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 获得新增客户数排行榜
*
* @param rankReqVO 排行参数
* @return 新增客户数排行榜
*/
List<CrmStatisticsRankRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 获得联系人数量排行榜
*
* @param rankReqVO 排行参数
* @return 联系人数量排行榜
*/
List<CrmStatisticsRankRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 获得跟进次数排行榜
*
* @param rankReqVO 排行参数
* @return 跟进次数排行榜
*/
List<CrmStatisticsRankRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
/**
* 获得跟进客户数排行榜
*
* @param rankReqVO 排行参数
* @return 跟进客户数排行榜
*/
List<CrmStatisticsRankRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
}

View File

@ -1,134 +0,0 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsRankMapper;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
/**
* CRM 排行榜统计 Service 实现类
*
* @author anhaohao
*/
@Service
@Validated
public class CrmStatisticsRankServiceImpl implements CrmStatisticsRankService {
@Resource
private CrmStatisticsRankMapper rankMapper;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Override
public List<CrmStatisticsRankRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO) {
return getRank(rankReqVO, rankMapper::selectContractPriceRank);
}
@Override
public List<CrmStatisticsRankRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO) {
return getRank(rankReqVO, rankMapper::selectReceivablePriceRank);
}
@Override
public List<CrmStatisticsRankRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO) {
return getRank(rankReqVO, rankMapper::selectContractCountRank);
}
@Override
public List<CrmStatisticsRankRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO) {
return getRank(rankReqVO, rankMapper::selectProductSalesRank);
}
@Override
public List<CrmStatisticsRankRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
return getRank(rankReqVO, rankMapper::selectCustomerCountRank);
}
@Override
public List<CrmStatisticsRankRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO) {
return getRank(rankReqVO, rankMapper::selectContactsCountRank);
}
@Override
public List<CrmStatisticsRankRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO) {
return getRank(rankReqVO, rankMapper::selectFollowCountRank);
}
@Override
public List<CrmStatisticsRankRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
return getRank(rankReqVO, rankMapper::selectFollowCustomerCountRank);
}
/**
* 获得排行版数据
*
* @param rankReqVO 参数
* @param rankFunction 排行榜方法
* @return 排行版数据
*/
private List<CrmStatisticsRankRespVO> getRank(CrmStatisticsRankReqVO rankReqVO, Function<CrmStatisticsRankReqVO, List<CrmStatisticsRankRespVO>> rankFunction) {
// 1. 获得用户编号数组
rankReqVO.setUserIds(getUserIds(rankReqVO.getDeptId()));
if (CollUtil.isEmpty(rankReqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 获得排行数据
List<CrmStatisticsRankRespVO> ranks = rankFunction.apply(rankReqVO);
if (CollUtil.isEmpty(ranks)) {
return Collections.emptyList();
}
ranks.sort(Comparator.comparing(CrmStatisticsRankRespVO::getCount).reversed());
// 3. 拼接用户信息
appendUserInfo(ranks);
return ranks;
}
/**
* 拼接用户信息昵称部门
*
* @param ranks 排行榜数据
*/
private void appendUserInfo(List<CrmStatisticsRankRespVO> ranks) {
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(ranks, CrmStatisticsRankRespVO::getOwnerUserId));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
ranks.forEach(rank -> MapUtils.findAndThen(userMap, rank.getOwnerUserId(), user -> {
rank.setNickname(user.getNickname());
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> rank.setDeptName(dept.getName()));
}));
}
/**
* 获得部门下的用户编号数组包括子部门的
*
* @param deptId 部门编号
* @return 用户编号数组
*/
public List<Long> getUserIds(Long deptId) {
// 1. 获得部门列表
List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId);
deptIds.add(deptId);
// 2. 获得用户编号
return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
}
}

View File

@ -1,224 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper">
<select id="selectCustomerCreateCountGroupByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
COUNT(*) AS customerCreateCount
FROM crm_customer
WHERE deleted = 0
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY time
</select>
<!-- TODO 芋艿:应该不用过滤时间 -->
<select id="selectCustomerDealCountGroupByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
SELECT DATE_FORMAT(customer.create_time, '%Y-%m-%d') AS time,
COUNT(DISTINCT customer.id) AS customer_deal_count
FROM crm_customer AS customer
LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
WHERE customer.deleted = 0 AND contract.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND customer.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY time
</select>
<select id="selectCustomerCreateCountGroupByUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
SELECT owner_user_id,
COUNT(*) AS customer_create_count
FROM crm_customer
WHERE deleted = 0
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
<select id="selectCustomerDealCountGroupByUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
SELECT customer.owner_user_id,
COUNT(DISTINCT customer.id) AS customer_deal_count
FROM crm_customer AS customer
LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
WHERE customer.deleted = 0 AND contract.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND customer.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY customer.owner_user_id
</select>
<select id="selectContractPriceGroupByUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
SELECT owner_user_id,
IFNULL(SUM(total_price), 0) AS contract_price
FROM crm_contract
WHERE deleted = 0
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND order_date BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
<select id="selectReceivablePriceGroupByUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
SELECT owner_user_id,
IFNULL(SUM(price), 0) AS receivable_price
FROM crm_receivable
WHERE deleted = 0
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND return_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
<select id="selectFollowUpRecordCountGroupByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByDateRespVO">
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
COUNT(*) AS follow_up_record_count
FROM crm_follow_up_record
WHERE creator IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
GROUP BY time
</select>
<select id="selectFollowUpCustomerCountGroupByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByDateRespVO">
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
COUNT(DISTINCT biz_id) AS follow_up_customer_count
FROM crm_follow_up_record
WHERE creator IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
GROUP BY time
</select>
<select id="selectFollowUpRecordCountGroupByUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByUserRespVO">
SELECT creator as owner_user_id,
COUNT(*) AS follow_up_record_count
FROM crm_follow_up_record
WHERE creator IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
GROUP BY creator
</select>
<select id="selectFollowUpCustomerCountGroupByUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByUserRespVO">
SELECT
creator as owner_user_id,
COUNT(DISTINCT biz_id) AS follow_up_customer_count
FROM crm_follow_up_record
WHERE creator IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
GROUP BY creator
</select>
<select id="selectFollowUpRecordCountGroupByType"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsFollowUpSummaryByTypeRespVO">
SELECT type AS follow_up_type,
COUNT(*) AS follow_up_record_count
FROM crm_follow_up_record
WHERE creator IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
GROUP BY follow_up_type
</select>
<select id="selectContractSummary"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerContractSummaryRespVO">
SELECT customer.name AS customer_name,
customer.industry_id,
customer.source,
customer.owner_user_id,
customer.creator,
customer.create_time,
contract.name AS contract_name,
contract.total_price,
contract.order_date,
IFNULL(receivable.price, 0) AS receivable_price
FROM crm_customer AS customer
INNER JOIN crm_contract AS contract ON customer.id = contract.customer_id
LEFT JOIN crm_receivable AS receivable ON contract.id = receivable.contract_id
WHERE customer.deleted = 0 AND contract.deleted = 0 AND receivable.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND customer.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
</select>
<!-- TIMESTAMPDIFF 用于求差值AVG 求平均TRUNCATE 去掉小数点、只保留整数 -->
<select id="selectCustomerDealCycleGroupByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByDateRespVO">
SELECT DATE_FORMAT(contract.order_date, '%Y-%m-%d') AS time,
IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle
FROM crm_customer AS customer
LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
WHERE customer.deleted = 0 AND contract.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND customer.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY time
</select>
<select id="selectCustomerDealCycleGroupByUser"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByUserRespVO">
SELECT customer.owner_user_id,
IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle
FROM crm_customer AS customer
LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
WHERE customer.deleted = 0 AND contract.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND customer.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY customer.owner_user_id
</select>
</mapper>

View File

@ -1,152 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPerformanceMapper">
<select id="selectContractCountPerformance"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
SELECT
t.time as time,
COALESCE(t.currentMonthCount,0) as currentMonthCount,
COALESCE(y.lastMonthCount,0) as lastMonthCount,
COALESCE(z.lastYearCount,0) as lastYearCount
FROM
(SELECT
COUNT(1) AS currentMonthCount,
DATE_FORMAT(order_date, '%Y-%m') AS time
FROM crm_contract
WHERE deleted = 0
AND audit_status = 20
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
GROUP BY time)t
LEFT JOIN
(SELECT
COUNT(1) AS lastMonthCount,
DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 MONTH), '%Y-%m') AS time
FROM crm_contract
WHERE deleted = 0
AND audit_status = 20
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1)
GROUP BY time)y ON t.time = y.time
LEFT JOIN
(SELECT
COUNT(1) AS lastYearCount,
DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 YEAR), '%Y-%m') AS time
FROM crm_contract
WHERE deleted = 0
AND audit_status = 20
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1
GROUP BY time)z ON t.time = z.time
</select>
<select id="selectContractPricePerformance"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
SELECT
t.time as time,
COALESCE(t.currentMonthCount,0) as currentMonthCount,
COALESCE(y.lastMonthCount,0) as lastMonthCount,
COALESCE(z.lastYearCount,0) as lastYearCount
FROM
(SELECT
IFNULL(SUM(total_price), 0) AS currentMonthCount,
DATE_FORMAT(order_date, '%Y-%m') AS time
FROM crm_contract
WHERE deleted = 0
AND audit_status = 20
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
GROUP BY time)t
LEFT JOIN
(SELECT
IFNULL(SUM(total_price), 0) AS lastMonthCount,
DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 MONTH), '%Y-%m') AS time
FROM crm_contract
WHERE deleted = 0
AND audit_status = 20
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1)
GROUP BY time)y ON t.time = y.time
LEFT JOIN
(SELECT
IFNULL(SUM(total_price), 0) AS lastYearCount,
DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 YEAR), '%Y-%m') AS time
FROM crm_contract
WHERE deleted = 0
AND audit_status = 20
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1
GROUP BY time)z ON t.time = z.time
</select>
<select id="selectReceivablePricePerformance"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
SELECT
t.time as time,
COALESCE(t.currentMonthCount,0) as currentMonthCount,
COALESCE(y.lastMonthCount,0) as lastMonthCount,
COALESCE(z.lastYearCount,0) as lastYearCount
FROM
(SELECT
IFNULL(SUM(price), 0) AS currentMonthCount,
DATE_FORMAT(return_time, '%Y-%m') AS time
FROM crm_receivable
WHERE deleted = 0
AND audit_status = 20
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
GROUP BY time)t
LEFT JOIN
(SELECT
IFNULL(SUM(price), 0) AS lastMonthCount,
DATE_FORMAT(DATE_ADD(return_time,INTERVAL 1 MONTH), '%Y-%m') AS time
FROM crm_receivable
WHERE deleted = 0
AND audit_status = 20
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND (DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
or DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1)
GROUP BY time)y ON t.time = y.time
LEFT JOIN
(SELECT
IFNULL(SUM(price), 0) AS lastYearCount,
DATE_FORMAT(DATE_ADD(return_time,INTERVAL 1 YEAR), '%Y-%m') AS time
FROM crm_receivable
WHERE deleted = 0
AND audit_status = 20
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1
GROUP BY time)z ON t.time = z.time
</select>
</mapper>

View File

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPortraitMapper">
<select id="selectSummaryListGroupByAreaId"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerAreaRespVO">
SELECT area_id, COUNT(*) AS customerCount, SUM(deal_status) AS dealCount
FROM crm_customer
WHERE deleted = 0 AND area_id IS NOT NULL
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime}
AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY area_id
</select>
<select id="selectCustomerIndustryListGroupByIndustryId"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerIndustryRespVO">
SELECT industry_id, COUNT(*) AS customerCount, SUM(deal_status) AS dealCount
FROM crm_customer
WHERE deleted = 0 AND industry_id IS NOT NULL
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime}
AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY industry_id
</select>
<select id="selectCustomerSourceListGroupBySource"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerSourceRespVO">
SELECT source, COUNT(*) AS customerCount, SUM(deal_status) AS dealCount
FROM crm_customer
WHERE deleted = 0 AND source IS NOT NULL
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime}
AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY source
</select>
<select id="selectCustomerLevelListGroupByLevel"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait.CrmStatisticCustomerLevelRespVO">
SELECT level, COUNT(*) AS customerCount, SUM(deal_status) AS dealCount
FROM crm_customer
WHERE deleted = 0 AND level IS NOT NULL
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime}
AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY level
</select>
</mapper>

View File

@ -1,118 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsRankMapper">
<select id="selectContractPriceRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
SELECT IFNULL(SUM(total_price), 0) AS count, owner_user_id
FROM crm_contract
WHERE deleted = 0
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
and owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND order_date between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
<select id="selectReceivablePriceRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
SELECT IFNULL(SUM(price), 0) AS count, owner_user_id
FROM crm_receivable
WHERE deleted = 0
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND return_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
<select id="selectContractCountRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
SELECT COUNT(1) AS count, owner_user_id
FROM crm_contract
WHERE deleted = 0
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND order_date between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
<select id="selectProductSalesRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
SELECT COUNT(product.count) AS count, contract.owner_user_id
FROM crm_contract_product product
INNER JOIN crm_contract contract ON product.contract_id = contract.id
WHERE contract.deleted = 0 AND contract.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND contract.owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND contract.order_date between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY contract.owner_user_id
</select>
<select id="selectCustomerCountRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
SELECT COUNT(1) AS count, owner_user_id
FROM crm_customer
WHERE deleted = 0
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
<select id="selectContactsCountRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
SELECT COUNT(1) AS count, owner_user_id
FROM crm_contact
WHERE deleted = 0
AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
<select id="selectFollowCountRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
SELECT COUNT(1) AS count, cc.owner_user_id
FROM crm_follow_up_record AS cfur
LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
WHERE cfur.deleted = 0
AND cc.deleted = 0
AND cc.owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND cfur.create_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY cc.owner_user_id
</select>
<select id="selectFollowCustomerCountRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
SELECT COUNT(DISTINCT cc.id) AS count, cc.owner_user_id
FROM crm_follow_up_record AS cfur
LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
WHERE cfur.deleted = 0
AND cc.deleted = 0
AND cc.owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND cfur.create_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY cc.owner_user_id
</select>
</mapper>

View File

@ -1,49 +0,0 @@
spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data:
redis:
host: 127.0.0.1 # 地址
port: 16379 # 端口(单元测试,使用 16379 端口)
database: 0 # 数据库索引
mybatis-plus:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
--- #################### 监控相关配置 ####################
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
base-package: cn.iocoder.yudao

View File

@ -1,116 +0,0 @@
package cn.iocoder.yudao.module.erp.controller.admin.finance;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
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.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - ERP 结算账户")
@RestController
@RequestMapping("/erp/account")
@Validated
public class ErpAccountController {
@Resource
private ErpAccountService accountService;
@PostMapping("/create")
@Operation(summary = "创建结算账户")
@PreAuthorize("@ss.hasPermission('erp:account:create')")
public CommonResult<Long> createAccount(@Valid @RequestBody ErpAccountSaveReqVO createReqVO) {
return success(accountService.createAccount(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新结算账户")
@PreAuthorize("@ss.hasPermission('erp:account:update')")
public CommonResult<Boolean> updateAccount(@Valid @RequestBody ErpAccountSaveReqVO updateReqVO) {
accountService.updateAccount(updateReqVO);
return success(true);
}
@PutMapping("/update-default-status")
@Operation(summary = "更新结算账户默认状态")
@Parameters({
@Parameter(name = "id", description = "编号", required = true),
@Parameter(name = "status", description = "状态", required = true)
})
public CommonResult<Boolean> updateAccountDefaultStatus(@RequestParam("id") Long id,
@RequestParam("defaultStatus") Boolean defaultStatus) {
accountService.updateAccountDefaultStatus(id, defaultStatus);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除结算账户")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('erp:account:delete')")
public CommonResult<Boolean> deleteAccount(@RequestParam("id") Long id) {
accountService.deleteAccount(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得结算账户")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:account:query')")
public CommonResult<ErpAccountRespVO> getAccount(@RequestParam("id") Long id) {
ErpAccountDO account = accountService.getAccount(id);
return success(BeanUtils.toBean(account, ErpAccountRespVO.class));
}
@GetMapping("/simple-list")
@Operation(summary = "获得结算账户精简列表", description = "只包含被开启的结算账户,主要用于前端的下拉选项")
public CommonResult<List<ErpAccountRespVO>> getWarehouseSimpleList() {
List<ErpAccountDO> list = accountService.getAccountListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, account -> new ErpAccountRespVO().setId(account.getId())
.setName(account.getName()).setDefaultStatus(account.getDefaultStatus())));
}
@GetMapping("/page")
@Operation(summary = "获得结算账户分页")
@PreAuthorize("@ss.hasPermission('erp:account:query')")
public CommonResult<PageResult<ErpAccountRespVO>> getAccountPage(@Valid ErpAccountPageReqVO pageReqVO) {
PageResult<ErpAccountDO> pageResult = accountService.getAccountPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ErpAccountRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出结算账户 Excel")
@PreAuthorize("@ss.hasPermission('erp:account:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportAccountExcel(@Valid ErpAccountPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ErpAccountDO> list = accountService.getAccountPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "结算账户.xls", "数据", ErpAccountRespVO.class,
BeanUtils.toBean(list, ErpAccountRespVO.class));
}
}

View File

@ -1,153 +0,0 @@
package cn.iocoder.yudao.module.erp.controller.admin.finance;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.erp.service.finance.ErpFinancePaymentService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@Tag(name = "管理后台 - ERP 付款单")
@RestController
@RequestMapping("/erp/finance-payment")
@Validated
public class ErpFinancePaymentController {
@Resource
private ErpFinancePaymentService financePaymentService;
@Resource
private ErpSupplierService supplierService;
@Resource
private ErpAccountService accountService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建付款单")
@PreAuthorize("@ss.hasPermission('erp:finance-payment:create')")
public CommonResult<Long> createFinancePayment(@Valid @RequestBody ErpFinancePaymentSaveReqVO createReqVO) {
return success(financePaymentService.createFinancePayment(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新付款单")
@PreAuthorize("@ss.hasPermission('erp:finance-payment:update')")
public CommonResult<Boolean> updateFinancePayment(@Valid @RequestBody ErpFinancePaymentSaveReqVO updateReqVO) {
financePaymentService.updateFinancePayment(updateReqVO);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新付款单的状态")
@PreAuthorize("@ss.hasPermission('erp:finance-payment:update-status')")
public CommonResult<Boolean> updateFinancePaymentStatus(@RequestParam("id") Long id,
@RequestParam("status") Integer status) {
financePaymentService.updateFinancePaymentStatus(id, status);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除付款单")
@Parameter(name = "ids", description = "编号数组", required = true)
@PreAuthorize("@ss.hasPermission('erp:finance-payment:delete')")
public CommonResult<Boolean> deleteFinancePayment(@RequestParam("ids") List<Long> ids) {
financePaymentService.deleteFinancePayment(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得付款单")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:finance-payment:query')")
public CommonResult<ErpFinancePaymentRespVO> getFinancePayment(@RequestParam("id") Long id) {
ErpFinancePaymentDO payment = financePaymentService.getFinancePayment(id);
if (payment == null) {
return success(null);
}
List<ErpFinancePaymentItemDO> paymentItemList = financePaymentService.getFinancePaymentItemListByPaymentId(id);
return success(BeanUtils.toBean(payment, ErpFinancePaymentRespVO.class, financePaymentVO ->
financePaymentVO.setItems(BeanUtils.toBean(paymentItemList, ErpFinancePaymentRespVO.Item.class))));
}
@GetMapping("/page")
@Operation(summary = "获得付款单分页")
@PreAuthorize("@ss.hasPermission('erp:finance-payment:query')")
public CommonResult<PageResult<ErpFinancePaymentRespVO>> getFinancePaymentPage(@Valid ErpFinancePaymentPageReqVO pageReqVO) {
PageResult<ErpFinancePaymentDO> pageResult = financePaymentService.getFinancePaymentPage(pageReqVO);
return success(buildFinancePaymentVOPageResult(pageResult));
}
@GetMapping("/export-excel")
@Operation(summary = "导出付款单 Excel")
@PreAuthorize("@ss.hasPermission('erp:finance-payment:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportFinancePaymentExcel(@Valid ErpFinancePaymentPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ErpFinancePaymentRespVO> list = buildFinancePaymentVOPageResult(financePaymentService.getFinancePaymentPage(pageReqVO)).getList();
// 导出 Excel
ExcelUtils.write(response, "付款单.xls", "数据", ErpFinancePaymentRespVO.class, list);
}
private PageResult<ErpFinancePaymentRespVO> buildFinancePaymentVOPageResult(PageResult<ErpFinancePaymentDO> pageResult) {
if (CollUtil.isEmpty(pageResult.getList())) {
return PageResult.empty(pageResult.getTotal());
}
// 1.1 付款项
List<ErpFinancePaymentItemDO> paymentItemList = financePaymentService.getFinancePaymentItemListByPaymentIds(
convertSet(pageResult.getList(), ErpFinancePaymentDO::getId));
Map<Long, List<ErpFinancePaymentItemDO>> financePaymentItemMap = convertMultiMap(paymentItemList, ErpFinancePaymentItemDO::getPaymentId);
// 1.2 供应商信息
Map<Long, ErpSupplierDO> supplierMap = supplierService.getSupplierMap(
convertSet(pageResult.getList(), ErpFinancePaymentDO::getSupplierId));
// 1.3 结算账户信息
Map<Long, ErpAccountDO> accountMap = accountService.getAccountMap(
convertSet(pageResult.getList(), ErpFinancePaymentDO::getAccountId));
// 1.4 管理员信息
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getFinanceUserId())));
// 2. 开始拼接
return BeanUtils.toBean(pageResult, ErpFinancePaymentRespVO.class, payment -> {
payment.setItems(BeanUtils.toBean(financePaymentItemMap.get(payment.getId()), ErpFinancePaymentRespVO.Item.class));
MapUtils.findAndThen(supplierMap, payment.getSupplierId(), supplier -> payment.setSupplierName(supplier.getName()));
MapUtils.findAndThen(accountMap, payment.getAccountId(), account -> payment.setAccountName(account.getName()));
MapUtils.findAndThen(userMap, Long.parseLong(payment.getCreator()), user -> payment.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, payment.getFinanceUserId(), user -> payment.setFinanceUserName(user.getNickname()));
});
}
}

View File

@ -1,153 +0,0 @@
package cn.iocoder.yudao.module.erp.controller.admin.finance;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.erp.service.finance.ErpFinanceReceiptService;
import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@Tag(name = "管理后台 - ERP 收款单")
@RestController
@RequestMapping("/erp/finance-receipt")
@Validated
public class ErpFinanceReceiptController {
@Resource
private ErpFinanceReceiptService financeReceiptService;
@Resource
private ErpCustomerService customerService;
@Resource
private ErpAccountService accountService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建收款单")
@PreAuthorize("@ss.hasPermission('erp:finance-receipt:create')")
public CommonResult<Long> createFinanceReceipt(@Valid @RequestBody ErpFinanceReceiptSaveReqVO createReqVO) {
return success(financeReceiptService.createFinanceReceipt(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新收款单")
@PreAuthorize("@ss.hasPermission('erp:finance-receipt:update')")
public CommonResult<Boolean> updateFinanceReceipt(@Valid @RequestBody ErpFinanceReceiptSaveReqVO updateReqVO) {
financeReceiptService.updateFinanceReceipt(updateReqVO);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新收款单的状态")
@PreAuthorize("@ss.hasPermission('erp:finance-receipt:update-status')")
public CommonResult<Boolean> updateFinanceReceiptStatus(@RequestParam("id") Long id,
@RequestParam("status") Integer status) {
financeReceiptService.updateFinanceReceiptStatus(id, status);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除收款单")
@Parameter(name = "ids", description = "编号数组", required = true)
@PreAuthorize("@ss.hasPermission('erp:finance-receipt:delete')")
public CommonResult<Boolean> deleteFinanceReceipt(@RequestParam("ids") List<Long> ids) {
financeReceiptService.deleteFinanceReceipt(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得收款单")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:finance-receipt:query')")
public CommonResult<ErpFinanceReceiptRespVO> getFinanceReceipt(@RequestParam("id") Long id) {
ErpFinanceReceiptDO receipt = financeReceiptService.getFinanceReceipt(id);
if (receipt == null) {
return success(null);
}
List<ErpFinanceReceiptItemDO> receiptItemList = financeReceiptService.getFinanceReceiptItemListByReceiptId(id);
return success(BeanUtils.toBean(receipt, ErpFinanceReceiptRespVO.class, financeReceiptVO ->
financeReceiptVO.setItems(BeanUtils.toBean(receiptItemList, ErpFinanceReceiptRespVO.Item.class))));
}
@GetMapping("/page")
@Operation(summary = "获得收款单分页")
@PreAuthorize("@ss.hasPermission('erp:finance-receipt:query')")
public CommonResult<PageResult<ErpFinanceReceiptRespVO>> getFinanceReceiptPage(@Valid ErpFinanceReceiptPageReqVO pageReqVO) {
PageResult<ErpFinanceReceiptDO> pageResult = financeReceiptService.getFinanceReceiptPage(pageReqVO);
return success(buildFinanceReceiptVOPageResult(pageResult));
}
@GetMapping("/export-excel")
@Operation(summary = "导出收款单 Excel")
@PreAuthorize("@ss.hasPermission('erp:finance-receipt:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportFinanceReceiptExcel(@Valid ErpFinanceReceiptPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ErpFinanceReceiptRespVO> list = buildFinanceReceiptVOPageResult(financeReceiptService.getFinanceReceiptPage(pageReqVO)).getList();
// 导出 Excel
ExcelUtils.write(response, "收款单.xls", "数据", ErpFinanceReceiptRespVO.class, list);
}
private PageResult<ErpFinanceReceiptRespVO> buildFinanceReceiptVOPageResult(PageResult<ErpFinanceReceiptDO> pageResult) {
if (CollUtil.isEmpty(pageResult.getList())) {
return PageResult.empty(pageResult.getTotal());
}
// 1.1 收款项
List<ErpFinanceReceiptItemDO> receiptItemList = financeReceiptService.getFinanceReceiptItemListByReceiptIds(
convertSet(pageResult.getList(), ErpFinanceReceiptDO::getId));
Map<Long, List<ErpFinanceReceiptItemDO>> financeReceiptItemMap = convertMultiMap(receiptItemList, ErpFinanceReceiptItemDO::getReceiptId);
// 1.2 客户信息
Map<Long, ErpCustomerDO> customerMap = customerService.getCustomerMap(
convertSet(pageResult.getList(), ErpFinanceReceiptDO::getCustomerId));
// 1.3 结算账户信息
Map<Long, ErpAccountDO> accountMap = accountService.getAccountMap(
convertSet(pageResult.getList(), ErpFinanceReceiptDO::getAccountId));
// 1.4 管理员信息
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getFinanceUserId())));
// 2. 开始拼接
return BeanUtils.toBean(pageResult, ErpFinanceReceiptRespVO.class, receipt -> {
receipt.setItems(BeanUtils.toBean(financeReceiptItemMap.get(receipt.getId()), ErpFinanceReceiptRespVO.Item.class));
MapUtils.findAndThen(customerMap, receipt.getCustomerId(), customer -> receipt.setCustomerName(customer.getName()));
MapUtils.findAndThen(accountMap, receipt.getAccountId(), account -> receipt.setAccountName(account.getName()));
MapUtils.findAndThen(userMap, Long.parseLong(receipt.getCreator()), user -> receipt.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, receipt.getFinanceUserId(), user -> receipt.setFinanceUserName(user.getNickname()));
});
}
}

View File

@ -1,101 +0,0 @@
package cn.iocoder.yudao.module.erp.controller.admin.product;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategoryListReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategoryRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategorySaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO;
import cn.iocoder.yudao.module.erp.service.product.ErpProductCategoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - ERP 产品分类")
@RestController
@RequestMapping("/erp/product-category")
@Validated
public class ErpProductCategoryController {
@Resource
private ErpProductCategoryService productCategoryService;
@PostMapping("/create")
@Operation(summary = "创建产品分类")
@PreAuthorize("@ss.hasPermission('erp:product-category:create')")
public CommonResult<Long> createProductCategory(@Valid @RequestBody ErpProductCategorySaveReqVO createReqVO) {
return success(productCategoryService.createProductCategory(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新产品分类")
@PreAuthorize("@ss.hasPermission('erp:product-category:update')")
public CommonResult<Boolean> updateProductCategory(@Valid @RequestBody ErpProductCategorySaveReqVO updateReqVO) {
productCategoryService.updateProductCategory(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除产品分类")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('erp:product-category:delete')")
public CommonResult<Boolean> deleteProductCategory(@RequestParam("id") Long id) {
productCategoryService.deleteProductCategory(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得产品分类")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:product-category:query')")
public CommonResult<ErpProductCategoryRespVO> getProductCategory(@RequestParam("id") Long id) {
ErpProductCategoryDO category = productCategoryService.getProductCategory(id);
return success(BeanUtils.toBean(category, ErpProductCategoryRespVO.class));
}
@GetMapping("/list")
@Operation(summary = "获得产品分类列表")
@PreAuthorize("@ss.hasPermission('erp:product-category:query')")
public CommonResult<List<ErpProductCategoryRespVO>> getProductCategoryList(@Valid ErpProductCategoryListReqVO listReqVO) {
List<ErpProductCategoryDO> list = productCategoryService.getProductCategoryList(listReqVO);
return success(BeanUtils.toBean(list, ErpProductCategoryRespVO.class));
}
@GetMapping("/simple-list")
@Operation(summary = "获得产品分类精简列表", description = "只包含被开启的分类,主要用于前端的下拉选项")
public CommonResult<List<ErpProductCategoryRespVO>> getProductCategorySimpleList() {
List<ErpProductCategoryDO> list = productCategoryService.getProductCategoryList(
new ErpProductCategoryListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));
return success(convertList(list, category -> new ErpProductCategoryRespVO()
.setId(category.getId()).setName(category.getName()).setParentId(category.getParentId())));
}
@GetMapping("/export-excel")
@Operation(summary = "导出产品分类 Excel")
@PreAuthorize("@ss.hasPermission('erp:product-category:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportProductCategoryExcel(@Valid ErpProductCategoryListReqVO listReqVO,
HttpServletResponse response) throws IOException {
List<ErpProductCategoryDO> list = productCategoryService.getProductCategoryList(listReqVO);
// 导出 Excel
ExcelUtils.write(response, "产品分类.xls", "数据", ErpProductCategoryRespVO.class,
BeanUtils.toBean(list, ErpProductCategoryRespVO.class));
}
}

View File

@ -1,105 +0,0 @@
package cn.iocoder.yudao.module.erp.controller.admin.product;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - ERP 产品")
@RestController
@RequestMapping("/erp/product")
@Validated
public class ErpProductController {
@Resource
private ErpProductService productService;
@PostMapping("/create")
@Operation(summary = "创建产品")
@PreAuthorize("@ss.hasPermission('erp:product:create')")
public CommonResult<Long> createProduct(@Valid @RequestBody ProductSaveReqVO createReqVO) {
return success(productService.createProduct(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新产品")
@PreAuthorize("@ss.hasPermission('erp:product:update')")
public CommonResult<Boolean> updateProduct(@Valid @RequestBody ProductSaveReqVO updateReqVO) {
productService.updateProduct(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除产品")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('erp:product:delete')")
public CommonResult<Boolean> deleteProduct(@RequestParam("id") Long id) {
productService.deleteProduct(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得产品")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:product:query')")
public CommonResult<ErpProductRespVO> getProduct(@RequestParam("id") Long id) {
ErpProductDO product = productService.getProduct(id);
return success(BeanUtils.toBean(product, ErpProductRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得产品分页")
@PreAuthorize("@ss.hasPermission('erp:product:query')")
public CommonResult<PageResult<ErpProductRespVO>> getProductPage(@Valid ErpProductPageReqVO pageReqVO) {
return success(productService.getProductVOPage(pageReqVO));
}
@GetMapping("/simple-list")
@Operation(summary = "获得产品精简列表", description = "只包含被开启的产品,主要用于前端的下拉选项")
public CommonResult<List<ErpProductRespVO>> getProductSimpleList() {
List<ErpProductRespVO> list = productService.getProductVOListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, product -> new ErpProductRespVO().setId(product.getId())
.setName(product.getName()).setBarCode(product.getBarCode())
.setCategoryId(product.getCategoryId()).setCategoryName(product.getCategoryName())
.setUnitId(product.getUnitId()).setUnitName(product.getUnitName())
.setPurchasePrice(product.getPurchasePrice()).setSalePrice(product.getSalePrice()).setMinPrice(product.getMinPrice())));
}
@GetMapping("/export-excel")
@Operation(summary = "导出产品 Excel")
@PreAuthorize("@ss.hasPermission('erp:product:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportProductExcel(@Valid ErpProductPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
PageResult<ErpProductRespVO> pageResult = productService.getProductVOPage(pageReqVO);
// 导出 Excel
ExcelUtils.write(response, "产品.xls", "数据", ErpProductRespVO.class,
pageResult.getList());
}
}

View File

@ -1,102 +0,0 @@
package cn.iocoder.yudao.module.erp.controller.admin.product;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO;
import cn.iocoder.yudao.module.erp.service.product.ErpProductUnitService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - ERP 产品单位")
@RestController
@RequestMapping("/erp/product-unit")
@Validated
public class ErpProductUnitController {
@Resource
private ErpProductUnitService productUnitService;
@PostMapping("/create")
@Operation(summary = "创建产品单位")
@PreAuthorize("@ss.hasPermission('erp:product-unit:create')")
public CommonResult<Long> createProductUnit(@Valid @RequestBody ErpProductUnitSaveReqVO createReqVO) {
return success(productUnitService.createProductUnit(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新产品单位")
@PreAuthorize("@ss.hasPermission('erp:product-unit:update')")
public CommonResult<Boolean> updateProductUnit(@Valid @RequestBody ErpProductUnitSaveReqVO updateReqVO) {
productUnitService.updateProductUnit(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除产品单位")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('erp:product-unit:delete')")
public CommonResult<Boolean> deleteProductUnit(@RequestParam("id") Long id) {
productUnitService.deleteProductUnit(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得产品单位")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:product-unit:query')")
public CommonResult<ErpProductUnitRespVO> getProductUnit(@RequestParam("id") Long id) {
ErpProductUnitDO productUnit = productUnitService.getProductUnit(id);
return success(BeanUtils.toBean(productUnit, ErpProductUnitRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得产品单位分页")
@PreAuthorize("@ss.hasPermission('erp:product-unit:query')")
public CommonResult<PageResult<ErpProductUnitRespVO>> getProductUnitPage(@Valid ErpProductUnitPageReqVO pageReqVO) {
PageResult<ErpProductUnitDO> pageResult = productUnitService.getProductUnitPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ErpProductUnitRespVO.class));
}
@GetMapping("/simple-list")
@Operation(summary = "获得产品单位精简列表", description = "只包含被开启的单位,主要用于前端的下拉选项")
public CommonResult<List<ErpProductUnitRespVO>> getProductUnitSimpleList() {
List<ErpProductUnitDO> list = productUnitService.getProductUnitListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, unit -> new ErpProductUnitRespVO().setId(unit.getId()).setName(unit.getName())));
}
@GetMapping("/export-excel")
@Operation(summary = "导出产品单位 Excel")
@PreAuthorize("@ss.hasPermission('erp:product-unit:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportProductUnitExcel(@Valid ErpProductUnitPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ErpProductUnitDO> list = productUnitService.getProductUnitPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "产品单位.xls", "数据", ErpProductUnitRespVO.class,
BeanUtils.toBean(list, ErpProductUnitRespVO.class));
}
}

View File

@ -1,165 +0,0 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.erp.service.stock.ErpStockService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@Tag(name = "管理后台 - ERP 采购入库")
@RestController
@RequestMapping("/erp/purchase-in")
@Validated
public class ErpPurchaseInController {
@Resource
private ErpPurchaseInService purchaseInService;
@Resource
private ErpStockService stockService;
@Resource
private ErpProductService productService;
@Resource
private ErpSupplierService supplierService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建采购入库")
@PreAuthorize("@ss.hasPermission('erp:purchase-in:create')")
public CommonResult<Long> createPurchaseIn(@Valid @RequestBody ErpPurchaseInSaveReqVO createReqVO) {
return success(purchaseInService.createPurchaseIn(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新采购入库")
@PreAuthorize("@ss.hasPermission('erp:purchase-in:update')")
public CommonResult<Boolean> updatePurchaseIn(@Valid @RequestBody ErpPurchaseInSaveReqVO updateReqVO) {
purchaseInService.updatePurchaseIn(updateReqVO);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新采购入库的状态")
@PreAuthorize("@ss.hasPermission('erp:purchase-in:update-status')")
public CommonResult<Boolean> updatePurchaseInStatus(@RequestParam("id") Long id,
@RequestParam("status") Integer status) {
purchaseInService.updatePurchaseInStatus(id, status);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除采购入库")
@Parameter(name = "ids", description = "编号数组", required = true)
@PreAuthorize("@ss.hasPermission('erp:purchase-in:delete')")
public CommonResult<Boolean> deletePurchaseIn(@RequestParam("ids") List<Long> ids) {
purchaseInService.deletePurchaseIn(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得采购入库")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('erp:purchase-in:query')")
public CommonResult<ErpPurchaseInRespVO> getPurchaseIn(@RequestParam("id") Long id) {
ErpPurchaseInDO purchaseIn = purchaseInService.getPurchaseIn(id);
if (purchaseIn == null) {
return success(null);
}
List<ErpPurchaseInItemDO> purchaseInItemList = purchaseInService.getPurchaseInItemListByInId(id);
Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
convertSet(purchaseInItemList, ErpPurchaseInItemDO::getProductId));
return success(BeanUtils.toBean(purchaseIn, ErpPurchaseInRespVO.class, purchaseInVO ->
purchaseInVO.setItems(BeanUtils.toBean(purchaseInItemList, ErpPurchaseInRespVO.Item.class, item -> {
ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId());
item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO);
MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
.setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()));
}))));
}
@GetMapping("/page")
@Operation(summary = "获得采购入库分页")
@PreAuthorize("@ss.hasPermission('erp:purchase-in:query')")
public CommonResult<PageResult<ErpPurchaseInRespVO>> getPurchaseInPage(@Valid ErpPurchaseInPageReqVO pageReqVO) {
PageResult<ErpPurchaseInDO> pageResult = purchaseInService.getPurchaseInPage(pageReqVO);
return success(buildPurchaseInVOPageResult(pageResult));
}
@GetMapping("/export-excel")
@Operation(summary = "导出采购入库 Excel")
@PreAuthorize("@ss.hasPermission('erp:purchase-in:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportPurchaseInExcel(@Valid ErpPurchaseInPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ErpPurchaseInRespVO> list = buildPurchaseInVOPageResult(purchaseInService.getPurchaseInPage(pageReqVO)).getList();
// 导出 Excel
ExcelUtils.write(response, "采购入库.xls", "数据", ErpPurchaseInRespVO.class, list);
}
private PageResult<ErpPurchaseInRespVO> buildPurchaseInVOPageResult(PageResult<ErpPurchaseInDO> pageResult) {
if (CollUtil.isEmpty(pageResult.getList())) {
return PageResult.empty(pageResult.getTotal());
}
// 1.1 入库项
List<ErpPurchaseInItemDO> purchaseInItemList = purchaseInService.getPurchaseInItemListByInIds(
convertSet(pageResult.getList(), ErpPurchaseInDO::getId));
Map<Long, List<ErpPurchaseInItemDO>> purchaseInItemMap = convertMultiMap(purchaseInItemList, ErpPurchaseInItemDO::getInId);
// 1.2 产品信息
Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
convertSet(purchaseInItemList, ErpPurchaseInItemDO::getProductId));
// 1.3 供应商信息
Map<Long, ErpSupplierDO> supplierMap = supplierService.getSupplierMap(
convertSet(pageResult.getList(), ErpPurchaseInDO::getSupplierId));
// 1.4 管理员信息
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(pageResult.getList(), purchaseIn -> Long.parseLong(purchaseIn.getCreator())));
// 2. 开始拼接
return BeanUtils.toBean(pageResult, ErpPurchaseInRespVO.class, purchaseIn -> {
purchaseIn.setItems(BeanUtils.toBean(purchaseInItemMap.get(purchaseIn.getId()), ErpPurchaseInRespVO.Item.class,
item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
.setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()))));
purchaseIn.setProductNames(CollUtil.join(purchaseIn.getItems(), "", ErpPurchaseInRespVO.Item::getProductName));
MapUtils.findAndThen(supplierMap, purchaseIn.getSupplierId(), supplier -> purchaseIn.setSupplierName(supplier.getName()));
MapUtils.findAndThen(userMap, Long.parseLong(purchaseIn.getCreator()), user -> purchaseIn.setCreatorName(user.getNickname()));
});
}
}

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