From 5c60202c1471f9238cdd1cd4c0ef65d05ca7b604 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Fri, 8 Dec 2023 22:17:13 +0800
Subject: [PATCH 01/15] =?UTF-8?q?=F0=9F=90=9B=20=E7=A7=BB=E9=99=A4?=
 =?UTF-8?q?=E5=A4=9A=E4=BD=99=E7=9A=84=20yudao-spring-boot-starter-securit?=
 =?UTF-8?q?y=20=E4=BE=9D=E8=B5=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../yudao-spring-boot-starter-websocket/pom.xml       | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/yudao-framework/yudao-spring-boot-starter-websocket/pom.xml b/yudao-framework/yudao-spring-boot-starter-websocket/pom.xml
index b18ee4783..b534f10d3 100644
--- a/yudao-framework/yudao-spring-boot-starter-websocket/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-websocket/pom.xml
@@ -38,17 +38,6 @@
             <artifactId>spring-boot-starter-websocket</artifactId>
         </dependency>
 
-        <!-- Web 相关 -->
-        <dependency>
-            <!-- 为什么是 websocket 依赖 security 呢?而不是 security 拓展 websocket 呢?
-                 因为 websocket 和 LoginUser 当前登录的用户有一定的相关性,具体可见 WebSocketSessionManagerImpl 逻辑。
-                 如果让 security 拓展 websocket 的话,会导致 websocket 组件的封装很散,进而增大理解成本。
-            -->
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-spring-boot-starter-security</artifactId>
-            <scope>provided</scope>
-        </dependency>
-
         <!-- 消息队列相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

From a8c14614abb277d68668c119e06a36b9ae4378e7 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Sat, 9 Dec 2023 01:05:25 +0800
Subject: [PATCH 02/15] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=E3=80=8A?=
 =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=8A=9F=E8=83=BD=E5=88=B0=E7=B2=BE=E7=AE=80?=
 =?UTF-8?q?=E7=89=88=E3=80=8B=E7=9A=84=E8=AF=B4=E6=98=8E=E5=9C=B0=E5=9D=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index 23fa09d6f..f59f99b2c 100644
--- a/README.md
+++ b/README.md
@@ -88,6 +88,8 @@
 * JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/yudaocode/yudao-boot-mini> 的 `master` 分支
 * JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/yudaocode/yudao-boot-mini> 的 `master-jdk8` 分支
 
