liu
7 天以前 4bc513be11e671caf20ec6013a51dee42c2ed96e
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java
@@ -27,13 +27,26 @@
public class CrmPermissionUtils {
    /**
     * 校验用户是否是 CRM 管理员
     * 校验用户是否是 CRM 管理员(CRM 管理员或系统超级管理员)
     *
     * 超级管理员(super_admin)不受 CRM 数据权限限制,可以查看全部数据
     *
     * @return 是/否
     */
    public static boolean isCrmAdmin() {
        PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
        return permissionApi.hasAnyRoles(getLoginUserId(), RoleCodeEnum.CRM_ADMIN.getCode());
        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");
    }
    /**
@@ -47,6 +60,10 @@
     */
    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";
        // 场景一:我负责的数据
@@ -55,14 +72,17 @@
        }
        // 场景二:我参与的数据(我有读或写权限,并且不是负责人)
        if (CrmSceneTypeEnum.isInvolved(sceneType)) {
            if (CrmPermissionUtils.isCrmAdmin()) { // 特殊逻辑:如果是超管,直接查询所有,不过滤数据权限
                return;
            // 需要继承客户团队的模块:数据自身团队 ∪ 客户负责人/客户团队
            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);
            }
            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)) {
@@ -74,6 +94,74 @@
                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);
    }
}