Browse Source

✨ CRM:优化客户的详情、更新成交状态

YunaiV 1 year ago
parent
commit
3e6524932b
19 changed files with 204 additions and 229 deletions
  1. 1 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
  2. 2 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
  3. 0 16
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http
  4. 70 33
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
  5. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerDistributeReqVO.java
  6. 17 20
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java
  7. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportReqVO.java
  8. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportRespVO.java
  9. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerLockReqVO.java
  10. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java
  11. 45 45
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
  12. 25 28
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java
  13. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerTransferReqVO.java
  14. 0 66
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java
  15. 0 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
  16. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
  17. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
  18. 9 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
  19. 27 8
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java

+ 1 - 0
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java

@@ -51,6 +51,7 @@ public interface ErrorCodeConstants {
     ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!");
     ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!");
     ErrorCode CUSTOMER_NAME_EXISTS = new ErrorCode(1_020_006_014, "已存在名为【{}】的客户!");
+    ErrorCode CUSTOMER_UPDATE_DEAL_STATUS_FAIL = new ErrorCode(1_020_006_015, "更新客户的成交状态失败,原因:已经是该状态,无需更新");
 
     // ========== 权限管理 1_020_007_000 ==========
     ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");

+ 2 - 0
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java

@@ -43,6 +43,8 @@ public interface LogRecordConstants {
     String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}";
     String CRM_CUSTOMER_IMPORT_SUB_TYPE = "{{#isUpdate ? '导入并更新客户' : '导入客户'}}";
     String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}";
+    String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE = "更新客户成交状态";
+    String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS = "更新了客户【{{#customerName}}】的成交状态为【{{#dealStatus ? '已成交' : '未成交'}}】";
 
     // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 =======================
 

+ 0 - 16
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http

@@ -1,16 +0,0 @@
-### 请求 /transfer
-PUT {{baseUrl}}/crm/customer/transfer
-Content-Type: application/-id: {{adminTenentId}}json
-Authorization: Bearer {{token}}
-tenant
-
-{
-  "id": 10,
-  "newOwnerUserId": 127
-}
-
-### 自定义日志记录结果
-### 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=客户转移, bizId=10, content=把客户【张三】的负责人从【芋道源码(15612345678)】变更为了【tttt】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/transfer, userIp=127.0.0.1, userAgent=Apache-HttpClient/4.5.14 (Java/17.0.9))
-
-### diff 日志
-### | 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=更新客户, bizId=11, content=更新了客户【所属行业】从【H 住宿和餐饮业】修改为【D 电力、热力、燃气及水生产和供应业】;【客户等级】从【C (非优先客户)】修改为【A (重点客户)】;【客户来源】从【线上咨询】修改为【预约上门】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/update, userIp=0:0:0:0:0:0:0:1, userAgent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36)

+ 70 - 33
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java

@@ -5,12 +5,14 @@ import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
+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.ip.core.utils.AreaUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
-import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
+import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
@@ -22,11 +24,11 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
-import org.mapstruct.ap.internal.util.Collections;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -45,6 +47,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED;
+import static java.util.Collections.singletonList;
 
 @Tag(name = "管理后台 - CRM 客户")
 @RestController
@@ -77,6 +80,18 @@ public class CrmCustomerController {
         return success(true);
     }
 
+    @PutMapping("/update-deal-status")
+    @Operation(summary = "更新客户的成交状态")
+    @Parameters({
+            @Parameter(name = "id", description = "客户编号", required = true),
+            @Parameter(name = "dealStatus", description = "成交状态", required = true)
+    })
+    public CommonResult<Boolean> updateCustomerDealStatus(@RequestParam("id") Long id,
+                                                          @RequestParam("dealStatus") Boolean dealStatus) {
+        customerService.updateCustomerDealStatus(id, dealStatus);
+        return success(true);
+    }
+
     @DeleteMapping("/delete")
     @Operation(summary = "删除客户")
     @Parameter(name = "id", description = "客户编号", required = true)
