Commit ca9f3c522d9c0d89a9168e5c630d5b583c81057c
1 parent
8675d135
CRM-线索:完善操作日志
Showing
3 changed files
with
90 additions
and
38 deletions
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
| @@ -14,9 +14,13 @@ public interface LogRecordConstants { | @@ -14,9 +14,13 @@ public interface LogRecordConstants { | ||
| 14 | String CRM_LEADS_CREATE_SUB_TYPE = "创建线索"; | 14 | String CRM_LEADS_CREATE_SUB_TYPE = "创建线索"; |
| 15 | String CRM_LEADS_CREATE_SUCCESS = "创建了线索{{#clue.name}}"; | 15 | String CRM_LEADS_CREATE_SUCCESS = "创建了线索{{#clue.name}}"; |
| 16 | String CRM_LEADS_UPDATE_SUB_TYPE = "更新线索"; | 16 | String CRM_LEADS_UPDATE_SUB_TYPE = "更新线索"; |
| 17 | - String CRM_LEADS_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReqVO}}"; | 17 | + String CRM_LEADS_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}"; |
| 18 | String CRM_LEADS_DELETE_SUB_TYPE = "删除线索"; | 18 | String CRM_LEADS_DELETE_SUB_TYPE = "删除线索"; |
| 19 | String CRM_LEADS_DELETE_SUCCESS = "删除了线索【{{#clueName}}】"; | 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}}】转化为客户"; | ||
| 20 | 24 | ||
| 21 | // ======================= CRM_CUSTOMER 客户 ======================= | 25 | // ======================= CRM_CUSTOMER 客户 ======================= |
| 22 | 26 |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.validation.Mobile; | 3 | import cn.iocoder.yudao.framework.common.validation.Mobile; |
| 4 | import cn.iocoder.yudao.framework.common.validation.Telephone; | 4 | import cn.iocoder.yudao.framework.common.validation.Telephone; |
| 5 | +import com.mzt.logapi.starter.annotation.DiffLogField; | ||
| 5 | import io.swagger.v3.oas.annotations.media.Schema; | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 6 | import jakarta.validation.constraints.NotEmpty; | 7 | import jakarta.validation.constraints.NotEmpty; |
| 7 | import lombok.Data; | 8 | import lombok.Data; |
| @@ -19,32 +20,39 @@ public class CrmClueSaveReqVO { | @@ -19,32 +20,39 @@ public class CrmClueSaveReqVO { | ||
| 19 | private Long id; | 20 | private Long id; |
| 20 | 21 | ||
| 21 | @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx") | 22 | @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx") |
| 23 | + @DiffLogField(name = "线索名称") | ||
| 22 | @NotEmpty(message = "线索名称不能为空") | 24 | @NotEmpty(message = "线索名称不能为空") |
| 23 | private String name; | 25 | private String name; |
| 24 | 26 | ||
| 25 | @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") | 27 | @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") |
| 28 | + @DiffLogField(name = "下次联系时间") | ||
| 26 | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | 29 | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| 27 | private LocalDateTime contactNextTime; | 30 | private LocalDateTime contactNextTime; |
| 28 | 31 | ||
| 29 | @Schema(description = "电话", example = "18000000000") | 32 | @Schema(description = "电话", example = "18000000000") |
| 33 | + @DiffLogField(name = "电话") | ||
| 30 | @Telephone | 34 | @Telephone |
| 31 | private String telephone; | 35 | private String telephone; |
| 32 | 36 | ||
| 33 | @Schema(description = "手机号", example = "18000000000") | 37 | @Schema(description = "手机号", example = "18000000000") |
| 38 | + @DiffLogField(name = "手机号") | ||
| 34 | @Mobile | 39 | @Mobile |
| 35 | private String mobile; | 40 | private String mobile; |
| 36 | 41 | ||
| 37 | @Schema(description = "地址", example = "北京市海淀区") | 42 | @Schema(description = "地址", example = "北京市海淀区") |
| 43 | + @DiffLogField(name = "地址") | ||
| 38 | private String address; | 44 | private String address; |
| 39 | 45 | ||
| 40 | @Schema(description = "最后跟进时间") | 46 | @Schema(description = "最后跟进时间") |
| 41 | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | 47 | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) |
| 48 | + @DiffLogField(name = "最后跟进时间") | ||
| 42 | private LocalDateTime contactLastTime; | 49 | private LocalDateTime contactLastTime; |
| 43 | 50 | ||
| 44 | @Schema(description = "负责人编号", example = "2048") | 51 | @Schema(description = "负责人编号", example = "2048") |
| 45 | private Long ownerUserId; | 52 | private Long ownerUserId; |
| 46 | 53 | ||
| 47 | @Schema(description = "备注", example = "随便") | 54 | @Schema(description = "备注", example = "随便") |
| 55 | + @DiffLogField(name = "备注") | ||
| 48 | private String remark; | 56 | private String remark; |
| 49 | 57 | ||
| 50 | } | 58 | } |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
| @@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil; | @@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil; | ||
| 5 | import cn.hutool.core.lang.Assert; | 5 | import cn.hutool.core.lang.Assert; |
| 6 | import cn.hutool.core.util.ObjUtil; | 6 | import cn.hutool.core.util.ObjUtil; |
| 7 | import cn.hutool.core.util.ObjectUtil; | 7 | import cn.hutool.core.util.ObjectUtil; |
| 8 | +import cn.hutool.extra.spring.SpringUtil; | ||
| 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 9 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 10 | import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; | 11 | import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; |
| @@ -76,7 +77,7 @@ public class CrmClueServiceImpl implements CrmClueService { | @@ -76,7 +77,7 @@ public class CrmClueServiceImpl implements CrmClueService { | ||
| 76 | validateRelationDataExists(createReqVO); | 77 | validateRelationDataExists(createReqVO); |
| 77 | 78 | ||
| 78 | // 2. 插入 | 79 | // 2. 插入 |
| 79 | - CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class); | 80 | + CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class).setId(null); |
| 80 | if (ObjUtil.isNull(createReqVO.getOwnerUserId())) { | 81 | if (ObjUtil.isNull(createReqVO.getOwnerUserId())) { |
| 81 | clue.setOwnerUserId(userId); // 如果没有设置负责人那么默认操作人为负责人 | 82 | clue.setOwnerUserId(userId); // 如果没有设置负责人那么默认操作人为负责人 |
| 82 | } else { | 83 | } else { |
| @@ -99,16 +100,16 @@ public class CrmClueServiceImpl implements CrmClueService { | @@ -99,16 +100,16 @@ public class CrmClueServiceImpl implements CrmClueService { | ||
| 99 | @Transactional(rollbackFor = Exception.class) | 100 | @Transactional(rollbackFor = Exception.class) |
| 100 | @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", | 101 | @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", |
| 101 | success = CRM_LEADS_UPDATE_SUCCESS) | 102 | success = CRM_LEADS_UPDATE_SUCCESS) |
| 102 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) | ||
| 103 | - public void updateClue(CrmClueSaveReqVO updateReqVO) { | ||
| 104 | - Assert.notNull(updateReqVO.getId(), "线索编号不能为空"); | 103 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER) |
| 104 | + public void updateClue(CrmClueSaveReqVO updateReq) { | ||
| 105 | + Assert.notNull(updateReq.getId(), "线索编号不能为空"); | ||
| 105 | // 1. 校验线索是否存在 | 106 | // 1. 校验线索是否存在 |
| 106 | - CrmClueDO oldClue = validateClueExists(updateReqVO.getId()); | 107 | + CrmClueDO oldClue = validateClueExists(updateReq.getId()); |
| 107 | // 2. 校验关联数据 | 108 | // 2. 校验关联数据 |
| 108 | - validateRelationDataExists(updateReqVO); | 109 | + validateRelationDataExists(updateReq); |
| 109 | 110 | ||
| 110 | // 3. 更新 | 111 | // 3. 更新 |
| 111 | - CrmClueDO updateObj = BeanUtils.toBean(updateReqVO, CrmClueDO.class); | 112 | + CrmClueDO updateObj = BeanUtils.toBean(updateReq, CrmClueDO.class); |
| 112 | clueMapper.updateById(updateObj); | 113 | clueMapper.updateById(updateObj); |
| 113 | 114 | ||
| 114 | // 3. 记录操作日志上下文 | 115 | // 3. 记录操作日志上下文 |
| @@ -117,8 +118,19 @@ public class CrmClueServiceImpl implements CrmClueService { | @@ -117,8 +118,19 @@ public class CrmClueServiceImpl implements CrmClueService { | ||
| 117 | } | 118 | } |
| 118 | 119 | ||
| 119 | @Override | 120 | @Override |
| 120 | - public void updateClueFollowUp(CrmUpdateFollowUpReqBO clueUpdateFollowUpReqBO) { | ||
| 121 | - clueMapper.updateById(BeanUtils.toBean(clueUpdateFollowUpReqBO, CrmClueDO.class).setId(clueUpdateFollowUpReqBO.getBizId())); | 121 | + @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReq.bizId}", |
| 122 | + success = CRM_LEADS_UPDATE_SUCCESS) | ||
| 123 | + public void updateClueFollowUp(CrmUpdateFollowUpReqBO updateReq) { | ||
| 124 | + // 校验线索是否存在 | ||
| 125 | + CrmClueDO oldClue = validateClueExists(updateReq.getBizId()); | ||
| 126 | + | ||
| 127 | + // 更新 | ||
| 128 | + clueMapper.updateById(BeanUtils.toBean(updateReq, CrmClueDO.class).setId(updateReq.getBizId())); | ||
| 129 | + | ||
| 130 | + // 3. 记录操作日志上下文 | ||
| 131 | + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmUpdateFollowUpReqBO.class)); | ||
| 132 | + LogRecordContext.putVariable("clueName", oldClue.getName()); | ||
| 133 | + | ||
| 122 | } | 134 | } |
| 123 | 135 | ||
| 124 | @Override | 136 | @Override |
| @@ -143,48 +155,28 @@ public class CrmClueServiceImpl implements CrmClueService { | @@ -143,48 +155,28 @@ public class CrmClueServiceImpl implements CrmClueService { | ||
| 143 | LogRecordContext.putVariable("clueName", clue.getName()); | 155 | LogRecordContext.putVariable("clueName", clue.getName()); |
| 144 | } | 156 | } |
| 145 | 157 | ||
| 146 | - private CrmClueDO validateClueExists(Long id) { | ||
| 147 | - CrmClueDO crmClueDO = clueMapper.selectById(id); | ||
| 148 | - if (crmClueDO == null) { | ||
| 149 | - throw exception(CLUE_NOT_EXISTS); | ||
| 150 | - } | ||
| 151 | - return crmClueDO; | ||
| 152 | - } | ||
| 153 | - | ||
| 154 | - @Override | ||
| 155 | - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.READ) | ||
| 156 | - public CrmClueDO getClue(Long id) { | ||
| 157 | - return clueMapper.selectById(id); | ||
| 158 | - } | ||
| 159 | - | ||
| 160 | - @Override | ||
| 161 | - public List<CrmClueDO> getClueList(Collection<Long> ids, Long userId) { | ||
| 162 | - if (CollUtil.isEmpty(ids)) { | ||
| 163 | - return ListUtil.empty(); | ||
| 164 | - } | ||
| 165 | - return clueMapper.selectBatchIds(ids, userId); | ||
| 166 | - } | ||
| 167 | - | ||
| 168 | - @Override | ||
| 169 | - public PageResult<CrmClueDO> getCluePage(CrmCluePageReqVO pageReqVO, Long userId) { | ||
| 170 | - return clueMapper.selectPage(pageReqVO, userId); | ||
| 171 | - } | ||
| 172 | 158 | ||
| 173 | @Override | 159 | @Override |
| 160 | + @Transactional(rollbackFor = Exception.class) | ||
| 161 | + @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", | ||
| 162 | + success = CRM_LEADS_TRANSFER_SUCCESS) | ||
| 163 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) | ||
| 174 | public void transferClue(CrmClueTransferReqVO reqVO, Long userId) { | 164 | public void transferClue(CrmClueTransferReqVO reqVO, Long userId) { |
| 175 | // 1 校验线索是否存在 | 165 | // 1 校验线索是否存在 |
| 176 | - validateClueExists(reqVO.getId()); | 166 | + CrmClueDO clue = validateClueExists(reqVO.getId()); |
| 177 | 167 | ||
| 178 | // 2.1 数据权限转移 | 168 | // 2.1 数据权限转移 |
| 179 | crmPermissionService.transferPermission(CrmClueConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_LEADS.getType())); | 169 | crmPermissionService.transferPermission(CrmClueConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_LEADS.getType())); |
| 180 | // 2.2 设置新的负责人 | 170 | // 2.2 设置新的负责人 |
| 181 | clueMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); | 171 | clueMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); |
| 182 | 172 | ||
| 183 | - // 3. TODO 记录转移日志 | 173 | + // 3. 记录转移日志 |
| 174 | + LogRecordContext.putVariable("clue", clue); | ||
| 184 | } | 175 | } |
| 185 | 176 | ||
| 186 | @Override | 177 | @Override |
| 187 | @Transactional(rollbackFor = Exception.class) | 178 | @Transactional(rollbackFor = Exception.class) |
| 179 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) | ||
| 188 | public void translateCustomer(CrmClueTransformReqVO reqVO, Long userId) { | 180 | public void translateCustomer(CrmClueTransformReqVO reqVO, Long userId) { |
| 189 | // 1.1 校验线索都存在 | 181 | // 1.1 校验线索都存在 |
| 190 | Set<Long> clueIds = reqVO.getIds(); | 182 | Set<Long> clueIds = reqVO.getIds(); |
| @@ -214,6 +206,11 @@ public class CrmClueServiceImpl implements CrmClueService { | @@ -214,6 +206,11 @@ public class CrmClueServiceImpl implements CrmClueService { | ||
| 214 | })); | 206 | })); |
| 215 | // 2.3 复制跟进 | 207 | // 2.3 复制跟进 |
| 216 | updateFollowUpRecords(clueCustomerIdMap); | 208 | updateFollowUpRecords(clueCustomerIdMap); |
| 209 | + | ||
| 210 | + // 3. 记录操作日志 | ||
| 211 | + for (CrmClueDO clue : translateClues) { | ||
| 212 | + getSelf().receiveClueLog(clue); | ||
| 213 | + } | ||
| 217 | } | 214 | } |
| 218 | 215 | ||
| 219 | private void updateFollowUpRecords(Map<Long, Long> clueCustomerIdMap) { | 216 | private void updateFollowUpRecords(Map<Long, Long> clueCustomerIdMap) { |
| @@ -229,6 +226,13 @@ public class CrmClueServiceImpl implements CrmClueService { | @@ -229,6 +226,13 @@ public class CrmClueServiceImpl implements CrmClueService { | ||
| 229 | .setBizId(clueCustomerIdMap.get(followUpRecord.getBizId())))); | 226 | .setBizId(clueCustomerIdMap.get(followUpRecord.getBizId())))); |
| 230 | } | 227 | } |
| 231 | 228 | ||
| 229 | + @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSLATE_SUB_TYPE, bizNo = "{{#clue.id}}", | ||
| 230 | + success = CRM_LEADS_TRANSLATE_SUCCESS) | ||
| 231 | + public void receiveClueLog(CrmClueDO clue) { | ||
| 232 | + // 记录操作日志上下文 | ||
| 233 | + LogRecordContext.putVariable("clue", clue); | ||
| 234 | + } | ||
| 235 | + | ||
| 232 | private void validateRelationDataExists(CrmClueSaveReqVO reqVO) { | 236 | private void validateRelationDataExists(CrmClueSaveReqVO reqVO) { |
| 233 | // 校验负责人 | 237 | // 校验负责人 |
| 234 | if (Objects.nonNull(reqVO.getOwnerUserId()) && | 238 | if (Objects.nonNull(reqVO.getOwnerUserId()) && |
| @@ -237,4 +241,40 @@ public class CrmClueServiceImpl implements CrmClueService { | @@ -237,4 +241,40 @@ public class CrmClueServiceImpl implements CrmClueService { | ||
| 237 | } | 241 | } |
| 238 | } | 242 | } |
| 239 | 243 | ||
| 244 | + private CrmClueDO validateClueExists(Long id) { | ||
| 245 | + CrmClueDO crmClueDO = clueMapper.selectById(id); | ||
| 246 | + if (crmClueDO == null) { | ||
| 247 | + throw exception(CLUE_NOT_EXISTS); | ||
| 248 | + } | ||
| 249 | + return crmClueDO; | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + @Override | ||
| 253 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.READ) | ||
| 254 | + public CrmClueDO getClue(Long id) { | ||
| 255 | + return clueMapper.selectById(id); | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + @Override | ||
| 259 | + public List<CrmClueDO> getClueList(Collection<Long> ids, Long userId) { | ||
| 260 | + if (CollUtil.isEmpty(ids)) { | ||
| 261 | + return ListUtil.empty(); | ||
| 262 | + } | ||
| 263 | + return clueMapper.selectBatchIds(ids, userId); | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + @Override | ||
| 267 | + public PageResult<CrmClueDO> getCluePage(CrmCluePageReqVO pageReqVO, Long userId) { | ||
| 268 | + return clueMapper.selectPage(pageReqVO, userId); | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + /** | ||
| 272 | + * 获得自身的代理对象,解决 AOP 生效问题 | ||
| 273 | + * | ||
| 274 | + * @return 自己 | ||
| 275 | + */ | ||
| 276 | + private CrmClueServiceImpl getSelf() { | ||
| 277 | + return SpringUtil.getBean(getClass()); | ||
| 278 | + } | ||
| 279 | + | ||
| 240 | } | 280 | } |