package cn.iocoder.yudao.module.crm.util;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
|
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.common.CrmSceneTypeEnum;
|
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
|
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties;
|
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
|
import java.util.List;
|
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
|
/**
|
* 数据权限工具类
|
*
|
* @author HUIHUI
|
*/
|
public class CrmPermissionUtils {
|
|
/**
|
* 校验用户是否是 CRM 管理员(CRM 管理员或系统超级管理员)
|
*
|
* 超级管理员(super_admin)不受 CRM 数据权限限制,可以查看全部数据
|
*
|
* @return 是/否
|
*/
|
public static boolean isCrmAdmin() {
|
PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
|
return permissionApi.hasAnyRoles(getLoginUserId(),
|
RoleCodeEnum.CRM_ADMIN.getCode(), RoleCodeEnum.SUPER_ADMIN.getCode());
|
}
|
|
/**
|
* 校验用户是否有查看全部客户的权限
|
*
|
* @return 是/否
|
*/
|
private static boolean hasCustomerViewAllPermission() {
|
PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
|
return permissionApi.hasAnyPermissions(getLoginUserId(), "crm:customer:view-all");
|
}
|
|
/**
|
* 构造 CRM 数据类型数据【分页】查询条件
|
*
|
* @param query 连表查询对象
|
* @param bizType 数据类型 {@link CrmBizTypeEnum}
|
* @param bizId 数据编号
|
* @param userId 用户编号
|
* @param sceneType 场景类型
|
*/
|
public static <T extends MPJLambdaWrapper<?>, S> void appendPermissionCondition(T query, Integer bizType, SFunction<S, ?> bizId,
|
Long userId, Integer sceneType) {
|
// 特殊逻辑:CRM 管理员 / 超级管理员直接查询所有,不过滤数据权限
|
if (isCrmAdmin()) {
|
return;
|
}
|
MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class);
|
final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id";
|
// 场景一:我负责的数据
|
if (CrmSceneTypeEnum.isOwner(sceneType)) {
|
query.eq(ownerUserIdField, userId);
|
}
|
// 场景二:我参与的数据(我有读或写权限,并且不是负责人)
|
if (CrmSceneTypeEnum.isInvolved(sceneType)) {
|
// 需要继承客户团队的模块:数据自身团队 ∪ 客户负责人/客户团队
|
String tableName = getInheritCustomerTeamTable(bizType);
|
if (tableName != null) {
|
appendCustomerInheritedPermissionCondition(query, bizType, tableName, userId, ownerUserIdField);
|
} else {
|
query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
|
.eq(CrmPermissionDO::getBizId, bizId)
|
.in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel())
|
.eq(CrmPermissionDO::getUserId, userId));
|
query.ne(ownerUserIdField, userId);
|
}
|
}
|
// 场景三:下属负责的数据(下属是负责人)
|
if (CrmSceneTypeEnum.isSubordinate(sceneType)) {
|
AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class);
|
List<AdminUserRespDTO> subordinateUsers = adminUserApi.getUserListBySubordinate(userId);
|
if (CollUtil.isEmpty(subordinateUsers)) {
|
query.eq(ownerUserIdField, -1); // 不返回任何结果
|
} else {
|
query.in(ownerUserIdField, convertSet(subordinateUsers, AdminUserRespDTO::getId));
|
}
|
}
|
// 场景四:全部数据(需要有 view-all 权限或 CRM 管理员)
|
if (CrmSceneTypeEnum.isAll(sceneType)) {
|
if (isCrmAdmin() || hasCustomerViewAllPermission()) {
|
return; // 跳过过滤,查看全部
|
}
|
// 无权限则降级为"我负责的"
|
query.eq(ownerUserIdField, userId);
|
}
|
}
|
|
/**
|
* 获得「继承客户团队」的业务表名;不需要继承的返回 null
|
*
|
* 目前统一为:联系人、商机、合同、销售报价、回款、回款计划、开票
|
*/
|
private static String getInheritCustomerTeamTable(Integer bizType) {
|
if (CrmBizTypeEnum.CRM_CONTACT.getType().equals(bizType)) {
|
return "crm_contact";
|
}
|
if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) {
|
return "crm_business";
|
}
|
if (CrmBizTypeEnum.CRM_CONTRACT.getType().equals(bizType)) {
|
return "crm_contract";
|
}
|
if (CrmBizTypeEnum.CRM_SALE_QUOTATION.getType().equals(bizType)) {
|
return "crm_sale_quotation";
|
}
|
if (CrmBizTypeEnum.CRM_RECEIVABLE.getType().equals(bizType)) {
|
return "crm_receivable";
|
}
|
if (CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType().equals(bizType)) {
|
return "crm_receivable_plan";
|
}
|
if (CrmBizTypeEnum.CRM_INVOICE.getType().equals(bizType)) {
|
return "crm_invoice";
|
}
|
return null;
|
}
|
|
/**
|
* 继承客户团队的数据权限:数据自身团队 ∪ 客户负责人/客户团队
|
*
|
* @param query 连表查询对象
|
* @param bizType 数据类型
|
* @param tableName 业务表名
|
* @param userId 用户编号
|
* @param ownerUserIdField 负责人字段
|
*/
|
private static <T extends MPJLambdaWrapper<?>> void appendCustomerInheritedPermissionCondition(
|
T query, Integer bizType, String tableName, Long userId, String ownerUserIdField) {
|
// 子查询1:数据自身的团队成员权限
|
String subQuery1 = "(SELECT p1.biz_id FROM crm_permission p1 " +
|
"WHERE p1.biz_type = " + bizType + " " +
|
"AND p1.level IN (" + CrmPermissionLevelEnum.OWNER.getLevel() + "," +
|
CrmPermissionLevelEnum.READ.getLevel() + "," + CrmPermissionLevelEnum.WRITE.getLevel() + ") " +
|
"AND p1.user_id = " + userId + " AND p1.deleted = 0)";
|
// 子查询2:所属客户的负责人 + 团队成员
|
// 注意:以 crm_customer.owner_user_id 为准,兼容权限表中负责人记录缺失/不一致的历史数据
|
String subQuery2 = "(SELECT x.id FROM " + tableName + " x " +
|
"INNER JOIN crm_customer cu ON cu.id = x.customer_id AND cu.deleted = 0 " +
|
"WHERE x.deleted = 0 AND (cu.owner_user_id = " + userId +
|
" OR EXISTS (SELECT 1 FROM crm_permission p2 " +
|
"WHERE p2.biz_type = " + CrmBizTypeEnum.CRM_CUSTOMER.getType() + " " +
|
"AND p2.biz_id = x.customer_id AND p2.user_id = " + userId + " AND p2.deleted = 0)))";
|
// 使用 OR 连接两个子查询,并排除自己是负责人的情况
|
query.apply("t.id IN (" + subQuery1 + " UNION " + subQuery2 + ")")
|
.ne(ownerUserIdField, userId);
|
}
|
|
}
|