Commit 69a974ef02f1ce829dbf20acb9fca72199c12862

Authored by YunaiV
1 parent bac476c1

📖 CRM:线索的跟进逻辑

yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
... ... @@ -16,8 +16,7 @@ public interface ErrorCodeConstants {
16 16  
17 17 // ========== 线索管理 1-020-001-000 ==========
18 18 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
19   - ErrorCode CLUE_NOT_EXISTS_ANY = new ErrorCode(1_020_001_001, "线索【{}】不存在");
20   - ErrorCode CLUE_TRANSFORM_FAIL_ALREADY = new ErrorCode(1_020_001_002, "线索【{}】已经转化过了,请勿重复转化");
  19 + ErrorCode CLUE_TRANSFORM_FAIL_ALREADY = new ErrorCode(1_020_001_001, "线索已经转化过了,请勿重复转化");
21 20  
22 21 // ========== 商机管理 1-020-002-000 ==========
23 22 ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在");
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
... ... @@ -8,19 +8,21 @@ package cn.iocoder.yudao.module.crm.enums;
8 8 */
9 9 public interface LogRecordConstants {
10 10  
11   - // ======================= CRM_LEADS 线索 =======================
12   -
13   - String CRM_LEADS_TYPE = "CRM 线索";
14   - String CRM_LEADS_CREATE_SUB_TYPE = "创建线索";
15   - String CRM_LEADS_CREATE_SUCCESS = "创建了线索{{#clue.name}}";
16   - String CRM_LEADS_UPDATE_SUB_TYPE = "更新线索";
17   - String CRM_LEADS_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}";
18   - String CRM_LEADS_DELETE_SUB_TYPE = "删除线索";
19   - String CRM_LEADS_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";
20   - String CRM_LEADS_TRANSFER_SUB_TYPE = "转移线索";
21   - String CRM_LEADS_TRANSFER_SUCCESS = "将线索【{{#clue.name}}】的负责人从【{getAdminUserById{#clue.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
22   - String CRM_LEADS_TRANSLATE_SUB_TYPE = "线索转化为客户";
23   - String CRM_LEADS_TRANSLATE_SUCCESS = "将线索【{{#clue.name}}】转化为客户";
  11 + // ======================= CRM_CLUE 线索 =======================
  12 +
  13 + String CRM_CLUE_TYPE = "CRM 线索";
  14 + String CRM_CLUE_CREATE_SUB_TYPE = "创建线索";
  15 + String CRM_CLUE_CREATE_SUCCESS = "创建了线索{{#clue.name}}";
  16 + String CRM_CLUE_UPDATE_SUB_TYPE = "更新线索";
  17 + String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}";
  18 + String CRM_CLUE_DELETE_SUB_TYPE = "删除线索";
  19 + String CRM_CLUE_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";
  20 + String CRM_CLUE_TRANSFER_SUB_TYPE = "转移线索";
  21 + String CRM_CLUE_TRANSFER_SUCCESS = "将线索【{{#clue.name}}】的负责人从【{getAdminUserById{#clue.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
  22 + String CRM_CLUE_TRANSLATE_SUB_TYPE = "线索转化为客户";
  23 + String CRM_CLUE_TRANSLATE_SUCCESS = "将线索【{{#clueName}}】转化为客户";
  24 + String CRM_CLUE_FOLLOW_UP_SUB_TYPE = "线索跟进";
  25 + String CRM_CLUE_FOLLOW_UP_SUCCESS = "线索跟进【{{#clueName}}】";
