Commit 38e410cae079fcdfdea8bcca3fd7330528db568a
1 parent
fe330482
📖 CRM:线索的转化逻辑
Showing
8 changed files
with
45 additions
and
56 deletions
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<CrmClueDO> { |
| 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 遍历线索(未转化的线索),创建对应的客户 | ... | ... |