@@ -93,14 +108,15 @@ public class CrmCustomerController {
     public CommonResult<CrmCustomerRespVO> getCustomer(@RequestParam("id") Long id) {
         // 1. 获取客户
         CrmCustomerDO customer = customerService.getCustomer(id);
+        // 2. 拼接数据
+        return success(buildCustomerDetail(customer));
+    }
+
+    public CrmCustomerRespVO buildCustomerDetail(CrmCustomerDO customer) {
         if (customer == null) {
-            return success(null);
+            return null;
         }
-        // 2. 拼接数据
-        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
-                Collections.asSet(Long.valueOf(customer.getCreator()), customer.getOwnerUserId()));
-        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
-        return success(CrmCustomerConvert.INSTANCE.convert(customer, userMap, deptMap));
+        return buildCustomerDetailList(singletonList(customer)).get(0);
     }
 
     @GetMapping("/page")
@@ -112,16 +128,38 @@ public class CrmCustomerController {
         if (CollUtil.isEmpty(pageResult.getList())) {
             return success(PageResult.empty(pageResult.getTotal()));
         }
-
         // 2. 拼接数据
-        Map<Long, Long> poolDayMap = Boolean.TRUE.equals(pageVO.getPool()) ? null :
-                getPoolDayMap(pageResult.getList()); // 客户界面,需要查看距离进入公海的时间
-        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
-                convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId())));
+        return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal()));
+    }
+
+    public List<CrmCustomerRespVO> buildCustomerDetailList(List<CrmCustomerDO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return java.util.Collections.emptyList();
+        }
+        // 1.1 获取创建人、负责人列表
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
+                contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
-        return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap, poolDayMap));
+        // 1.2 获取距离进入公海的时间
+        Map<Long, Long> poolDayMap = getPoolDayMap(list);
+        // 2. 转换成 VO
+        return BeanUtils.toBean(list, CrmCustomerRespVO.class, customerVO -> {
+            customerVO.setAreaName(AreaUtils.format(customerVO.getAreaId()));
+            // 2.1 设置创建人、负责人名称
+            MapUtils.findAndThen(userMap, NumberUtils.parseLong(customerVO.getCreator()),
+                    user -> customerVO.setCreatorName(user.getNickname()));
+            MapUtils.findAndThen(userMap, customerVO.getOwnerUserId(), user -> {
+                customerVO.setOwnerUserName(user.getNickname());
+                MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> customerVO.setOwnerUserDeptName(dept.getName()));
+            });
+            // 2.2 设置距离进入公海的时间
+            if (customerVO.getOwnerUserId() != null) {
+                customerVO.setPoolDay(poolDayMap.get(customerVO.getId()));
+            }
+        });
     }
 
+    // TODO @芋艿:需要 review 下
     @GetMapping("/put-in-pool-remind-page")
     @Operation(summary = "获得待进入公海客户分页")
     @PreAuthorize("@ss.hasPermission('crm:customer:query')")
@@ -141,12 +179,7 @@ public class CrmCustomerController {
         }
 
         // 2. 拼接数据
-        // TODO @芋艿:合并 getCustomerPage 和 getPutInPoolRemindCustomerPage 的后置处理;
-        Map<Long, Long> poolDayMap = getPoolDayMap(pageResult.getList()); // 客户界面,需要查看距离进入公海的时间
-        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));
-        return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap, poolDayMap));
+        return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal()));
     }
 
     @GetMapping("/put-in-pool-remind-count")
@@ -182,22 +215,26 @@ public class CrmCustomerController {
     }
 
     /**
-     * 获取距离进入公海的时间
+     * 获取距离进入公海的时间 Map
      *
-     * @param customerList 客户列表
-     * @return Map<key 客户编号, value 距离进入公海的时间>
+     * @param list 客户列表
+     * @return key 客户编号, value 距离进入公海的时间
      */