+如果你想把【完整版】的功能,迁移到【精简版】,可以参考 [《迁移功能到精简版》](https://doc.iocoder.cn/migrate-module/) 文档。
+
 ## 😎 开源协议
 
 **为什么推荐使用本项目?**

From 9bac10c6f77455e532995bb8f44ddbbc97321780 Mon Sep 17 00:00:00 2001
From: Joey <zhouhangsir@163.com>
Date: Sat, 9 Dec 2023 17:25:47 +0800
Subject: [PATCH 03/15] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=AE=A2=E6=88=B7?=
 =?UTF-8?q?=E9=94=81=E5=AE=9A/=E8=A7=A3=E9=94=81=E6=8E=A5=E5=8F=A3?=
 =?UTF-8?q?=EF=BC=8C=E6=A0=A1=E9=AA=8C=E9=94=81=E5=AE=9A=E4=B8=8A=E9=99=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../module/crm/enums/ErrorCodeConstants.java  |  6 ++
 .../admin/customer/CrmCustomerController.java |  4 +-
 .../customer/vo/CrmCustomerLockReqVO.java     | 18 ++++++
 .../CrmCustomerLimitConfigCreateReqVO.java    |  6 ++
 .../convert/customer/CrmCustomerConvert.java  |  2 +
 .../CrmCustomerLimitConfigMapper.java         | 16 +++++
 .../CrmCustomerLimitConfigService.java        |  5 ++
 .../CrmCustomerLimitConfigServiceImpl.java    |  5 ++
 .../service/customer/CrmCustomerService.java  |  9 +--
 .../customer/CrmCustomerServiceImpl.java      | 58 +++++++++++++++----
 10 files changed, 111 insertions(+), 18 deletions(-)
 create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java

diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
index a7494dc8d..d99e5b23a 100644
--- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
+++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -40,6 +40,12 @@ public interface ErrorCodeConstants {
     ErrorCode CUSTOMER_LOCKED_PUT_POOL_FAIL = new ErrorCode(1_020_006_005, "客户【{}】放入公海失败,原因:客户已锁定");
     ErrorCode CUSTOMER_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_006_006, "更新客户【{}】负责人失败, 原因:系统异常");
 
+    ErrorCode CUSTOMER_UNLOCK_STATUS_NO_REPETITION = new ErrorCode(1_020_006_001, "无需重复操作锁定/解锁状态");
+
+    ErrorCode CUSTOMER_NO_DEPARTMENT_FOUND = new ErrorCode(1_020_006_002, "操作失败,请先绑定部门再进行操作");
+
+    ErrorCode CUSTOMER_EXCEED_LOCK_LIMIT = new ErrorCode(1_020_006_003, "操作失败,超出锁定规则上限");
+
     // ========== 权限管理 1_020_007_000 ==========
     ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
     ErrorCode CRM_PERMISSION_DENIED = new ErrorCode(1_020_007_001, "{}操作失败,原因:没有权限");
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
index f06e26e4a..5ec7012a1 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -134,8 +134,8 @@ public class CrmCustomerController {
     @PutMapping("/lock")
     @Operation(summary = "锁定/解锁客户")
     @PreAuthorize("@ss.hasPermission('crm:customer:update')")
-    public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerUpdateReqVO updateReqVO) {
-        customerService.lockCustomer(updateReqVO);
+    public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerLockReqVO lockReqVO) {
+        customerService.lockCustomer(lockReqVO);
         return success(true);
     }
 
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java
new file mode 100644
index 000000000..50608049f
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 客户锁定/解锁 Request VO")
+@Data
+public class CrmCustomerLockReqVO {
+
+    @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+    private Long id;
+
+    @Schema(description = "客户锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    private Boolean lockStatus;
+
+
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java
index 7aa372901..2cc707c43 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java
@@ -5,10 +5,16 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
+import java.util.List;
+
 @Schema(description = "管理后台 - 客户限制配置创建 Request VO")
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 public class CrmCustomerLimitConfigCreateReqVO extends CrmCustomerLimitConfigBaseVO {
 
+
+    @Schema(description = "规则适用人群")
+    private Long userId;
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java
index 55e2a3518..b300c6416 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java
@@ -79,4 +79,6 @@ public interface CrmCustomerConvert {
 
     List<CrmCustomerQueryAllRespVO> convertQueryAll(List<CrmCustomerDO> crmCustomerDO);
 
+    CrmCustomerDO convert(CrmCustomerLockReqVO lockReqVO);
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java
index f5fb31b61..ea8f7d8bf 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java
@@ -3,10 +3,14 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer;
 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.customer.vo.limitconfig.CrmCustomerLimitConfigCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
 /**
  * 客户限制配置 Mapper
  *
@@ -21,4 +25,16 @@ public interface CrmCustomerLimitConfigMapper extends BaseMapperX<CrmCustomerLim
                 .orderByDesc(CrmCustomerLimitConfigDO::getId));
     }
 
+    default CrmCustomerLimitConfigDO selectByLimitConfig(CrmCustomerLimitConfigCreateReqVO reqVO){
+        LambdaQueryWrapperX<CrmCustomerLimitConfigDO> queryWrapper = new LambdaQueryWrapperX<>();
+        queryWrapper.apply("FIND_IN_SET({0}, user_ids) > 0", reqVO.getUserId());
+        queryWrapper.eq(CrmCustomerLimitConfigDO::getType, reqVO.getType());
+        // 将部门ID列表转换成逗号分隔的字符串
+        String deptIdsString = reqVO.getDeptIds().stream()
+                .map(String::valueOf)
+                .collect(Collectors.joining(","));
+        queryWrapper.apply("FIND_IN_SET({0}, dept_ids) > 0", deptIdsString);
+        return selectOne(queryWrapper);
+    }
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java
index 96090cdfc..a181032f5 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java
@@ -53,4 +53,9 @@ public interface CrmCustomerLimitConfigService {
      */
     PageResult<CrmCustomerLimitConfigDO> getCustomerLimitConfigPage(CrmCustomerLimitConfigPageReqVO pageReqVO);
 
+    /**
+     * 查询当前登录人客户限制配置
+     */
+    CrmCustomerLimitConfigDO selectByLimitConfig(CrmCustomerLimitConfigCreateReqVO configReqVO);
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java
index 4528eafad..24cd84d36 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java
@@ -90,4 +90,9 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig
         adminUserApi.validateUserList(userIds);
     }
 
+    @Override
+    public CrmCustomerLimitConfigDO selectByLimitConfig(CrmCustomerLimitConfigCreateReqVO configReqVO) {
+        return customerLimitConfigMapper.selectByLimitConfig(configReqVO);
+    }
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
index 73be18951..2ecfd9ca5 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
@@ -1,10 +1,7 @@
 package cn.iocoder.yudao.module.crm.service.customer;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerUpdateReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import jakarta.validation.Valid;
 
@@ -85,9 +82,9 @@ public interface CrmCustomerService {
     /**
      * 锁定/解锁客户
      *
-     * @param updateReqVO 更新信息
+     * @param lockReqVO 更新信息
      */
-    void lockCustomer(@Valid CrmCustomerUpdateReqVO updateReqVO);
+    void lockCustomer(@Valid CrmCustomerLockReqVO lockReqVO);
 
     // ==================== 公海相关操作 ====================
 
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
index 93589a5b0..b1a0e1dc8 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -2,12 +2,11 @@ package cn.iocoder.yudao.module.crm.service.customer;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerUpdateReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
+import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigCreateReqVO;
 import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
@@ -15,6 +14,7 @@ import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -23,7 +23,10 @@ import org.springframework.validation.annotation.Validated;
 import java.util.*;
 
 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.*;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_EXCEED_LOCK_LIMIT;
+import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT;
 import static java.util.Collections.singletonList;
 
 /**
@@ -43,6 +46,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
 
     @Resource
     private AdminUserApi adminUserApi;
+    @Resource
+    private CrmCustomerLimitConfigService crmCustomerLimitConfigService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -142,14 +147,47 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
     }
 
     @Override
-    public void lockCustomer(CrmCustomerUpdateReqVO updateReqVO) {
-        // 校验存在
-        validateCustomerExists(updateReqVO.getId());
-        // TODO @Joey:可以校验下,如果已经对应的锁定状态,报个业务异常;原因是:后续这个业务会记录操作日志,会记录多了;
-        // TODO @芋艿:业务完善,增加锁定上限;
+    public void lockCustomer(CrmCustomerLockReqVO lockReqVO) {
+        // 校验当前客户是否存在
+        validateCustomerExists(lockReqVO.getId());
+
+        CrmCustomerDO customerDO = customerMapper.selectById(lockReqVO.getId());
+
+        // 校验当前是否重复操作锁定/解锁状态
+        if (customerDO.getLockStatus().equals(lockReqVO.getLockStatus())) {
+            throw exception(CUSTOMER_UNLOCK_STATUS_NO_REPETITION);
+        }
+
+        // 获取当前登录信息,开始校验锁定上限
+        AdminUserRespDTO userRespDTO = adminUserApi.getUser(getLoginUserId());
+
+        if (userRespDTO.getDeptId() == null || userRespDTO.getId() == null) {
+            // 如有入参为空,提示业务异常
+            throw exception(CUSTOMER_NO_DEPARTMENT_FOUND);
+        }
+
+        // 开始校验规则限制
+        List<Long> userDeptIds = Collections.singletonList(userRespDTO.getDeptId());
+
+        CrmCustomerLimitConfigCreateReqVO configReqVO = new CrmCustomerLimitConfigCreateReqVO();
+        configReqVO.setUserId(userRespDTO.getId());
+        configReqVO.setDeptIds(userDeptIds);
+        configReqVO.setType(CUSTOMER_LOCK_LIMIT.getCode());
+
+        CrmCustomerLimitConfigDO crmCustomerLimitConfigDO = crmCustomerLimitConfigService.selectByLimitConfig(configReqVO);
+
+        // 统计当前用户已锁定客户数量
+        List<CrmCustomerDO> crmCustomerDOS = customerMapper.selectList("owner_user_id", getLoginUserId());
+        long customerLockCount = crmCustomerDOS.stream().filter(CrmCustomerDO::getLockStatus).count();
+
+        // 锁定操作的时候校验当前用户可锁定客户的上限
+        if (crmCustomerLimitConfigDO != null && lockReqVO.getLockStatus() && customerLockCount >= crmCustomerLimitConfigDO.getMaxCount()) {
+            // 超出锁定数量上限,提示业务异常
+            throw exception(CUSTOMER_EXCEED_LOCK_LIMIT);
+        }
 
         // 更新
-        CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(updateReqVO);
+        CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(lockReqVO);
         customerMapper.updateById(updateObj);
     }
 

From 8434a8cd6864fa31b7e6cfa292adb564ce12c2bf Mon Sep 17 00:00:00 2001
From: zyna <chenjidemenglin20@126.com>
Date: Sun, 17 Dec 2023 17:33:52 +0800
Subject: [PATCH 04/15] =?UTF-8?q?crm=E8=81=94=E7=B3=BB=E4=BA=BA=E5=95=86?=
 =?UTF-8?q?=E6=9C=BA=E5=8A=9F=E8=83=BD=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 sql/mysql/optinal/crm.sql                     |   5 +-
 .../module/crm/enums/ErrorCodeConstants.java  |   2 +-
 .../admin/business/CrmBusinessController.java |  34 +++++-
 .../vo/business/CrmBusinessRespVO.java        |   2 +
 .../admin/contact/CrmContactController.java   |  23 +++-
 .../vo/CrmContactBusinessLinkPageReqVO.java   |   2 +-
 .../vo/CrmContactBusinessLinkRespVO.java      |   2 +-
 .../vo/CrmContactBusinessLinkSaveReqVO.java   |   2 +-
 .../CrmContactBusinessLinkController.java     | 115 ------------------
 .../crm/convert/contact/ContactConvert.java   |  12 +-
 .../CrmContactBusinessLinkConvert.java        |  16 ---
 .../CrmContactBusinessLinkDO.java             |   7 +-
 .../CrmContactBusinessLinkMapper.java         |   6 +-
 .../service/business/CrmBusinessService.java  |  12 +-
 .../business/CrmBusinessServiceImpl.java      |  33 +++++
 .../CrmContactBusinessLinkService.java        |  12 +-
 .../CrmContactBusinessLinkServiceImpl.java    |  47 ++++---
 .../service/contact/CrmContactService.java    |  10 ++
 .../contact/CrmContactServiceImpl.java        |  22 +++-
 19 files changed, 181 insertions(+), 183 deletions(-)
 rename yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/{contactbusinesslink => contact}/vo/CrmContactBusinessLinkPageReqVO.java (92%)
 rename yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/{contactbusinesslink => contact}/vo/CrmContactBusinessLinkRespVO.java (91%)
 rename yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/{contactbusinesslink => contact}/vo/CrmContactBusinessLinkSaveReqVO.java (90%)
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contactbusinessslink/CrmContactBusinessLinkConvert.java
 rename yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/{contactbusinesslink => contact}/CrmContactBusinessLinkDO.java (74%)
 rename yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/{contactbusinesslink => contact}/CrmContactBusinessLinkService.java (76%)
 rename yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/{contactbusinesslink => contact}/CrmContactBusinessLinkServiceImpl.java (68%)

diff --git a/sql/mysql/optinal/crm.sql b/sql/mysql/optinal/crm.sql
index 1cb1371ae..77ec46d97 100644
--- a/sql/mysql/optinal/crm.sql
+++ b/sql/mysql/optinal/crm.sql
@@ -1,6 +1,6 @@
 -- `ruoyi-vue-pro`.crm_contact_business_link definition
 
-CREATE TABLE `crm_contact_business_link` (
+CREATE TABLE `crm_contact_business` (
                                              `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
                                              `contact_id` int(11) DEFAULT NULL COMMENT '联系人id',
                                              `business_id` int(11) DEFAULT NULL COMMENT '商机id',
@@ -10,6 +10,5 @@ CREATE TABLE `crm_contact_business_link` (
                                              `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
                                              `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
                                              `tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '租户编号',
-                                             PRIMARY KEY (`id`),
-                                             UNIQUE KEY `crm_contact_business_link_un` (`contact_id`,`business_id`,`deleted`,`tenant_id`)
+                                             PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='联系人商机关联表';
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
index 222253f9f..567846e46 100644
--- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
+++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -24,7 +24,7 @@ public interface ErrorCodeConstants {
     // ========== 联系人管理 1-020-003-000 ==========
     ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
     ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode( 1_020_003_001, "联系人商机关联不存在");
-
+    ErrorCode CONTACT_BUSINESS_LINK_CREATE_EMPTY = new ErrorCode( 1_020_003_002, "联系人商机关联参数为空");
     // ========== 回款 1-020-004-000 ==========
     ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在");
 
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
index 7b924c88a..7f6d9481a 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
@@ -4,11 +4,13 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
@@ -31,6 +33,7 @@ import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -170,5 +173,34 @@ public class CrmBusinessController {
         businessService.transferBusiness(reqVO, getLoginUserId());
         return success(true);
     }
-
+    @GetMapping("/page-by-contact")
+    @Operation(summary = "获得联系人的商机分页")
+    @PreAuthorize("@ss.hasPermission('crm:business:query')")
+    public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmContactBusinessLinkPageReqVO pageVO) {
+        PageResult<CrmBusinessRespVO> pageResult = businessService.getBusinessPageByContact(pageVO);
+        // 处理商机状态类型名称回显
+        Set<Long> statusTypeIds = pageResult.getList().stream()
+                .map(CrmBusinessRespVO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet());
+        CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO();
+        queryStatusTypeVO.setIdList(statusTypeIds);
+        List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO);
+        Map<Long,String> statusTypeMap = CollectionUtils.convertMap(statusTypeList,CrmBusinessStatusTypeDO::getId,CrmBusinessStatusTypeDO::getName);
+        // 处理商机状态名称回显
+        Set<Long> statusIds = pageResult.getList().stream()
+                .map(CrmBusinessRespVO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet());
+        CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
+        queryVO.setIdList(statusIds);
+        List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);
+        Map<Long,String> statusMap = CollectionUtils.convertMap(statusList,CrmBusinessStatusDO::getId,CrmBusinessStatusDO::getName);
+        // 处理客户名称回显
+        Set<Long> customerIds = CollectionUtils.convertSet(pageResult.getList(),CrmBusinessRespVO::getCustomerId);
+        List<CrmCustomerDO> customerList = customerService.getCustomerList(customerIds);
+        Map<Long,String> customerMap = CollectionUtils.convertMap(customerList,CrmCustomerDO::getId,CrmCustomerDO::getName);
+        pageResult.getList().forEach(item -> {
+            item.setStatusTypeName(statusTypeMap.get(item.getStatusTypeId()));
+            item.setStatusName(statusMap.get(item.getStatusId()));
+            item.setCustomerName(customerMap.get(item.getCustomerId()));
+        });
+       return success(pageResult);
+    }
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
index 53c8f45da..c0d61fa93 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
@@ -25,4 +25,6 @@ public class CrmBusinessRespVO extends CrmBusinessBaseVO {
     @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中")
     private String statusName;
 
+    @Schema(description = "联系人商机关联ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
+    private Long businessContactId;
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
index f39f67ac4..b984f14a0 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
@@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.crm.convert.contact.ContactConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
+import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessLinkService;
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -30,6 +31,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
@@ -54,10 +56,12 @@ public class CrmContactController {
     private CrmContactService contactService;
     @Resource
     private CrmCustomerService customerService;
-
     @Resource
     private AdminUserApi adminUserApi;
 
+    @Resource
+    private CrmContactBusinessLinkService contactBusinessLinkService;
+
     @PostMapping("/create")
     @Operation(summary = "创建联系人")
     @PreAuthorize("@ss.hasPermission('crm:contact:create')")
@@ -137,6 +141,23 @@ public class CrmContactController {
                 convertDetailContactPage(pageResult).getList());
     }
 
+    @DeleteMapping("/delete-batch-business")
+    @Operation(summary = "批量删除联系人商机关联")
+    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:delete')")
+    public CommonResult<Boolean> deleteContactBusinessLinkBatch(@Valid @RequestBody List<Long> businessContactIds) {
+        contactBusinessLinkService.deleteContactBusinessLink(businessContactIds);
+        return success(true);
+    }
+
+    @PostMapping("/create-batch-business")
+    @Operation(summary = "创建联系人商机关联")
+    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')")
+    public CommonResult<Boolean> createContactBusinessLinkBatch(
+            @Valid @NotEmpty @RequestBody List<CrmContactBusinessLinkSaveReqVO> createReqVO) {
+        contactBusinessLinkService.createContactBusinessLinkBatch(createReqVO);
+        return success(true);
+    }
+
     /**
      * 转换成详细的联系人分页,即读取关联信息
      *
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkPageReqVO.java
similarity index 92%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkPageReqVO.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkPageReqVO.java
index c8e98c15a..0755b6715 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkPageReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkPageReqVO.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import io.swagger.v3.oas.annotations.media.Schema;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkRespVO.java
similarity index 91%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkRespVO.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkRespVO.java
index 17aadf667..7dd061b48 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkRespVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkRespVO.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
 
 import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkSaveReqVO.java
similarity index 90%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkSaveReqVO.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkSaveReqVO.java
index 1db4d2717..3613124c3 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/vo/CrmContactBusinessLinkSaveReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkSaveReqVO.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java
deleted file mode 100644
index 52234369c..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
-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.service.business.CrmBusinessService;
-import cn.iocoder.yudao.module.crm.service.contactbusinesslink.CrmContactBusinessLinkService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import javax.validation.Valid;
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
-
-@Tag(name = "管理后台 - CRM 联系人商机关联")
-@RestController
-@RequestMapping("/crm/contact-business-link")
-@Validated
-public class CrmContactBusinessLinkController {
-
-    @Resource
-    private CrmContactBusinessLinkService contactBusinessLinkService;
-    @Resource
-    private CrmBusinessService crmBusinessService;
-
-    // TODO @zyna:createContactBusinessLink 和 createContactBusinessLinkBatch 是不是合并成一个接口?contactId、List<businessId>
-    @PostMapping("/create")
-    @Operation(summary = "创建联系人商机关联")
-    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')")
-    public CommonResult<Long> createContactBusinessLink(@Valid @RequestBody CrmContactBusinessLinkSaveReqVO createReqVO) {
-        return success(contactBusinessLinkService.createContactBusinessLink(createReqVO));
-    }
-
-    @PostMapping("/create-batch")
-    @Operation(summary = "创建联系人商机关联")
-    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')")
-    @Transactional(rollbackFor = Exception.class)
-    public CommonResult<Boolean> createContactBusinessLinkBatch(
-            @Valid @RequestBody List<CrmContactBusinessLinkSaveReqVO> createReqVO) {
-        createReqVO.stream().forEach(item -> {
-            CrmBusinessDO crmBusinessDO = crmBusinessService.getBusiness(item.getBusinessId());
-            if(crmBusinessDO == null){
-                throw exception(BUSINESS_NOT_EXISTS);
-            }
-        });
-        contactBusinessLinkService.createContactBusinessLinkBatch(createReqVO);
-        return success(true);
-    }
-
-    // TODO @zyna:这个接口是不是可以删除掉了哈?应该不存在更新。
-    @PutMapping("/update")
-    @Operation(summary = "更新联系人商机关联")
-    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:update')")
-    public CommonResult<Boolean> updateContactBusinessLink(@Valid @RequestBody CrmContactBusinessLinkSaveReqVO updateReqVO) {
-        contactBusinessLinkService.updateContactBusinessLink(updateReqVO);
-        return success(true);
-    }
-
-    // TODO @zyna:删除,是不是传递 ids?
-    @DeleteMapping("/delete-batch")
-    @Operation(summary = "批量删除联系人商机关联")
-    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:delete')")
-    public CommonResult<Boolean> deleteContactBusinessLinkBatch(@Valid @RequestBody List<CrmContactBusinessLinkSaveReqVO> deleteList) {
-        contactBusinessLinkService.deleteContactBusinessLink(deleteList);
-        return success(true);
-    }
-
-    // TODO @zyna:这个接口是不是可以删除掉了哈?应该不存在单个读取;
-    @GetMapping("/get")
-    @Operation(summary = "获得联系人商机关联")
-    @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')")
-    public CommonResult<CrmContactBusinessLinkRespVO> getContactBusinessLink(@RequestParam("id") Long id) {
-        CrmContactBusinessLinkDO contactBusinessLink = contactBusinessLinkService.getContactBusinessLink(id);
-        return success(BeanUtils.toBean(contactBusinessLink, CrmContactBusinessLinkRespVO.class));
-    }
-
-    // TODO @zyna:这个可以转化下,使用客户编号去查询,就是使用 CrmBusinessController 的 getBusinessPageByCustomer 接口;目的是:复用
-    @GetMapping("/page-by-contact")
-    @Operation(summary = "获得联系人商机关联")
-    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')")
-    public CommonResult<PageResult<CrmBusinessRespVO>> getContactBusinessLinkByContact(
-            @Valid CrmContactBusinessLinkPageReqVO pageReqVO) {
-        PageResult<CrmBusinessRespVO> contactBusinessLink = contactBusinessLinkService.getContactBusinessLinkPageByContact(pageReqVO);
-        return success(contactBusinessLink);
-    }
-
-    // TODO @zyna:这个优化下,搞到 CrmBusinessController 里去,加一个 CrmBusinessController 的 getBusinessPageByContact 接口;目的是:
-    @GetMapping("/page")
-    @Operation(summary = "获得联系人商机关联分页")
-    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:query')")
-    public CommonResult<PageResult<CrmContactBusinessLinkRespVO>> getContactBusinessLinkPage(
-            @Valid CrmContactBusinessLinkPageReqVO pageReqVO) {
-        PageResult<CrmContactBusinessLinkDO> pageResult = contactBusinessLinkService.getContactBusinessLinkPage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, CrmContactBusinessLinkRespVO.class));
-    }
-
-    // TODO @zyna:最终梳理完后,应该就 2 个接口,要不直接合并到 CrmContactController 中,不作为独立模块,就关联、接触关联。其实和 user 设置它有哪些岗位、部门是类似的。
-
-}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/ContactConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/ContactConvert.java
index 636835be7..48e18b5b8 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/ContactConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/ContactConvert.java
@@ -80,12 +80,12 @@ public interface ContactConvert {
         Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
         result.forEach(item -> {
             setUserInfo(item, userMap);
-            findAndThen(customerMap, item.getCustomerId(), customer -> { // TODO @zyna:这里的 { 可以去掉
-                item.setCustomerName(customer.getName());
-            });
-            findAndThen(parentContactMap, item.getParentId(), contactDO -> {  // TODO @zyna:这里的 { 可以去掉
-                item.setParentName(contactDO.getName());
-            });
+            findAndThen(customerMap, item.getCustomerId(), customer ->
+                item.setCustomerName(customer.getName())
+            );
+            findAndThen(parentContactMap, item.getParentId(), contactDO ->
+                item.setParentName(contactDO.getName())
+            );
         });
         return result;
     }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contactbusinessslink/CrmContactBusinessLinkConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contactbusinessslink/CrmContactBusinessLinkConvert.java
deleted file mode 100644
index dfb71bad5..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contactbusinessslink/CrmContactBusinessLinkConvert.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.module.crm.convert.contactbusinessslink;
-
-import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-
-// TODO @zyna:使用 BeanUtils 慢慢替代现有的 mapstruct 哈
-@Mapper
-public interface CrmContactBusinessLinkConvert {
-    CrmContactBusinessLinkConvert INSTANCE = Mappers.getMapper(CrmContactBusinessLinkConvert.class);
-    CrmContactBusinessLinkDO convert(CrmContactBusinessLinkSaveReqVO bean);
-    List<CrmContactBusinessLinkDO> convert(List<CrmContactBusinessLinkSaveReqVO> bean);
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contactbusinesslink/CrmContactBusinessLinkDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessLinkDO.java
similarity index 74%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contactbusinesslink/CrmContactBusinessLinkDO.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessLinkDO.java
index ad6c898d7..7e7c50c89 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contactbusinesslink/CrmContactBusinessLinkDO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessLinkDO.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink;
+package cn.iocoder.yudao.module.crm.dal.dataobject.contact;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
@@ -8,14 +8,13 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
-// TODO @zyna:可以放到 contact 包下
 /**
  * CRM 联系人商机关联 DO
  *
  * @author 芋道源码
  */
-@TableName("crm_contact_business_link")
-@KeySequence("crm_contact_business_link_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName("crm_contact_business")
+@KeySequence("crm_contact_business_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java
index 1692def86..df67bf6e5 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java
@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink;
 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.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -21,7 +21,7 @@ public interface CrmContactBusinessLinkMapper extends BaseMapperX<CrmContactBusi
                 .eqIfPresent(CrmContactBusinessLinkDO::getBusinessId, reqVO.getBusinessId())
                 .betweenIfPresent(CrmContactBusinessLinkDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(CrmContactBusinessLinkDO::getId));
-    } // TODO @zyna:方法和方法之间要有空行
+    }
     default PageResult<CrmContactBusinessLinkDO> selectPageByContact(CrmContactBusinessLinkPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<CrmContactBusinessLinkDO>()
                 .eqIfPresent(CrmContactBusinessLinkDO::getContactId, reqVO.getContactId())
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
index 2e62db48d..cdde95422 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
@@ -1,10 +1,8 @@
 package cn.iocoder.yudao.module.crm.service.business;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@@ -88,4 +86,10 @@ public interface CrmBusinessService {
      */
     void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId);
 
+    /**
+     * 获取联系人商机列表
+     * @param pageReqVO 分页参数
+     * @return 联系人商机
+     */
+    PageResult<CrmBusinessRespVO> getBusinessPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO);
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
index d96124799..a496f0eb4 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -3,15 +3,20 @@ package cn.iocoder.yudao.module.crm.service.business;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
 import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
 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.contact.CrmContactService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import org.springframework.stereotype.Service;
@@ -21,6 +26,7 @@ import org.springframework.validation.annotation.Validated;
 import javax.annotation.Resource;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -42,6 +48,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     @Resource
     private CrmPermissionService crmPermissionService;
 
+    @Resource
+    private CrmContactService crmContactService;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createBusiness(CrmBusinessCreateReqVO createReqVO, Long userId) {
@@ -134,5 +143,29 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
 
         // 3. TODO 记录转移日志
     }
+    @Override
+    public PageResult<CrmBusinessRespVO> getBusinessPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO) {
+        CrmContactBusinessLinkPageReqVO crmContactBusinessLinkPageReqVO = new CrmContactBusinessLinkPageReqVO();
+        crmContactBusinessLinkPageReqVO.setContactId(pageReqVO.getContactId());
+        PageResult<CrmContactBusinessLinkDO> businessLinkDOS = crmContactService.selectBusinessPageByContact(crmContactBusinessLinkPageReqVO);
+        if (CollUtil.isEmpty(businessLinkDOS.getList())){
+            return PageResult.empty();
+        }
+        List<CrmBusinessDO> businessList = this.getBusinessList(CollectionUtils.convertList(businessLinkDOS.getList(),
+                CrmContactBusinessLinkDO::getBusinessId));
+        if (CollUtil.isEmpty(businessList)){
+            return PageResult.empty();
+        }
+        PageResult<CrmBusinessRespVO> pageResult = new PageResult<CrmBusinessRespVO>();
+        List<CrmBusinessRespVO> respVOList = BeanUtils.toBean(businessList,CrmBusinessRespVO.class);
+        Map<Long,Long> businessContactMap = CollectionUtils.convertMap(businessLinkDOS.getList(),
+                CrmContactBusinessLinkDO::getBusinessId,CrmContactBusinessLinkDO::getId);
+        respVOList.forEach(item -> {
+            item.setBusinessContactId(businessContactMap.get(item.getId()));
+        });
+        pageResult.setList(respVOList);
+        pageResult.setTotal(businessLinkDOS.getTotal());
+        return pageResult;
 
+    }
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkService.java
similarity index 76%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkService.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkService.java
index 34a62b341..b06ddbf18 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkService.java
@@ -1,10 +1,10 @@
-package cn.iocoder.yudao.module.crm.service.contactbusinesslink;
+package cn.iocoder.yudao.module.crm.service.contact;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contactbusinesslink.CrmContactBusinessLinkDO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
 
 import javax.validation.Valid;
 import java.util.List;
@@ -41,9 +41,9 @@ public interface CrmContactBusinessLinkService {
     /**
      * 删除联系人商机关联
      *
-     * @param createReqVO  删除列表
+     * @param businessContactIds  删除列表
      */
-    void deleteContactBusinessLink(@Valid List<CrmContactBusinessLinkSaveReqVO> createReqVO);
+    void deleteContactBusinessLink(@Valid List<Long> businessContactIds);
 
     /**
      * 获得联系人商机关联
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkServiceImpl.java
similarity index 68%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkServiceImpl.java
index 1ad91e6ec..4400a09f0 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contactbusinesslink/CrmContactBusinessLinkServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkServiceImpl.java
@@ -1,25 +1,31 @@
-package cn.iocoder.yudao.module.crm.service.contactbusinesslink;
+package cn.iocoder.yudao.module.crm.service.contact;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contactbusinesslink.vo.CrmContactBusinessLinkSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkSaveReqVO;
 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
-import cn.iocoder.yudao.module.crm.convert.contactbusinessslink.CrmContactBusinessLinkConvert;
 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.dataobject.contact.CrmContactBusinessLinkDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessLinkMapper;
+import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.util.ArrayList;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_BUSINESS_LINK_NOT_EXISTS;
 
 // TODO @puhui999:数据权限的校验;每个操作;
@@ -37,6 +43,9 @@ public class CrmContactBusinessLinkServiceImpl implements CrmContactBusinessLink
     @Resource
     private CrmBusinessService crmBusinessService;
 
+    @Resource
+    private CrmContactService crmContactService;
+
     @Override
     public Long createContactBusinessLink(CrmContactBusinessLinkSaveReqVO createReqVO) {
         CrmContactBusinessLinkDO contactBusinessLink = BeanUtils.toBean(createReqVO, CrmContactBusinessLinkDO.class);
@@ -47,9 +56,22 @@ public class CrmContactBusinessLinkServiceImpl implements CrmContactBusinessLink
     @Override
     public void createContactBusinessLinkBatch(List<CrmContactBusinessLinkSaveReqVO> createReqVOList) {
         // 插入
-        // TODO @zyna:如果已经关联过,不用重复插入;
-        // TODO @zyna:contact 和 business 存在校验,挪到这里,Controller 不用 @Transactional 注解,添加到这里哈。尽量业务都在 Service;
-        List<CrmContactBusinessLinkDO> saveDoList = CrmContactBusinessLinkConvert.INSTANCE.convert(createReqVOList);
+        CrmContactDO contactDO = crmContactService.getContact(createReqVOList.stream().findFirst().get().getContactId());
+        Assert.notNull(contactDO,ErrorCodeConstants.CONTACT_NOT_EXISTS.getMsg());
+        List<CrmContactBusinessLinkDO> saveDoList = new ArrayList<CrmContactBusinessLinkDO>();
+        createReqVOList.forEach(item -> {
+            CrmBusinessDO crmBusinessDO = crmBusinessService.getBusiness(item.getBusinessId());
+            if(crmBusinessDO == null){
+                throw exception(BUSINESS_NOT_EXISTS);
+            }
+            // 判重
+            CrmContactBusinessLinkDO crmContactBusinessLinkDO = contactBusinessLinkMapper.selectOne(new LambdaQueryWrapper<CrmContactBusinessLinkDO>()
+                    .eq(CrmContactBusinessLinkDO::getBusinessId,item.getBusinessId())
+                    .eq(CrmContactBusinessLinkDO::getContactId,item.getContactId()));
+            if(crmContactBusinessLinkDO == null){
+                saveDoList.add(BeanUtils.toBean(item,CrmContactBusinessLinkDO.class));
+            }
+        });
         contactBusinessLinkMapper.insertBatch(saveDoList);
     }
 
@@ -63,14 +85,9 @@ public class CrmContactBusinessLinkServiceImpl implements CrmContactBusinessLink
     }
 
     @Override
-    public void deleteContactBusinessLink(List<CrmContactBusinessLinkSaveReqVO> createReqVO) {
+    public void deleteContactBusinessLink(List<Long> businessContactIds) {
         // 删除
-        createReqVO.forEach(item -> {
-            contactBusinessLinkMapper.delete(new LambdaQueryWrapperX<CrmContactBusinessLinkDO>()
-                    .eq(CrmContactBusinessLinkDO::getBusinessId,item.getBusinessId())
-                    .eq(CrmContactBusinessLinkDO::getContactId,item.getContactId())
-                    .eq(CrmContactBusinessLinkDO::getDeleted,0));
-        });
+        contactBusinessLinkMapper.deleteBatchIds(businessContactIds);
     }
 
     private void validateContactBusinessLinkExists(Long id) {
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
index 6f49f7661..17797561d 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
@@ -1,9 +1,11 @@
 package cn.iocoder.yudao.module.crm.service.contact;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactUpdateReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 
@@ -84,4 +86,12 @@ public interface CrmContactService {
      */
     List<CrmContactDO> getContactList();
 
+
+    /**
+     * 获取联系人商机关联分页列表
+     * @param reqVO 联系人
+     * @return 商机联系人关联列表
+     */
+    PageResult<CrmContactBusinessLinkDO> selectBusinessPageByContact(CrmContactBusinessLinkPageReqVO reqVO);
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
index 57ebdf4e1..db91399f3 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
@@ -3,13 +3,16 @@ package cn.iocoder.yudao.module.crm.service.contact;
 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.contact.vo.CrmContactBaseVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactUpdateReqVO;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
+import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
 import cn.iocoder.yudao.module.crm.convert.contact.ContactConvert;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessLinkMapper;
 import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
@@ -46,10 +49,12 @@ public class CrmContactServiceImpl implements CrmContactService {
     private CrmCustomerService customerService;
     @Resource
     private CrmPermissionService crmPermissionService;
-
     @Resource
     private AdminUserApi adminUserApi;
 
+    @Resource
+    private CrmContactBusinessLinkMapper contactBusinessLinkMapper;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createContact(CrmContactCreateReqVO createReqVO, Long userId) {
@@ -144,4 +149,11 @@ public class CrmContactServiceImpl implements CrmContactService {
         return contactMapper.selectList();
     }
 
+    @Override
+    public PageResult<CrmContactBusinessLinkDO> selectBusinessPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO) {
+        CrmContactBusinessLinkPageReqVO crmContactBusinessLinkPageReqVO = new CrmContactBusinessLinkPageReqVO();
+        crmContactBusinessLinkPageReqVO.setContactId(pageReqVO.getContactId());
+        return contactBusinessLinkMapper.selectPageByContact(crmContactBusinessLinkPageReqVO);
+
+    }
 }

From 9e3055ee1ee2ef043bbb24314a87df11002c09d6 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Tue, 2 Jan 2024 18:49:14 +0800
Subject: [PATCH 05/15] =?UTF-8?q?=F0=9F=93=96=20CRM=EF=BC=9Acode=20review?=
 =?UTF-8?q?=20=E5=95=86=E6=9C=BA=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../module/crm/enums/ErrorCodeConstants.java  |   2 +-
 .../admin/business/CrmBusinessController.java | 111 ++++-----------
 .../vo/business/CrmBusinessPageReqVO.java     |   3 +
 .../vo/business/CrmBusinessRespVO.java        |   2 -
 .../admin/contact/CrmContactController.java   |  47 +++----
 .../vo/CrmContactBusinessLinkPageReqVO.java   |  30 ----
 .../vo/CrmContactBusinessLinkRespVO.java      |  26 ----
 .../vo/CrmContactBusinessLinkSaveReqVO.java   |  23 ----
 .../contact/vo/CrmContactBusinessReqVO.java   |  22 +++
 .../CrmContactBusinessLinkController.java     |   0
 .../admin/contract/CrmContractController.java |   2 +-
 .../receivable/CrmReceivableController.java   |   2 +-
 .../CrmReceivablePlanController.java          |   2 +-
 .../convert/contact/CrmContactConvert.java    |  14 +-
 ...sLinkDO.java => CrmContactBusinessDO.java} |   5 +-
 .../dal/mysql/business/CrmBusinessMapper.java |   9 +-
 .../CrmContactBusinessLinkMapper.java         |  30 ----
 .../CrmContactBusinessMapper.java             |  34 +++++
 .../service/business/CrmBusinessService.java  |  22 +--
 .../business/CrmBusinessServiceImpl.java      |  66 ++++-----
 .../business/CrmBusinessStatusService.java    |  10 ++
 .../CrmBusinessStatusServiceImpl.java         |   6 +
 .../CrmBusinessStatusTypeService.java         |  11 +-
 .../CrmBusinessStatusTypeServiceImpl.java     |   7 +
 .../CrmContactBusinessLinkService.java        |  72 ----------
 .../CrmContactBusinessLinkServiceImpl.java    | 129 ------------------
 .../contact/CrmContactBusinessService.java    |  38 ++++++
 .../CrmContactBusinessServiceImpl.java        |  83 +++++++++++
 .../service/contact/CrmContactService.java    |  10 --
 .../contact/CrmContactServiceImpl.java        |  21 +--
 .../service/customer/CrmCustomerService.java  |   2 +-
 .../customer/CrmCustomerServiceImpl.java      |   4 +-
 32 files changed, 326 insertions(+), 519 deletions(-)
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkPageReqVO.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkRespVO.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkSaveReqVO.java
 create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java
 rename yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/{CrmContactBusinessLinkDO.java => CrmContactBusinessDO.java} (86%)
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java
 create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkService.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkServiceImpl.java
 create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java
 create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java

diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
index f3e3d2523..a7494dc8d 100644
--- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
+++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -24,7 +24,7 @@ public interface ErrorCodeConstants {
     // ========== 联系人管理 1-020-003-000 ==========
     ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
     ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode( 1_020_003_001, "联系人商机关联不存在");
-    ErrorCode CONTACT_BUSINESS_LINK_CREATE_EMPTY = new ErrorCode( 1_020_003_002, "联系人商机关联参数为空");
+
     // ========== 回款 1-020-004-000 ==========
     ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在");
 
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
index fed2da2af..20355610b 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
@@ -4,14 +4,9 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
@@ -33,13 +28,10 @@ import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
 import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 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.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
@@ -51,13 +43,10 @@ public class CrmBusinessController {
 
     @Resource
     private CrmBusinessService businessService;
-
     @Resource
     private CrmCustomerService customerService;
-
     @Resource
     private CrmBusinessStatusTypeService businessStatusTypeService;
-
     @Resource
     private CrmBusinessStatusService businessStatusService;
 
@@ -99,27 +88,7 @@ public class CrmBusinessController {
     @PreAuthorize("@ss.hasPermission('crm:business:query')")
     public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) {
         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId());
-        if (CollUtil.isEmpty(pageResult.getList())) {
-            return success(PageResult.empty(pageResult.getTotal()));
-        }
-        // 处理客户名称回显
-        // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈;
-        Set<Long> customerIds = pageResult.getList().stream()
-                .map(CrmBusinessDO::getCustomerId).filter(Objects::nonNull).collect(Collectors.toSet());
-        List<CrmCustomerDO> customerList = customerService.getCustomerList(customerIds, getLoginUserId());
-        // 处理商机状态类型名称回显
-        Set<Long> statusTypeIds = pageResult.getList().stream()
-                .map(CrmBusinessDO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet());
-        CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO();
-        queryStatusTypeVO.setIdList(statusTypeIds);
-        List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO);
-        // 处理商机状态名称回显
-        Set<Long> statusIds = pageResult.getList().stream()
-                .map(CrmBusinessDO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet());
-        CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
-        queryVO.setIdList(statusIds);
-        List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);
-        return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList));
+        return success(buildBusinessDetailPageResult(pageResult));
     }
 
     @GetMapping("/page-by-customer")
@@ -127,24 +96,16 @@ public class CrmBusinessController {
     public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByCustomer(@Valid CrmBusinessPageReqVO pageReqVO) {
         Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO);
-        // 处理客户名称回显
-        // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈;
-        Set<Long> customerIds = pageResult.getList().stream()
-                .map(CrmBusinessDO::getCustomerId).filter(Objects::nonNull).collect(Collectors.toSet());
-        List<CrmCustomerDO> customerList = customerService.getCustomerList(customerIds, getLoginUserId());
-        // 处理商机状态类型名称回显
-        Set<Long> statusTypeIds = pageResult.getList().stream()
-                .map(CrmBusinessDO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet());
-        CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO();
-        queryStatusTypeVO.setIdList(statusTypeIds);
-        List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO);
-        // 处理商机状态名称回显
-        Set<Long> statusIds = pageResult.getList().stream()
-                .map(CrmBusinessDO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet());
-        CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
-        queryVO.setIdList(statusIds);
-        List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);
-        return success(CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList));
+        return success(buildBusinessDetailPageResult(pageResult));
+    }
+
+
+    @GetMapping("/page-by-contact")
+    @Operation(summary = "获得联系人的商机分页")
+    @PreAuthorize("@ss.hasPermission('crm:business:query')")
+    public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) {
+        PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByContact(pageReqVO);
+        return success(buildBusinessDetailPageResult(pageResult));
     }
 
     @GetMapping("/export-excel")
