CRM:code review 客户权限的实现

This commit is contained in:
YunaiV 2024-03-09 11:58:05 +08:00
parent e2ec426a9f
commit 89fc83c419
21 changed files with 71 additions and 42 deletions

View File

@ -11,7 +11,7 @@
Target Server Version : 80200 (8.2.0) Target Server Version : 80200 (8.2.0)
File Encoding : 65001 File Encoding : 65001
Date: 01/03/2024 19:39:45 Date: 05/03/2024 23:36:35
*/ */
SET NAMES utf8mb4; SET NAMES utf8mb4;

View File

@ -627,11 +627,11 @@
<artifactId>ureport2-console</artifactId> <artifactId>ureport2-console</artifactId>
<version>${ureport2.version}</version> <version>${ureport2.version}</version>
<exclusions> <exclusions>
<exclusion><!-- 包冲突 积木报表 导出Excel报错 --> <exclusion><!-- 包冲突:解决积木报表导出 Excel 报错 -->
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId> <artifactId>poi</artifactId>
</exclusion> </exclusion>
<exclusion><!-- 包冲突 积木报表 导出Excel报错 --> <exclusion><!-- 包冲突:解决积木报表导出 Excel 报错 -->
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId> <artifactId>poi-ooxml</artifactId>
</exclusion> </exclusion>

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.core.KeyValue;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
@ -40,6 +41,7 @@ public class MapUtils {
/** /**
* 从哈希表查找到 key 对应的 value然后进一步处理 * 从哈希表查找到 key 对应的 value然后进一步处理
* key null , 不处理
* 注意如果查找到的 value null 不进行处理 * 注意如果查找到的 value null 不进行处理
* *
* @param map 哈希表 * @param map 哈希表
@ -47,7 +49,7 @@ public class MapUtils {
* @param consumer 进一步处理的逻辑 * @param consumer 进一步处理的逻辑
*/ */
public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) { public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
if (CollUtil.isEmpty(map)) { if (ObjUtil.isNull(key) || CollUtil.isEmpty(map)) {
return; return;
} }
V value = map.get(key); V value = map.get(key);

View File

@ -13,8 +13,6 @@ import lombok.extern.slf4j.Slf4j;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/** /**
* 字典工具类 * 字典工具类
* *
@ -27,6 +25,7 @@ public class DictFrameworkUtils {
private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO(); private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();
// TODO @puhui999GET_DICT_DATA_CACHEGET_DICT_DATA_LIST_CACHEPARSE_DICT_DATA_CACHE 3 个缓存是有点重叠可以思考下有没可能减少 1 微信讨论好私聊再具体改哈
/** /**
* 针对 {@link #getDictDataLabel(String, String)} 的缓存 * 针对 {@link #getDictDataLabel(String, String)} 的缓存
*/ */

View File

@ -3,7 +3,9 @@ package cn.iocoder.yudao.framework.excel.core.annotations;
import java.lang.annotation.*; import java.lang.annotation.*;
/** /**
* 给列添加下拉选择数据 * Excel 列添加下拉选择数据
*
* 其中 {@link #dictType()} {@link #functionName()} 二选一
* *
* @author HUIHUI * @author HUIHUI
*/ */

View File

@ -1,8 +1,9 @@
package cn.iocoder.yudao.framework.excel.core.handler; package cn.iocoder.yudao.framework.excel.core.handler;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.ExcelUtil;
@ -59,7 +60,6 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
} }
// 解析下拉数据 // 解析下拉数据
// TODO @puhui999感觉可以 head 循环 field如果有 ExcelColumnSelect 则进行处理 ExcelProperty 可能是非必须的回答主要是用于定位到列索引
Map<String, Field> excelPropertyFields = getFieldsWithAnnotation(head, ExcelProperty.class); Map<String, Field> excelPropertyFields = getFieldsWithAnnotation(head, ExcelProperty.class);
Map<String, Field> excelColumnSelectFields = getFieldsWithAnnotation(head, ExcelColumnSelect.class); Map<String, Field> excelColumnSelectFields = getFieldsWithAnnotation(head, ExcelColumnSelect.class);
int colIndex = 0; int colIndex = 0;
@ -71,39 +71,47 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
if (index != -1) { if (index != -1) {
colIndex = index; colIndex = index;
} }
getSelectDataList(colIndex, field); buildSelectDataList(colIndex, field);
} }
colIndex++; colIndex++;
} }
// TODO @puhui999感觉可以 head 循环 field如果有 ExcelColumnSelect 则进行处理 ExcelProperty 可能是非必须的回答主要是用于定位到列索引补充可以看看下面这样写
// for (Field field : head.getDeclaredFields()) {
// if (field.isAnnotationPresent(ExcelColumnSelect.class)) {
// ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
// if (excelProperty != null) {
// colIndex = excelProperty.index();
// }
// getSelectDataList(colIndex, field);
// }
// colIndex++;
// }
} }
/** /**
* 获得下拉数据 * 获得下拉数据并添加到 {@link #selectMap}
* *
* @param colIndex 列索引 * @param colIndex 列索引
* @param field 字段 * @param field 字段
*/ */
private void getSelectDataList(int colIndex, Field field) { private void buildSelectDataList(int colIndex, Field field) {
// 获得下拉注解信息
ExcelColumnSelect columnSelect = field.getAnnotation(ExcelColumnSelect.class); ExcelColumnSelect columnSelect = field.getAnnotation(ExcelColumnSelect.class);
String dictType = columnSelect.dictType(); String dictType = columnSelect.dictType();
String functionName = columnSelect.functionName();
Assert.isTrue(ObjectUtil.isNotEmpty(dictType) || ObjectUtil.isNotEmpty(functionName),
"Field({}) 的 @ExcelColumnSelect 注解dictType 和 functionName 不能同时为空", field.getName());
// 情况一使用 dictType 获得下拉数据
if (StrUtil.isNotEmpty(dictType)) { // 情况一 字典数据 默认 if (StrUtil.isNotEmpty(dictType)) { // 情况一 字典数据 默认
selectMap.put(colIndex, DictFrameworkUtils.getDictDataLabelList(dictType)); selectMap.put(colIndex, DictFrameworkUtils.getDictDataLabelList(dictType));
return; return;
} }
String functionName = columnSelect.functionName();
if (StrUtil.isEmpty(functionName)) { // 情况二 获取自定义数据 // 情况二使用 functionName 获得下拉数据
log.warn("[getSelectDataList]解析下拉数据失败,参数信息 dictType[{}] functionName[{}]", dictType, functionName);
return;
}
// 获得所有的下拉数据源获取方法
Map<String, ExcelColumnSelectFunction> functionMap = SpringUtil.getApplicationContext().getBeansOfType(ExcelColumnSelectFunction.class); Map<String, ExcelColumnSelectFunction> functionMap = SpringUtil.getApplicationContext().getBeansOfType(ExcelColumnSelectFunction.class);
functionMap.values().forEach(func -> { ExcelColumnSelectFunction function = CollUtil.findOne(functionMap.values(), item -> item.getName().equals(functionName));
if (ObjUtil.notEqual(func.getName(), functionName)) { Assert.notNull(function, "未找到对应的 function({})", functionName);
return; selectMap.put(colIndex, function.getOptions());
}
selectMap.put(colIndex, func.getOptions());
});
} }
@Override @Override
@ -121,7 +129,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME); Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME);
for (KeyValue<Integer, List<String>> keyValue : keyValues) { for (KeyValue<Integer, List<String>> keyValue : keyValues) {
int rowLength = keyValue.getValue().size(); int rowLength = keyValue.getValue().size();
// 2.1 设置字典 sheet 页的值 每一列一个字典项 // 2.1 设置字典 sheet 页的值每一列一个字典项
for (int i = 0; i < rowLength; i++) { for (int i = 0; i < rowLength; i++) {
Row row = dictSheet.getRow(i); Row row = dictSheet.getRow(i);
if (row == null) { if (row == null) {

View File

@ -13,6 +13,7 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
public class CrmBusinessTransferReqVO { public class CrmBusinessTransferReqVO {
// TODO @puhui999这里最好还是用 id 主要还是在 Business 业务里
@Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "商机编号不能为空") @NotNull(message = "商机编号不能为空")
private Long bizId; private Long bizId;

View File

@ -4,7 +4,7 @@ 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.annotations.ExcelColumnSelect;
import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert; import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunctionImpl; import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunction;
import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@ -43,7 +43,7 @@ public class CrmCustomerImportExcelVO {
private String email; private String email;
@ExcelProperty(value = "地区", converter = AreaConvert.class) @ExcelProperty(value = "地区", converter = AreaConvert.class)
@ExcelColumnSelect(functionName = AreaExcelColumnSelectFunctionImpl.NAME) @ExcelColumnSelect(functionName = AreaExcelColumnSelectFunction.NAME)
private Integer areaId; private Integer areaId;
@ExcelProperty("详细地址") @ExcelProperty("详细地址")

View File

@ -31,7 +31,7 @@ public class CrmCustomerTransferReqVO {
private Integer oldOwnerPermissionLevel; private Integer oldOwnerPermissionLevel;
/** /**
* 转移客户时需要额外有联系人商机合同 checkbox 选择 * 转移客户时需要额外有联系人商机合同 checkbox 选择选中时也一起转移
*/ */
@Schema(description = "同时转移", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") @Schema(description = "同时转移", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
private List<Integer> toBizTypes; private List<Integer> toBizTypes;

View File

@ -68,22 +68,24 @@ public class CrmPermissionController {
@Resource @Resource
private PostApi postApi; private PostApi postApi;
// TODO @puhui999是不是还是叫 create 好点哈
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建数据权限") @Operation(summary = "创建数据权限")
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@PreAuthorize("@ss.hasPermission('crm:permission:create')") @PreAuthorize("@ss.hasPermission('crm:permission:create')")
@CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER) @CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER)
public CommonResult<Boolean> addPermission(@Valid @RequestBody CrmPermissionSaveReqVO reqVO) { public CommonResult<Boolean> savePermission(@Valid @RequestBody CrmPermissionSaveReqVO reqVO) {
permissionService.createPermission(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class)); permissionService.createPermission(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class));
// 处理同时添加至的权限
if (CollUtil.isNotEmpty(reqVO.getToBizTypes())) { if (CollUtil.isNotEmpty(reqVO.getToBizTypes())) {
createBizTypePermissions(reqVO); createBizTypePermissions(reqVO);
} }
return success(true); return success(true);
} }
private void createBizTypePermissions(CrmPermissionSaveReqVO reqVO) { private void createBizTypePermissions(CrmPermissionSaveReqVO reqVO) {
List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>(); List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();
// TODO @puhui999需要考虑被添加人是不是应该有对应的权限了
if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTACT.getType())) { if (reqVO.getToBizTypes().contains(CrmBizTypeEnum.CRM_CONTACT.getType())) {
List<CrmContactDO> contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getBizId(), getLoginUserId()); List<CrmContactDO> contactList = contactService.getContactListByCustomerIdOwnerUserId(reqVO.getBizId(), getLoginUserId());
contactList.forEach(item -> { contactList.forEach(item -> {

View File

@ -33,7 +33,8 @@ public class CrmPermissionSaveReqVO {
private Integer level; private Integer level;
/** /**
* 添加客户团队成员时需要额外有联系人商机合同 checkbox 选择 * 添加客户团队成员时需要额外有联系人商机合同 checkbox 选择
* 选中时同时添加对应的权限
*/ */
@Schema(description = "同时添加", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") @Schema(description = "同时添加", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
private List<Integer> toBizTypes; private List<Integer> toBizTypes;

View File

@ -74,6 +74,7 @@ public interface CrmContactMapper extends BaseMapperX<CrmContactDO> {
} }
default List<CrmContactDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) { default List<CrmContactDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
// TODO @puhui999父类有 selectList查询 2 个字段的简化方法哈可以用下
return selectList(new LambdaQueryWrapperX<CrmContactDO>() return selectList(new LambdaQueryWrapperX<CrmContactDO>()
.eq(CrmContactDO::getCustomerId, customerId) .eq(CrmContactDO::getCustomerId, customerId)
.eq(CrmContactDO::getOwnerUserId, ownerUserId)); .eq(CrmContactDO::getOwnerUserId, ownerUserId));

View File

@ -99,4 +99,8 @@ public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> {
return convertMap(result, obj -> (Long) obj.get("contract_id"), obj -> (BigDecimal) obj.get("total_price")); 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

@ -10,10 +10,12 @@ import java.util.List;
/** /**
* Excel 所属地区列下拉数据源获取接口实现类 * Excel 所属地区列下拉数据源获取接口实现类
* *
* // TODO @puhui999类名叫地区下拉框数据源的 {@link ExcelColumnSelectFunction} 实现类这样看起来会更简洁一点哈
*
* @author HUIHUI * @author HUIHUI
*/ */
@Service @Service
public class AreaExcelColumnSelectFunctionImpl implements ExcelColumnSelectFunction { public class AreaExcelColumnSelectFunction implements ExcelColumnSelectFunction {
public static final String NAME = "getCrmAreaNameList"; // 防止和别的模块重名 public static final String NAME = "getCrmAreaNameList"; // 防止和别的模块重名

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; 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.BeanUtils;
@ -27,7 +26,6 @@ import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPerm
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; 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.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerServiceImpl;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; 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.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
@ -231,7 +229,7 @@ public class CrmContractServiceImpl implements CrmContractService {
// 1.1 校验存在 // 1.1 校验存在
CrmContractDO contract = validateContractExists(id); CrmContractDO contract = validateContractExists(id);
// 1.2 如果被 CrmReceivableDO 所使用则不允许删除 // 1.2 如果被 CrmReceivableDO 所使用则不允许删除
if (receivableService.getReceivableByContractId(contract.getId()) != 0) { if (receivableService.getReceivableCountByContractId(contract.getId()) > 0) {
throw exception(CONTRACT_DELETE_FAIL); throw exception(CONTRACT_DELETE_FAIL);
} }

View File

@ -123,11 +123,11 @@ public interface CrmReceivableService {
Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds); Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds);
/** /**
* 更具合同编号查询回款列表 * 根据合同编号查询回款数量
* *
* @param contractId 合同编号 * @param contractId 合同编号
* @return 回款 * @return 回款数量
*/ */
Long getReceivableByContractId(Long contractId); Long getReceivableCountByContractId(Long contractId);
} }

View File

@ -302,8 +302,8 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
} }
@Override @Override
public Long getReceivableByContractId(Long contractId) { public Long getReceivableCountByContractId(Long contractId) {
return receivableMapper.selectCount(CrmReceivableDO::getContractId, contractId); return receivableMapper.selectCountByContractId(contractId);
} }
} }