-    private Map<Long, Long> getPoolDayMap(List<CrmCustomerDO> customerList) {
+    private Map<Long, Long> getPoolDayMap(List<CrmCustomerDO> list) {
         CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
         if (poolConfig == null || !poolConfig.getEnabled()) {
             return MapUtil.empty();
         }
-        return convertMap(customerList, CrmCustomerDO::getId, customer -> {
+        return convertMap(list, CrmCustomerDO::getId, customer -> {
+            // TODO 芋艿:这样计算,貌似有点问题
             // 1.1 未成交放入公海天数
             long dealExpireDay = 0;
             if (!customer.getDealStatus()) {
                 dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime());
             }
+            if (dealExpireDay < 0) {
+                dealExpireDay = 0;
+            }
             // 1.2 未跟进放入公海天数
             LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime());
             long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime);
@@ -230,7 +267,7 @@ public class CrmCustomerController {
         List<CrmCustomerDO> list = customerService.getCustomerPage(pageVO, getLoginUserId()).getList();
         // 导出 Excel
         ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerRespVO.class,
-                BeanUtils.toBean(list, CrmCustomerRespVO.class));
+                buildCustomerDetailList(list));
     }
 
     @GetMapping("/get-import-template")
@@ -238,12 +275,12 @@ public class CrmCustomerController {
     public void importTemplate(HttpServletResponse response) throws IOException {
         // 手动创建导出 demo
         List<CrmCustomerImportExcelVO> list = Arrays.asList(
-                CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1).mobile("15601691300").telephone("")
-                        .qq("").wechat("").email("yunai@iocoder.cn").description("").remark("")
-                        .areaId(null).detailAddress("").build(),
-                CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1).mobile("15601691300").telephone("")
-                        .qq("").wechat("").email("yunai@iocoder.cn").description("").remark("")
-                        .areaId(null).detailAddress("").build()
+                CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1)
+                        .mobile("15601691300").telephone("").qq("").wechat("").email("yunai@iocoder.cn")
+                        .areaId(null).detailAddress("").remark("").build(),
+                CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1)
+                        .mobile("15601691300").telephone("").qq("").wechat("").email("yunai@iocoder.cn")
+                        .areaId(null).detailAddress("").remark("").build()
         );
         // 输出
         ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list);

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerDistributeReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotEmpty;