@@ -156,8 +117,21 @@ public class CrmBusinessController {
         exportReqVO.setPageSize(PAGE_SIZE_NONE);
         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId());
         // 导出 Excel
-        ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessExcelVO.class,
-                CrmBusinessConvert.INSTANCE.convertList02(pageResult.getList()));
+        ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class,
+                buildBusinessDetailPageResult(pageResult).getList());
+    }
+
+    private PageResult<CrmBusinessRespVO> buildBusinessDetailPageResult(PageResult<CrmBusinessDO> pageResult) {
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return PageResult.empty(pageResult.getTotal());
+        }
+        List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.getBusinessStatusTypeList(
+                convertSet(pageResult.getList(), CrmBusinessDO::getStatusTypeId));
+        List<CrmBusinessStatusDO> statusList = businessStatusService.getBusinessStatusList(
+                convertSet(pageResult.getList(), CrmBusinessDO::getStatusId));
+        List<CrmCustomerDO> customerList = customerService.getCustomerList(
+                convertSet(pageResult.getList(), CrmBusinessDO::getCustomerId));
+        return CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList);
     }
 
     @PutMapping("/transfer")
@@ -167,34 +141,5 @@ public class CrmBusinessController {
         businessService.transferBusiness(reqVO, getLoginUserId());
         return success(true);
     }
-    @GetMapping("/page-by-contact")
-    @Operation(summary = "获得联系人的商机分页")
-    @PreAuthorize("@ss.hasPermission('crm:business:query')")
-    public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmContactBusinessLinkPageReqVO pageVO) {
-        PageResult<CrmBusinessRespVO> pageResult = businessService.getBusinessPageByContact(pageVO);
-        // 处理商机状态类型名称回显
-        Set<Long> statusTypeIds = pageResult.getList().stream()
-                .map(CrmBusinessRespVO::getStatusTypeId).filter(Objects::nonNull).collect(Collectors.toSet());
-        CrmBusinessStatusTypeQueryVO queryStatusTypeVO = new CrmBusinessStatusTypeQueryVO();
-        queryStatusTypeVO.setIdList(statusTypeIds);
-        List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.selectList(queryStatusTypeVO);
-        Map<Long,String> statusTypeMap = CollectionUtils.convertMap(statusTypeList,CrmBusinessStatusTypeDO::getId,CrmBusinessStatusTypeDO::getName);
-        // 处理商机状态名称回显
-        Set<Long> statusIds = pageResult.getList().stream()
-                .map(CrmBusinessRespVO::getStatusId).filter(Objects::nonNull).collect(Collectors.toSet());
-        CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
-        queryVO.setIdList(statusIds);
-        List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);
-        Map<Long,String> statusMap = CollectionUtils.convertMap(statusList,CrmBusinessStatusDO::getId,CrmBusinessStatusDO::getName);
-        // 处理客户名称回显
-        Set<Long> customerIds = CollectionUtils.convertSet(pageResult.getList(),CrmBusinessRespVO::getCustomerId);
-        List<CrmCustomerDO> customerList = customerService.getCustomerList(customerIds);
-        Map<Long,String> customerMap = CollectionUtils.convertMap(customerList,CrmCustomerDO::getId,CrmCustomerDO::getName);
-        pageResult.getList().forEach(item -> {
-            item.setStatusTypeName(statusTypeMap.get(item.getStatusTypeId()));
-            item.setStatusName(statusMap.get(item.getStatusId()));
-            item.setCustomerName(customerMap.get(item.getCustomerId()));
-        });
-       return success(pageResult);
-    }
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java
index 3c9520607..0e47bf5be 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java
@@ -20,6 +20,9 @@ public class CrmBusinessPageReqVO extends PageParam {
     @Schema(description = "客户编号", example = "10795")
     private Long customerId;
 
+    @Schema(description = "联系人编号", example = "10795")
+    private Long contactId;
+
     @Schema(description = "场景类型", example = "1")
     @InEnum(CrmSceneTypeEnum.class)
     private Integer sceneType; // 场景类型,为 null 时则表示全部
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
index c0d61fa93..53c8f45da 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
@@ -25,6 +25,4 @@ public class CrmBusinessRespVO extends CrmBusinessBaseVO {
     @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中")
     private String statusName;
 
-    @Schema(description = "联系人商机关联ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
-    private Long businessContactId;
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
index ed640d503..6974b4902 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
@@ -13,7 +13,7 @@ import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
-import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessLinkService;
+import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -30,10 +30,6 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletResponse;
-import javax.validation.Valid;
-import javax.validation.constraints.NotEmpty;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
@@ -63,7 +59,7 @@ public class CrmContactController {
     private AdminUserApi adminUserApi;
 
     @Resource
-    private CrmContactBusinessLinkService contactBusinessLinkService;
+    private CrmContactBusinessService contactBusinessLinkService;
 
     @PostMapping("/create")
     @Operation(summary = "创建联系人")
@@ -103,7 +99,7 @@ public class CrmContactController {
                 NumberUtil.parseLong(contact.getCreator()), contact.getOwnerUserId())));
         // 2. 获取客户信息
         List<CrmCustomerDO> customerList = customerService.getCustomerList(
-                Collections.singletonList(contact.getCustomerId()), getLoginUserId());
+                Collections.singletonList(contact.getCustomerId()));
         // 3. 直属上级
         List<CrmContactDO> parentContactList = contactService.getContactList(
                 Collections.singletonList(contact.getParentId()), getLoginUserId());
@@ -148,23 +144,6 @@ public class CrmContactController {
                 convertDetailContactPage(pageResult).getList());
     }
 
-    @DeleteMapping("/delete-batch-business")
-    @Operation(summary = "批量删除联系人商机关联")
-    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:delete')")
-    public CommonResult<Boolean> deleteContactBusinessLinkBatch(@Valid @RequestBody List<Long> businessContactIds) {
-        contactBusinessLinkService.deleteContactBusinessLink(businessContactIds);
-        return success(true);
-    }
-
-    @PostMapping("/create-batch-business")
-    @Operation(summary = "创建联系人商机关联")
-    @PreAuthorize("@ss.hasPermission('crm:contact-business-link:create')")
-    public CommonResult<Boolean> createContactBusinessLinkBatch(
-            @Valid @NotEmpty @RequestBody List<CrmContactBusinessLinkSaveReqVO> createReqVO) {
-        contactBusinessLinkService.createContactBusinessLinkBatch(createReqVO);
-        return success(true);
-    }
-
     /**
      * 转换成详细的联系人分页,即读取关联信息
      *
@@ -178,7 +157,7 @@ public class CrmContactController {
         }
         // 1. 获取客户列表
         List<CrmCustomerDO> crmCustomerDOList = customerService.getCustomerList(
-                convertSet(contactList, CrmContactDO::getCustomerId), getLoginUserId());
+                convertSet(contactList, CrmContactDO::getCustomerId));
         // 2. 获取创建人、负责人列表
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
                 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
@@ -196,4 +175,22 @@ public class CrmContactController {
         return success(true);
     }
 
+    // ================== 关联/取关商机  ===================
+
+    @PostMapping("/create-business-list")
+    @Operation(summary = "创建联系人与商机的关联")
+    @PreAuthorize("@ss.hasPermission('crm:contact:create-business')")
+    public CommonResult<Boolean> createContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO createReqVO) {
+        contactBusinessLinkService.createContactBusinessList(createReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete-business-list")
+    @Operation(summary = "删除联系人与商机的关联")
+    @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
+    public CommonResult<Boolean> deleteContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO deleteReqVO) {
+        contactBusinessLinkService.deleteContactBusinessList(deleteReqVO);
+        return success(true);
+    }
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkPageReqVO.java
deleted file mode 100644
index 0755b6715..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkPageReqVO.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-@Schema(description = "管理后台 - CRM 联系人商机关联分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmContactBusinessLinkPageReqVO extends PageParam {
-
-    @Schema(description = "联系人编号", example = "20878")
-    private Long contactId;
-
-    @Schema(description = "商机编号", example = "7638")
-    private Long businessId;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
-
-}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkRespVO.java
deleted file mode 100644
index 7dd061b48..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkRespVO.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
-
-import com.alibaba.excel.annotation.ExcelProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-@Schema(description = "管理后台 - CRM 联系人商机关联 Response VO")
-@Data
-public class CrmContactBusinessLinkRespVO {
-
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "17220")
-    @ExcelProperty("主键")
-    private Long id;
-
-    @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
-    private Long contactId;
-
-    @Schema(description = "商机编号", requiredMode =  Schema.RequiredMode.REQUIRED, example = "7638")
-    private Long businessId;
-
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
-
-}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkSaveReqVO.java
deleted file mode 100644
index 9d59b48f6..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessLinkSaveReqVO.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import jakarta.validation.constraints.NotNull;
-
-@Schema(description = "管理后台 - CRM 联系人商机关联新增/修改 Request VO")
-@Data
-public class CrmContactBusinessLinkSaveReqVO {
-
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "17220")
-    private Long id;
-
-    @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
-    @NotNull(message="联系人不能为空")
-    private Long contactId;
-
-    @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
-    @NotNull(message="商机不能为空")
-    private Long businessId;
-
-}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java
new file mode 100644
index 000000000..9b360f84b
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 用于关联,取消关联的操作
+@Data
+public class CrmContactBusinessReqVO {
+
+    @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
+    @NotNull(message="联系人不能为空")
+    private Long contactId;
+
+    @Schema(description = "商机编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
+    @NotEmpty(message="商机不能为空")
+    private List<Long> businessIds;
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contactbusinesslink/CrmContactBusinessLinkController.java
deleted file mode 100644
index e69de29bb..000000000
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
index f0442c4d3..849e424a2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -124,7 +124,7 @@ public class CrmContractController {
         }
         // 1. 获取客户列表
         List<CrmCustomerDO> customerList = customerService.getCustomerList(
-                convertSet(contactList, CrmContractDO::getCustomerId), getLoginUserId());
+                convertSet(contactList, CrmContractDO::getCustomerId));
         // 2. 获取创建人、负责人列表
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
                 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
index a353491e7..98355077e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
@@ -131,7 +131,7 @@ public class CrmReceivableController {
         }
         // 1. 获取客户列表
         List<CrmCustomerDO> customerList = customerService.getCustomerList(
-                convertSet(receivableList, CrmReceivableDO::getCustomerId), getLoginUserId());
+                convertSet(receivableList, CrmReceivableDO::getCustomerId));
         // 2. 获取创建人、负责人列表
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList,
                 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
index 9f3c23895..6dc540e42 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
@@ -135,7 +135,7 @@ public class CrmReceivablePlanController {
         }
         // 1. 获取客户列表
         List<CrmCustomerDO> customerList = customerService.getCustomerList(
-                convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId), getLoginUserId());
+                convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId));
         // 2. 获取创建人、负责人列表
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList,
                 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
index ca84f4736..d41a4f6b3 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.crm.convert.contact;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
@@ -13,7 +14,6 @@ import org.mapstruct.factory.Mappers;
 
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
@@ -55,16 +55,16 @@ public interface CrmContactConvert {
      *
      * @param contactDO         联系人
      * @param userMap           用户列表
-     * @param crmCustomerDOList 客户
+     * @param customerDOList 客户
      * @return ContactRespVO
      */
-    default CrmContactRespVO convert(CrmContactDO contactDO, Map<Long, AdminUserRespDTO> userMap, List<CrmCustomerDO> crmCustomerDOList,
-                                     List<CrmContactDO> contactList) {
+    default CrmContactRespVO convert(CrmContactDO contactDO, Map<Long, AdminUserRespDTO> userMap,
+                                     List<CrmCustomerDO> customerDOList, List<CrmContactDO> contactList) {
         CrmContactRespVO contactVO = convert(contactDO);
         setUserInfo(contactVO, userMap);
-        Map<Long, CrmCustomerDO> ustomerMap = crmCustomerDOList.stream().collect(Collectors.toMap(CrmCustomerDO::getId, v -> v));
-        Map<Long, CrmContactDO> contactMap = contactList.stream().collect(Collectors.toMap(CrmContactDO::getId, v -> v));
-        findAndThen(ustomerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName()));
+        Map<Long, CrmCustomerDO> customerMap = CollectionUtils.convertMap(customerDOList, CrmCustomerDO::getId);
+        Map<Long, CrmContactDO> contactMap = CollectionUtils.convertMap(contactList, CrmContactDO::getId);
+        findAndThen(customerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName()));
         findAndThen(contactMap, contactDO.getParentId(), contact -> contactVO.setParentName(contact.getName()));
         return contactVO;
     }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessLinkDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java
similarity index 86%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessLinkDO.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java
index 7e7c50c89..46185a5ac 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessLinkDO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java
@@ -2,14 +2,13 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.contact;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
 /**
- * CRM 联系人商机关联 DO
+ * CRM 联系人与商机的关联 DO
  *
  * @author 芋道源码
  */
@@ -21,7 +20,7 @@ import lombok.*;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class CrmContactBusinessLinkDO extends BaseDO {
+public class CrmContactBusinessDO extends BaseDO {
 
     /**
      * 主键
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
index eb9d50a64..d01352007 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
@@ -30,7 +30,14 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
 
     default PageResult<CrmBusinessDO> selectPageByCustomerId(CrmBusinessPageReqVO pageReqVO) {
         return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
-                .eq(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId())  // 指定客户编号
+                .eq(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号
+                .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
+                .orderByDesc(CrmBusinessDO::getId));
+    }
+
+    default PageResult<CrmBusinessDO> selectPageByContactId(CrmBusinessPageReqVO pageReqVO, Collection<Long> businessIds) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmBusinessDO>()
+                .in(CrmBusinessDO::getId, businessIds) // 指定商机编号
                 .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName())
                 .orderByDesc(CrmBusinessDO::getId));
     }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java
deleted file mode 100644
index df67bf6e5..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessLinkMapper.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink;
-
-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.contact.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
-import org.apache.ibatis.annotations.Mapper;
-
-/**
- * CRM 联系人商机关联 Mapper
- *
- * @author 芋道源码
- */
-@Mapper
-public interface CrmContactBusinessLinkMapper extends BaseMapperX<CrmContactBusinessLinkDO> {
-
-    default PageResult<CrmContactBusinessLinkDO> selectPage(CrmContactBusinessLinkPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<CrmContactBusinessLinkDO>()
-                .eqIfPresent(CrmContactBusinessLinkDO::getContactId, reqVO.getContactId())
-                .eqIfPresent(CrmContactBusinessLinkDO::getBusinessId, reqVO.getBusinessId())
-                .betweenIfPresent(CrmContactBusinessLinkDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(CrmContactBusinessLinkDO::getId));
-    }
-    default PageResult<CrmContactBusinessLinkDO> selectPageByContact(CrmContactBusinessLinkPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<CrmContactBusinessLinkDO>()
-                .eqIfPresent(CrmContactBusinessLinkDO::getContactId, reqVO.getContactId())
-                .orderByDesc(CrmContactBusinessLinkDO::getId));
-    }
-}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java
new file mode 100644
index 000000000..3eae483bc
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * CRM 联系人与商机的关联 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface CrmContactBusinessMapper extends BaseMapperX<CrmContactBusinessDO> {
+
+    default CrmContactBusinessDO selectByContactIdAndBusinessId(Long contactId, Long businessId) {
+        return selectOne(CrmContactBusinessDO::getContactId, contactId,
+                CrmContactBusinessDO::getBusinessId, businessId);
+    }
+
+    default void deleteByContactIdAndBusinessId(Long contactId, Collection<Long> businessIds) {
+        delete(new LambdaQueryWrapper<CrmContactBusinessDO>()
+                .eq(CrmContactBusinessDO::getContactId, contactId)
+                .in(CrmContactBusinessDO::getBusinessId, businessIds));
+    }
+
+    default List<CrmContactBusinessDO> selectListByContactId(Long contactId) {
+        return selectList(CrmContactBusinessDO::getContactId, contactId);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
index a7f07896a..fad8930e4 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
@@ -5,10 +5,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
 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.customer.CrmCustomerDO;
 import jakarta.validation.Valid;
 
@@ -78,10 +76,20 @@ public interface CrmBusinessService {
      * 数据权限:基于 {@link CrmCustomerDO} 读取
      *
      * @param pageReqVO 分页查询
-     * @return 联系人分页
+     * @return 商机分页
      */
     PageResult<CrmBusinessDO> getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO);
 
+    /**
+     * 获得商机分页,基于指定联系人
+     *
+     * 数据权限:基于 {@link CrmContactDO} 读取
+     *
+     * @param pageReqVO 分页参数
+     * @return 商机分页
+     */
+    PageResult<CrmBusinessDO> getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO);
+
     /**
      * 商机转移
      *
@@ -90,10 +98,4 @@ public interface CrmBusinessService {
      */
     void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId);
 
-    /**
-     * 获取联系人商机列表
-     * @param pageReqVO 分页参数
-     * @return 联系人商机
-     */
-    PageResult<CrmBusinessRespVO> getBusinessPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO);
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
index 4c39ba22d..d2d45ba99 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -7,20 +7,14 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
 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;
