Commit 38e410cae079fcdfdea8bcca3fd7330528db568a

Authored by YunaiV
1 parent fe330482

📖 CRM:线索的转化逻辑

yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
... ... @@ -16,8 +16,8 @@ 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_ANY_CLUE_NOT_EXISTS = new ErrorCode(1_020_001_001, "线索【{}】不存在");
20   - ErrorCode CLUE_ANY_CLUE_ALREADY_TRANSLATED = new ErrorCode(1_020_001_002, "线索【{}】已经转化过了,请勿重复转化");
  19 + ErrorCode CLUE_NOT_EXISTS_ANY = new ErrorCode(1_020_001_001, "线索【{}】不存在");
  20 + ErrorCode CLUE_TRANSFORM_FAIL_ALREADY = new ErrorCode(1_020_001_002, "线索【{}】已经转化过了,请勿重复转化");
21 21  
22 22 // ========== 商机管理 1-020-002-000 ==========
23 23 ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在");
... ... @@ -25,7 +25,6 @@ public interface ErrorCodeConstants {
25 25  
26 26 // TODO @lilleo:商机状态、商机类型,都单独错误码段
27 27  
28   -
29 28 // ========== 联系人管理 1-020-003-000 ==========
30 29 ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
31 30 ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode(1_020_003_001, "联系人商机关联不存在");
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java
... ... @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  10 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
10 11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
11 12 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*;
12 13 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
... ... @@ -35,8 +36,7 @@ import java.util.stream.Stream;
35 36  
36 37 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
37 38 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
38   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
39   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  39 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
40 40 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
41 41 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
42 42  
... ... @@ -60,7 +60,7 @@ public class CrmClueController {
60 60 @Operation(summary = "创建线索")
61 61 @PreAuthorize("@ss.hasPermission('crm:clue:create')")
62 62 public CommonResult<Long> createClue(@Valid @RequestBody CrmClueSaveReqVO createReqVO) {
63   - return success(clueService.createClue(createReqVO, getLoginUserId()));
  63 + return success(clueService.createClue(createReqVO));
64 64 }
65 65  
66 66 @PutMapping("/update")
... ... @@ -86,7 +86,14 @@ public class CrmClueController {
86 86 @PreAuthorize("@ss.hasPermission('crm:clue:query')")
87 87 public CommonResult<CrmClueRespVO> getClue(@RequestParam("id") Long id) {
88 88 CrmClueDO clue = clueService.getClue(id);
89   - return success(BeanUtils.toBean(clue, CrmClueRespVO.class));
  89 + return success(buildClueDetail(clue));
  90 + }
  91 +
  92 + public CrmClueRespVO buildClueDetail(CrmClueDO clue) {
  93 + if (clue == null) {
  94 + return null;
  95 + }
  96 + return buildClueDetailList(Collections.singletonList(clue)).get(0);
90 97 }
91 98  
92 99 @GetMapping("/page")
... ... @@ -121,6 +128,7 @@ public class CrmClueController {
121 128 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
122 129 // 2. 转换成 VO
123 130 return BeanUtils.toBean(list, CrmClueRespVO.class, clueVO -> {
  131 + clueVO.setAreaName(AreaUtils.format(clueVO.getAreaId()));
124 132 // 2.1 设置客户名称
125 133 MapUtils.findAndThen(customerMap, clueVO.getCustomerId(), customer -> clueVO.setCustomerName(customer.getName()));
126 134 // 2.2 设置创建人、负责人名称
... ... @@ -141,11 +149,12 @@ public class CrmClueController {
141 149 return success(true);
142 150 }
143 151  
144   - @PostMapping("/transform")
  152 + @PutMapping("/transform")
145 153 @Operation(summary = "线索转化为客户")
  154 + @Parameter(name = "ids", description = "线索编号数组", required = true)
146 155 @PreAuthorize("@ss.hasPermission('crm:clue:update')")
147   - public CommonResult<Boolean> translateCustomer(@Valid @RequestBody CrmClueTranslateReqVO reqVO) {
148   - clueService.translateCustomer(reqVO, getLoginUserId());
  156 + public CommonResult<Boolean> transformClue(@RequestParam("ids") List<Long> ids) {
  157 + clueService.transformClue(ids, getLoginUserId());
149 158 return success(Boolean.TRUE);
150 159 }
151 160  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java
... ... @@ -17,6 +17,9 @@ public class CrmCluePageReqVO extends PageParam {
17 17 @Schema(description = "线索名称", example = "线索xxx")
18 18 private String name;
19 19  
  20 + @Schema(description = "转化状态", example = "2048")
  21 + private Boolean transformStatus;
  22 +
20 23 @Schema(description = "电话", example = "18000000000")
21 24 private String telephone;
22 25  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java
... ... @@ -13,6 +13,7 @@ import com.mzt.logapi.starter.annotation.DiffLogField;
13 13 import io.swagger.v3.oas.annotations.media.Schema;
14 14 import jakarta.validation.constraints.Email;
15 15 import jakarta.validation.constraints.NotEmpty;
  16 +import jakarta.validation.constraints.NotNull;
16 17 import jakarta.validation.constraints.Size;
17 18 import lombok.Data;
18 19 import org.springframework.format.annotation.DateTimeFormat;
... ... @@ -45,6 +46,7 @@ public class CrmClueSaveReqVO {
45 46 private LocalDateTime contactNextTime;
46 47  
47 48 @Schema(description = "负责人编号", example = "2048")
  49 + @NotNull(message = "负责人编号不能为空")
48 50 private Long ownerUserId;
49 51  
50 52 @Schema(description = "手机号", example = "18000000000")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTranslateReqVO.java deleted
1   -package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import jakarta.validation.constraints.NotEmpty;
5   -import lombok.Data;
6   -
7   -import java.util.Set;
8   -
9   -@Schema(description = "管理后台 - 线索转化为客户 Request VO")
10   -@Data
11   -public class CrmClueTranslateReqVO {
12   -
13   - @Schema(description = "线索编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024, 1025]")
14   - @NotEmpty(message = "线索编号不能为空")
15   - private Set<Long> ids;
16   -
17   -}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
... ... @@ -36,6 +36,7 @@ public interface CrmClueMapper extends BaseMapperX&lt;CrmClueDO&gt; {
36 36 // 拼接自身的查询条件
37 37 query.selectAll(CrmClueDO.class)
38 38 .likeIfPresent(CrmClueDO::getName, pageReqVO.getName())
  39 + .eqIfPresent(CrmClueDO::getTransformStatus, pageReqVO.getTransformStatus())
39 40 .likeIfPresent(CrmClueDO::getTelephone, pageReqVO.getTelephone())
40 41 .likeIfPresent(CrmClueDO::getMobile, pageReqVO.getMobile())
41 42 .eqIfPresent(CrmClueDO::getIndustryId, pageReqVO.getIndustryId())
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java
... ... @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 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   -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTranslateReqVO;
8 7 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
9 8 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
10 9 import jakarta.validation.Valid;
... ... @@ -23,10 +22,9 @@ public interface CrmClueService {
23 22 * 创建线索
24 23 *
25 24 * @param createReqVO 创建信息
26   - * @param userId 用户编号
27 25 * @return 编号
28 26 */
29   - Long createClue(@Valid CrmClueSaveReqVO createReqVO, Long userId);
  27 + Long createClue(@Valid CrmClueSaveReqVO createReqVO);
30 28  
31 29 /**
32 30 * 更新线索
... ... @@ -85,10 +83,10 @@ public interface CrmClueService {
85 83 /**
86 84 * 线索转化为客户
87 85 *
88   - * @param reqVO 线索编号
  86 + * @param ids 线索编号数组
89 87 * @param userId 用户编号
90 88 */
91   - void translateCustomer(CrmClueTranslateReqVO reqVO, Long userId);
  89 + void transformClue(List<Long> ids, Long userId);
92 90  
93 91 /**
94 92 * 获得分配给我的线索数量
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
... ... @@ -10,7 +10,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 10 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
11 11 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
12 12 import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
13   -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTranslateReqVO;
14 13 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO;
15 14 import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert;
16 15 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
... ... @@ -36,14 +35,16 @@ import org.springframework.transaction.annotation.Transactional;
36 35 import org.springframework.validation.annotation.Validated;
37 36  
38 37 import java.time.LocalDateTime;
39   -import java.util.*;
  38 +import java.util.Collection;
  39 +import java.util.List;
  40 +import java.util.Map;
  41 +import java.util.Objects;
40 42  
41 43 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
42 44 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
43 45 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
44 46 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
45 47 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS;
46   -import static java.util.Collections.singletonList;
47 48  
48 49 /**
49 50 * 线索 Service 实现类
... ... @@ -71,17 +72,13 @@ public class CrmClueServiceImpl implements CrmClueService {
71 72 @Transactional(rollbackFor = Exception.class)
72 73 @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_CREATE_SUB_TYPE, bizNo = "{{#clue.id}}",
73 74 success = CRM_LEADS_CREATE_SUCCESS)
74   - public Long createClue(CrmClueSaveReqVO createReqVO, Long userId) {
  75 + public Long createClue(CrmClueSaveReqVO createReqVO) {
75 76 // 1.1 校验关联数据
76 77 validateRelationDataExists(createReqVO);
77 78 // 1.2 校验负责人是否存在
78   - if (createReqVO.getOwnerUserId() != null) {
79   - adminUserApi.validateUserList(singletonList(createReqVO.getOwnerUserId()));
80   - } else {
81   - createReqVO.setOwnerUserId(userId); // 如果没有设置负责人那么默认操作人为负责人
82   - }
  79 + adminUserApi.validateUser(createReqVO.getOwnerUserId());
83 80  
84   - // 2. 插入
  81 + // 2. 插入线索
85 82 CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class)
86 83 .setContactLastTime(LocalDateTime.now());
87 84 clueMapper.insert(clue);
... ... @@ -103,12 +100,12 @@ public class CrmClueServiceImpl implements CrmClueService {
103 100 @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER)
104 101 public void updateClue(CrmClueSaveReqVO updateReq) {
105 102 Assert.notNull(updateReq.getId(), "线索编号不能为空");
106   - // 1. 校验线索是否存在
  103 + // 1.1 校验线索是否存在
107 104 CrmClueDO oldClue = validateClueExists(updateReq.getId());
108   - // 2. 校验关联数据
  105 + // 1.2 校验关联数据
109 106 validateRelationDataExists(updateReq);
110 107  
111   - // 3. 更新
  108 + // 2. 更新线索
112 109 CrmClueDO updateObj = BeanUtils.toBean(updateReq, CrmClueDO.class);
113 110 clueMapper.updateById(updateObj);
114 111  
... ... @@ -130,7 +127,6 @@ public class CrmClueServiceImpl implements CrmClueService {
130 127 // 3. 记录操作日志上下文
131 128 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmUpdateFollowUpReqBO.class));
132 129 LogRecordContext.putVariable("clueName", oldClue.getName());
133   -
134 130 }
135 131  
136 132 @Override
... ... @@ -155,12 +151,11 @@ public class CrmClueServiceImpl implements CrmClueService {
155 151 LogRecordContext.putVariable("clueName", clue.getName());
156 152 }
157 153  
158   -
159 154 @Override
160 155 @Transactional(rollbackFor = Exception.class)
161 156 @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
162 157 success = CRM_LEADS_TRANSFER_SUCCESS)
163   - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
  158 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER)
164 159 public void transferClue(CrmClueTransferReqVO reqVO, Long userId) {
165 160 // 1 校验线索是否存在
166 161 CrmClueDO clue = validateClueExists(reqVO.getId());
... ... @@ -176,20 +171,19 @@ public class CrmClueServiceImpl implements CrmClueService {
176 171  
177 172 @Override
178 173 @Transactional(rollbackFor = Exception.class)
179   - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
180   - public void translateCustomer(CrmClueTranslateReqVO reqVO, Long userId) {
  174 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#ids", level = CrmPermissionLevelEnum.OWNER)
  175 + public void transformClue(List<Long> ids, Long userId) {
181 176 // 1.1 校验线索都存在
182   - Set<Long> clueIds = reqVO.getIds();
183   - List<CrmClueDO> clues = getClueList(clueIds, userId);
184   - if (CollUtil.isEmpty(clues) || ObjectUtil.notEqual(clues.size(), clueIds.size())) {
185   - clueIds.removeAll(convertSet(clues, CrmClueDO::getId));
186   - throw exception(CLUE_ANY_CLUE_NOT_EXISTS, clueIds);
  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);
187 181 }
188 182 // 1.2 存在已经转化的,直接提示哈。避免操作的用户,以为都转化成功了
189 183 List<CrmClueDO> translatedClues = filterList(clues,
190 184 clue -> ObjectUtil.equal(Boolean.TRUE, clue.getTransformStatus()));
191 185 if (CollUtil.isNotEmpty(translatedClues)) {
192   - throw exception(CLUE_ANY_CLUE_ALREADY_TRANSLATED, convertSet(translatedClues, CrmClueDO::getId));
  186 + throw exception(CLUE_TRANSFORM_FAIL_ALREADY, convertSet(translatedClues, CrmClueDO::getId));
193 187 }
194 188  
195 189 // 2.1 遍历线索(未转化的线索),创建对应的客户
... ...