24 26  
25 27 // ======================= CRM_CUSTOMER 客户 =======================
26 28  
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java
... ... @@ -17,7 +17,7 @@ import java.util.Arrays;
17 17 @Getter
18 18 public enum CrmBizTypeEnum implements IntArrayValuable {
19 19  
20   - CRM_LEADS(1, "线索"),
  20 + CRM_CLUE(1, "线索"),
21 21 CRM_CUSTOMER(2, "客户"),
22 22 CRM_CONTACT(3, "联系人"),
23 23 CRM_BUSINESS(4, "商机"),
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java
... ... @@ -9,7 +9,10 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
10 10 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
11 11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
12   -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*;
  12 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
  13 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueRespVO;
  14 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
  15 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
13 16 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
14 17 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
15 18 import cn.iocoder.yudao.module.crm.service.clue.CrmClueService;
... ... @@ -36,7 +39,8 @@ import java.util.stream.Stream;
36 39  
37 40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
38 41 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
39   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  42 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
40 44 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
41 45 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
42 46  
... ... @@ -151,18 +155,18 @@ public class CrmClueController {
151 155  
152 156 @PutMapping("/transform")
153 157 @Operation(summary = "线索转化为客户")
154   - @Parameter(name = "ids", description = "线索编号数组", required = true)
  158 + @Parameter(name = "id", description = "编号", required = true)
155 159 @PreAuthorize("@ss.hasPermission('crm:clue:update')")
156   - public CommonResult<Boolean> transformClue(@RequestParam("ids") List<Long> ids) {
157   - clueService.transformClue(ids, getLoginUserId());
  160 + public CommonResult<Boolean> transformClue(@RequestParam("id") Long id) {
  161 + clueService.transformClue(id, getLoginUserId());
158 162 return success(Boolean.TRUE);
159 163 }
160 164  
161   - @GetMapping("/follow-leads-count")
162   - @Operation(summary = "获得分配给我的线索数量")
  165 + @GetMapping("/follow-count")
  166 + @Operation(summary = "获得分配给我的、待跟进的线索数量")
163 167 @PreAuthorize("@ss.hasPermission('crm:clue:query')")
164   - public CommonResult<Long> getFollowLeadsCount() {
165   - return success(clueService.getFollowLeadsCount(getLoginUserId()));
  168 + public CommonResult<Long> getFollowClueCount() {
  169 + return success(clueService.getFollowClueCount(getLoginUserId()));
166 170 }
167 171  
168 172 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java
... ... @@ -41,7 +41,7 @@ public class CrmOperateLogController {
41 41 private static final Map<Integer, String> BIZ_TYPE_MAP = new HashMap<>();
42 42  
43 43 static {
44   - BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_LEADS.getType(), CRM_LEADS_TYPE);
  44 + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CLUE.getType(), CRM_CLUE_TYPE);
45 45 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CUSTOMER.getType(), CRM_CUSTOMER_TYPE);
46 46 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTACT.getType(), CRM_CONTACT_TYPE);
47 47 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_BUSINESS.getType(), CRM_BUSINESS_TYPE);
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java deleted
1   -package cn.iocoder.yudao.module.crm.convert.clue;
2   -
3   -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
4   -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
5   -import org.mapstruct.Mapper;
6   -import org.mapstruct.Mapping;
7   -import org.mapstruct.factory.Mappers;
8   -
9   -/**
10   - * 线索 Convert
11   - *
12   - * @author Wanwan
13   - */
14   -@Mapper
15   -public interface CrmClueConvert {
16   -
17   - CrmClueConvert INSTANCE = Mappers.getMapper(CrmClueConvert.class);
18   -
19   - @Mapping(target = "bizId", source = "reqVO.id")
20   - CrmPermissionTransferReqBO convert(CrmClueTransferReqVO reqVO, Long userId);
21   -
22   -}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
... ... @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
8 8 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
9 9 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
10 10 import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
11   -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
12 11 import org.apache.ibatis.annotations.Mapper;
13 12  
14 13 import java.util.Collection;
... ... @@ -22,16 +21,10 @@ import java.util.List;
22 21 @Mapper
23 22 public interface CrmClueMapper extends BaseMapperX<CrmClueDO> {
24 23  
25   - default int updateOwnerUserIdById(Long id, Long ownerUserId) {
26   - return update(new LambdaUpdateWrapper<CrmClueDO>()
27   - .eq(CrmClueDO::getId, id)
28   - .set(CrmClueDO::getOwnerUserId, ownerUserId));
29   - }
30   -
31 24 default PageResult<CrmClueDO> selectPage(CrmCluePageReqVO pageReqVO, Long userId) {
32 25 MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
33 26 // 拼接数据权限的查询条件
34   - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
  27 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CLUE.getType(),
35 28 CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool());
36 29 // 拼接自身的查询条件
37 30 query.selectAll(CrmClueDO.class)
... ... @@ -50,16 +43,16 @@ public interface CrmClueMapper extends BaseMapperX&lt;CrmClueDO&gt; {
50 43 default List<CrmClueDO> selectBatchIds(Collection<Long> ids, Long userId) {
51 44 MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
52 45 // 拼接数据权限的查询条件
53   - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(), ids, userId);
  46 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CLUE.getType(), ids, userId);
54 47 query.selectAll(CrmClueDO.class).in(CrmClueDO::getId, ids).orderByDesc(CrmClueDO::getId);
55 48 // 拼接自身的查询条件
56 49 return selectJoinList(CrmClueDO.class, query);
57 50 }
58 51  
59   - default Long selectFollowLeadsCount(Long userId) {
  52 + default Long selectCountByFollow(Long userId) {
60 53 MPJLambdaWrapperX<CrmClueDO> query = new MPJLambdaWrapperX<>();
61 54 // 我负责的 + 非公海
62   - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(),
  55 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CLUE.getType(),
63 56 CrmClueDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE);
64 57 // 未跟进 + 未转化
65 58 query.eq(CrmClueDO::getFollowUpStatus, false)
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java
... ... @@ -5,9 +5,9 @@ import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
5 5 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
6 6 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
7 7 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
8   -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
9 8 import jakarta.validation.Valid;
10 9  
  10 +import java.time.LocalDateTime;
11 11 import java.util.Collection;
12 12 import java.util.List;
13 13  
... ... @@ -36,9 +36,11 @@ public interface CrmClueService {
36 36 /**
37 37 * 更新线索相关的跟进信息
38 38 *
39   - * @param clueUpdateFollowUpReqBO 信息
  39 + * @param id 编号
  40 + * @param contactNextTime 下次联系时间
  41 + * @param contactLastContent 最后联系内容
40 42 */
41   - void updateClueFollowUp(CrmUpdateFollowUpReqBO clueUpdateFollowUpReqBO);
  43 + void updateClueFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
42 44  
43 45 /**
44 46 * 删除线索
... ... @@ -83,17 +85,17 @@ public interface CrmClueService {
83 85 /**
84 86 * 线索转化为客户
85 87 *
86   - * @param ids 线索编号数组
  88 + * @param id 线索编号
87 89 * @param userId 用户编号
88 90 */
89   - void transformClue(List<Long> ids, Long userId);
  91 + void transformClue(Long id, Long userId);
90 92  
91 93 /**
92   - * 获得分配给我的线索数量
  94 + * 获得分配给我的、待跟进的线索数量
93 95 *
94 96 * @param userId 用户编号
95   - * @return 提醒数量
  97 + * @return 数量
96 98 */
97   - Long getFollowLeadsCount(Long userId);
  99 + Long getFollowClueCount(Long userId);
98 100  
99 101 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
... ... @@ -3,15 +3,12 @@ package cn.iocoder.yudao.module.crm.service.clue;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.ListUtil;
5 5 import cn.hutool.core.lang.Assert;
6   -import cn.hutool.core.util.ObjectUtil;
7   -import cn.hutool.extra.spring.SpringUtil;
8 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 8 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
11 9 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
12 10 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
13 11 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO;
14   -import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert;
15 12 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
16 13 import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
17 14 import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
... ... @@ -25,6 +22,7 @@ import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO;
25 22 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
26 23 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
27 24 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
  25 +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
28 26 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
29 27 import com.mzt.logapi.context.LogRecordContext;
30 28 import com.mzt.logapi.service.impl.DiffParseFunction;
... ... @@ -37,12 +35,13 @@ import org.springframework.validation.annotation.Validated;
37 35 import java.time.LocalDateTime;
38 36 import java.util.Collection;
39 37 import java.util.List;
40   -import java.util.Map;
41 38 import java.util.Objects;
42 39  
43 40 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
44   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
45   -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
  41 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  42 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton;
  43 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_NOT_EXISTS;
  44 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_TRANSFORM_FAIL_ALREADY;
46 45 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
47 46 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS;
48 47  
... ... @@ -60,18 +59,18 @@ public class CrmClueServiceImpl implements CrmClueService {
60 59  
61 60 @Resource
62 61 private CrmCustomerService customerService;
63   -
64 62 @Resource
65 63 private CrmPermissionService crmPermissionService;
66 64 @Resource
67 65 private CrmFollowUpRecordService followUpRecordService;
  66 +
68 67 @Resource
69 68 private AdminUserApi adminUserApi;
70 69  
71 70 @Override
72 71 @Transactional(rollbackFor = Exception.class)
73   - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_CREATE_SUB_TYPE, bizNo = "{{#clue.id}}",
74   - success = CRM_LEADS_CREATE_SUCCESS)
  72 + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_CREATE_SUB_TYPE, bizNo = "{{#clue.id}}",
  73 + success = CRM_CLUE_CREATE_SUCCESS)
75 74 public Long createClue(CrmClueSaveReqVO createReqVO) {
76 75 // 1.1 校验关联数据
77 76 validateRelationDataExists(createReqVO);
... ... @@ -84,7 +83,7 @@ public class CrmClueServiceImpl implements CrmClueService {
84 83 clueMapper.insert(clue);
85 84  
86 85 // 3. 创建数据权限
87   - CrmPermissionCreateReqBO createReqBO = new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_LEADS.getType())
  86 + CrmPermissionCreateReqBO createReqBO = new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CLUE.getType())
88 87 .setBizId(clue.getId()).setUserId(clue.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel());
89 88 crmPermissionService.createPermission(createReqBO);
90 89  
... ... @@ -95,9 +94,9 @@ public class CrmClueServiceImpl implements CrmClueService {
95 94  
96 95 @Override
97 96 @Transactional(rollbackFor = Exception.class)
98   - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
99   - success = CRM_LEADS_UPDATE_SUCCESS)
100   - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER)
  97 + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
  98 + success = CRM_CLUE_UPDATE_SUCCESS)
  99 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER)