-import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
+import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
@@ -31,10 +25,9 @@ import org.springframework.validation.annotation.Validated;
 
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
 
 /**
@@ -48,14 +41,13 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
 
     @Resource
     private CrmBusinessMapper businessMapper;
-    @Resource
-    private CrmCustomerService customerService;
 
     @Resource
-    private CrmPermissionService crmPermissionService;
-
+    private CrmPermissionService permissionService;
     @Resource
-    private CrmContactService crmContactService;
+    private CrmContactService contactService;
+    @Resource
+    private CrmContactBusinessService contactBusinessService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -65,7 +57,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         businessMapper.insert(business);
 
         // 创建数据权限
-        crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
+        permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
                 .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
 
         // 返回
@@ -93,7 +85,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         // 删除
         businessMapper.deleteById(id);
         // 删除数据权限
-        crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id);
+        permissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id);
     }
 
     private CrmBusinessDO validateBusinessExists(Long id) {
@@ -129,6 +121,20 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         return businessMapper.selectPageByCustomerId(pageReqVO);
     }
 
+    @Override
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#pageReqVO.contactId", level = CrmPermissionLevelEnum.READ)
+    public PageResult<CrmBusinessDO> getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO) {
+        // 1. 查询关联的商机编号
+        List<CrmContactBusinessDO> contactBusinessList = contactBusinessService.getContactBusinessListByContactId(
+                pageReqVO.getContactId());
+        if (CollUtil.isEmpty(contactBusinessList)) {
+            return PageResult.empty();
+        }
+        // 2. 查询商机分页
+        return businessMapper.selectPageByContactId(pageReqVO,
+                convertSet(contactBusinessList, CrmContactBusinessDO::getBusinessId));
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId) {
@@ -136,36 +142,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         validateBusinessExists(reqVO.getId());
 
         // 2.1 数据权限转移
-        crmPermissionService.transferPermission(
+        permissionService.transferPermission(
                 CrmBusinessConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()));
         // 2.2 设置新的负责人
         businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
 
         // 3. TODO 记录转移日志
     }
-    @Override
-    public PageResult<CrmBusinessRespVO> getBusinessPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO) {
-        CrmContactBusinessLinkPageReqVO crmContactBusinessLinkPageReqVO = new CrmContactBusinessLinkPageReqVO();
-        crmContactBusinessLinkPageReqVO.setContactId(pageReqVO.getContactId());
-        PageResult<CrmContactBusinessLinkDO> businessLinkDOS = crmContactService.selectBusinessPageByContact(crmContactBusinessLinkPageReqVO);
-        if (CollUtil.isEmpty(businessLinkDOS.getList())){
-            return PageResult.empty();
-        }
-        List<CrmBusinessDO> businessList = this.getBusinessList(CollectionUtils.convertList(businessLinkDOS.getList(),
-                CrmContactBusinessLinkDO::getBusinessId));
-        if (CollUtil.isEmpty(businessList)){
-            return PageResult.empty();
-        }
-        PageResult<CrmBusinessRespVO> pageResult = new PageResult<CrmBusinessRespVO>();
-        List<CrmBusinessRespVO> respVOList = BeanUtils.toBean(businessList,CrmBusinessRespVO.class);
-        Map<Long,Long> businessContactMap = CollectionUtils.convertMap(businessLinkDOS.getList(),
-                CrmContactBusinessLinkDO::getBusinessId,CrmContactBusinessLinkDO::getId);
-        respVOList.forEach(item -> {
-            item.setBusinessContactId(businessContactMap.get(item.getId()));
-        });
-        pageResult.setList(respVOList);
-        pageResult.setTotal(businessLinkDOS.getTotal());
-        return pageResult;
 
-    }
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java
index ffad53556..a2fc2d18d 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusine
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
 
 import jakarta.validation.Valid;
+
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -64,4 +66,12 @@ public interface CrmBusinessStatusService {
      */
     List<CrmBusinessStatusDO> selectList(CrmBusinessStatusQueryVO queryVO);
 
+    /**
+     * 获得商机状态列表
+     *
+     * @param ids 编号数组
+     * @return 商机状态列表
+     */
+    List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids);
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java
index fa6ad3d03..2e49e99d7 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java
@@ -12,6 +12,7 @@ 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;
@@ -77,4 +78,9 @@ public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService {
         return businessStatusMapper.selectList(queryVO);
     }
 
+    @Override
+    public List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids) {
+        return businessStatusMapper.selectBatchIds(ids);
+    }
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java
index 39dc3128c..20509994e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java
@@ -5,8 +5,9 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusiness
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
-
 import jakarta.validation.Valid;
+
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -63,4 +64,12 @@ public interface CrmBusinessStatusTypeService {
      */
     List<CrmBusinessStatusTypeDO> selectList(CrmBusinessStatusTypeQueryVO queryVO);
 
+    /**
+     * 获得商机状态类型列表
+     *
+     * @param ids 编号数组
+     * @return 商机状态类型列表
+     */
+    List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids);
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java
index 501780abd..0ebcda87c 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java
@@ -17,6 +17,8 @@ import org.springframework.transaction.annotation.Transactional;
 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;
@@ -118,4 +120,9 @@ public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeSe
         return businessStatusTypeMapper.selectList(queryVO);
     }
 
+    @Override
+    public List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids) {
+        return businessStatusTypeMapper.selectBatchIds(ids);
+    }
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkService.java
deleted file mode 100644
index 7eab5cff3..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkService.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package cn.iocoder.yudao.module.crm.service.contact;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkSaveReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
-
-import jakarta.validation.Valid;
-import java.util.List;
-
-/**
- * CRM 联系人商机关联 Service 接口
- *
- * @author 芋道源码
- */
-public interface CrmContactBusinessLinkService {
-
-    /**
-     * 创建联系人商机关联
-     *
-     * @param createReqVO 创建信息
-     * @return 编号
-     */
-    Long createContactBusinessLink(@Valid CrmContactBusinessLinkSaveReqVO createReqVO);
-
-    /**
-     * 创建联系人商机关联
-     *
-     * @param createReqVO 创建信息
-     */
-    void createContactBusinessLinkBatch(@Valid List<CrmContactBusinessLinkSaveReqVO> createReqVO);
-
-    /**
-     * 更新联系人商机关联
-     *
-     * @param updateReqVO 更新信息
-     */
-    void updateContactBusinessLink(@Valid CrmContactBusinessLinkSaveReqVO updateReqVO);
-
-    /**
-     * 删除联系人商机关联
-     *
-     * @param businessContactIds  删除列表
-     */
-    void deleteContactBusinessLink(@Valid List<Long> businessContactIds);
-
-    /**
-     * 获得联系人商机关联
-     *
-     * @param id 编号
-     * @return 联系人商机关联
-     */
-    CrmContactBusinessLinkDO getContactBusinessLink(Long id);
-
-    /**
-     * 获得联系人商机关联分页
-     *
-     * @param pageReqVO 编号
-     * @return 联系人商机关联
-     */
-    PageResult<CrmBusinessRespVO> getContactBusinessLinkPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO);
-
-    /**
-     * 获得联系人商机关联分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 联系人商机关联分页
-     */
-    PageResult<CrmContactBusinessLinkDO> getContactBusinessLinkPage(CrmContactBusinessLinkPageReqVO pageReqVO);
-
-}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkServiceImpl.java
deleted file mode 100644
index d2387179e..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessLinkServiceImpl.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package cn.iocoder.yudao.module.crm.service.contact;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.lang.Assert;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkSaveReqVO;
-import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
-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.enums.ErrorCodeConstants;
-import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
-import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-import java.util.ArrayList;
-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.BUSINESS_NOT_EXISTS;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_BUSINESS_LINK_NOT_EXISTS;
-
-// TODO @puhui999:数据权限的校验;每个操作;
-/**
- * 联系人商机关联 Service 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-public class CrmContactBusinessLinkServiceImpl implements CrmContactBusinessLinkService {
-
-    @Resource
-    private CrmContactBusinessLinkMapper contactBusinessLinkMapper;
-    @Resource
-    private CrmBusinessService crmBusinessService;
-
-    @Resource
-    private CrmContactService crmContactService;
-
-    @Override
-    public Long createContactBusinessLink(CrmContactBusinessLinkSaveReqVO createReqVO) {
-        CrmContactBusinessLinkDO contactBusinessLink = BeanUtils.toBean(createReqVO, CrmContactBusinessLinkDO.class);
-        contactBusinessLinkMapper.insert(contactBusinessLink);
-        return contactBusinessLink.getId();
-    }
-
-    @Override
-    public void createContactBusinessLinkBatch(List<CrmContactBusinessLinkSaveReqVO> createReqVOList) {
-        // 插入
-        CrmContactDO contactDO = crmContactService.getContact(createReqVOList.stream().findFirst().get().getContactId());
-        Assert.notNull(contactDO,ErrorCodeConstants.CONTACT_NOT_EXISTS.getMsg());
-        List<CrmContactBusinessLinkDO> saveDoList = new ArrayList<CrmContactBusinessLinkDO>();
-        createReqVOList.forEach(item -> {
-            CrmBusinessDO crmBusinessDO = crmBusinessService.getBusiness(item.getBusinessId());
-            if(crmBusinessDO == null){
-                throw exception(BUSINESS_NOT_EXISTS);
-            }
-            // 判重
-            CrmContactBusinessLinkDO crmContactBusinessLinkDO = contactBusinessLinkMapper.selectOne(new LambdaQueryWrapper<CrmContactBusinessLinkDO>()
-                    .eq(CrmContactBusinessLinkDO::getBusinessId,item.getBusinessId())
-                    .eq(CrmContactBusinessLinkDO::getContactId,item.getContactId()));
-            if(crmContactBusinessLinkDO == null){
-                saveDoList.add(BeanUtils.toBean(item,CrmContactBusinessLinkDO.class));
-            }
-        });
-        contactBusinessLinkMapper.insertBatch(saveDoList);
-    }
-
-    @Override
-    public void updateContactBusinessLink(CrmContactBusinessLinkSaveReqVO updateReqVO) {
-        // 校验存在
-        validateContactBusinessLinkExists(updateReqVO.getId());
-        // 更新
-        CrmContactBusinessLinkDO updateObj = BeanUtils.toBean(updateReqVO, CrmContactBusinessLinkDO.class);
-        contactBusinessLinkMapper.updateById(updateObj);
-    }
-
-    @Override
-    public void deleteContactBusinessLink(List<Long> businessContactIds) {
-        // 删除
-        contactBusinessLinkMapper.deleteBatchIds(businessContactIds);
-    }
-
-    private void validateContactBusinessLinkExists(Long id) {
-        if (contactBusinessLinkMapper.selectById(id) == null) {
-            throw exception(CONTACT_BUSINESS_LINK_NOT_EXISTS);
-        }
-    }
-
-    @Override
-    @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.READ)
-    public CrmContactBusinessLinkDO getContactBusinessLink(Long id) {
-        return contactBusinessLinkMapper.selectById(id);
-    }
-
-    @Override
-    public PageResult<CrmBusinessRespVO> getContactBusinessLinkPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO) {
-        CrmContactBusinessLinkPageReqVO crmContactBusinessLinkPageReqVO = new CrmContactBusinessLinkPageReqVO();
-        crmContactBusinessLinkPageReqVO.setContactId(pageReqVO.getContactId());
-        PageResult<CrmContactBusinessLinkDO> businessLinkDOS = contactBusinessLinkMapper.selectPageByContact(crmContactBusinessLinkPageReqVO);
-        List<CrmBusinessDO> businessDOS = crmBusinessService.getBusinessList(CollectionUtils.convertList(businessLinkDOS.getList(),
-                CrmContactBusinessLinkDO::getBusinessId), getLoginUserId());
-        PageResult<CrmBusinessRespVO> pageResult = new PageResult<CrmBusinessRespVO>();
-        pageResult.setList(CrmBusinessConvert.INSTANCE.convert(businessDOS));
-        pageResult.setTotal(businessLinkDOS.getTotal());
-        return pageResult;
-
-    }
-
-    @Override
-    public PageResult<CrmContactBusinessLinkDO> getContactBusinessLinkPage(CrmContactBusinessLinkPageReqVO pageReqVO) {
-        return contactBusinessLinkMapper.selectPage(pageReqVO);
-    }
-
-}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java
new file mode 100644
index 000000000..4a4d7a42d
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.crm.service.contact;
+
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * CRM 联系人与商机的关联 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface CrmContactBusinessService {
+
+    /**
+     * 创建联系人与商机的关联
+     *
+     * @param createReqVO 创建信息
+     */
+    void createContactBusinessList(@Valid CrmContactBusinessReqVO createReqVO);
+
+    /**
+     * 删除联系人与商机的关联
+     *
+     * @param deleteReqVO 删除信息
+     */
+    void deleteContactBusinessList(@Valid CrmContactBusinessReqVO deleteReqVO);
+
+    /**
+     * 获得联系人与商机的关联列表,基于联系人编号
+     *
+     * @param contactId 联系人编号
+     * @return 联系人商机关联
+     */
+    List<CrmContactBusinessDO> getContactBusinessListByContactId(Long contactId);
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java
new file mode 100644
index 000000000..d0c92b548
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java
@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.crm.service.contact;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper;
+import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS;
+
+// TODO @puhui999:数据权限的校验;每个操作;
+/**
+ * 联系人与商机的关联 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class CrmContactBusinessServiceImpl implements CrmContactBusinessService {
+
+    @Resource
+    private CrmContactBusinessMapper contactBusinessMapper;
+
+    @Resource
+    @Lazy // 延迟加载,为了解决延迟加载
+    private CrmBusinessService businessService;
+    @Resource
+    @Lazy // 延迟加载,为了解决延迟加载
+    private CrmContactService contactService;
+
+    @Override
+    public void createContactBusinessList(CrmContactBusinessReqVO createReqVO) {
+        CrmContactDO contact = contactService.getContact(createReqVO.getContactId());
+        if (contact == null) {
+            throw exception(CONTACT_NOT_EXISTS);
+        }
+        // 遍历处理,考虑到一般数量不会太多,代码处理简单
+        List<CrmContactBusinessDO> saveDOList = new ArrayList<>();
+        createReqVO.getBusinessIds().forEach(businessId -> {
+            CrmBusinessDO business = businessService.getBusiness(businessId);
+            if (business == null) {
+                throw exception(BUSINESS_NOT_EXISTS);
+            }
+            // 关联判重
+            if (contactBusinessMapper.selectByContactIdAndBusinessId(createReqVO.getContactId(), businessId) != null) {
+                return;
+            }
+            saveDOList.add(new CrmContactBusinessDO(null, createReqVO.getContactId(), businessId));
+        });
+        // 批量插入
+        if (CollUtil.isNotEmpty(saveDOList)) {
+            contactBusinessMapper.insertBatch(saveDOList);
+        }
+    }
+
+    @Override
+    public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) {
+        CrmContactDO contact = contactService.getContact(deleteReqVO.getContactId());
+        if (contact == null) {
+            throw exception(CONTACT_NOT_EXISTS);
+        }
+        // 直接删除
+        contactBusinessMapper.deleteByContactIdAndBusinessId(
+                deleteReqVO.getContactId(), deleteReqVO.getBusinessIds());
+    }
+
+    @Override
+    public List<CrmContactBusinessDO> getContactBusinessListByContactId(Long contactId) {
+        return contactBusinessMapper.selectListByContactId(contactId);
+    }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
index 17ae00f44..7d0c1dc0c 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
@@ -1,12 +1,10 @@
 package cn.iocoder.yudao.module.crm.service.contact;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessLinkPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactUpdateReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import jakarta.validation.Valid;
@@ -90,12 +88,4 @@ public interface CrmContactService {
      */
     void transferContact(CrmContactTransferReqVO reqVO, Long userId);
 
-
-    /**
-     * 获取联系人商机关联分页列表
-     * @param reqVO 联系人
-     * @return 商机联系人关联列表
-     */
-    PageResult<CrmContactBusinessLinkDO> selectBusinessPageByContact(CrmContactBusinessLinkPageReqVO reqVO);
-
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
index b1bf83d46..520038008 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
@@ -5,17 +5,9 @@ import cn.hutool.core.collection.ListUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
 import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
-import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
-import cn.iocoder.yudao.module.crm.convert.contact.ContactConvert;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessLinkDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
-import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessLinkMapper;
-import cn.iocoder.yudao.module.crm.framework.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper;
 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;
