Commit ca9f3c522d9c0d89a9168e5c630d5b583c81057c

Authored by puhui999
1 parent 8675d135

CRM-线索:完善操作日志

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 }