101 100 public void updateClue(CrmClueSaveReqVO updateReq) {
102 101 Assert.notNull(updateReq.getId(), "线索编号不能为空");
103 102 // 1.1 校验线索是否存在
... ... @@ -114,15 +113,25 @@ public class CrmClueServiceImpl implements CrmClueService {
114 113 LogRecordContext.putVariable("clueName", oldClue.getName());
115 114 }
116 115  
  116 + private void validateRelationDataExists(CrmClueSaveReqVO reqVO) {
  117 + // 校验负责人
  118 + if (Objects.nonNull(reqVO.getOwnerUserId()) &&
  119 + Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) {
  120 + throw exception(USER_NOT_EXISTS);
  121 + }
  122 + }
  123 +
117 124 @Override
118   - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReq.bizId}",
119   - success = CRM_LEADS_UPDATE_SUCCESS)
120   - public void updateClueFollowUp(CrmUpdateFollowUpReqBO updateReq) {
  125 + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}",
  126 + success = CRM_CLUE_FOLLOW_UP_SUCCESS)
  127 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
  128 + public void updateClueFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) {
121 129 // 校验线索是否存在
122   - CrmClueDO oldClue = validateClueExists(updateReq.getBizId());
  130 + CrmClueDO oldClue = validateClueExists(id);
123 131  
124 132 // 更新
125   - clueMapper.updateById(BeanUtils.toBean(updateReq, CrmClueDO.class).setId(updateReq.getBizId()));
  133 + clueMapper.updateById(new CrmClueDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime)
  134 + .setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent));