@@ -56,7 +48,7 @@ public class CrmContactServiceImpl implements CrmContactService {
     private AdminUserApi adminUserApi;
 
     @Resource
-    private CrmContactBusinessLinkMapper contactBusinessLinkMapper;
+    private CrmContactBusinessMapper contactBusinessLinkMapper;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -161,11 +153,4 @@ public class CrmContactServiceImpl implements CrmContactService {
         // 3. TODO 记录转移日志
     }
 
-    @Override
-    public PageResult<CrmContactBusinessLinkDO> selectBusinessPageByContact(CrmContactBusinessLinkPageReqVO pageReqVO) {
-        CrmContactBusinessLinkPageReqVO crmContactBusinessLinkPageReqVO = new CrmContactBusinessLinkPageReqVO();
-        crmContactBusinessLinkPageReqVO.setContactId(pageReqVO.getContactId());
-        return contactBusinessLinkMapper.selectPageByContact(crmContactBusinessLinkPageReqVO);
-
-    }
-}
+}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
index 73be18951..917196335 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
@@ -56,7 +56,7 @@ public interface CrmCustomerService {
      * @return 客户列表
      * @author ljlleo
      */
-    List<CrmCustomerDO> getCustomerList(Collection<Long> ids, Long userId);
+    List<CrmCustomerDO> getCustomerList(Collection<Long> ids);
 
     /**
      * 获得客户分页
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
index 0ab626b8f..c6fcae0e2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -116,11 +116,11 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
     }
 
     @Override
-    public List<CrmCustomerDO> getCustomerList(Collection<Long> ids, Long userId) {
+    public List<CrmCustomerDO> getCustomerList(Collection<Long> ids) {
         if (CollUtil.isEmpty(ids)) {
             return Collections.emptyList();
         }
-        return customerMapper.selectBatchIds(ids, userId);
+        return customerMapper.selectBatchIds(ids);
     }
 
     @Override

From 1da8006af35f2ab6a7aeb3636260df21e650b7f5 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Tue, 2 Jan 2024 21:45:02 +0800
Subject: [PATCH 06/15] =?UTF-8?q?=F0=9F=93=96=20CRM=EF=BC=9A=E3=80=90?=
 =?UTF-8?q?=E5=AE=A2=E6=88=B7=E3=80=91=E5=AE=8C=E5=96=84=E5=88=97=E8=A1=A8?=
 =?UTF-8?q?=E3=80=81=E6=96=B0=E5=A2=9E=E3=80=81=E4=BF=AE=E6=94=B9=E6=8E=A5?=
 =?UTF-8?q?=E5=8F=A3=E7=9A=84=20todo?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../admin/customer/CrmCustomerController.java         |  2 ++
 .../crm/service/customer/CrmCustomerServiceImpl.java  | 11 ++++++++---
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
index cc40ae257..5c766c5b3 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -58,6 +58,7 @@ public class CrmCustomerController {
     @Resource
     private OperateLogApi operateLogApi;
 
+    // TODO @puhui999:把 CrmCustomerCreateReqVO、CrmCustomerUpdateReqVO、CrmCustomerRespVO 按照新的规范,搞一下哈;
     @PostMapping("/create")
     @Operation(summary = "创建客户")
     @OperateLog(enable = false) // TODO 关闭原有日志记录;@puhui999:注解都先删除。先记录,没关系。我们下个迭代,就都删除掉操作日志了;
@@ -114,6 +115,7 @@ public class CrmCustomerController {
         }
 
         // 2. 拼接数据
+        // TODO @puhui999:距离进入公海的时间
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                 convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId())));
         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
index c6fcae0e2..2ab693c59 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -24,6 +24,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.time.LocalDateTime;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -52,10 +53,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    @LogRecord(type = CRM_CUSTOMER, subType = "创建客户", bizNo = "{{#customerId}}", success = "创建了客户")
+    @LogRecord(type = CRM_CUSTOMER, subType = "创建客户", bizNo = "{{#customerId}}", success = "创建了客户") // TODO @puhui999:客户名字,要记录进去;不然在展示操作日志的全列表,看不清楚是哪个客户哈;
     public Long createCustomer(CrmCustomerCreateReqVO createReqVO, Long userId) {
-        // 插入
-        CrmCustomerDO customer = CrmCustomerConvert.INSTANCE.convert(createReqVO);
+        // 插入客户
+        CrmCustomerDO customer = CrmCustomerConvert.INSTANCE.convert(createReqVO)
+                .setLockStatus(false).setDealStatus(false)
+                .setContactLastTime(LocalDateTime.now());
+        // TODO @puhui999:可能要加个 receiveTime 字段,记录最后接收时间
         customerMapper.insert(customer);
 
         // 创建数据权限
@@ -72,6 +76,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
     @LogRecord(type = CRM_CUSTOMER, subType = "更新客户", bizNo = "{{#updateReqVO.id}}", success = "更新了客户{_DIFF{#updateReqVO}}", extra = "{{#extra}}")
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
     public void updateCustomer(CrmCustomerUpdateReqVO updateReqVO) {
+        // TODO @puhui999:更新的时候,要把 updateReqVO 负责人设置为空,避免修改。
         // 校验存在
         CrmCustomerDO oldCustomer = validateCustomerExists(updateReqVO.getId());
 

From d0df0e8e161ec9df00db23916181465daca83a25 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Wed, 3 Jan 2024 13:06:49 +0800
Subject: [PATCH 07/15] =?UTF-8?q?=F0=9F=93=96=20CRM=EF=BC=9A=E3=80=90?=
 =?UTF-8?q?=E5=AE=A2=E6=88=B7=E3=80=91=E4=BC=98=E5=8C=96=E9=94=81=E5=AE=9A?=
 =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=9A=84=E9=80=BB=E8=BE=91=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../module/crm/enums/ErrorCodeConstants.java  |  10 +-
 .../CrmCustomerLimitConfigTypeEnum.java       |   6 +-
 .../admin/customer/CrmCustomerController.java |   3 +-
 .../customer/vo/CrmCustomerLockReqVO.java     |   2 -
 .../CrmCustomerLimitConfigCreateReqVO.java    |   6 -
 .../convert/customer/CrmCustomerConvert.java  |   3 -
 .../CrmCustomerLimitConfigMapper.java         |  23 ++-
 .../dal/mysql/customer/CrmCustomerMapper.java |  14 ++
 .../CrmCustomerLimitConfigService.java        |  10 +-
 .../CrmCustomerLimitConfigServiceImpl.java    |  10 +-
 .../service/customer/CrmCustomerService.java  |   3 +-
 .../customer/CrmCustomerServiceImpl.java      | 141 ++++++++++--------
 12 files changed, 128 insertions(+), 103 deletions(-)

diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
index d99e5b23a..137acee10 100644
--- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
+++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -39,12 +39,10 @@ public interface ErrorCodeConstants {
     ErrorCode CUSTOMER_IN_POOL = new ErrorCode(1_020_006_004, "客户【{}】放入公海失败,原因:已经是公海客户");
     ErrorCode CUSTOMER_LOCKED_PUT_POOL_FAIL = new ErrorCode(1_020_006_005, "客户【{}】放入公海失败,原因:客户已锁定");
     ErrorCode CUSTOMER_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_006_006, "更新客户【{}】负责人失败, 原因:系统异常");
-
-    ErrorCode CUSTOMER_UNLOCK_STATUS_NO_REPETITION = new ErrorCode(1_020_006_001, "无需重复操作锁定/解锁状态");
-
-    ErrorCode CUSTOMER_NO_DEPARTMENT_FOUND = new ErrorCode(1_020_006_002, "操作失败,请先绑定部门再进行操作");
-
-    ErrorCode CUSTOMER_EXCEED_LOCK_LIMIT = new ErrorCode(1_020_006_003, "操作失败,超出锁定规则上限");
+    ErrorCode CUSTOMER_LOCK_FAIL_IS_LOCK = new ErrorCode(1_020_006_007, "锁定客户失败,它已经处于锁定状态");
+    ErrorCode CUSTOMER_UNLOCK_FAIL_IS_UNLOCK = new ErrorCode(1_020_006_008, "锁定客户失败,它已经处于未锁定状态");
+    ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限");
+    ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限");
 
     // ========== 权限管理 1_020_007_000 ==========
     ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java
index 6300dee0e..ec362d484 100644
--- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java
+++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java
@@ -18,19 +18,19 @@ public enum CrmCustomerLimitConfigTypeEnum implements IntArrayValuable {
     /**
      * 拥有客户数限制
      */
-    CUSTOMER_QUANTITY_LIMIT(1, "拥有客户数限制"),
+    CUSTOMER_OWNER_LIMIT(1, "拥有客户数限制"),
     /**
      * 锁定客户数限制
      */
     CUSTOMER_LOCK_LIMIT(2, "锁定客户数限制"),
     ;
 
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLimitConfigTypeEnum::getCode).toArray();
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLimitConfigTypeEnum::getType).toArray();
 
     /**
      * 状态
      */
-    private final Integer code;
+    private final Integer type;
     /**
      * 状态名
      */
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
index 1ad8f7d5a..dfa02c0b2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -154,13 +154,12 @@ public class CrmCustomerController {
         return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class)));
     }
 
-    // TODO @Joey:单独建一个属于自己业务的 ReqVO;因为前端如果模拟请求,是不是可以更新其它字段了;
     @PutMapping("/lock")
     @Operation(summary = "锁定/解锁客户")
     @OperateLog(enable = false) // TODO 关闭原有日志记录
     @PreAuthorize("@ss.hasPermission('crm:customer:update')")
     public CommonResult<Boolean> lockCustomer(@Valid @RequestBody CrmCustomerLockReqVO lockReqVO) {
-        customerService.lockCustomer(lockReqVO);
+        customerService.lockCustomer(lockReqVO, getLoginUserId());
         return success(true);
     }
 
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java
index 50608049f..1cf9ff382 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java
@@ -13,6 +13,4 @@ public class CrmCustomerLockReqVO {
     @Schema(description = "客户锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
     private Boolean lockStatus;
 
-
-
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java
index 2cc707c43..7aa372901 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigCreateReqVO.java
@@ -5,16 +5,10 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
-import java.util.List;
-
 @Schema(description = "管理后台 - 客户限制配置创建 Request VO")
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 public class CrmCustomerLimitConfigCreateReqVO extends CrmCustomerLimitConfigBaseVO {
 
-
-    @Schema(description = "规则适用人群")
-    private Long userId;
-
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java
index b300c6416..6c8fdcbef 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java
@@ -12,7 +12,6 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
-import org.mapstruct.Mappings;
 import org.mapstruct.factory.Mappers;
 
 import java.util.List;
@@ -79,6 +78,4 @@ public interface CrmCustomerConvert {
 
     List<CrmCustomerQueryAllRespVO> convertQueryAll(List<CrmCustomerDO> crmCustomerDO);
 
-    CrmCustomerDO convert(CrmCustomerLockReqVO lockReqVO);
-
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java
index ea8f7d8bf..08beaf808 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java
@@ -3,13 +3,11 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer;
 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.customer.vo.limitconfig.CrmCustomerLimitConfigCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
 import org.apache.ibatis.annotations.Mapper;
 
-import java.util.Arrays;
-import java.util.stream.Collectors;
+import java.util.List;
 
 /**
  * 客户限制配置 Mapper
@@ -25,16 +23,15 @@ public interface CrmCustomerLimitConfigMapper extends BaseMapperX<CrmCustomerLim
                 .orderByDesc(CrmCustomerLimitConfigDO::getId));
     }
 
-    default CrmCustomerLimitConfigDO selectByLimitConfig(CrmCustomerLimitConfigCreateReqVO reqVO){
-        LambdaQueryWrapperX<CrmCustomerLimitConfigDO> queryWrapper = new LambdaQueryWrapperX<>();
-        queryWrapper.apply("FIND_IN_SET({0}, user_ids) > 0", reqVO.getUserId());
-        queryWrapper.eq(CrmCustomerLimitConfigDO::getType, reqVO.getType());
-        // 将部门ID列表转换成逗号分隔的字符串
-        String deptIdsString = reqVO.getDeptIds().stream()
-                .map(String::valueOf)
-                .collect(Collectors.joining(","));
-        queryWrapper.apply("FIND_IN_SET({0}, dept_ids) > 0", deptIdsString);
-        return selectOne(queryWrapper);
+    default List<CrmCustomerLimitConfigDO> selectListByTypeAndUserIdAndDeptId(
+            Integer type, Long userId, Long deptId) {
+        LambdaQueryWrapperX<CrmCustomerLimitConfigDO> query = new LambdaQueryWrapperX<CrmCustomerLimitConfigDO>()
+                .eq(CrmCustomerLimitConfigDO::getType, type);
+        query.apply("FIND_IN_SET({0}, user_ids) > 0", userId);
+        if (deptId != null) {
+            query.apply("FIND_IN_SET({0}, dept_ids) > 0", deptId);
+        }
+        return selectList(query);
     }
 
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
index 77304575c..8acd92b3e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer;
 
 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.framework.mybatis.core.query.MPJLambdaWrapperX;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@@ -9,6 +10,7 @@ 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 org.springframework.lang.Nullable;
 
 import java.util.Collection;
 import java.util.List;
@@ -21,6 +23,18 @@ import java.util.List;
 @Mapper
 public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
 
+    default Long selectCountByLockStatusAndOwnerUserId(Boolean lockStatus, Long ownerUserId) {
+        return selectCount(new LambdaUpdateWrapper<CrmCustomerDO>()
+                .eq(CrmCustomerDO::getLockStatus, lockStatus)
+                .eq(CrmCustomerDO::getOwnerUserId, ownerUserId));
+    }
+
+    default Long selectCountByDealStatusAndOwnerUserId(@Nullable Boolean dealStatus, Long ownerUserId) {
+        return selectCount(new LambdaQueryWrapperX<CrmCustomerDO>()
+                .eqIfPresent(CrmCustomerDO::getDealStatus, dealStatus)
+                .eq(CrmCustomerDO::getOwnerUserId, ownerUserId));
+    }
+
     default int updateOwnerUserIdById(Long id, Long ownerUserId) {
         return update(new LambdaUpdateWrapper<CrmCustomerDO>()
                 .eq(CrmCustomerDO::getId, id)
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java
index a181032f5..585d49f0a 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java
@@ -5,9 +5,10 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmC
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigUpdateReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
-
 import jakarta.validation.Valid;
 
+import java.util.List;
+
 /**
  * 客户限制配置 Service 接口
  *
@@ -54,8 +55,11 @@ public interface CrmCustomerLimitConfigService {
     PageResult<CrmCustomerLimitConfigDO> getCustomerLimitConfigPage(CrmCustomerLimitConfigPageReqVO pageReqVO);
 
     /**
-     * 查询当前登录人客户限制配置
+     * 查询用户对应的配置列表
+     *
+     * @param type 类型
+     * @param userId 用户类型
      */
-    CrmCustomerLimitConfigDO selectByLimitConfig(CrmCustomerLimitConfigCreateReqVO configReqVO);
+    List<CrmCustomerLimitConfigDO> getCustomerLimitConfigListByUserId(Integer type, Long userId);
 
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java
index 24cd84d36..be8ddd968 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.crm.service.customer;
 
+import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO;
@@ -9,12 +10,14 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfi
 import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerLimitConfigMapper;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 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.CUSTOMER_LIMIT_CONFIG_NOT_EXISTS;
@@ -30,6 +33,7 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig
 
     @Resource
     private CrmCustomerLimitConfigMapper customerLimitConfigMapper;
+
     @Resource
     private DeptApi deptApi;
     @Resource
@@ -91,8 +95,10 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig
     }
 
     @Override
-    public CrmCustomerLimitConfigDO selectByLimitConfig(CrmCustomerLimitConfigCreateReqVO configReqVO) {
-        return customerLimitConfigMapper.selectByLimitConfig(configReqVO);
+    public List<CrmCustomerLimitConfigDO> getCustomerLimitConfigListByUserId(Integer type, Long userId) {
+        AdminUserRespDTO user = adminUserApi.getUser(userId);
+        Assert.notNull(user, "用户({})不存在", userId);
+        return customerLimitConfigMapper.selectListByTypeAndUserIdAndDeptId(type, userId, user.getDeptId());
     }
 
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
index e1a1b938a..c7eeaaf4a 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
@@ -83,8 +83,9 @@ public interface CrmCustomerService {
      * 锁定/解锁客户
      *
      * @param lockReqVO 更新信息
+     * @param userId 用户编号
      */
-    void lockCustomer(@Valid CrmCustomerLockReqVO lockReqVO);
+    void lockCustomer(@Valid CrmCustomerLockReqVO lockReqVO, Long userId);
 
     // ==================== 公海相关操作 ====================
 
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
index 246663477..bb900b784 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -2,13 +2,9 @@ package cn.iocoder.yudao.module.crm.service.customer;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerUpdateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigCreateReqVO;
 import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
@@ -19,11 +15,9 @@ import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPerm
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
-import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import com.mzt.logapi.context.LogRecordContext;
 import com.mzt.logapi.service.impl.DiffParseFunction;
 import com.mzt.logapi.starter.annotation.LogRecord;
-import com.mzt.logapi.starter.annotation.LogRecord;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -33,13 +27,11 @@ import java.time.LocalDateTime;
 import java.util.*;
 
 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.*;
 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CUSTOMER;
 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.TRANSFER_CUSTOMER_LOG_SUCCESS;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_EXCEED_LOCK_LIMIT;
 import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT;
-import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CUSTOMER;
+import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_OWNER_LIMIT;
 import static java.util.Collections.singletonList;
 
 /**
@@ -55,29 +47,32 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
     private CrmCustomerMapper customerMapper;
 
     @Resource
-    private CrmPermissionService crmPermissionService;
+    private CrmPermissionService permissionService;
     @Resource
-    private CrmCustomerLimitConfigService crmCustomerLimitConfigService;
+    private CrmCustomerLimitConfigService customerLimitConfigService;
 
     @Resource
     private AdminUserApi adminUserApi;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    @LogRecord(type = CRM_CUSTOMER, subType = "创建客户", bizNo = "{{#customerId}}", success = "创建了客户") // TODO @puhui999:客户名字,要记录进去;不然在展示操作日志的全列表,看不清楚是哪个客户哈;
+    @LogRecord(type = CRM_CUSTOMER, subType = "创建客户", bizNo = "{{#customerId}}", success = "创建了客户") // TODO @puhui999:创建了客户【客户名】,要记录进去;不然在展示操作日志的全列表,看不清楚是哪个客户哈;
     public Long createCustomer(CrmCustomerCreateReqVO createReqVO, Long userId) {
-        // 插入客户
+        // 1. 校验拥有客户是否到达上限
+        validateCustomerExceedOwnerLimit(createReqVO.getOwnerUserId(), 1);
+
+        // 2. 插入客户
         CrmCustomerDO customer = CrmCustomerConvert.INSTANCE.convert(createReqVO)
                 .setLockStatus(false).setDealStatus(false)
                 .setContactLastTime(LocalDateTime.now());
         // TODO @puhui999:可能要加个 receiveTime 字段,记录最后接收时间
         customerMapper.insert(customer);
 
-        // 创建数据权限
-        crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
+        // 3. 创建数据权限
+        permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
                 .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
 
-        // 记录操作日志
+        // 4. 记录操作日志
         LogRecordContext.putVariable("customerId", customer.getId());
         return customer.getId();
     }
@@ -88,14 +83,14 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
     public void updateCustomer(CrmCustomerUpdateReqVO updateReqVO) {
         // TODO @puhui999:更新的时候,要把 updateReqVO 负责人设置为空,避免修改。
-        // 校验存在
+        // 1. 校验存在
         CrmCustomerDO oldCustomer = validateCustomerExists(updateReqVO.getId());
 
-        // 更新
+        // 2. 更新客户
         CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(updateReqVO);
         customerMapper.updateById(updateObj);
 
-        // 记录操作日志
+        // 3. 记录操作日志
         LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerUpdateReqVO.class));
         // TODO 扩展信息测试 @puhui999:看着没啥问题,可以删除啦;
         HashMap<String, Object> extra = new HashMap<>();
@@ -114,7 +109,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
         // 删除
         customerMapper.deleteById(id);
         // 删除数据权限
-        crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
+        permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
     }
 
     private CrmCustomerDO validateCustomerExists(Long id) {
@@ -159,63 +154,83 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
     @LogRecord(type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}", success = TRANSFER_CUSTOMER_LOG_SUCCESS)
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
     public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) {
-        // 1. 校验客户是否存在
-        CrmCustomerDO customerDO = validateCustomerExists(reqVO.getId());
+        // 1.1 校验客户是否存在
+        CrmCustomerDO customer = validateCustomerExists(reqVO.getId());
+        // 1.2 校验拥有客户是否到达上限
+        validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1);
 
         // 2.1 数据权限转移
-        crmPermissionService.transferPermission(
+        permissionService.transferPermission(
                 CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()));
         // 2.2 转移后重新设置负责人
         customerMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
 
         // 3. TODO 记录转移日志
-        LogRecordContext.putVariable("crmCustomer", customerDO);
+        LogRecordContext.putVariable("crmCustomer", customer);
     }
 
     @Override
+    // TODO @puhui999:看看这个能不能根据条件,写操作日志;
+    // TODO 如果是 锁定,则 subType 为 锁定客户;success 为 将客户【】锁定
+    // TODO 如果是 解锁,则 subType 为 解锁客户;success 为 将客户【】解锁
     @LogRecord(type = CRM_CUSTOMER, subType = "锁定/解锁客户", bizNo = "{{#updateReqVO.id}}", success = "锁定了客户")
-    public void lockCustomer(CrmCustomerLockReqVO lockReqVO) {
-        // 校验当前客户是否存在
+    // TODO @puhui999:数据权限
+    public void lockCustomer(CrmCustomerLockReqVO lockReqVO, Long userId) {
+        // 1.1 校验当前客户是否存在
         validateCustomerExists(lockReqVO.getId());
-
-        CrmCustomerDO customerDO = customerMapper.selectById(lockReqVO.getId());
-
-        // 校验当前是否重复操作锁定/解锁状态
-        if (customerDO.getLockStatus().equals(lockReqVO.getLockStatus())) {
-            throw exception(CUSTOMER_UNLOCK_STATUS_NO_REPETITION);
+        // 1.2 校验当前是否重复操作锁定/解锁状态
+        CrmCustomerDO customer = customerMapper.selectById(lockReqVO.getId());
+        if (customer.getLockStatus().equals(lockReqVO.getLockStatus())) {
+            throw exception(customer.getLockStatus() ? CUSTOMER_LOCK_FAIL_IS_LOCK : CUSTOMER_UNLOCK_FAIL_IS_UNLOCK);
+        }
+        // 1.3 校验锁定上限。
+        if (lockReqVO.getLockStatus()) {
+            validateCustomerExceedLockLimit(userId);
         }
 
-        // 获取当前登录信息,开始校验锁定上限
-        AdminUserRespDTO userRespDTO = adminUserApi.getUser(getLoginUserId());
+        // 2. 更新锁定状态
+        customerMapper.updateById(BeanUtils.toBean(lockReqVO, CrmCustomerDO.class));
+    }
 
-        if (userRespDTO.getDeptId() == null || userRespDTO.getId() == null) {
-            // 如有入参为空,提示业务异常
-            throw exception(CUSTOMER_NO_DEPARTMENT_FOUND);
+    /**
+     * 校验用户拥有的客户数量,是否到达上限
+     *
+     * @param userId 用户编号
+     * @param newCount 附加数量
+     */
+    private void validateCustomerExceedOwnerLimit(Long userId, int newCount) {
+        List<CrmCustomerLimitConfigDO> limitConfigs = customerLimitConfigService.getCustomerLimitConfigListByUserId(
+                CUSTOMER_OWNER_LIMIT.getType(), userId);
+        if (CollUtil.isEmpty(limitConfigs)) {
+            return;
         }
+        Long ownerCount = customerMapper.selectCountByDealStatusAndOwnerUserId(null, userId);
+        Long dealOwnerCount = customerMapper.selectCountByDealStatusAndOwnerUserId(true, userId);
+        limitConfigs.forEach(limitConfig -> {
+            long nowCount = limitConfig.getDealCountEnabled() ? ownerCount : ownerCount - dealOwnerCount;
+            if (nowCount + newCount > limitConfig.getMaxCount()) {
+                throw exception(CUSTOMER_OWNER_EXCEED_LIMIT);
+            }
+        });
+    }
 
-        // 开始校验规则限制
-        List<Long> userDeptIds = Collections.singletonList(userRespDTO.getDeptId());
-
-        CrmCustomerLimitConfigCreateReqVO configReqVO = new CrmCustomerLimitConfigCreateReqVO();
-        configReqVO.setUserId(userRespDTO.getId());
-        configReqVO.setDeptIds(userDeptIds);
-        configReqVO.setType(CUSTOMER_LOCK_LIMIT.getCode());
-
-        CrmCustomerLimitConfigDO crmCustomerLimitConfigDO = crmCustomerLimitConfigService.selectByLimitConfig(configReqVO);
-
-        // 统计当前用户已锁定客户数量
-        List<CrmCustomerDO> crmCustomerDOS = customerMapper.selectList("owner_user_id", getLoginUserId());
-        long customerLockCount = crmCustomerDOS.stream().filter(CrmCustomerDO::getLockStatus).count();
-
-        // 锁定操作的时候校验当前用户可锁定客户的上限
-        if (crmCustomerLimitConfigDO != null && lockReqVO.getLockStatus() && customerLockCount >= crmCustomerLimitConfigDO.getMaxCount()) {
-            // 超出锁定数量上限,提示业务异常
-            throw exception(CUSTOMER_EXCEED_LOCK_LIMIT);
+    /**
+     * 校验用户锁定的客户数量,是否到达上限
+     *
+     * @param userId 用户编号
+     */
+    private void validateCustomerExceedLockLimit(Long userId) {
+        List<CrmCustomerLimitConfigDO> limitConfigs = customerLimitConfigService.getCustomerLimitConfigListByUserId(
+                CUSTOMER_LOCK_LIMIT.getType(), userId);
+        if (CollUtil.isEmpty(limitConfigs)) {
+            return;
+        }
+        Long lockCount = customerMapper.selectCountByLockStatusAndOwnerUserId(true, userId);
+        Integer maxCount = CollectionUtils.getMaxValue(limitConfigs, CrmCustomerLimitConfigDO::getMaxCount);
+        assert maxCount != null;
+        if (lockCount >= maxCount) {
+            throw exception(CUSTOMER_LOCK_EXCEED_LIMIT);
         }
-
-        // 更新
-        CrmCustomerDO updateObj = CrmCustomerConvert.INSTANCE.convert(lockReqVO);
-        customerMapper.updateById(updateObj);
     }
 
     @Override
@@ -239,7 +254,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
             throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL);
         }
         // 3. 删除负责人数据权限
-        crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(),
+        permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(),
                 CrmPermissionLevelEnum.OWNER.getLevel());
     }
 
@@ -262,6 +277,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
             // 校验成交状态
             validateCustomerDeal(customer);
         });
