Browse Source

CRM-线索:完善数据权限,新增权限关联、场景分页查询,新增权限关联批量查询

puhui999 1 year ago
parent
commit
490418a1fc
18 changed files with 262 additions and 218 deletions
  1. 9 7
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java
  2. 0 52
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExportReqVO.java
  3. 12 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java
  4. 12 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java
  5. 12 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java
  6. 12 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java
  7. 12 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java
  8. 7 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java
  9. 27 21
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
  10. 1 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/annotations/CrmPermission.java
  11. 3 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
  12. 11 14
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java
  13. 16 12
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
  14. 8 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java
  15. 2 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
  16. 10 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java
  17. 12 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
  18. 96 104
      yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java

+ 9 - 7
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java

@@ -11,18 +11,20 @@ import cn.iocoder.yudao.module.crm.service.clue.CrmClueService;
 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 jakarta.annotation.Resource;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.Valid;
 import java.io.IOException;
 import java.util.List;
 
 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.operatelog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
 @Tag(name = "管理后台 - 线索")
 @RestController
@@ -70,7 +72,7 @@ public class CrmClueController {
     @Operation(summary = "获得线索分页")
     @PreAuthorize("@ss.hasPermission('crm:clue:query')")
     public CommonResult<PageResult<CrmClueRespVO>> getCluePage(@Valid CrmCluePageReqVO pageVO) {
-        PageResult<CrmClueDO> pageResult = clueService.getCluePage(pageVO);
+        PageResult<CrmClueDO> pageResult = clueService.getCluePage(pageVO, getLoginUserId());
         return success(CrmClueConvert.INSTANCE.convertPage(pageResult));
     }
 
@@ -78,9 +80,9 @@ public class CrmClueController {
     @Operation(summary = "导出线索 Excel")
     @PreAuthorize("@ss.hasPermission('crm:clue:export')")
     @OperateLog(type = EXPORT)
-    public void exportClueExcel(@Valid CrmClueExportReqVO exportReqVO,
-              HttpServletResponse response) throws IOException {
-        List<CrmClueDO> list = clueService.getClueList(exportReqVO);
+    public void exportClueExcel(@Valid CrmCluePageReqVO pageReqVO, HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PAGE_SIZE_NONE);
+        List<CrmClueDO> list = clueService.getCluePage(pageReqVO, getLoginUserId()).getList();
         // 导出 Excel
         List<CrmClueExcelVO> datas = CrmClueConvert.INSTANCE.convertList02(list);
         ExcelUtils.write(response, "线索.xls", "数据", CrmClueExcelVO.class, datas);

+ 0 - 52
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExportReqVO.java

@@ -1,52 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
-
-import lombok.*;
-import java.util.*;
-import io.swagger.v3.oas.annotations.media.Schema;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import java.time.LocalDateTime;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-@Schema(description = "管理后台 - 线索 Excel 导出 Request VO,参数和 CrmCluePageReqVO 是一致的")
-@Data
-public class CrmClueExportReqVO {
-
-    @Schema(description = "转化状态", example = "true")
-    private Boolean transformStatus;
-
-    @Schema(description = "跟进状态", example = "true")
-    private Boolean followUpStatus;
-
-    @Schema(description = "线索名称", example = "线索xxx")
-    private String name;
-
-    @Schema(description = "客户id", example = "520")
-    private Long customerId;
-
-    @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] contactNextTime;
-
-    @Schema(description = "电话", example = "18000000000")
-    private String telephone;
-
-    @Schema(description = "手机号", example = "18000000000")
-    private String mobile;
-
-    @Schema(description = "地址", example = "北京市海淀区")
-    private String address;
-
-    @Schema(description = "负责人的用户编号", example = "27199")
-    private Long ownerUserId;
-
-    @Schema(description = "最后跟进时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] contactLastTime;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
-
-}

+ 12 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -21,4 +23,14 @@ public class CrmCluePageReqVO extends PageParam {
     @Schema(description = "手机号", example = "18000000000")
     private String mobile;
 
+    /**
+     * 场景类型,为 null 时则表示全部
+     */
+    @Schema(description = "场景类型", example = "1")
+    @InEnum(CrmSceneTypeEnum.class)
+    private Integer sceneType;
+
+    @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+    private Boolean pool; // null 则表示为不是公海数据
+
 }

+ 12 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -33,4 +35,14 @@ public class CrmContactPageReqVO extends PageParam {
     @Schema(description = "微信", example = "zzZ98373")
     private String wechat;
 
+    /**
+     * 场景类型,为 null 时则表示全部
+     */
+    @Schema(description = "场景类型", example = "1")
+    @InEnum(CrmSceneTypeEnum.class)
+    private Integer sceneType;
+
+    @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+    private Boolean pool; // null 则表示为不是公海数据
+
 }

+ 12 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -24,4 +26,14 @@ public class CrmContractPageReqVO extends PageParam {
     @Schema(description = "商机编号", example = "10864")
     private Long businessId;
 
+    /**
+     * 场景类型,为 null 时则表示全部
+     */
+    @Schema(description = "场景类型", example = "1")
+    @InEnum(CrmSceneTypeEnum.class)
+    private Integer sceneType;
+
+    @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+    private Boolean pool; // null 则表示为不是公海数据
+
 }

+ 12 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -19,4 +21,14 @@ public class CrmReceivablePlanPageReqVO extends PageParam {
     @Schema(description = "合同名称", example = "3473")
     private Long contractId;
 
+    /**
+     * 场景类型,为 null 时则表示全部
+     */
+    @Schema(description = "场景类型", example = "1")
+    @InEnum(CrmSceneTypeEnum.class)
+    private Integer sceneType;
+
+    @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+    private Boolean pool; // null 则表示为不是公海数据
+
 }

+ 12 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -21,4 +23,14 @@ public class CrmReceivablePageReqVO extends PageParam {
     @Schema(description = "客户编号", example = "4963")
     private Long customerId;
 
+    /**
+     * 场景类型,为 null 时则表示全部
+     */
+    @Schema(description = "场景类型", example = "1")
+    @InEnum(CrmSceneTypeEnum.class)
+    private Integer sceneType;
+
+    @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+    private Boolean pool; // null 则表示为不是公海数据
+
 }

+ 7 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java

@@ -73,6 +73,13 @@ public class CrmClueDO extends BaseDO {
      */
     private String remark;
 
+    /**
+     * 负责人的用户编号
+     *
+     * 关联 AdminUserDO 的 id 字段
+     */
+    private Long ownerUserId;
+
     // TODO 芋艿:客户级别;
     // TODO 芋艿:线索来源;
     // TODO 芋艿:客户行业;

+ 27 - 21
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java

@@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.crm.dal.mysql.clue;
 
 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.module.crm.controller.admin.clue.vo.CrmClueExportReqVO;
+import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
+import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
+import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -18,27 +21,30 @@ import java.util.List;
 @Mapper
 public interface CrmClueMapper extends BaseMapperX<CrmClueDO> {
 
-    default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<CrmClueDO>()
-                .likeIfPresent(CrmClueDO::getName, reqVO.getName())
-                .likeIfPresent(CrmClueDO::getTelephone, reqVO.getTelephone())
-                .likeIfPresent(CrmClueDO::getMobile, reqVO.getMobile())
-                .orderByDesc(CrmClueDO::getId));
+    default int updateOwnerUserIdById(Long id, Long ownerUserId) {
+        return update(new LambdaUpdateWrapper<CrmClueDO>()
+                .eq(CrmClueDO::getId, id)
+                .set(CrmClueDO::getOwnerUserId, ownerUserId));
     }
 
-    default List<CrmClueDO> selectList(CrmClueExportReqVO reqVO) {
-        return selectList(new LambdaQueryWrapperX<CrmClueDO>()
-                .eqIfPresent(CrmClueDO::getTransformStatus, reqVO.getTransformStatus())
-                .eqIfPresent(CrmClueDO::getFollowUpStatus, reqVO.getFollowUpStatus())
-                .likeIfPresent(CrmClueDO::getName, reqVO.getName())
-                .eqIfPresent(CrmClueDO::getCustomerId, reqVO.getCustomerId())
-                .betweenIfPresent(CrmClueDO::getContactNextTime, reqVO.getContactNextTime())
-                .likeIfPresent(CrmClueDO::getTelephone, reqVO.getTelephone())
-                .likeIfPresent(CrmClueDO::getMobile, reqVO.getMobile())
-                .likeIfPresent(CrmClueDO::getAddress, reqVO.getAddress())
-                .betweenIfPresent(CrmClueDO::getContactLastTime, reqVO.getContactLastTime())
-                .betweenIfPresent(CrmClueDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(CrmClueDO::getId));
+    default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO pageReqVO, Long userId) {
+        MPJLambdaWrapperX<CrmClueDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
+        // 构建数据权限连表条件
+        CrmQueryWrapperUtils.builderPageQuery(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_LEADS.getType(), CrmClueDO::getId,
+                userId, pageReqVO.getSceneType(), pageReqVO.getPool());
+        mpjLambdaWrapperX.selectAll(CrmClueDO.class)
+                .likeIfPresent(CrmClueDO::getName, pageReqVO.getName())
+                .likeIfPresent(CrmClueDO::getTelephone, pageReqVO.getTelephone())
+                .likeIfPresent(CrmClueDO::getMobile, pageReqVO.getMobile())
+                .orderByDesc(CrmClueDO::getId);
+        return selectJoinPage(pageReqVO, CrmClueDO.class, mpjLambdaWrapperX);
+    }
+
+    default List<CrmClueDO> selectBatchIds(Collection<Long> ids, Long userId) {
+        MPJLambdaWrapperX<CrmClueDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
+        // 构建数据权限连表条件
+        CrmQueryWrapperUtils.builderListQueryBatch(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_LEADS.getType(), ids, userId);
+        return selectJoinList(CrmClueDO.class, mpjLambdaWrapperX);
     }
 
 }

+ 1 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/core/annotations/CrmPermission.java

@@ -35,9 +35,8 @@ public @interface CrmPermission {
 
     /**
      * 数据编号,通过 Spring EL 表达式获取
-     * TODO 数据权限完成后去除 default ""
      */
-    String bizId() default "";
+    String bizId();
 
     /**
      * 操作所需权限级别

+ 3 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java

@@ -71,12 +71,14 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, level = CrmPermissionLevelEnum.OWNER)
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
     public void deleteBusiness(Long id) {
         // 校验存在
         validateBusinessExists(id);
         // 删除
         businessMapper.deleteById(id);
+        // 删除数据权限
+        crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id);
     }
 
     private CrmBusinessDO validateBusinessExists(Long id) {

+ 11 - 14
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java

@@ -1,10 +1,14 @@
 package cn.iocoder.yudao.module.crm.service.clue;
 
-import java.util.*;
-import jakarta.validation.*;
-import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*;
-import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueCreateReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueUpdateReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
 
 /**
  * 线索 Service 接口
@@ -49,22 +53,15 @@ public interface CrmClueService {
      * @param ids 编号
      * @return 线索列表
      */
-    List<CrmClueDO> getClueList(Collection<Long> ids);
+    List<CrmClueDO> getClueList(Collection<Long> ids, Long userId);
 
     /**
      * 获得线索分页
      *
      * @param pageReqVO 分页查询
+     * @param userId    用户编号
      * @return 线索分页
      */
-    PageResult<CrmClueDO> getCluePage(CrmCluePageReqVO pageReqVO);
-
-    /**
-     * 获得线索列表, 用于 Excel 导出
-     *
-     * @param exportReqVO 查询条件
-     * @return 线索列表
-     */
-    List<CrmClueDO> getClueList(CrmClueExportReqVO exportReqVO);
+    PageResult<CrmClueDO> getCluePage(CrmCluePageReqVO pageReqVO, Long userId);
 
 }

+ 16 - 12
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java

@@ -4,23 +4,25 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueExportReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueUpdateReqVO;
 import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
+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.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
+import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
+import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
 import java.util.Collection;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_NOT_EXISTS;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
 
 /**
  * 线索 Service 实现类
@@ -35,6 +37,8 @@ public class CrmClueServiceImpl implements CrmClueService {
     private CrmClueMapper clueMapper;
     @Resource
     private CrmCustomerService customerService;
+    @Resource
+    private CrmPermissionService crmPermissionService;
 
     @Override
     public Long createClue(CrmClueCreateReqVO createReqVO) {
@@ -48,6 +52,7 @@ public class CrmClueServiceImpl implements CrmClueService {
     }
 
     @Override
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
     public void updateClue(CrmClueUpdateReqVO updateReqVO) {
         // 校验存在
         validateClueExists(updateReqVO.getId());
@@ -60,11 +65,14 @@ public class CrmClueServiceImpl implements CrmClueService {
     }
 
     @Override
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
     public void deleteClue(Long id) {
         // 校验存在
         validateClueExists(id);
         // 删除
         clueMapper.deleteById(id);
+        // 删除数据权限
+        crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_LEADS.getType(), id);
     }
 
     private void validateClueExists(Long id) {
@@ -74,26 +82,22 @@ public class CrmClueServiceImpl implements CrmClueService {
     }
 
     @Override
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.READ)
     public CrmClueDO getClue(Long id) {
         return clueMapper.selectById(id);
     }
 
     @Override
-    public List<CrmClueDO> getClueList(Collection<Long> ids) {
+    public List<CrmClueDO> getClueList(Collection<Long> ids, Long userId) {
         if (CollUtil.isEmpty(ids)) {
             return ListUtil.empty();
         }
-        return clueMapper.selectBatchIds(ids);
-    }
-
-    @Override
-    public PageResult<CrmClueDO> getCluePage(CrmCluePageReqVO pageReqVO) {
-        return clueMapper.selectPage(pageReqVO);
+        return clueMapper.selectBatchIds(ids, userId);
     }
 
     @Override
-    public List<CrmClueDO> getClueList(CrmClueExportReqVO exportReqVO) {
-        return clueMapper.selectList(exportReqVO);
+    public PageResult<CrmClueDO> getCluePage(CrmCluePageReqVO pageReqVO, Long userId) {
+        return clueMapper.selectPage(pageReqVO, userId);
     }
 
 }

+ 8 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java

@@ -12,14 +12,18 @@ import cn.iocoder.yudao.module.crm.convert.contactbusinessslink.CrmContactBusine
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessLinkMapper;
+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.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
+import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_BUSINESS_LINK_NOT_EXISTS;
 
 // TODO @puhui999:数据权限的校验;每个操作;
@@ -54,6 +58,7 @@ public class CrmContactBusinessLinkServiceImpl implements CrmContactBusinessLink
     }
 
     @Override
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
     public void updateContactBusinessLink(CrmContactBusinessLinkSaveReqVO updateReqVO) {
         // 校验存在
         validateContactBusinessLinkExists(updateReqVO.getId());
@@ -80,6 +85,7 @@ public class CrmContactBusinessLinkServiceImpl implements CrmContactBusinessLink
     }
 
     @Override
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.READ)
     public CrmContactBusinessLinkDO getContactBusinessLink(Long id) {
         return contactBusinessLinkMapper.selectById(id);
     }
@@ -90,7 +96,7 @@ public class CrmContactBusinessLinkServiceImpl implements CrmContactBusinessLink
         crmContactBusinessLinkPageReqVO.setContactId(pageReqVO.getContactId());
         PageResult<CrmContactBusinessLinkDO> businessLinkDOS = contactBusinessLinkMapper.selectPageByContact(crmContactBusinessLinkPageReqVO);
         List<CrmBusinessDO> businessDOS = crmBusinessService.getBusinessList(CollectionUtils.convertList(businessLinkDOS.getList(),
-                CrmContactBusinessLinkDO::getBusinessId));
+                CrmContactBusinessLinkDO::getBusinessId), getLoginUserId());
         PageResult<CrmBusinessRespVO> pageResult = new PageResult<CrmBusinessRespVO>();
         pageResult.setList(CrmBusinessConvert.INSTANCE.convert(businessDOS));
         pageResult.setTotal(businessLinkDOS.getTotal());

+ 2 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java

@@ -78,6 +78,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
 
         // 删除
         customerMapper.deleteById(id);
+        // 删除数据权限
+        crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
     }
 
     private void validateCustomerExists(Long id) {

+ 10 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java

@@ -7,8 +7,8 @@ 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;
 
@@ -58,9 +58,17 @@ public interface CrmPermissionService {
     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 ids    权限编号
      * @param userId 用户编号
      */
     void deletePermissionBatch(Collection<Long> ids, Long userId);

+ 12 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java

@@ -11,11 +11,11 @@ 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 cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -138,6 +138,17 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
     }
 
     @Override
+    public void deletePermission(Integer bizType, Long bizId) {
+        List<CrmPermissionDO> permissionList = crmPermissionMapper.selectByBizTypeAndBizId(bizType, bizId);
+        if (CollUtil.isEmpty(permissionList)) {
+            return;
+        }
+
+        // 删除数据权限
+        crmPermissionMapper.deleteBatchIds(convertSet(permissionList, CrmPermissionDO::getId));
+    }
+
+    @Override
     public void deletePermissionBatch(Collection<Long> ids, Long userId) {
         List<CrmPermissionDO> permissions = crmPermissionMapper.selectBatchIds(ids);
         if (CollUtil.isEmpty(permissions)) {

+ 96 - 104
yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java

@@ -3,19 +3,18 @@ package cn.iocoder.yudao.module.crm.service.clue;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueExportReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueUpdateReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
+import jakarta.annotation.Resource;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
-import jakarta.annotation.Resource;
 import java.util.List;
 
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
@@ -25,6 +24,7 @@ import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_NOT_EXIS
 import static org.junit.jupiter.api.Assertions.*;
 
 // TODO 芋艿:单测后续补;
+
 /**
  * {@link CrmClueServiceImpl} 的单元测试类
  *
@@ -90,8 +90,8 @@ public class CrmClueServiceImplTest extends BaseDbUnitTest {
 
         // 调用
         clueService.deleteClue(id);
-       // 校验数据不存在了
-       assertNull(clueMapper.selectById(id));
+        // 校验数据不存在了
+        assertNull(clueMapper.selectById(id));
     }
 
     @Test
@@ -106,110 +106,102 @@ public class CrmClueServiceImplTest extends BaseDbUnitTest {
     @Test
     @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
     public void testGetCluePage() {
-       // mock 数据
-       CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到
-           o.setTransformStatus(null);
-           o.setFollowUpStatus(null);
-           o.setName(null);
-           o.setCustomerId(null);
-           o.setContactNextTime(null);
-           o.setTelephone(null);
-           o.setMobile(null);
-           o.setAddress(null);
-           o.setContactLastTime(null);
-           o.setCreateTime(null);
-       });
-       clueMapper.insert(dbClue);
-       // 测试 transformStatus 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null)));
-       // 测试 followUpStatus 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null)));
-       // 测试 name 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null)));
-       // 测试 customerId 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null)));
-       // 测试 contactNextTime 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null)));
-       // 测试 telephone 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null)));
-       // 测试 mobile 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null)));
-       // 测试 address 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null)));
-       // 测试 contactLastTime 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null)));
-       // 测试 createTime 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null)));
-       // 准备参数
-       CrmCluePageReqVO reqVO = new CrmCluePageReqVO();
-       reqVO.setName(null);
-       reqVO.setTelephone(null);
-       reqVO.setMobile(null);
-
-       // 调用
-       PageResult<CrmClueDO> pageResult = clueService.getCluePage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbClue, pageResult.getList().get(0));
+        // mock 数据
+        CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到
+            o.setTransformStatus(null);
+            o.setFollowUpStatus(null);
+            o.setName(null);
+            o.setCustomerId(null);
+            o.setContactNextTime(null);
+            o.setTelephone(null);
+            o.setMobile(null);
+            o.setAddress(null);
+            o.setContactLastTime(null);
+            o.setCreateTime(null);
+        });
+        clueMapper.insert(dbClue);
+        // 测试 transformStatus 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null)));
+        // 测试 followUpStatus 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null)));
+        // 测试 name 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null)));
+        // 测试 customerId 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null)));
+        // 测试 contactNextTime 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null)));
+        // 测试 telephone 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null)));
+        // 测试 mobile 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null)));
+        // 测试 address 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null)));
+        // 测试 contactLastTime 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null)));
+        // 测试 createTime 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null)));
+        // 准备参数
+        CrmCluePageReqVO reqVO = new CrmCluePageReqVO();
+        reqVO.setName(null);
+        reqVO.setTelephone(null);
+        reqVO.setMobile(null);
+
+        // 调用
+        PageResult<CrmClueDO> pageResult = clueService.getCluePage(reqVO, getLoginUserId());
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbClue, pageResult.getList().get(0));
     }
 
     @Test
     @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
     public void testGetClueList() {
-       // mock 数据
-       CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到
-           o.setTransformStatus(null);
-           o.setFollowUpStatus(null);
-           o.setName(null);
-           o.setCustomerId(null);
-           o.setContactNextTime(null);
-           o.setTelephone(null);
-           o.setMobile(null);
-           o.setAddress(null);
-           o.setContactLastTime(null);
-           o.setCreateTime(null);
-       });
-       clueMapper.insert(dbClue);
-       // 测试 transformStatus 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null)));
-       // 测试 followUpStatus 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null)));
-       // 测试 name 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null)));
-       // 测试 customerId 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null)));
-       // 测试 contactNextTime 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null)));
-       // 测试 telephone 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null)));
-       // 测试 mobile 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null)));
-       // 测试 address 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null)));
-       // 测试 contactLastTime 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null)));
-       // 测试 createTime 不匹配
-       clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null)));
-       // 准备参数
-       CrmClueExportReqVO reqVO = new CrmClueExportReqVO();
-       reqVO.setTransformStatus(null);
-       reqVO.setFollowUpStatus(null);
-       reqVO.setName(null);
-       reqVO.setCustomerId(null);
-       reqVO.setContactNextTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-       reqVO.setTelephone(null);
-       reqVO.setMobile(null);
-       reqVO.setAddress(null);
-       reqVO.setOwnerUserId(null);
-       reqVO.setContactLastTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-
-       // 调用
-       List<CrmClueDO> list = clueService.getClueList(reqVO);
-       // 断言
-       assertEquals(1, list.size());
-       assertPojoEquals(dbClue, list.get(0));
+        // mock 数据
+        CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到
+            o.setTransformStatus(null);
+            o.setFollowUpStatus(null);
+            o.setName(null);
+            o.setCustomerId(null);
+            o.setContactNextTime(null);
+            o.setTelephone(null);
+            o.setMobile(null);
+            o.setAddress(null);
+            o.setContactLastTime(null);
+            o.setCreateTime(null);
+        });
+        clueMapper.insert(dbClue);
+        // 测试 transformStatus 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null)));
+        // 测试 followUpStatus 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null)));
+        // 测试 name 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null)));
+        // 测试 customerId 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null)));
+        // 测试 contactNextTime 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null)));
+        // 测试 telephone 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null)));
+        // 测试 mobile 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null)));
+        // 测试 address 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null)));
+        // 测试 contactLastTime 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null)));
+        // 测试 createTime 不匹配
+        clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null)));
+        // 准备参数
+        CrmCluePageReqVO reqVO = new CrmCluePageReqVO();
+        reqVO.setName(null);
+        reqVO.setTelephone(null);
+        reqVO.setMobile(null);
+        reqVO.setPageSize(PAGE_SIZE_NONE);
+        // 调用
+        List<CrmClueDO> list = clueService.getCluePage(reqVO, 1L).getList();
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbClue, list.get(0));
     }
 
 }