126 135  
127 136 // 3. 记录操作日志上下文
128 137 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmUpdateFollowUpReqBO.class));
... ... @@ -131,9 +140,9 @@ public class CrmClueServiceImpl implements CrmClueService {
131 140  
132 141 @Override
133 142 @Transactional(rollbackFor = Exception.class)
134   - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_DELETE_SUB_TYPE, bizNo = "{{#id}}",
135   - success = CRM_LEADS_DELETE_SUCCESS)
136   - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
  143 + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
  144 + success = CRM_CLUE_DELETE_SUCCESS)
  145 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
137 146 public void deleteClue(Long id) {
138 147 // 1. 校验存在
139 148 CrmClueDO clue = validateClueExists(id);
... ... @@ -142,28 +151,29 @@ public class CrmClueServiceImpl implements CrmClueService {
142 151 clueMapper.deleteById(id);
143 152  
144 153 // 3. 删除数据权限
145   - crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_LEADS.getType(), id);
  154 + crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CLUE.getType(), id);
146 155  
147 156 // 4. 删除跟进
148   - followUpRecordService.deleteFollowUpRecordByBiz(CrmBizTypeEnum.CRM_LEADS.getType(), id);
  157 + followUpRecordService.deleteFollowUpRecordByBiz(CrmBizTypeEnum.CRM_CLUE.getType(), id);
149 158  
150   - // 记录操作日志上下文
  159 + // 5. 记录操作日志上下文