+        // 1.4  校验负责人是否到达上限
+        validateCustomerExceedOwnerLimit(ownerUserId, customers.size());
 
         // 2. 领取公海数据
         List<CrmCustomerDO> updateCustomers = new ArrayList<>();
@@ -277,7 +294,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
         // 3.1 更新客户负责人
         customerMapper.updateBatch(updateCustomers);
         // 3.2 创建负责人数据权限
-        crmPermissionService.createPermissionBatch(createPermissions);
+        permissionService.createPermissionBatch(createPermissions);
     }
 
     private void validateCustomerOwnerExists(CrmCustomerDO customer, Boolean pool) {

From 2e7c3e09a9b1d8deba1b2dffe24e3187b3efb905 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Wed, 3 Jan 2024 19:38:13 +0800
Subject: [PATCH 08/15] =?UTF-8?q?=F0=9F=93=96=20CRM=EF=BC=9Acode=20review?=
 =?UTF-8?q?=20=E6=89=80=E6=9C=89=E4=BB=A3=E7=A0=81=EF=BC=8C=E8=A1=A5?=
 =?UTF-8?q?=E5=85=85=E5=AF=B9=E5=BA=94=20todo?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../admin/business/CrmBusinessController.java |  7 ++-
 .../admin/contact/CrmContactController.java   | 20 +++---
 .../admin/contract/CrmContractController.java | 12 ++--
 .../admin/customer/CrmCustomerController.java |  4 +-
 .../customer/vo/CrmCustomerTransferReqVO.java |  2 +
 .../permission/CrmPermissionController.java   |  1 +
 .../receivable/CrmReceivableController.java   | 14 ++---
 .../CrmReceivablePlanController.java          |  6 +-
 .../convert/business/CrmBusinessConvert.java  | 12 ++--
 .../convert/contact/CrmContactConvert.java    | 63 ++++++-------------
 .../convert/contract/CrmContractConvert.java  | 23 +++----
 .../receivable/CrmReceivableConvert.java      | 27 +++-----
 .../receivable/CrmReceivablePlanConvert.java  | 30 +++------
 .../customer/CrmCustomerServiceImpl.java      | 20 ++++--
 14 files changed, 102 insertions(+), 139 deletions(-)

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
index 20355610b..e7bc01e18 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
@@ -99,7 +99,6 @@ public class CrmBusinessController {
         return success(buildBusinessDetailPageResult(pageResult));
     }
 
-
     @GetMapping("/page-by-contact")
     @Operation(summary = "获得联系人的商机分页")
     @PreAuthorize("@ss.hasPermission('crm:business:query')")
@@ -121,6 +120,12 @@ public class CrmBusinessController {
                 buildBusinessDetailPageResult(pageResult).getList());
     }
 
+    /**
+     * 构建详细的商机分页结果
+     *
+     * @param pageResult 简单的商机分页结果
+     * @return 详细的商机分页结果
+     */
     private PageResult<CrmBusinessRespVO> buildBusinessDetailPageResult(PageResult<CrmBusinessDO> pageResult) {
         if (CollUtil.isEmpty(pageResult.getList())) {
             return PageResult.empty(pageResult.getTotal());
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
index 6974b4902..80e8b623b 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
@@ -121,7 +121,7 @@ public class CrmContactController {
     @PreAuthorize("@ss.hasPermission('crm:contact:query')")
     public CommonResult<PageResult<CrmContactRespVO>> getContactPage(@Valid CrmContactPageReqVO pageVO) {
         PageResult<CrmContactDO> pageResult = contactService.getContactPage(pageVO, getLoginUserId());
-        return success(convertDetailContactPage(pageResult));
+        return success(buildContactDetailPage(pageResult));
     }
 
     @GetMapping("/page-by-customer")
@@ -129,7 +129,7 @@ public class CrmContactController {
     public CommonResult<PageResult<CrmContactRespVO>> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) {
         Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
         PageResult<CrmContactDO> pageResult = contactService.getContactPageByCustomerId(pageVO);
-        return success(convertDetailContactPage(pageResult));
+        return success(buildContactDetailPage(pageResult));
     }
 
     @GetMapping("/export-excel")
@@ -141,16 +141,16 @@ public class CrmContactController {
         exportReqVO.setPageNo(PAGE_SIZE_NONE);
         PageResult<CrmContactDO> pageResult = contactService.getContactPage(exportReqVO, getLoginUserId());
         ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class,
-                convertDetailContactPage(pageResult).getList());
+                buildContactDetailPage(pageResult).getList());
     }
 
     /**
-     * 转换成详细的联系人分页,即读取关联信息
+     * 构建详细的联系人分页结果
      *
-     * @param pageResult 联系人分页
-     * @return 详细的联系人分页
+     * @param pageResult 简单的联系人分页结果
+     * @return 详细的联系人分页结果
      */
-    private PageResult<CrmContactRespVO> convertDetailContactPage(PageResult<CrmContactDO> pageResult) {
+    private PageResult<CrmContactRespVO> buildContactDetailPage(PageResult<CrmContactDO> pageResult) {
         List<CrmContactDO> contactList = pageResult.getList();
         if (CollUtil.isEmpty(contactList)) {
             return PageResult.empty(pageResult.getTotal());
@@ -175,10 +175,10 @@ public class CrmContactController {
         return success(true);
     }
 
-    // ================== 关联/取关商机  ===================
+    // ================== 关联/取关联系人  ===================
 
     @PostMapping("/create-business-list")
-    @Operation(summary = "创建联系人与商机的关联")
+    @Operation(summary = "创建联系人与联系人的关联")
     @PreAuthorize("@ss.hasPermission('crm:contact:create-business')")
     public CommonResult<Boolean> createContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO createReqVO) {
         contactBusinessLinkService.createContactBusinessList(createReqVO);
@@ -186,7 +186,7 @@ public class CrmContactController {
     }
 
     @DeleteMapping("/delete-business-list")
-    @Operation(summary = "删除联系人与商机的关联")
+    @Operation(summary = "删除联系人与联系人的关联")
     @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
     public CommonResult<Boolean> deleteContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO deleteReqVO) {
         contactBusinessLinkService.deleteContactBusinessList(deleteReqVO);
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
index 849e424a2..0a4043772 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -88,7 +88,7 @@ public class CrmContractController {
     @PreAuthorize("@ss.hasPermission('crm:contract:query')")
     public CommonResult<PageResult<ContractRespVO>> getContractPage(@Valid CrmContractPageReqVO pageVO) {
         PageResult<CrmContractDO> pageResult = contractService.getContractPage(pageVO, getLoginUserId());
-        return success(convertDetailContractPage(pageResult));
+        return success(buildContractDetailPage(pageResult));
     }
 
     @GetMapping("/page-by-customer")
@@ -96,7 +96,7 @@ public class CrmContractController {
     public CommonResult<PageResult<ContractRespVO>> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) {
         Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
         PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageVO);
-        return success(convertDetailContractPage(pageResult));
+        return success(buildContractDetailPage(pageResult));
     }
 
     @GetMapping("/export-excel")
@@ -112,12 +112,12 @@ public class CrmContractController {
     }
 
     /**
-     * 转换成详细的合同分页,即读取关联信息
+     * 构建详细的合同分页结果
      *
-     * @param pageResult 合同分页
-     * @return 详细的合同分页
+     * @param pageResult 简单的合同分页结果
+     * @return 详细的合同分页结果
      */
-    private PageResult<ContractRespVO> convertDetailContractPage(PageResult<CrmContractDO> pageResult) {
+    private PageResult<ContractRespVO> buildContractDetailPage(PageResult<CrmContractDO> pageResult) {
         List<CrmContractDO> contactList = pageResult.getList();
         if (CollUtil.isEmpty(contactList)) {
             return PageResult.empty(pageResult.getTotal());
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
index dfa02c0b2..1ba9cc010 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -136,7 +136,7 @@ public class CrmCustomerController {
     }
 
     @PutMapping("/transfer")
-    @Operation(summary = "客户转移")
+    @Operation(summary = "转移客户")
     @OperateLog(enable = false) // TODO 关闭原有日志记录
     @PreAuthorize("@ss.hasPermission('crm:customer:update')")
     public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
@@ -184,6 +184,7 @@ public class CrmCustomerController {
         return success(true);
     }
 
+    // TODO @puhui999:需要搞个 VO 类
     @PutMapping("/distribute")
     @Operation(summary = "分配公海给对应负责人")
     @Parameters({
@@ -193,7 +194,6 @@ public class CrmCustomerController {
     @PreAuthorize("@ss.hasPermission('crm:customer:distribute')")
     public CommonResult<Boolean> distributeCustomer(@RequestParam(value = "ids") List<Long> ids,
                                                     @RequestParam(value = "ownerUserId") Long ownerUserId) {
-        // 领取公海数据
         customerService.receiveCustomer(ids, ownerUserId);
         return success(true);
     }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java
index 9bdc43532..09eca73ea 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java
@@ -28,4 +28,6 @@ public class CrmCustomerTransferReqVO {
     @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
     private Integer oldOwnerPermissionLevel;
 
+    // TODO @puhui999:联系人、商机、合同的转移
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java
index 5dc3807f5..7350e6915 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java
@@ -103,6 +103,7 @@ public class CrmPermissionController {
         // 拼接数据
         List<AdminUserRespDTO> userList = adminUserApi.getUserList(convertSet(permission, CrmPermissionDO::getUserId));
         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId));
+        // TODO @puhui999:可能 postIds 为空的时候,会导致报错,看看怎么 fix 下
         Set<Long> postIds = CollectionUtils.convertSetByFlatMap(userList, AdminUserRespDTO::getPostIds, Collection::stream);
         Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
         return success(CrmPermissionConvert.INSTANCE.convert(permission, userList, deptMap, postMap));
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
index 98355077e..e5ae4fd9b 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
@@ -93,7 +93,7 @@ public class CrmReceivableController {
     @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
     public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) {
         PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId());
-        return success(convertDetailReceivablePage(pageResult));
+        return success(buildReceivableDetailPage(pageResult));
     }
 
     @GetMapping("/page-by-customer")
@@ -101,7 +101,7 @@ public class CrmReceivableController {
     public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) {
         Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
         PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO);
-        return success(convertDetailReceivablePage(pageResult));
+        return success(buildReceivableDetailPage(pageResult));
     }
 
     // TODO 芋艿:后面在优化导出
@@ -115,16 +115,16 @@ public class CrmReceivableController {
         PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId());
         // 导出 Excel
         ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class,
-                convertDetailReceivablePage(pageResult).getList());
+                buildReceivableDetailPage(pageResult).getList());
     }
 
     /**
-     * 转换成详细的回款分页,即读取关联信息
+     * 构建详细的回款分页结果
      *
-     * @param pageResult 回款分页
-     * @return 详细的回款分页
+     * @param pageResult 简单的回款分页结果
+     * @return 详细的回款分页结果
      */
-    private PageResult<CrmReceivableRespVO> convertDetailReceivablePage(PageResult<CrmReceivableDO> pageResult) {
+    private PageResult<CrmReceivableRespVO> buildReceivableDetailPage(PageResult<CrmReceivableDO> pageResult) {
         List<CrmReceivableDO> receivableList = pageResult.getList();
         if (CollUtil.isEmpty(receivableList)) {
             return PageResult.empty(pageResult.getTotal());
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
index 6dc540e42..7ff0a9385 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
@@ -123,10 +123,10 @@ public class CrmReceivablePlanController {
     }
 
     /**
-     * 转换成详细的回款计划分页,即读取关联信息
+     * 构建详细的回款计划分页结果
      *
-     * @param pageResult 回款计划分页
-     * @return 详细的回款计划分页
+     * @param pageResult 简单的回款计划分页结果
+     * @return 详细的回款计划分页结果
      */
     private PageResult<CrmReceivablePlanRespVO> convertDetailReceivablePlanPage(PageResult<CrmReceivablePlanDO> pageResult) {
         List<CrmReceivablePlanDO> receivablePlanList = pageResult.getList();
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java
index 3effb21e0..a67812f8b 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.crm.convert.business;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
@@ -9,7 +10,6 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
-import org.mapstruct.Mappings;
 import org.mapstruct.factory.Mappers;
 
 import java.util.List;
@@ -34,25 +34,23 @@ public interface CrmBusinessConvert {
     CrmBusinessRespVO convert(CrmBusinessDO bean);
     List<CrmBusinessRespVO> convert(List<CrmBusinessDO> bean);
 
-    PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> page);
-
     List<CrmBusinessExcelVO> convertList02(List<CrmBusinessDO> list);
 
     @Mapping(target = "bizId", source = "reqVO.id")
     CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId);
 
-    default PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> page, List<CrmCustomerDO> customerList,
+    default PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> pageResult, List<CrmCustomerDO> customerList,
                                                       List<CrmBusinessStatusTypeDO> statusTypeList, List<CrmBusinessStatusDO> statusList) {
-        PageResult<CrmBusinessRespVO> result = convertPage(page);
+        PageResult<CrmBusinessRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmBusinessRespVO.class);
         // 拼接关联字段
         Map<Long, String> customerMap = convertMap(customerList, CrmCustomerDO::getId, CrmCustomerDO::getName);
         Map<Long, String> statusTypeMap = convertMap(statusTypeList, CrmBusinessStatusTypeDO::getId, CrmBusinessStatusTypeDO::getName);
         Map<Long, String> statusMap = convertMap(statusList, CrmBusinessStatusDO::getId, CrmBusinessStatusDO::getName);
-        result.getList().forEach(type -> type
+        voPageResult.getList().forEach(type -> type
                 .setCustomerName(customerMap.get(type.getCustomerId()))
                 .setStatusTypeName(statusTypeMap.get(type.getStatusTypeId()))
                 .setStatusName(statusMap.get(type.getStatusId())));
-        return result;
+        return voPageResult;
     }
 
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
index d41a4f6b3..8d023618f 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.convert.contact;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
@@ -18,7 +19,6 @@ import java.util.Map;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
 
-// TODO 芋艿:convert 后面在梳理下,略微有点乱
 /**
  * CRM 联系人 Convert
  *
@@ -39,64 +39,39 @@ public interface CrmContactConvert {
 
     PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> page);
 
-    default PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
-                                                     List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
-        List<CrmContactRespVO> list = converList(pageResult.getList(), userMap, customerList, parentContactList);
-        return convertPage(pageResult).setList(list);
-    }
-
     List<CrmContactSimpleRespVO> convertAllList(List<CrmContactDO> list);
 
     @Mapping(target = "bizId", source = "reqVO.id")
     CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId);
 
-    /**
-     * 转换详情信息
-     *
-     * @param contactDO         联系人
-     * @param userMap           用户列表
-     * @param customerDOList 客户
-     * @return ContactRespVO
-     */
+    default PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
+                                                     List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
+        PageResult<CrmContactRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmContactRespVO.class);
+        // 拼接关联字段
+        Map<Long, CrmContactDO> parentContactMap = convertMap(parentContactList, CrmContactDO::getId);
+        Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
+        voPageResult.getList().forEach(item -> {
+            setUserInfo(item, userMap);
+            findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
+            findAndThen(parentContactMap, item.getParentId(), contactDO -> item.setParentName(contactDO.getName()));
+        });
+        return voPageResult;
+    }
+
     default CrmContactRespVO convert(CrmContactDO contactDO, Map<Long, AdminUserRespDTO> userMap,
-                                     List<CrmCustomerDO> customerDOList, List<CrmContactDO> contactList) {
+                                     List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
         CrmContactRespVO contactVO = convert(contactDO);
         setUserInfo(contactVO, userMap);
-        Map<Long, CrmCustomerDO> customerMap = CollectionUtils.convertMap(customerDOList, CrmCustomerDO::getId);
-        Map<Long, CrmContactDO> contactMap = CollectionUtils.convertMap(contactList, CrmContactDO::getId);
+        Map<Long, CrmCustomerDO> customerMap = CollectionUtils.convertMap(customerList, CrmCustomerDO::getId);
+        Map<Long, CrmContactDO> contactMap = CollectionUtils.convertMap(parentContactList, CrmContactDO::getId);
         findAndThen(customerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName()));
         findAndThen(contactMap, contactDO.getParentId(), contact -> contactVO.setParentName(contact.getName()));
         return contactVO;
     }
 
-    default List<CrmContactRespVO> converList(List<CrmContactDO> contactList, Map<Long, AdminUserRespDTO> userMap,
-                                              List<CrmCustomerDO> customerList, List<CrmContactDO> parentContactList) {
-        List<CrmContactRespVO> result = convertList(contactList);
-        Map<Long, CrmContactDO> parentContactMap = convertMap(parentContactList, CrmContactDO::getId);
-        Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
-        result.forEach(item -> {
-            setUserInfo(item, userMap);
-            findAndThen(customerMap, item.getCustomerId(), customer ->
-                item.setCustomerName(customer.getName())
-            );
-            findAndThen(parentContactMap, item.getParentId(), contactDO ->
-                item.setParentName(contactDO.getName())
-            );
-        });
-        return result;
-    }
-
-    /**
-     * 设置用户信息
-     *
-     * @param contactRespVO 联系人Response VO
-     * @param userMap       用户信息 map
-     */
     static void setUserInfo(CrmContactRespVO contactRespVO, Map<Long, AdminUserRespDTO> userMap) {
         contactRespVO.setAreaName(AreaUtils.format(contactRespVO.getAreaId()));
-        findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> {
-            contactRespVO.setOwnerUserName(user == null ? "" : user.getNickname());
-        });
+        findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> contactRespVO.setOwnerUserName(user.getNickname()));
         findAndThen(userMap, Long.parseLong(contactRespVO.getCreator()), user -> contactRespVO.setCreatorName(user.getNickname()));
     }
 
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
index 74b7a4663..b898ef4e3 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.crm.convert.contract;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@@ -43,23 +44,15 @@ public interface CrmContractConvert {
 
     default PageResult<ContractRespVO> convertPage(PageResult<CrmContractDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
                                                      List<CrmCustomerDO> customerList) {
-        return new PageResult<>(converList(pageResult.getList(), userMap, customerList), pageResult.getTotal());
-    }
-
-    default List<ContractRespVO> converList(List<CrmContractDO> contractList, Map<Long, AdminUserRespDTO> userMap,
-                                            List<CrmCustomerDO> customerList) {
-        List<ContractRespVO> result = convertList(contractList);
+        PageResult<ContractRespVO> voPageResult = BeanUtils.toBean(pageResult, ContractRespVO.class);
+        // 拼接关联字段
         Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
-        result.forEach(item -> {
-            setUserInfo(item, userMap);
-            findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
+        voPageResult.getList().forEach(contract -> {
+            findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname()));
+            findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname()));
+            findAndThen(customerMap, contract.getCustomerId(), customer -> contract.setCustomerName(customer.getName()));
         });
-        return result;
-    }
-
-    static void setUserInfo(ContractRespVO contract, Map<Long, AdminUserRespDTO> userMap) {
-        findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname()));
-        findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname()));
+        return voPageResult;
     }
 
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java
index e7340fc86..9f343238a 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.crm.convert.receivable;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableTransferReqVO;
@@ -36,29 +37,19 @@ public interface CrmReceivableConvert {
 
     CrmReceivableRespVO convert(CrmReceivableDO bean);
 
-    List<CrmReceivableRespVO> convertList(List<CrmReceivableDO> list);
-
     default PageResult<CrmReceivableRespVO> convertPage(PageResult<CrmReceivableDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
                                                         List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) {
-        return new PageResult<>(converList(pageResult.getList(), userMap, customerList, contractList), pageResult.getTotal());
-    }
-
-    default List<CrmReceivableRespVO> converList(List<CrmReceivableDO> receivableList, Map<Long, AdminUserRespDTO> userMap,
-                                                 List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) {
-        List<CrmReceivableRespVO> result = convertList(receivableList);
+        PageResult<CrmReceivableRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class);
+        // 拼接关联字段
         Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
         Map<Long, CrmContractDO> contractMap = convertMap(contractList, CrmContractDO::getId);
-        result.forEach(item -> {
-            setUserInfo(item, userMap);
-            findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
-            findAndThen(contractMap, item.getContractId(), contract -> item.setContractNo(contract.getNo()));
+        voPageResult.getList().forEach(receivable -> {
+            findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname()));
+            findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname()));
+            findAndThen(customerMap, receivable.getCustomerId(), customer -> receivable.setCustomerName(customer.getName()));
+            findAndThen(contractMap, receivable.getContractId(), contract -> receivable.setContractNo(contract.getNo()));
         });