+ 17 - 20
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
 
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
@@ -24,19 +24,6 @@ public class CrmCustomerImportExcelVO {
     @ExcelProperty("客户名称")
     private String name;
 
-    // TODO @puhui999:industryId、level、source 字段,可以研究下怎么搞下拉框
-    @ExcelProperty(value = "所属行业", converter = DictConvert.class)
-    @DictFormat(CRM_CUSTOMER_INDUSTRY)
-    private Integer industryId;
-
-    @ExcelProperty(value = "客户等级", converter = DictConvert.class)
-    @DictFormat(CRM_CUSTOMER_LEVEL)
-    private Integer level;
-
-    @ExcelProperty(value = "客户来源", converter = DictConvert.class)
-    @DictFormat(CRM_CUSTOMER_SOURCE)
-    private Integer source;
-
     @ExcelProperty("手机")
     private String mobile;
 
@@ -52,12 +39,6 @@ public class CrmCustomerImportExcelVO {
     @ExcelProperty("邮箱")
     private String email;
 
-    @ExcelProperty("客户描述")
-    private String description;
-
-    @ExcelProperty("备注")
-    private String remark;
-
     // TODO @puhui999:需要选择省市区,需要研究下,怎么搞合理点;
     @ExcelProperty("地区编号")
     private Integer areaId;
@@ -65,4 +46,20 @@ public class CrmCustomerImportExcelVO {
     @ExcelProperty("详细地址")
     private String detailAddress;
 
+    // TODO @puhui999:industryId、level、source 字段,可以研究下怎么搞下拉框
+    @ExcelProperty(value = "所属行业", converter = DictConvert.class)
+    @DictFormat(CRM_CUSTOMER_INDUSTRY)
+    private Integer industryId;
+
+    @ExcelProperty(value = "客户等级", converter = DictConvert.class)
+    @DictFormat(CRM_CUSTOMER_LEVEL)
+    private Integer level;
+
+    @ExcelProperty(value = "客户来源", converter = DictConvert.class)
+    @DictFormat(CRM_CUSTOMER_SOURCE)
+    private Integer source;
+
+    @ExcelProperty("备注")
+    private String remark;
+
 }

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Builder;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerLockReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.validation.InEnum;

+ 45 - 45
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
 
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
@@ -7,12 +7,9 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-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 客户 Response VO")
 @Data
 @ExcelIgnoreUnannotated
@@ -31,6 +28,28 @@ public class CrmCustomerRespVO {
     @DictFormat(DictTypeConstants.BOOLEAN_STRING)
     private Boolean followUpStatus;
 
+    @Schema(description = "最后跟进时间")
+    @ExcelProperty("最后跟进时间")
+    private LocalDateTime contactLastTime;
+
+    @Schema(description = "最后跟进内容", example = "吃饭、睡觉、打逗逗")
+    @ExcelProperty("最后跟进内容")
+    private String contactLastContent;
+
+    @Schema(description = "下次联系时间")
+    @ExcelProperty("下次联系时间")
+    private LocalDateTime contactNextTime;
+
+    @Schema(description = "负责人的用户编号", example = "25682")
+    @ExcelProperty("负责人的用户编号")
+    private Long ownerUserId;
+    @Schema(description = "负责人名字", example = "25682")
+    @ExcelProperty("负责人名字")
+    private String ownerUserName;
+    @Schema(description = "负责人部门")
+    @ExcelProperty("负责人部门")
+    private String ownerUserDeptName;
+
     @Schema(description = "锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
     @ExcelProperty(value = "锁定状态", converter = DictConvert.class)
     @DictFormat(DictTypeConstants.BOOLEAN_STRING)
@@ -41,55 +60,26 @@ public class CrmCustomerRespVO {
     @DictFormat(DictTypeConstants.BOOLEAN_STRING)
     private Boolean dealStatus;
 
-    @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
-    @ExcelProperty(value = "所属行业", converter = DictConvert.class)
-    @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY)
-    private Integer industryId;
-
-    @Schema(description = "客户等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
-    @ExcelProperty(value = "客户等级", converter = DictConvert.class)
-    @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL)
-    private Integer level;
-
-    @Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
-    @ExcelProperty(value = "客户来源", converter = DictConvert.class)
-    @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE)
-    private Integer source;
-
-    @Schema(description = "负责人的用户编号", example = "25682")
+    @Schema(description = "手机", example = "25682")
     @ExcelProperty("手机")
     private String mobile;
 
-    @Schema(description = "负责人的用户编号", example = "25682")
+    @Schema(description = "电话", example = "25682")
     @ExcelProperty("电话")
     private String telephone;
 
-    @Schema(description = "负责人的用户编号", example = "25682")
+    @Schema(description = "QQ", example = "25682")
     @ExcelProperty("QQ")
     private String qq;
 
-    @Schema(description = "负责人的用户编号", example = "25682")
+    @Schema(description = "wechat", example = "25682")
     @ExcelProperty("wechat")
     private String wechat;
 
-    @Schema(description = "负责人的用户编号", example = "25682")
+    @Schema(description = "email", example = "25682")
     @ExcelProperty("email")
     private String email;
 
-    @Schema(description = "负责人的用户编号", example = "25682")
-    @ExcelProperty("备注")
-    private String remark;
-
-    @Schema(description = "负责人的用户编号", example = "25682")
-    @ExcelProperty("负责人的用户编号")
-    private Long ownerUserId;
-    @Schema(description = "负责人名字", example = "25682")
-    @ExcelProperty("负责人名字")
-    private String ownerUserName;
-    @Schema(description = "负责人部门")
-    @ExcelProperty("负责人部门")
-    private String ownerUserDeptName;
-
     @Schema(description = "地区编号", example = "1024")
     @ExcelProperty("地区编号")
     private Integer areaId;
@@ -100,14 +90,24 @@ public class CrmCustomerRespVO {
     @ExcelProperty("详细地址")
     private String detailAddress;
 
-    @Schema(description = "最后跟进时间")
-    @ExcelProperty("最后跟进时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime contactLastTime;
+    @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+    @ExcelProperty(value = "所属行业", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY)
+    private Integer industryId;
 
-    @Schema(description = "下次联系时间")
-    @ExcelProperty("下次联系时间")
-    private LocalDateTime contactNextTime;
+    @Schema(description = "客户等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+    @ExcelProperty(value = "客户等级", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL)
+    private Integer level;
+
+    @Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+    @ExcelProperty(value = "客户来源", converter = DictConvert.class)
+    @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE)
+    private Integer source;
+
+    @Schema(description = "负责人的用户编号", example = "25682")
+    @ExcelProperty("备注")
+    private String remark;
 
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("创建时间")

+ 25 - 28
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
 
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.framework.common.validation.Mobile;
@@ -13,6 +13,7 @@ import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.Email;
 import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Size;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
@@ -34,19 +35,14 @@ public class CrmCustomerSaveReqVO {
     @NotEmpty(message = "客户名称不能为空")
     private String name;
 
-    @Schema(description = "所属行业", example = "1")
-    @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME)
-    @DictFormat(CRM_CUSTOMER_INDUSTRY)
-    private Integer industryId;
-
-    @Schema(description = "客户等级", example = "2")
-    @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME)
-    @InEnum(CrmCustomerLevelEnum.class)
-    private Integer level;
+    @Schema(description = "下次联系时间")
+    @DiffLogField(name = "下次联系时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime contactNextTime;
 
-    @Schema(description = "客户来源", example = "3")
-    @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME)
-    private Integer source;
+    @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+    @NotNull(message = "负责人的用户编号不能为空")
+    private Long ownerUserId;
 
     @Schema(description = "手机", example = "18000000000")
     @DiffLogField(name = "手机")
@@ -74,15 +70,6 @@ public class CrmCustomerSaveReqVO {
     @Size(max = 255, message = "邮箱长度不能超过 255 个字符")
     private String email;
 
-    @Schema(description = "客户描述", example = "任意文字")
-    @DiffLogField(name = "客户描述")
-    @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")
-    private String description;
-
-    @Schema(description = "备注", example = "随便")
-    @DiffLogField(name = "备注")
-    private String remark;
-
     @Schema(description = "地区编号", example = "20158")
     @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME)
     private Integer areaId;
@@ -91,12 +78,22 @@ public class CrmCustomerSaveReqVO {
     @DiffLogField(name = "详细地址")
     private String detailAddress;
 
-    @Schema(description = "下次联系时间")
-    @DiffLogField(name = "下次联系时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime contactNextTime;
+    @Schema(description = "所属行业", example = "1")
+    @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME)
+    @DictFormat(CRM_CUSTOMER_INDUSTRY)
+    private Integer industryId;
 
-    @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
-    private Long ownerUserId;
+    @Schema(description = "客户等级", example = "2")
+    @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME)
+    @InEnum(CrmCustomerLevelEnum.class)
+    private Integer level;
+
+    @Schema(description = "客户来源", example = "3")
+    @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME)
+    private Integer source;
+
+    @Schema(description = "备注", example = "随便")
+    @DiffLogField(name = "备注")
+    private String remark;
 
 }

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerTransferReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
 
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
 import io.swagger.v3.oas.annotations.media.Schema;

+ 0 - 66
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java

@@ -1,66 +0,0 @@
-package cn.iocoder.yudao.module.crm.convert.customer;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-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.customer.vo.CrmCustomerRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
-import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
-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.factory.Mappers;
-
-import java.util.Map;
-
-import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
-
-/**
- * 客户 Convert
- *
- * @author Wanwan
- */
-@Mapper
-public interface CrmCustomerConvert {
-
-    CrmCustomerConvert INSTANCE = Mappers.getMapper(CrmCustomerConvert.class);
-
-    default CrmCustomerRespVO convert(CrmCustomerDO customer, Map<Long, AdminUserRespDTO> userMap,
-                                      Map<Long, DeptRespDTO> deptMap) {
-        CrmCustomerRespVO customerResp = BeanUtils.toBean(customer, CrmCustomerRespVO.class);
-        setUserInfo(customerResp, userMap, deptMap);
-        return customerResp;
-    }
-
-    default PageResult<CrmCustomerRespVO> convertPage(PageResult<CrmCustomerDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
-                                                      Map<Long, DeptRespDTO> deptMap, Map<Long, Long> poolDayMap) {
-        PageResult<CrmCustomerRespVO> result = BeanUtils.toBean(pageResult, CrmCustomerRespVO.class);
-        result.getList().forEach(item -> {
-            setUserInfo(item, userMap, deptMap);
-            findAndThen(poolDayMap, item.getId(), item::setPoolDay);
-        });
-        return result;
-    }
-
-    /**
-     * 设置用户信息
-     *
-     * @param customer CRM 客户 Response VO
-     * @param userMap  用户信息 map
-     * @param deptMap  用户部门信息 map
-     */
-    static void setUserInfo(CrmCustomerRespVO customer, Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
-        customer.setAreaName(AreaUtils.format(customer.getAreaId()));
-        findAndThen(userMap, customer.getOwnerUserId(), user -> {
-            customer.setOwnerUserName(user.getNickname());
-            findAndThen(deptMap, user.getDeptId(), dept -> customer.setOwnerUserDeptName(dept.getName()));
-        });
-        findAndThen(userMap, Long.parseLong(customer.getCreator()), user -> customer.setCreatorName(user.getNickname()));
-    }
-
-    @Mapping(target = "bizId", source = "reqVO.id")
-    CrmPermissionTransferReqBO convert(CrmCustomerTransferReqVO reqVO, Long userId);
-
-}

+ 0 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java

@@ -57,10 +57,6 @@ public class CrmCustomerDO extends BaseDO {
      * 关联 AdminUserDO 的 id 字段
      */
     private Long ownerUserId;
-    /**
-     * 最后接收时间
-     */
-    private LocalDateTime receiveTime;
 
     /**
      * 锁定状态

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java

@@ -6,7 +6,7 @@ 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.controller.admin.customer.vo.customer.CrmCustomerPageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;

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

@@ -8,7 +8,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.CrmCustomerSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;

+ 9 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java

@@ -1,7 +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.*;
+import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
 import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
@@ -37,6 +37,14 @@ public interface CrmCustomerService {
      */
     void updateCustomer(@Valid CrmCustomerSaveReqVO updateReqVO);
 
+    /**
+     * 更新客户的跟进状态
+     *
+     * @param id        编号
+     * @param dealStatus 跟进状态
+     */
+    void updateCustomerDealStatus(Long id, Boolean dealStatus);
+
     /**
      * 删除客户
      *

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

@@ -10,8 +10,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
-import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
+import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
 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.dataobject.customer.CrmCustomerPoolConfigDO;
@@ -27,6 +26,7 @@ import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
+import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import com.mzt.logapi.context.LogRecordContext;
@@ -137,6 +137,26 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
         LogRecordContext.putVariable("customerName", oldCustomer.getName());
     }
 
+    @Override
+    @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE, bizNo = "{{#id}}",
+            success = CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS)
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
+    public void updateCustomerDealStatus(Long id, Boolean dealStatus) {
+        // 1.1 校验存在
+        CrmCustomerDO customer = validateCustomerExists(id);
+        // 1.2 校验是否重复操作
+        if (Objects.equals(customer.getDealStatus(), dealStatus)) {
+            throw exception(CUSTOMER_UPDATE_DEAL_STATUS_FAIL);
+        }
+
+        // 2. 更新客户的成交状态
+        customerMapper.updateById(new CrmCustomerDO().setId(id).setDealStatus(dealStatus));
+
+        // 3. 记录操作日志上下文
+        LogRecordContext.putVariable("customerName", customer.getName());
+        LogRecordContext.putVariable("dealStatus", dealStatus);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
@@ -146,7 +166,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
         // 1.1 校验存在
         CrmCustomerDO customer = validateCustomerExists(id);
         // 1.2 检查引用
-        checkCustomerReference(id);
+        validateCustomerReference(id);
 
         // 2. 删除
         customerMapper.deleteById(id);
@@ -162,7 +182,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
      *
      * @param id 客户编号
      */
-    private void checkCustomerReference(Long id) {
+    private void validateCustomerReference(Long id) {
         if (contactService.getContactCountByCustomerId(id) > 0) {
             throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTACT.getName());
         }
@@ -186,8 +206,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
         validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1);
 
         // 2.1 数据权限转移
-        permissionService.transferPermission(
-                CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()));
+        permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
+                        reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
         // 2.2 转移后重新设置负责人
         customerMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
 
@@ -230,8 +250,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
             success = CRM_CUSTOMER_CREATE_SUCCESS)
     public Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId) {
         // 1. 插入客户
-        CrmCustomerDO customer = BeanUtils.toBean(customerCreateReq, CrmCustomerDO.class).setOwnerUserId(userId)
-                .setLockStatus(false).setDealStatus(false).setReceiveTime(LocalDateTime.now());
+        CrmCustomerDO customer = BeanUtils.toBean(customerCreateReq, CrmCustomerDO.class).setOwnerUserId(userId);
         customerMapper.insert(customer);
 
         // 2. 创建数据权限