151 160 LogRecordContext.putVariable("clueName", clue.getName());
152 161 }
153 162  
154 163 @Override
155 164 @Transactional(rollbackFor = Exception.class)
156   - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
157   - success = CRM_LEADS_TRANSFER_SUCCESS)
158   - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
  165 + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
  166 + success = CRM_CLUE_TRANSFER_SUCCESS)
  167 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
159 168 public void transferClue(CrmClueTransferReqVO reqVO, Long userId) {
160 169 // 1 校验线索是否存在
161 170 CrmClueDO clue = validateClueExists(reqVO.getId());
162 171  
163 172 // 2.1 数据权限转移
164   - crmPermissionService.transferPermission(CrmClueConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_LEADS.getType()));
  173 + crmPermissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CLUE.getType(),
  174 + reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
165 175 // 2.2 设置新的负责人
166   - clueMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
  176 + clueMapper.updateById(new CrmClueDO().setId(reqVO.getId()).setOwnerUserId(reqVO.getNewOwnerUserId()));
167 177  
168 178 // 3. 记录转移日志
169 179 LogRecordContext.putVariable("clue", clue);
... ... @@ -171,69 +181,32 @@ public class CrmClueServiceImpl implements CrmClueService {
171 181  
172 182 @Override
173 183 @Transactional(rollbackFor = Exception.class)
174   - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#ids", level = CrmPermissionLevelEnum.OWNER)
175   - public void transformClue(List<Long> ids, Long userId) {
  184 + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_TRANSLATE_SUB_TYPE, bizNo = "{{#id}}",
  185 + success = CRM_CLUE_TRANSLATE_SUCCESS)
  186 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
  187 + public void transformClue(Long id, Long userId) {
176 188 // 1.1 校验线索都存在
177   - List<CrmClueDO> clues = getClueList(ids, userId);
178   - if (CollUtil.isEmpty(clues) || ObjectUtil.notEqual(clues.size(), ids.size())) {
179   - ids.removeAll(convertSet(clues, CrmClueDO::getId));
180   - throw exception(CLUE_NOT_EXISTS_ANY, ids);
181   - }
182   - // 1.2 存在已经转化的,直接提示哈。避免操作的用户,以为都转化成功了
183   - List<CrmClueDO> translatedClues = filterList(clues,
184   - clue -> ObjectUtil.equal(Boolean.TRUE, clue.getTransformStatus()));
185   - if (CollUtil.isNotEmpty(translatedClues)) {
186   - throw exception(CLUE_TRANSFORM_FAIL_ALREADY, convertSet(translatedClues, CrmClueDO::getId));
  189 + CrmClueDO clue = validateClueExists(id);
  190 + // 1.2 存在已经转化的
  191 + if (clue.getTransformStatus()) {
  192 + throw exception(CLUE_TRANSFORM_FAIL_ALREADY);
187 193 }
188 194  
189 195 // 2.1 遍历线索(未转化的线索),创建对应的客户
190   - clues.forEach(clue -> {
191   - Long customerId = customerService.createCustomer(BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class), userId);
192   - clue.setCustomerId(customerId);
193   - });
  196 + Long customerId = customerService.createCustomer(BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class), userId);
194 197 // 2.2 更新线索
195   - clueMapper.updateBatch(convertList(clues, clue -> new CrmClueDO().setId(clue.getId())
196   - .setTransformStatus(Boolean.TRUE).setCustomerId(clue.getCustomerId())));
  198 + clueMapper.updateById(new CrmClueDO().setId(id).setTransformStatus(Boolean.TRUE).setCustomerId(customerId));
197 199 // 2.3 复制跟进记录
198   - copyFollowUpRecords(clues);
199   -
200   - // 3. 记录操作日志
201   - for (CrmClueDO clue : clues) {
202   - getSelf().translateCustomerLog(clue);
203   - }
204   - }
205   -
206   - /**
207   - * 线索被转换客户后,需要将线索的跟进记录,复制到客户上
208   - *
209   - * @param clues 被转化的线索
210   - */
211   - private void copyFollowUpRecords(List<CrmClueDO> clues) {
212 200 List<CrmFollowUpRecordDO> followUpRecords = followUpRecordService.getFollowUpRecordByBiz(
213   - CrmBizTypeEnum.CRM_LEADS.getType(), convertSet(clues, CrmClueDO::getId));
214   - if (CollUtil.isEmpty(followUpRecords)) {
215   - return;
  201 + CrmBizTypeEnum.CRM_CLUE.getType(), singleton(clue.getId()));
  202 + if (CollUtil.isNotEmpty(followUpRecords)) {
  203 + followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, record ->
  204 + BeanUtils.toBean(record, CrmFollowUpCreateReqBO.class)
  205 + .setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()).setBizId(customerId)));