-        return result;
-    }
-
-    static void setUserInfo(CrmReceivableRespVO receivable, Map<Long, AdminUserRespDTO> userMap) {
-        findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname()));
-        findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname()));
+        return voPageResult;
     }
 
     @Mapping(target = "bizId", source = "reqVO.id")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java
index 70e930880..eb459e071 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.crm.convert.receivable;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanTransferReqVO;
@@ -37,33 +38,22 @@ public interface CrmReceivablePlanConvert {
 
     CrmReceivablePlanRespVO convert(CrmReceivablePlanDO bean);
 
-    List<CrmReceivablePlanRespVO> convertList(List<CrmReceivablePlanDO> list);
-
     default PageResult<CrmReceivablePlanRespVO> convertPage(PageResult<CrmReceivablePlanDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
                                                             List<CrmCustomerDO> customerList, List<CrmContractDO> contractList,
                                                             List<CrmReceivableDO> receivableList) {
-        return new PageResult<>(converList(pageResult.getList(), userMap, customerList, contractList, receivableList), pageResult.getTotal());
-    }
-
-    default List<CrmReceivablePlanRespVO> converList(List<CrmReceivablePlanDO> receivablePlanList, Map<Long, AdminUserRespDTO> userMap,
-                                                     List<CrmCustomerDO> customerList, List<CrmContractDO> contractList,
-                                                     List<CrmReceivableDO> receivableList) {
-        List<CrmReceivablePlanRespVO> result = convertList(receivablePlanList);
+        PageResult<CrmReceivablePlanRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivablePlanRespVO.class);
+        // 拼接关联字段
         Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
         Map<Long, CrmContractDO> contractMap = convertMap(contractList, CrmContractDO::getId);
         Map<Long, CrmReceivableDO> receivableMap = convertMap(receivableList, CrmReceivableDO::getId);
-        result.forEach(item -> {
-            setUserInfo(item, userMap);
-            findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName()));
-            findAndThen(contractMap, item.getContractId(), contract -> item.setContractNo(contract.getNo()));
-            findAndThen(receivableMap, item.getReceivableId(), receivable -> item.setReturnType(receivable.getReturnType()));
+        voPageResult.getList().forEach(receivablePlan -> {
+            findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname()));
+            findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname()));
+            findAndThen(customerMap, receivablePlan.getCustomerId(), customer -> receivablePlan.setCustomerName(customer.getName()));
+            findAndThen(contractMap, receivablePlan.getContractId(), contract -> receivablePlan.setContractNo(contract.getNo()));
+            findAndThen(receivableMap, receivablePlan.getReceivableId(), receivable -> receivablePlan.setReturnType(receivable.getReturnType()));
         });
-        return result;
-    }
-
-    static void setUserInfo(CrmReceivablePlanRespVO receivablePlan, Map<Long, AdminUserRespDTO> userMap) {
-        findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname()));
-        findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname()));
+        return voPageResult;
     }
 
     @Mapping(target = "bizId", source = "reqVO.id")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
