| | |
| | | import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; |
| | | import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.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.invoice.CrmInvoiceDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO; |
| | | 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.business.CrmBusinessMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.invoice.CrmInvoiceMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.quotation.CrmSaleQuotationMapper; |
| | | import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper; |
| | | 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; |
| | |
| | | |
| | | @Resource |
| | | private CrmBusinessMapper businessMapper; |
| | | @Resource |
| | | private CrmContractMapper contractMapper; |
| | | @Resource |
| | | private CrmContactMapper contactMapper; |
| | | @Resource |
| | | private CrmCustomerMapper customerMapper; |
| | | @Resource |
| | | private CrmSaleQuotationMapper quotationMapper; |
| | | @Resource |
| | | private CrmReceivableMapper receivableMapper; |
| | | @Resource |
| | | private CrmReceivablePlanMapper receivablePlanMapper; |
| | | @Resource |
| | | private CrmInvoiceMapper invoiceMapper; |
| | | |
| | | @Before("@annotation(crmPermission)") |
| | | public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) { |
| | |
| | | if (CrmPermissionUtils.isCrmAdmin()) { |
| | | return; |
| | | } |
| | | // 特殊:没有数据权限的情况,针对 READ 的特殊处理 |
| | | // 2. 数据本身的负责人:直接通过(兼容权限记录缺失的历史数据) |
| | | Long userId = getUserId(); |
| | | if (isRecordOwner(bizType, bizId, userId)) { |
| | | return; |
| | | } |
| | | // 3. 联系人/商机/合同/报价/回款/回款计划/开票:继承所属客户的权限 |
| | | // 放在「无数据权限」判断之前,避免权限记录缺失的历史数据导致负责人无法写操作 |
| | | if (validateCustomerPermission(bizType, bizId, userId, permissionLevel)) { |
| | | return; |
| | | } |
| | | |
| | | // 3. 特殊:没有数据权限的情况,针对 READ 的特殊处理 |
| | | if (CollUtil.isEmpty(bizPermissions)) { |
| | | // 1.1 公海数据,如果没有团队成员,大家也应该有 READ 权限才对 |
| | | if (CrmPermissionLevelEnum.isRead(permissionLevel)) { |
| | | // 3.1 真正的公海数据(父客户也没有团队),大家有 READ 权限; |
| | | // 如果父客户有团队,则走上面继承逻辑,这里不能放行,避免越权读取 |
| | | if (CrmPermissionLevelEnum.isRead(permissionLevel) && !hasParentCustomerTeam(bizType, bizId)) { |
| | | return; |
| | | } |
| | | // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针 |
| | | throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType)); |
| | | } else { // 1.2 有数据权限但是没有负责人的情况 |
| | | } else { // 3.2 有数据权限但是没有负责人的情况 |
| | | if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel())) |
| | | && CrmPermissionLevelEnum.isRead(permissionLevel)) { |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 2. 只考虑自身的权限 |
| | | Long userId = getUserId(); |
| | | // 4. 只考虑自身的权限 |
| | | CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), userId)); |
| | | if (userPermission != null) { |
| | | if (isUserPermissionValid(userPermission, permissionLevel)) { |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 3. 商机特殊处理:检查客户团队权限 |
| | | if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) { |
| | | if (validateBusinessCustomerPermission(bizId, userId, permissionLevel)) { |
| | | return; |
| | | } |
| | | } |
| | |
| | | if (subordinatePermission != null && isUserPermissionValid(subordinatePermission, permissionLevel)) { |
| | | return; |
| | | } |
| | | // 商机特殊处理:检查客户团队权限(下属的) |
| | | if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) { |
| | | if (validateBusinessCustomerPermission(bizId, subordinateUserId, permissionLevel)) { |
| | | return; |
| | | } |
| | | // 继承所属客户的权限(下属的) |
| | | if (validateCustomerPermission(bizType, bizId, subordinateUserId, permissionLevel)) { |
| | | return; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 商机特殊权限校验:检查用户是否有该商机所属客户的团队权限 |
| | | * 继承所属客户的权限校验:客户负责人、客户团队成员 |
| | | * |
| | | * @param businessId 商机编号 |
| | | * @param userId 用户编号 |
| | | * 适用于:联系人、商机、合同、销售报价、回款、回款计划、开票 |
| | | * |
| | | * @param bizType 数据类型 |
| | | * @param bizId 数据编号 |
| | | * @param userId 用户编号 |
| | | * @param permissionLevel 需要的权限级别 |
| | | * @return 是否有权限 |
| | | */ |
| | | private boolean validateBusinessCustomerPermission(Long businessId, Long userId, Integer permissionLevel) { |
| | | // 获取商机的客户ID |
| | | CrmBusinessDO business = businessMapper.selectById(businessId); |
| | | if (business == null || business.getCustomerId() == null) { |
| | | private boolean validateCustomerPermission(Integer bizType, Long bizId, Long userId, Integer permissionLevel) { |
| | | // 1. 获取所属客户 |
| | | Long customerId = getCustomerId(bizType, bizId); |
| | | if (customerId == null) { |
| | | return false; |
| | | } |
| | | // 检查客户团队权限 |
| | | List<CrmPermissionDO> customerPermissions = crmPermissionService.getPermissionListByBiz( |
| | | CrmBizTypeEnum.CRM_CUSTOMER.getType(), Collections.singleton(business.getCustomerId())); |
| | | if (CollUtil.isEmpty(customerPermissions)) { |
| | | // 客户公海数据,READ 权限放行 |
| | | return CrmPermissionLevelEnum.isRead(permissionLevel); |
| | | } |
| | | // 检查用户是否有客户团队的权限 |
| | | CrmPermissionDO userCustomerPermission = CollUtil.findOne(customerPermissions, |
| | | permission -> ObjUtil.equal(permission.getUserId(), userId)); |
| | | if (userCustomerPermission != null && isUserPermissionValid(userCustomerPermission, permissionLevel)) { |
| | | CrmCustomerDO customer = customerMapper.selectById(customerId); |
| | | // 2. 客户负责人:直接通过(兼容权限表中负责人记录缺失/不一致的历史数据) |
| | | if (customer != null && ObjUtil.equal(customer.getOwnerUserId(), userId)) { |
| | | return true; |
| | | } |
| | | return false; |
| | | // 3. 检查客户团队权限 |
| | | List<CrmPermissionDO> customerPermissions = crmPermissionService.getPermissionListByBiz( |
| | | CrmBizTypeEnum.CRM_CUSTOMER.getType(), Collections.singleton(customerId)); |
| | | if (CollUtil.isEmpty(customerPermissions)) { |
| | | // 客户有负责人但不是当前用户:不是公海,不能放行 |
| | | if (customer != null && customer.getOwnerUserId() != null) { |
| | | return false; |
| | | } |
| | | // 真正的公海数据,READ 权限放行 |
| | | return CrmPermissionLevelEnum.isRead(permissionLevel); |
| | | } |
| | | // 4. 检查用户是否有客户团队的权限 |
| | | CrmPermissionDO userCustomerPermission = CollUtil.findOne(customerPermissions, |
| | | permission -> ObjUtil.equal(permission.getUserId(), userId)); |
| | | return userCustomerPermission != null && isUserPermissionValid(userCustomerPermission, permissionLevel); |
| | | } |
| | | |
| | | /** |
| | | * 获取数据所属的客户编号 |
| | | * |
| | | * @param bizType 数据类型 |
| | | * @param bizId 数据编号 |
| | | * @return 客户编号,不存在时返回 null |
| | | */ |
| | | private Long getCustomerId(Integer bizType, Long bizId) { |
| | | if (CrmBizTypeEnum.CRM_CONTACT.getType().equals(bizType)) { |
| | | CrmContactDO contact = contactMapper.selectById(bizId); |
| | | return contact == null ? null : contact.getCustomerId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) { |
| | | CrmBusinessDO business = businessMapper.selectById(bizId); |
| | | return business == null ? null : business.getCustomerId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_CONTRACT.getType().equals(bizType)) { |
| | | CrmContractDO contract = contractMapper.selectById(bizId); |
| | | return contract == null ? null : contract.getCustomerId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_SALE_QUOTATION.getType().equals(bizType)) { |
| | | CrmSaleQuotationDO quotation = quotationMapper.selectById(bizId); |
| | | return quotation == null ? null : quotation.getCustomerId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_RECEIVABLE.getType().equals(bizType)) { |
| | | CrmReceivableDO receivable = receivableMapper.selectById(bizId); |
| | | return receivable == null ? null : receivable.getCustomerId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType().equals(bizType)) { |
| | | CrmReceivablePlanDO plan = receivablePlanMapper.selectById(bizId); |
| | | return plan == null ? null : plan.getCustomerId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_INVOICE.getType().equals(bizType)) { |
| | | CrmInvoiceDO invoice = invoiceMapper.selectById(bizId); |
| | | return invoice == null ? null : invoice.getCustomerId(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 获取数据本身的负责人编号 |
| | | * |
| | | * @param bizType 数据类型 |
| | | * @param bizId 数据编号 |
| | | * @return 负责人编号,不存在时返回 null |
| | | */ |
| | | private Long getOwnerUserId(Integer bizType, Long bizId) { |
| | | if (CrmBizTypeEnum.CRM_CONTACT.getType().equals(bizType)) { |
| | | CrmContactDO contact = contactMapper.selectById(bizId); |
| | | return contact == null ? null : contact.getOwnerUserId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_BUSINESS.getType().equals(bizType)) { |
| | | CrmBusinessDO business = businessMapper.selectById(bizId); |
| | | return business == null ? null : business.getOwnerUserId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_CONTRACT.getType().equals(bizType)) { |
| | | CrmContractDO contract = contractMapper.selectById(bizId); |
| | | return contract == null ? null : contract.getOwnerUserId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_SALE_QUOTATION.getType().equals(bizType)) { |
| | | CrmSaleQuotationDO quotation = quotationMapper.selectById(bizId); |
| | | return quotation == null ? null : quotation.getOwnerUserId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_RECEIVABLE.getType().equals(bizType)) { |
| | | CrmReceivableDO receivable = receivableMapper.selectById(bizId); |
| | | return receivable == null ? null : receivable.getOwnerUserId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType().equals(bizType)) { |
| | | CrmReceivablePlanDO plan = receivablePlanMapper.selectById(bizId); |
| | | return plan == null ? null : plan.getOwnerUserId(); |
| | | } |
| | | if (CrmBizTypeEnum.CRM_INVOICE.getType().equals(bizType)) { |
| | | CrmInvoiceDO invoice = invoiceMapper.selectById(bizId); |
| | | return invoice == null ? null : invoice.getOwnerUserId(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 判断用户是否是数据本身的负责人 |
| | | * |
| | | * 不依赖 crm_permission 记录,兼容历史数据中权限记录缺失的情况 |
| | | * |
| | | * @param bizType 数据类型 |
| | | * @param bizId 数据编号 |
| | | * @param userId 用户编号 |
| | | * @return 是否是负责人 |
| | | */ |
| | | private boolean isRecordOwner(Integer bizType, Long bizId, Long userId) { |
| | | return userId != null && ObjUtil.equal(getOwnerUserId(bizType, bizId), userId); |
| | | } |
| | | |
| | | /** |
| | | * 判断子数据所属客户是否存在团队(负责人或团队成员) |
| | | * |
| | | * 用于区分「真正的公海数据」和「父客户有团队、但子数据本身没有权限记录」的历史数据 |
| | | * |
| | | * @param bizType 数据类型 |
| | | * @param bizId 数据编号 |
| | | * @return 是否存在客户团队 |
| | | */ |
| | | private boolean hasParentCustomerTeam(Integer bizType, Long bizId) { |
| | | Long customerId = getCustomerId(bizType, bizId); |
| | | if (customerId == null) { |
| | | return false; |
| | | } |
| | | // 客户有负责人,说明不是公海 |
| | | CrmCustomerDO customer = customerMapper.selectById(customerId); |
| | | if (customer == null) { |
| | | return false; |
| | | } |
| | | if (customer.getOwnerUserId() != null) { |
| | | return true; |
| | | } |
| | | // 客户没有负责人时,检查是否有团队成员 |
| | | return CollUtil.isNotEmpty(crmPermissionService.getPermissionListByBiz( |
| | | CrmBizTypeEnum.CRM_CUSTOMER.getType(), Collections.singleton(customerId))); |
| | | } |
| | | |
| | | /** |