216 206 }
217   - // 创建跟进
218   - Map<Long, CrmClueDO> clueMap = convertMap(clues, CrmClueDO::getId);
219   - followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, followUpRecord ->
220   - BeanUtils.toBean(followUpRecord, CrmFollowUpCreateReqBO.class).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
221   - .setBizId(clueMap.get(followUpRecord.getBizId()).getCustomerId())));
222   - }
223   -
224   - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSLATE_SUB_TYPE, bizNo = "{{#clue.id}}",
225   - success = CRM_LEADS_TRANSLATE_SUCCESS)
226   - public void translateCustomerLog(CrmClueDO clue) {
227   - // 记录操作日志上下文
228   - LogRecordContext.putVariable("clue", clue);
229   - }
230 207  
231   - private void validateRelationDataExists(CrmClueSaveReqVO reqVO) {
232   - // 校验负责人
233   - if (Objects.nonNull(reqVO.getOwnerUserId()) &&
234   - Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) {
235   - throw exception(USER_NOT_EXISTS);
236   - }
  208 + // 3. 记录操作日志上下文
  209 + LogRecordContext.putVariable("clueName", clue.getName());
237 210 }
238 211  
239 212 private CrmClueDO validateClueExists(Long id) {
... ... @@ -245,7 +218,7 @@ public class CrmClueServiceImpl implements CrmClueService {
245 218 }
246 219  
247 220 @Override
248   - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.READ)
  221 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.READ)
249 222 public CrmClueDO getClue(Long id) {
250 223 return clueMapper.selectById(id);
251 224 }
... ... @@ -263,18 +236,9 @@ public class CrmClueServiceImpl implements CrmClueService {
263 236 return clueMapper.selectPage(pageReqVO, userId);
264 237 }
265 238  
266   - /**
267   - * 获得自身的代理对象,解决 AOP 生效问题
268   - *
269   - * @return 自己
270   - */
271   - private CrmClueServiceImpl getSelf() {
272   - return SpringUtil.getBean(getClass());
273   - }
274   -
275 239 @Override
276   - public Long getFollowLeadsCount(Long userId) {
277   - return clueMapper.selectFollowLeadsCount(userId);
  240 + public Long getFollowClueCount(Long userId) {
  241 + return clueMapper.selectCountByFollow(userId);
278 242 }
279 243  
280 244 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
... ... @@ -79,8 +79,8 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
79 79 if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_BUSINESS.getType(), followUpRecord.getBizType())) { // 更新商机跟进信息
80 80 businessService.updateBusinessFollowUpBatch(Collections.singletonList(updateFollowUpReqBO));
81 81 }
82   - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_LEADS.getType(), followUpRecord.getBizType())) { // 更新线索跟进信息
83   - clueService.updateClueFollowUp(updateFollowUpReqBO);
  82 + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CLUE.getType(), followUpRecord.getBizType())) { // 更新线索跟进信息
  83 + clueService.updateClueFollowUp(followUpRecord.getId(), followUpRecord.getNextTime(), followUpRecord.getContent());
84 84 }
85 85 if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTACT.getType(), followUpRecord.getBizType())) { // 更新联系人跟进信息
86 86 contactService.updateContactFollowUpBatch(Collections.singletonList(updateFollowUpReqBO));
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java
... ... @@ -3,9 +3,11 @@ package cn.iocoder.yudao.module.crm.service.permission.bo;
3 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
5 5 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
  6 +import lombok.AllArgsConstructor;
6 7 import lombok.Data;
7 8  
8 9 import jakarta.validation.constraints.NotNull;
  10 +import lombok.NoArgsConstructor;
9 11  
10 12 /**
11 13 * 数据权限转移 Request BO
... ... @@ -13,6 +15,8 @@ import jakarta.validation.constraints.NotNull;
13 15 * @author HUIHUI
14 16 */
15 17 @Data
  18 +@NoArgsConstructor
  19 +@AllArgsConstructor
16 20 public class CrmPermissionTransferReqBO {
17 21  
18 22 /**
... ...