index bb900b784..ad0ee9a76 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -105,11 +105,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
     public void deleteCustomer(Long id) {
         // 校验存在
         validateCustomerExists(id);
+        // TODO @puhui999:如果有联系人、商机,则不允许删除;
 
         // 删除
         customerMapper.deleteById(id);
         // 删除数据权限
         permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
+        // TODO @puhui999:删除跟进记录
     }
 
     private CrmCustomerDO validateCustomerExists(Long id) {
@@ -151,7 +153,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    @LogRecord(type = CRM_CUSTOMER, subType = "客户转移", bizNo = "{{#reqVO.id}}", success = TRANSFER_CUSTOMER_LOG_SUCCESS)
+    @LogRecord(type = CRM_CUSTOMER, subType = "转移客户", bizNo = "{{#reqVO.id}}", success = TRANSFER_CUSTOMER_LOG_SUCCESS)
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
     public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) {
         // 1.1 校验客户是否存在
@@ -235,7 +237,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    @LogRecord(type = CRM_CUSTOMER, subType = "客户放入公海", bizNo = "{{#id}}", success = "将客户放入了公海")
+    @LogRecord(type = CRM_CUSTOMER, subType = "客户放入公海", bizNo = "{{#id}}", success = "将客户放入了公海") // TODO @puhui999:将客户【】放入了公海
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
     public void putCustomerPool(Long id) {
         // 1. 校验存在
@@ -256,10 +258,16 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
         // 3. 删除负责人数据权限
         permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(),
                 CrmPermissionLevelEnum.OWNER.getLevel());
+        // TODO @puhui999:联系人的负责人,也要设置为 null;这块和领取是对应的;因为领取后,负责人也要关联过来;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    // TODO @puhui999:权限校验
+
+    // TODO @puhui999:如果是分配,操作日志是 “将客户【】分配给【】”
+    // TODO @puhui999:如果是领取,操作日志是“领取客户【】”;
+    // TODO @puhui999:如果是多条,则需要记录多条操作日志;不然 bizId 不好关联
     public void receiveCustomer(List<Long> ids, Long ownerUserId) {
         // 1.1 校验存在
         List<CrmCustomerDO> customers = customerMapper.selectBatchIds(ids);
@@ -280,7 +288,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
         // 1.4  校验负责人是否到达上限
         validateCustomerExceedOwnerLimit(ownerUserId, customers.size());
 
-        // 2. 领取公海数据
+        // 2.1 领取公海数据
         List<CrmCustomerDO> updateCustomers = new ArrayList<>();
         List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();
         customers.forEach(customer -> {
@@ -290,11 +298,11 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
             createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
                     .setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
         });
-
-        // 3.1 更新客户负责人
+        // 2.2 更新客户负责人
         customerMapper.updateBatch(updateCustomers);
-        // 3.2 创建负责人数据权限
+        // 2.3 创建负责人数据权限
         permissionService.createPermissionBatch(createPermissions);
+        // TODO @芋艿:要不要处理关联的联系人???
     }
 
     private void validateCustomerOwnerExists(CrmCustomerDO customer, Boolean pool) {

From f8f5e525be05a2476ab3e59084541a4cefd6bcfa Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Wed, 3 Jan 2024 20:56:28 +0800
Subject: [PATCH 09/15] =?UTF-8?q?=E2=9C=A8=20CRM=EF=BC=9A=E6=96=B0?=
 =?UTF-8?q?=E5=A2=9E=E8=B7=9F=E8=BF=9B=E8=AE=B0=E5=BD=95=E8=A1=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../CrmCustomerLimitConfigController.java     |  1 +
 .../CrmCustomerPoolConfigController.java      |  1 +
 .../CrmCustomerPoolConfigSaveReqVO.java       |  6 +-
 .../dataobject/customer/CrmCustomerDO.java    |  1 +
 .../followup/CrmFollowUpRecordDO.java         | 81 +++++++++++++++++++
 5 files changed, 87 insertions(+), 3 deletions(-)
 create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java
index ec86c99d5..7636bc200 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java
@@ -43,6 +43,7 @@ public class CrmCustomerLimitConfigController {
     @Resource
     private AdminUserApi adminUserApi;
 
+    // TODO @puhui999:可以把 vo 改下哈
     @PostMapping("/create")
     @Operation(summary = "创建客户限制配置")
     @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:create')")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java
index a6da45b7d..8ae809337 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java
@@ -26,6 +26,7 @@ public class CrmCustomerPoolConfigController {
     @Resource
     private CrmCustomerPoolConfigService customerPoolConfigService;
 
+    // TODO @puhui999:可以把 vo 改下哈
     @GetMapping("/get")
     @Operation(summary = "获取客户公海规则设置")
     @PreAuthorize("@ss.hasPermission('crm:customer-pool-config:query')")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java
index fa72f5f74..93b6557a4 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java
@@ -16,9 +16,9 @@ import java.util.Objects;
 @ToString(callSuper = true)
 public class CrmCustomerPoolConfigSaveReqVO extends CrmCustomerPoolConfigBaseVO {
 
-    // TODO @wanwan:AssertTrue 必须 is 开头哈;注意需要 json 忽略下,避免被序列化;
+    // TODO @puhui999:AssertTrue 必须 is 开头哈;注意需要 json 忽略下,避免被序列化;
     @AssertTrue(message = "客户公海规则设置不正确")
-    // TODO @wanwan:这个方法,是不是拆成 2 个,一个校验 contactExpireDays、一个校验 dealExpireDays;
+    // TODO @puhui999:这个方法,是不是拆成 2 个,一个校验 contactExpireDays、一个校验 dealExpireDays;
     public boolean poolEnableValid() {
         if (!BooleanUtil.isTrue(getEnabled())) {
             return true;
@@ -27,7 +27,7 @@ public class CrmCustomerPoolConfigSaveReqVO extends CrmCustomerPoolConfigBaseVO
     }
 
     @AssertTrue(message = "客户公海规则设置不正确")
-    // TODO @wanwan:这个方法,是不是改成 isNotifyDaysValid() 更好点?本质校验的是 notifyDays 是否为空
+    // TODO @puhui999:这个方法,是不是改成 isNotifyDaysValid() 更好点?本质校验的是 notifyDays 是否为空
     public boolean notifyEnableValid() {
         if (!BooleanUtil.isTrue(getNotifyEnabled())) {
             return true;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
index c5826b7c3..3cc42a5d2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
@@ -115,6 +115,7 @@ public class CrmCustomerDO extends BaseDO {
      * 最后跟进时间
      */
     private LocalDateTime contactLastTime;
+    // TODO @puhui999:增加一个字段 contactLastContent;最后跟进内容
     /**
      * 下次联系时间
      */
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java
new file mode 100644
index 000000000..b9138da92
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.followup;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+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.enums.common.CrmBizTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+// TODO @puhui999:界面:做成一个 list 列表,字段是 id、跟进人、跟进方式、跟进时间、跟进内容、下次联系时间、关联联系人、关联商机
+// TODO @puhui999:界面:记录时,弹窗,表单字段是跟进方式、跟进内容、下次联系时间、关联联系人、关联商机;其中关联联系人、关联商机,要做成对应的组件列。
+/**
+ * 跟进记录 DO
+ *
+ * 用于记录客户、联系人的每一次跟进
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "crm_follow_up_record")
+@KeySequence("crm_follow_up_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmFollowUpRecordDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 数据类型
+     *
+     * 枚举 {@link CrmBizTypeEnum}
+     */
+    private Integer bizType;
+    /**
+     * 数据编号
+     *
+     * 关联 {@link CrmBizTypeEnum} 对应模块 DO 的 id 字段
+     */
+    private Long bizId;
+
+    /**
+     * 跟进类型
+     *
+     * TODO @puhui999:可以搞个数据字典,打电话、发短信、上门拜访、微信、邮箱、QQ
+     */
+    private Integer type;
+    /**
+     * 跟进内容
+     */
+    private String content;
+    /**
+     * 下次联系时间
+     */
+    private LocalDateTime nextTime;
+
+    /**
+     * 关联的商机编号数组
+     *
+     * 关联 {@link CrmBusinessDO#getId()}
+     */
+    private List<Long> businessIds;
+    /**
+     * 关联的联系人编号数组
+     *
+     * 关联 {@link CrmContactDO#getId()}
+     */
+    private List<Long> contactIds;
+
+}

From cff6e520ee99c519e497423fc5b75fb9f5bc4321 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Wed, 3 Jan 2024 22:07:20 +0800
Subject: [PATCH 10/15] =?UTF-8?q?=F0=9F=93=96=20CRM=EF=BC=9A=E3=80=90?=
 =?UTF-8?q?=E8=81=94=E7=B3=BB=E4=BA=BA=E3=80=91code=20review=20=E8=81=94?=
 =?UTF-8?q?=E7=B3=BB=E4=BA=BA=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=8C=E8=A1=A5?=
 =?UTF-8?q?=E5=85=A8=E5=AF=B9=E5=BA=94=E7=9A=84=20todo=20list?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../admin/contact/CrmContactController.java   |  5 +++-
 .../convert/contact/CrmContactConvert.java    |  2 --
 .../contact/CrmContactServiceImpl.java        | 25 +++++++++++++------
 3 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
index 80e8b623b..dfb269c8f 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
@@ -6,6 +6,7 @@ import cn.hutool.core.util.NumberUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
@@ -61,6 +62,7 @@ public class CrmContactController {
     @Resource
     private CrmContactBusinessService contactBusinessLinkService;
 
+    // TODO @zyna:CrmContactCreateReqVO、CrmContactUpdateReqVO、CrmContactRespVO 按照新的 VO 规范搞哈;可以参考 dept 模块
     @PostMapping("/create")
     @Operation(summary = "创建联系人")
     @PreAuthorize("@ss.hasPermission('crm:contact:create')")
@@ -110,10 +112,11 @@ public class CrmContactController {
     @Operation(summary = "获得联系人列表")
     @PreAuthorize("@ss.hasPermission('crm:contact:query')")
     public CommonResult<List<CrmContactSimpleRespVO>> getSimpleContactList() {
+        // TODO @zyna:建议 contactService 单独搞个 list 接口哈
         CrmContactPageReqVO pageReqVO = new CrmContactPageReqVO();
         pageReqVO.setPageSize(PAGE_SIZE_NONE);
         List<CrmContactDO> list = contactService.getContactPage(pageReqVO, getLoginUserId()).getList();
-        return success(CrmContactConvert.INSTANCE.convertAllList(list));
+        return success(BeanUtils.toBean(list, CrmContactSimpleRespVO.class));
     }
 
     @GetMapping("/page")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
index 8d023618f..de248d2c6 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
@@ -39,8 +39,6 @@ public interface CrmContactConvert {
 
     PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> page);
 
-    List<CrmContactSimpleRespVO> convertAllList(List<CrmContactDO> list);
-
     @Mapping(target = "bizId", source = "reqVO.id")
     CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId);
 
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
index 520038008..d55ce447b 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
@@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
 import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper;
-import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper;
 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;
@@ -47,32 +46,35 @@ public class CrmContactServiceImpl implements CrmContactService {
     @Resource
     private AdminUserApi adminUserApi;
 
-    @Resource
-    private CrmContactBusinessMapper contactBusinessLinkMapper;
-
     @Override
     @Transactional(rollbackFor = Exception.class)
+    // TODO @zyna:增加操作日志,可以参考 CustomerService;内容是 新建了联系人【名字】
     public Long createContact(CrmContactCreateReqVO createReqVO, Long userId) {
-        // 1.1 校验
+        // 1. 校验
         validateRelationDataExists(createReqVO);
-        // 1.2 插入
+
+        // 2. 插入联系人
         CrmContactDO contact = CrmContactConvert.INSTANCE.convert(createReqVO);
         contactMapper.insert(contact);
 
-        // 2. 创建数据权限
+        // 3. 创建数据权限
         crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId)
                 .setBizType(CrmBizTypeEnum.CRM_CONTACT.getType()).setBizId(contact.getId())
                 .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
+
+        // TODO @zyna:特殊逻辑:如果在【商机】详情那,点击【新增联系人】时,可以自动绑定商机
         return contact.getId();
     }
 
     @Override
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
+    // TODO @zyna:增加操作日志,可以参考 CustomerService;需要 diff 出字段
     public void updateContact(CrmContactUpdateReqVO updateReqVO) {
         // 1. 校验存在
         validateContactExists(updateReqVO.getId());
         validateRelationDataExists(updateReqVO);
-        // 2. 更新
+
+        // 2. 更新联系人
         CrmContactDO updateObj = CrmContactConvert.INSTANCE.convert(updateReqVO);
         contactMapper.updateById(updateObj);
     }
@@ -102,10 +104,15 @@ public class CrmContactServiceImpl implements CrmContactService {
     public void deleteContact(Long id) {
         // 校验存在
         validateContactExists(id);
+        // TODO @zyna:如果有关联的合同,不允许删除;Contract.contactId
+
         // 删除
         contactMapper.deleteById(id);
         // 删除数据权限
         crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
+        // TODO @zyna:删除商机联系人关联
+
+        // TODO @puhui999:删除跟进记录
     }
 
     private void validateContactExists(Long id) {
@@ -140,6 +147,8 @@ public class CrmContactServiceImpl implements CrmContactService {
     }
 
     @Override
+    // TODO @puhui999:权限校验
+    // TODO @puhui999:记录操作日志;将联系人【名字】转移给【新负责人】
     public void transferContact(CrmContactTransferReqVO reqVO, Long userId) {
         // 1 校验联系人是否存在
         validateContactExists(reqVO.getId());

From 02324f9e6091fbaa6c867e830ea1294717a2033a Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Wed, 3 Jan 2024 23:30:25 +0800
Subject: [PATCH 11/15] =?UTF-8?q?=E2=9C=A8=20CRM=EF=BC=9A=E3=80=90?=
 =?UTF-8?q?=E8=81=94=E7=B3=BB=E4=BA=BA=E3=80=91=E7=BB=9F=E4=B8=80=E5=AD=97?=
 =?UTF-8?q?=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../admin/contact/vo/CrmContactBaseVO.java    | 30 ++++++++---------
 .../dal/dataobject/contact/CrmContactDO.java  | 33 +++++++++++--------
 .../dataobject/customer/CrmCustomerDO.java    |  7 ++--
 3 files changed, 39 insertions(+), 31 deletions(-)

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBaseVO.java
index eb984884d..f210bfd8f 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBaseVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBaseVO.java
@@ -8,11 +8,11 @@ import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 
-import jakarta.validation.constraints.Email;
-import jakarta.validation.constraints.NotNull;
 import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@@ -75,29 +75,29 @@ public class CrmContactBaseVO {
     @ExcelProperty(value = "邮箱",order = 4)
     private String email;
 
+    @Schema(description = "地区编号", example = "20158")
+    private Integer areaId;
+
     @ExcelProperty(value = "地址",order = 5)
     @Schema(description = "地址")
-    private String address;
-
-    @Schema(description = "下次联系时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
-    @ExcelProperty(value = "下次联系时间",order = 6)
-    private LocalDateTime nextTime;
+    private String detailAddress;
 
     @Schema(description = "备注", example = "你说的对")
     @ExcelProperty(value = "备注",order = 6)
     private String remark;
 
-    @Schema(description = "最后跟进时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @ExcelProperty(value = "最后跟进时间",order = 6)
-    private LocalDateTime lastTime;
-
     @Schema(description = "负责人用户编号", example = "14334")
     @NotNull(message = "负责人不能为空")
     private Long ownerUserId;
 
-    @Schema(description = "地区编号", example = "20158")
-    private Integer areaId;
+    @Schema(description = "最后跟进时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ExcelProperty(value = "最后跟进时间",order = 6)
+    private LocalDateTime contactLastTime;
+
+    @Schema(description = "下次联系时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    @ExcelProperty(value = "下次联系时间",order = 6)
+    private LocalDateTime contactNextTime;
 
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java
index 75d6bd565..74ced6032 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.crm.dal.dataobject.contact;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -29,9 +30,11 @@ public class CrmContactDO extends BaseDO {
     @TableId
     private Long id;
     /**
-     * 下次联系时间
+     * 客户编号
+     *
+     * 关联 {@link CrmCustomerDO#getId()}
      */
-    private LocalDateTime nextTime;
+    private Long customerId;
     /**
      * 手机号
      */
@@ -45,21 +48,20 @@ public class CrmContactDO extends BaseDO {
      */
     private String email;
     /**
-     * 客户编号
+     * 所在地
+     *
+     * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
      */
-    private Long customerId;
+    private Integer areaId;
     /**
-     * 地址
+     * 详细地址
      */
-    private String address;
+    private String detailAddress;
     /**
      * 备注
      */
     private String remark;
-    /**
-     * 最后跟进时间
-     */
-    private LocalDateTime lastTime;
+
     /**
      * 直属上级
      *
@@ -100,10 +102,13 @@ public class CrmContactDO extends BaseDO {
     private Long ownerUserId;
 
     /**
-     * 所在地
-     *
-     * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
+     * 最后跟进时间
      */
-    private Integer areaId;
+    private LocalDateTime contactLastTime;
+    // TODO @puhui999:增加一个字段 contactLastContent;最后跟进内容
+    /**
+     * 下次联系时间
+     */
+    private LocalDateTime contactNextTime;
 
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
index 3cc42a5d2..c35778e3e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
@@ -12,7 +12,7 @@ import java.time.LocalDateTime;
 // TODO 芋艿:调整下字段
 
 /**
- * 客户 DO
+ * CRM 客户 DO
  *
  * @author Wanwan
  */
@@ -104,13 +104,16 @@ public class CrmCustomerDO extends BaseDO {
      */
     private Long ownerUserId;
     /**
-     * 地区编号
+     * 所在地
+     *
+     * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
      */
     private Integer areaId;
     /**
      * 详细地址
      */
     private String detailAddress;
+
     /**
      * 最后跟进时间
      */

From b9f1e8ffaf831e03867a063d21e4a61bd31c7623 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Thu, 4 Jan 2024 07:53:00 +0800
Subject: [PATCH 12/15] =?UTF-8?q?=F0=9F=93=96=20CRM=EF=BC=9A=E3=80=90?=
 =?UTF-8?q?=E8=81=94=E7=B3=BB=E4=BA=BA=E3=80=91code=20review=20=E5=95=86?=
 =?UTF-8?q?=E6=9C=BA=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=8C=E8=A1=A5=E5=85=A8?=
 =?UTF-8?q?=E5=AF=B9=E5=BA=94=E7=9A=84=20todo=20list?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../admin/business/CrmBusinessController.java |  1 +
 .../dal/dataobject/business/package-info.java |  4 ---
 .../business/CrmBusinessServiceImpl.java      | 27 +++++++++++--------
 .../CrmProductCategoryServiceImpl.java        | 13 +++++----
 4 files changed, 23 insertions(+), 22 deletions(-)
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/package-info.java

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
index e7bc01e18..5a5a423cd 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
@@ -50,6 +50,7 @@ public class CrmBusinessController {
     @Resource
     private CrmBusinessStatusService businessStatusService;
 
+    // TODO @商机待定:CrmBusinessCreateReqVO、CrmBusinessUpdateReqVO、CrmBusinessRespVO 按照新的 VO 规范
     @PostMapping("/create")
     @Operation(summary = "创建商机")
     @PreAuthorize("@ss.hasPermission('crm:business:create')")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/package-info.java
deleted file mode 100644
index df6e44536..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * 商机(销售机会)
- */
-package cn.iocoder.yudao.module.crm.dal.dataobject.business;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
index d2d45ba99..58c2bccfe 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -15,7 +15,6 @@ 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;
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
-import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import jakarta.annotation.Resource;
@@ -45,22 +44,22 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     @Resource
     private CrmPermissionService permissionService;
     @Resource
-    private CrmContactService contactService;
-    @Resource
     private CrmContactBusinessService contactBusinessService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    // TODO @商机待定:操作日志;
     public Long createBusiness(CrmBusinessCreateReqVO createReqVO, Long userId) {
-        // 插入
+        // 1. 插入商机
         CrmBusinessDO business = CrmBusinessConvert.INSTANCE.convert(createReqVO);
         businessMapper.insert(business);
+        // TODO 商机待定:插入商机与产品的关联表;校验商品存在
 
-        // 创建数据权限
+        // TODO 商机待定:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表
+
+        // 2. 创建数据权限
         permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
                 .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
-
-        // 返回
         return business.getId();
     }
 
@@ -68,12 +67,17 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     @Transactional(rollbackFor = Exception.class)
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id",
             level = CrmPermissionLevelEnum.WRITE)
+    // TODO @商机待定:操作日志;
     public void updateBusiness(CrmBusinessUpdateReqVO updateReqVO) {
-        // 校验存在
+        // 1. 校验存在
         validateBusinessExists(updateReqVO.getId());
-        // 更新
+
+        // 2. 更新商机
         CrmBusinessDO updateObj = CrmBusinessConvert.INSTANCE.convert(updateReqVO);
         businessMapper.updateById(updateObj);
+        // TODO 商机待定:插入商机与产品的关联表;校验商品存在
+
+        // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表
     }
 
     @Override
@@ -82,6 +86,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     public void deleteBusiness(Long id) {
         // 校验存在
         validateBusinessExists(id);
+        // TODO @商机待定:需要校验有没关联合同。CrmContractDO 的 businessId 字段
+
         // 删除
         businessMapper.deleteById(id);
         // 删除数据权限
@@ -137,6 +143,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    // TODO @puhui999:操作日志
     public void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId) {
         // 1 校验商机是否存在
         validateBusinessExists(reqVO.getId());
@@ -146,8 +153,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
                 CrmBusinessConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()));
         // 2.2 设置新的负责人
         businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
-
-        // 3. TODO 记录转移日志
     }
 
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java
index 388f6133b..23fab65e4 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java
@@ -40,7 +40,7 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService
         validateParentProductCategory(createReqVO.getParentId());
         // 1.2 分类名称是否存在
         validateProductNameExists(null, createReqVO.getParentId(), createReqVO.getName());
-        // 2. 插入
+        // 2. 插入分类
         CrmProductCategoryDO category = BeanUtils.toBean(createReqVO, CrmProductCategoryDO.class);
         productCategoryMapper.insert(category);
         return category.getId();
@@ -54,7 +54,7 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService
         validateParentProductCategory(updateReqVO.getParentId());
         // 1.3 分类名称是否存在
         validateProductNameExists(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
-        // 2. 更新
+        // 2. 更新分类
         CrmProductCategoryDO updateObj = BeanUtils.toBean(updateReqVO, CrmProductCategoryDO.class);
         productCategoryMapper.updateById(updateObj);
     }
@@ -92,18 +92,17 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService
 
     @Override
     public void deleteProductCategory(Long id) {
-        // TODO zange:参考 mall: ProductCategoryServiceImpl 补充下必要的参数校验;
-        // 校验存在
+        // 1.1 校验存在
         validateProductCategoryExists(id);
-        // 校验是否还有子分类
+        // 1.2 校验是否还有子分类
         if (productCategoryMapper.selectCountByParentId(id) > 0) {
             throw exception(product_CATEGORY_EXISTS_CHILDREN);
         }
-        // 校验是否被产品使用
+        // 1.3 校验是否被产品使用
         if (crmProductService.getProductByCategoryId(id) !=null) {
             throw exception(PRODUCT_CATEGORY_USED);
         }
-        // 删除
+        // 2. 删除
         productCategoryMapper.deleteById(id);
     }
 

From 2668e0cfebd0b3793d6698dfa66ecb4444e97c8e Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Thu, 4 Jan 2024 08:01:04 +0800
Subject: [PATCH 13/15] =?UTF-8?q?=F0=9F=93=96=20CRM=EF=BC=9A=E3=80=90?=
 =?UTF-8?q?=E4=BA=A7=E5=93=81=E3=80=91code=20review=20=E4=BA=A7=E5=93=81?=
 =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=8C=E8=A1=A5=E5=85=A8=E5=AF=B9?=
 =?UTF-8?q?=E5=BA=94=E7=9A=84=20todo=20list?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../service/customer/CrmCustomerLimitConfigServiceImpl.java    | 3 +++
 .../crm/service/customer/CrmCustomerPoolConfigServiceImpl.java | 1 +
 .../crm/service/product/CrmProductCategoryServiceImpl.java     | 3 +++
 .../module/crm/service/product/CrmProductServiceImpl.java      | 3 +++
 4 files changed, 10 insertions(+)

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java
index be8ddd968..d9f177549 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java
@@ -40,6 +40,7 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig
     private AdminUserApi adminUserApi;
 
     @Override
+    // TODO @puhui999:操作日志
     public Long createCustomerLimitConfig(CrmCustomerLimitConfigCreateReqVO createReqVO) {
         validateUserAndDept(createReqVO.getUserIds(), createReqVO.getDeptIds());
         // 插入
@@ -50,6 +51,7 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig
     }
 
     @Override
+    // TODO @puhui999:操作日志
     public void updateCustomerLimitConfig(CrmCustomerLimitConfigUpdateReqVO updateReqVO) {
         // 校验存在
         validateCustomerLimitConfigExists(updateReqVO.getId());
@@ -60,6 +62,7 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig
     }
 
     @Override
+    // TODO @puhui999:操作日志
     public void deleteCustomerLimitConfig(Long id) {
         // 校验存在
         validateCustomerLimitConfigExists(id);
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java
index 069bb9ab6..82aa4378f 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java
@@ -38,6 +38,7 @@ public class CrmCustomerPoolConfigServiceImpl implements CrmCustomerPoolConfigSe
      * @param saveReqVO 更新信息
      */
     @Override
+    // TODO @puhui999:操作日志
     public void saveCustomerPoolConfig(CrmCustomerPoolConfigSaveReqVO saveReqVO) {
         // 存在,则进行更新
         CrmCustomerPoolConfigDO dbConfig = getCustomerPoolConfig();
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java
index 23fab65e4..c91ab87e1 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java
@@ -35,6 +35,7 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService
     private CrmProductService crmProductService;
 
     @Override
+    // TODO @puhui999:操作日志
     public Long createProductCategory(CrmProductCategoryCreateReqVO createReqVO) {
         // 1.1 校验父分类存在
         validateParentProductCategory(createReqVO.getParentId());
@@ -47,6 +48,7 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService
     }
 
     @Override
+    // TODO @puhui999:操作日志
     public void updateProductCategory(CrmProductCategoryCreateReqVO updateReqVO) {
         // 1.1 校验存在
         validateProductCategoryExists(updateReqVO.getId());
@@ -91,6 +93,7 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService
     }
 
     @Override
+    // TODO @puhui999:操作日志
     public void deleteProductCategory(Long id) {
         // 1.1 校验存在
         validateProductCategoryExists(id);
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
index ba7b8ac83..e604a47ac 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
@@ -48,6 +48,7 @@ public class CrmProductServiceImpl implements CrmProductService {
     private AdminUserApi adminUserApi;
 
     @Override
+    // TODO @puhui999:操作日志
     public Long createProduct(CrmProductSaveReqVO createReqVO) {
         // 校验产品
         adminUserApi.validateUserList(Collections.singleton(createReqVO.getOwnerUserId()));
@@ -66,6 +67,7 @@ public class CrmProductServiceImpl implements CrmProductService {
     }
 
     @Override
+    // TODO @puhui999:操作日志
     public void updateProduct(CrmProductSaveReqVO updateReqVO) {
         // 校验产品
         updateReqVO.setOwnerUserId(null); // 不修改负责人
@@ -102,6 +104,7 @@ public class CrmProductServiceImpl implements CrmProductService {
     }
 
     @Override
+    // TODO @puhui999:操作日志
     public void deleteProduct(Long id) {
         // 校验存在
         validateProductExists(id);

From c6a975151102dc6f221ac86159ad8a38d871ac55 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Thu, 4 Jan 2024 09:18:44 +0800
Subject: [PATCH 14/15] =?UTF-8?q?=F0=9F=93=96=20CRM=EF=BC=9A=E3=80=90?=
 =?UTF-8?q?=E5=90=88=E5=90=8C=E3=80=91code=20review=20=E5=90=88=E5=90=8C?=
 =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=8C=E8=A1=A5=E5=85=A8=E5=AF=B9?=
 =?UTF-8?q?=E5=BA=94=E7=9A=84=20todo=20list?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../contract/CrmContractServiceImpl.java      | 20 ++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
index 00d6c5e4c..bb010265f 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -42,8 +42,10 @@ public class CrmContractServiceImpl implements CrmContractService {
     private CrmPermissionService crmPermissionService;
 
     @Override
+    // TODO @puhui999:添加操作日志
     public Long createContract(CrmContractCreateReqVO createReqVO, Long userId) {
-        // 插入
+        // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO
+        // 插入合同
         CrmContractDO contract = CrmContractConvert.INSTANCE.convert(createReqVO);
         contractMapper.insert(contract);
 
@@ -57,18 +59,26 @@ public class CrmContractServiceImpl implements CrmContractService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
+    // TODO @puhui999:添加操作日志
     public void updateContract(CrmContractUpdateReqVO updateReqVO) {
+        // TODO @合同待定:只有草稿、审批中,可以编辑;
         // 校验存在
         validateContractExists(updateReqVO.getId());
-        // 更新
+        // 更新合同
         CrmContractDO updateObj = CrmContractConvert.INSTANCE.convert(updateReqVO);
         contractMapper.updateById(updateObj);
+        // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO
     }
 
+    // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
+
+    // TODO @合同待定:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
     public void deleteContract(Long id) {
+        // TODO @合同待定:如果被 CrmReceivableDO 所使用,则不允许删除
         // 校验存在
         validateContractExists(id);
         // 删除
@@ -112,6 +122,8 @@ public class CrmContractServiceImpl implements CrmContractService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    // 3. TODO @puhui999:记录转移日志
+    // TODO @puhui999:权限校验,这里要搞哇?
     public void transferContract(CrmContractTransferReqVO reqVO, Long userId) {
         // 1. 校验合同是否存在
         validateContractExists(reqVO.getId());
@@ -121,9 +133,7 @@ public class CrmContractServiceImpl implements CrmContractService {
                 CrmContractConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()));
         // 2.2 设置负责人
         contractMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
-
-        // 3. TODO 记录转移日志
-
     }
 
+    // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒;
 }

From a974d055b6e2ed323f47aae550e985d38ae8ab08 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Thu, 4 Jan 2024 09:41:58 +0800
Subject: [PATCH 15/15] =?UTF-8?q?=F0=9F=93=96=20CRM=EF=BC=9A=E3=80=90?=
 =?UTF-8?q?=E8=BF=98=E6=AC=BE=E3=80=91code=20review=20=E8=BF=98=E6=AC=BE?=
 =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=8C=E8=A1=A5=E5=85=A8=E5=AF=B9?=
 =?UTF-8?q?=E5=BA=94=E7=9A=84=20todo=20list?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../crm/service/business/package-info.java    |  4 ----
 .../module/crm/service/clue/package-info.java |  4 ----
 .../crm/service/product/package-info.java     |  4 ----
 .../CrmReceivablePlanServiceImpl.java         |  6 +++++
 .../receivable/CrmReceivableServiceImpl.java  | 22 +++++++++++++++----
 .../crm/service/receivable/package-info.java  |  4 ----
 6 files changed, 24 insertions(+), 20 deletions(-)
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/package-info.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/package-info.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/package-info.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/package-info.java

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/package-info.java
deleted file mode 100644
index 8995e1242..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * 商机(销售机会)
- */
-package cn.iocoder.yudao.module.crm.service.business;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/package-info.java
deleted file mode 100644
index 5cb8b6ec7..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * 线索
- */
-package cn.iocoder.yudao.module.crm.service.clue;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/package-info.java
deleted file mode 100644
index cae179aea..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * 产品表
- */
-package cn.iocoder.yudao.module.crm.service.product;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
index e6940e793..453c82d39 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
@@ -52,7 +52,10 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
     private CrmPermissionService crmPermissionService;
 
     @Override
+    // TODO @puhui999:操作日志
     public Long createReceivablePlan(CrmReceivablePlanCreateReqVO createReqVO, Long userId) {
+        // TODO @liuhongfeng:第几期的计算;基于是 contractId + contractDO 的第几个还款
+        // TODO @liuhongfeng contractId:校验合同是否存在
         // 插入
         CrmReceivablePlanDO receivablePlan = CrmReceivablePlanConvert.INSTANCE.convert(createReqVO);
         receivablePlan.setFinishStatus(false);
@@ -87,7 +90,9 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
 
     @Override
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
+    // TODO @puhui999:操作日志
     public void updateReceivablePlan(CrmReceivablePlanUpdateReqVO updateReqVO) {
+        // TODO @liuhongfeng:如果已经有对应的还款,则不允许编辑;
         // 校验存在
         validateReceivablePlanExists(updateReqVO.getId());
 
@@ -136,6 +141,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
         return receivablePlanMapper.selectPageByCustomerId(pageReqVO);
     }
 
+    // TODO @puhui999:这个没有 transfer 接口;可能是的哈
     @Override
     public void transferReceivablePlan(CrmReceivablePlanTransferReqVO reqVO, Long userId) {
         // 1 校验回款计划是否存在
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
index 6071c2615..5100ddd6e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
@@ -53,10 +53,10 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
     @Resource
     private CrmPermissionService crmPermissionService;
 
-    // TODO @liuhongfeng:创建还款后,是不是什么时候,要更新 plan?
     @Override
+    // TODO @puhui999:操作日志
     public Long createReceivable(CrmReceivableCreateReqVO createReqVO) {
-        // 插入
+        // 插入还款
         CrmReceivableDO receivable = CrmReceivableConvert.INSTANCE.convert(createReqVO);
         if (ObjectUtil.isNull(receivable.getAuditStatus())) {
             receivable.setAuditStatus(CommonStatusEnum.ENABLE.getStatus());
@@ -64,15 +64,17 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
         receivable.setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
 
         // TODO @liuhongfeng:一般来说,逻辑的写法,是要先检查,后操作 db;所以,你这个 check 应该放到  CrmReceivableDO receivable 之前;
-        // 校验
         checkReceivable(receivable);
 
         receivableMapper.insert(receivable);
+
+        // TODO @liuhongfeng:需要更新关联的 plan
         return receivable.getId();
     }
 
     // TODO @liuhongfeng:这里的括号要注意排版;
     private void checkReceivable(CrmReceivableDO receivable) {
+        // TODO @liuhongfeng:校验 no 的唯一性
         // TODO @liuhongfeng:这个放在参数校验合适
         if (ObjectUtil.isNull(receivable.getContractId())) {
             throw exception(CONTRACT_NOT_EXISTS);
@@ -96,17 +98,29 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
     }
 
     @Override
+    // TODO @puhui999:操作日志
+    // TODO @puhui999:权限校验
     public void updateReceivable(CrmReceivableUpdateReqVO updateReqVO) {
         // 校验存在
         validateReceivableExists(updateReqVO.getId());
+        // TODO @liuhongfeng:只有在草稿、审核中,可以提交修改
 
-        // 更新
+        // 更新还款
         CrmReceivableDO updateObj = CrmReceivableConvert.INSTANCE.convert(updateReqVO);
         receivableMapper.updateById(updateObj);
+
+        // TODO @liuhongfeng:需要更新关联的 plan
     }
 
+    // TODO @liuhongfeng:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
+
+    // TODO @liuhongfeng:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum
+
     @Override
+    // TODO @puhui999:操作日志
+    // TODO @puhui999:权限校验
     public void deleteReceivable(Long id) {
+        // TODO @liuhongfeng:如果被 CrmReceivablePlanDO 所使用,则不允许删除
         // 校验存在
         validateReceivableExists(id);
         // 删除
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/package-info.java
deleted file mode 100644
index 4004b301d..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * 回款
- */
-package cn.iocoder.yudao.module.crm.service.receivable;