Commit 720b54c353cc199d86aae346ecacce7c471070f0

Authored by puhui999
1 parent c3337f21

CRM: 完善回款操作日志

yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
... ... @@ -15,6 +15,7 @@ public interface ErrorCodeConstants {
15 15 ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态");
16 16 ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态");
17 17 ErrorCode CONTRACT_NO_EXISTS = new ErrorCode(1_020_000_004, "生成合同序列号重复,请重试");
  18 + ErrorCode CONTRACT_DELETE_FAIL = new ErrorCode(1_020_000_005, "删除合同失败,原因:有被回款所使用");
18 19  
19 20 // ========== 线索管理 1-020-001-000 ==========
20 21 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
... ... @@ -40,6 +41,7 @@ public interface ErrorCodeConstants {
40 41 ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试");
41 42 ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态");
42 43 ErrorCode RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_004_007, "创建回款失败,原因:回款金额超出合同金额,目前剩余可退:{} 元");
  44 + ErrorCode RECEIVABLE_DELETE_FAIL_IS_APPROVE = new ErrorCode(1_020_004_008, "删除回款失败,原因:回款审批已通过");
43 45  
44 46 // ========== 回款计划 1-020-005-000 ==========
45 47 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
... ... @@ -142,11 +142,11 @@ public interface LogRecordConstants {
142 142  
143 143 String CRM_RECEIVABLE_TYPE = "CRM 回款";
144 144 String CRM_RECEIVABLE_CREATE_SUB_TYPE = "创建回款";
145   - String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
  145 + String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款";
146 146 String CRM_RECEIVABLE_UPDATE_SUB_TYPE = "更新回款";
147   - String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}";
  147 + String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款: {_DIFF{#updateReqVO}}";
148 148 String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款";
149   - String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
  149 + String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的{{#period != null ? '【第'+ #period +'期】' : '编号为【'+ #receivable.no +'】的'}}回款";
150 150 String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批";
151 151 String CRM_RECEIVABLE_SUBMIT_SUCCESS = "提交编号为【{{#receivableNo}}】的回款审批成功";
152 152  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java
1 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
2 2  
3 3 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
  4 +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 5 import com.alibaba.excel.annotation.ExcelProperty;
5 6 import io.swagger.v3.oas.annotations.media.Schema;
6 7 import lombok.Data;
... ... @@ -8,53 +9,69 @@ import lombok.Data;
8 9 import java.math.BigDecimal;
9 10 import java.time.LocalDateTime;
10 11  
11   -// TODO @puhui999:缺导出
12 12 @Schema(description = "管理后台 - CRM 回款计划 Response VO")
13 13 @Data
  14 +@ExcelIgnoreUnannotated
14 15 public class CrmReceivablePlanRespVO {
15 16  
16 17 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  18 + @ExcelProperty("编号")
17 19 private Long id;
18 20  
19 21 @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  22 + @ExcelProperty("期数")
20 23 private Integer period;
21 24  
22 25 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  26 + @ExcelProperty("客户编号")
23 27 private Long customerId;
24 28 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
  29 + @ExcelProperty("客户名字")
25 30 private String customerName;
26 31  
27 32 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  33 + @ExcelProperty("合同编号")
28 34 private Long contractId;
29 35 @Schema(description = "合同编号", example = "Q110")
  36 + @ExcelProperty("合同编号")
30 37 private String contractNo;
31 38  
32 39 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  40 + @ExcelProperty("负责人编号")
33 41 private Long ownerUserId;
34 42 @Schema(description = "负责人", example = "test")
  43 + @ExcelProperty("负责人")
35 44 private String ownerUserName;
36 45  
37 46 @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  47 + @ExcelProperty("计划回款日期")
38 48 private LocalDateTime returnTime;
39 49  
40 50 @Schema(description = "计划回款方式", example = "1")
  51 + @ExcelProperty("计划回款方式")
41 52 private Integer returnType;
42 53  
43 54 @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  55 + @ExcelProperty("计划回款金额")
44 56 private BigDecimal price;
45 57  
46 58 @Schema(description = "回款编号", example = "19852")
  59 + @ExcelProperty("回款编号")
47 60 private Long receivableId;
48 61 @Schema(description = "回款信息")
  62 + @ExcelProperty("回款信息")
49 63 private CrmReceivableRespVO receivable;
50 64  
51 65 @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  66 + @ExcelProperty("提前几天提醒")
52 67 private Integer remindDays;
53 68  
54 69 @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  70 + @ExcelProperty("提醒日期")
55 71 private LocalDateTime remindTime;
56 72  
57 73 @Schema(description = "备注", example = "备注")
  74 + @ExcelProperty("备注")
58 75 private String remark;
59 76  
60 77 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java
1 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
2 2  
3 3 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
  4 +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 5 import com.alibaba.excel.annotation.ExcelProperty;
5 6 import io.swagger.v3.oas.annotations.media.Schema;
6 7 import lombok.Data;
... ... @@ -8,37 +9,47 @@ import lombok.Data;
8 9 import java.math.BigDecimal;
9 10 import java.time.LocalDateTime;
10 11  
11   -// TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能
12 12 @Schema(description = "管理后台 - CRM 回款 Response VO")
13 13 @Data
  14 +@ExcelIgnoreUnannotated
14 15 public class CrmReceivableRespVO {
15 16  
16 17 @Schema(description = "编号", example = "25787")
  18 + @ExcelProperty("编号")
17 19 private Long id;
18 20  
19 21 @Schema(description = "回款编号", example = "31177")
  22 + @ExcelProperty("回款编号")
20 23 private String no;
21 24  
22 25 @Schema(description = "回款计划编号", example = "1024")
  26 + @ExcelProperty("回款计划编号")
23 27 private Long planId;
24 28  
25 29 @Schema(description = "回款方式", example = "2")
  30 + @ExcelProperty("回款方式")
26 31 private Integer returnType;
27 32  
28 33 @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  34 + @ExcelProperty("回款金额")
29 35 private BigDecimal price;
30 36  
31 37 @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  38 + @ExcelProperty("计划回款日期")
32 39 private LocalDateTime returnTime;
33 40  
34 41 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  42 + @ExcelProperty("客户编号")
35 43 private Long customerId;
36 44 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
  45 + @ExcelProperty("客户名字")
37 46 private String customerName;
38 47  
39 48 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  49 + @ExcelProperty("合同编号")
40 50 private Long contractId;
41 51 @Schema(description = "合同信息")
  52 + @ExcelProperty("合同信息")
42 53 private CrmContractRespVO contract;
43 54  
44 55 @Schema(description = "负责人的用户编号", example = "25682")
... ... @@ -56,20 +67,26 @@ public class CrmReceivableRespVO {
56 67 private String processInstanceId;
57 68  
58 69 @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  70 + @ExcelProperty("审批状态")
59 71 private Integer auditStatus;
60 72  
61   - @Schema(description = "备注", example = "备注")
  73 + @Schema(description = "工作流编号", example = "备注")
  74 + @ExcelProperty("工作流编号")
62 75 private String remark;
63 76  
64 77 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  78 + @ExcelProperty("创建时间")
65 79 private LocalDateTime createTime;
66 80  
67 81 @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  82 + @ExcelProperty("更新时间")
68 83 private LocalDateTime updateTime;
69 84  
70 85 @Schema(description = "创建人", example = "25682")
  86 + @ExcelProperty("创建人")
71 87 private String creator;
72 88 @Schema(description = "创建人名字", example = "test")
  89 + @ExcelProperty("创建人名字")
73 90 private String creatorName;
74 91  
75 92 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
... ... @@ -30,12 +30,14 @@ import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
30 30 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
31 31 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
32 32 import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
  33 +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
33 34 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
34 35 import com.mzt.logapi.context.LogRecordContext;
35 36 import com.mzt.logapi.service.impl.DiffParseFunction;
36 37 import com.mzt.logapi.starter.annotation.LogRecord;
37 38 import jakarta.annotation.Resource;
38 39 import lombok.extern.slf4j.Slf4j;
  40 +import org.springframework.context.annotation.Lazy;
39 41 import org.springframework.stereotype.Service;
40 42 import org.springframework.transaction.annotation.Transactional;
41 43 import org.springframework.validation.annotation.Validated;
... ... @@ -86,7 +88,9 @@ public class CrmContractServiceImpl implements CrmContractService {
86 88 private CrmContactService contactService;
87 89 @Resource
88 90 private CrmContractConfigService contractConfigService;
89   -
  91 + @Resource
  92 + @Lazy // 延迟加载,避免循环依赖
  93 + private CrmReceivableService receivableService;
90 94 @Resource
91 95 private AdminUserApi adminUserApi;
92 96 @Resource
... ... @@ -222,9 +226,12 @@ public class CrmContractServiceImpl implements CrmContractService {
222 226 success = CRM_CONTRACT_DELETE_SUCCESS)
223 227 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
224 228 public void deleteContract(Long id) {
225   - // TODO @puhui999:如果被 CrmReceivableDO 所使用,则不允许删除
226 229 // 校验存在
227 230 CrmContractDO contract = validateContractExists(id);
  231 + // 如果被 CrmReceivableDO 所使用,则不允许删除
  232 + if (CollUtil.isNotEmpty(receivableService.getReceivableByContractId(contract.getId()))) {
  233 + throw exception(CONTRACT_DELETE_FAIL);
  234 + }
228 235 // 删除
229 236 contractMapper.deleteById(id);
230 237 // 删除数据权限
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
... ... @@ -122,4 +122,12 @@ public interface CrmReceivableService {
122 122 */
123 123 Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds);
124 124  
  125 + /**
  126 + * 更具合同编号查询回款列表
  127 + *
  128 + * @param contractId 合同编号
  129 + * @return 回款
  130 + */
  131 + List<CrmReceivableDO> getReceivableByContractId(Long contractId);
  132 +
125 133 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
... ... @@ -37,10 +37,7 @@ import org.springframework.transaction.annotation.Transactional;
37 37 import org.springframework.validation.annotation.Validated;
38 38  
39 39 import java.math.BigDecimal;
40   -import java.util.Arrays;
41   -import java.util.Collection;
42   -import java.util.List;
43   -import java.util.Map;
  40 +import java.util.*;
44 41  
45 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
46 43 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
... ... @@ -81,7 +78,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
81 78 @Resource
82 79 private BpmProcessInstanceApi bpmProcessInstanceApi;
83 80  
84   - // TODO @puhui999:操作日志没记录上
85 81 @Override
86 82 @Transactional(rollbackFor = Exception.class)
87 83 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}",
... ... @@ -115,6 +111,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
115 111  
116 112 // 5. 记录操作日志上下文
117 113 LogRecordContext.putVariable("receivable", receivable);
  114 + LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
118 115 return receivable.getId();
119 116 }
120 117  
... ... @@ -156,7 +153,6 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
156 153 }
157 154 }
158 155  
159   - // TODO @puhui999:操作日志没记录上
160 156 @Override
161 157 @Transactional(rollbackFor = Exception.class)
162 158 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
... ... @@ -165,10 +161,13 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
165 161 public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) {
166 162 Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
167 163 updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
168   - // 1.1 校验可回款金额超过上限
169   - validateReceivablePriceExceedsLimit(updateReqVO);
170   - // 1.2 校验存在
  164 + // 1.1 校验存在
171 165 CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());
  166 + updateReqVO.setOwnerUserId(receivable.getOwnerUserId()).setCustomerId(receivable.getCustomerId())
  167 + .setContractId(receivable.getContractId()).setPlanId(receivable.getPlanId()); // 设置已存在的值
  168 + // 1.2 校验可回款金额超过上限
  169 + validateReceivablePriceExceedsLimit(updateReqVO);
  170 +
172 171 // 1.3 只有草稿、审批中,可以编辑;
173 172 if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
174 173 CrmAuditStatusEnum.PROCESS.getStatus())) {
... ... @@ -180,8 +179,17 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
180 179 receivableMapper.updateById(updateObj);
181 180  
182 181 // 3. 记录操作日志上下文
183   - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
184 182 LogRecordContext.putVariable("receivable", receivable);
  183 + LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
  184 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
  185 + }
  186 +
  187 + private Integer getReceivablePeriod(Long planId) {
  188 + if (Objects.isNull(planId)) {
  189 + return null;
  190 + }
  191 + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(planId);
  192 + return receivablePlan.getPeriod();
185 193 }
186 194  
187 195 @Override
... ... @@ -212,8 +220,10 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
212 220 if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
213 221 throw exception(RECEIVABLE_DELETE_FAIL);
214 222 }
215   - // TODO @puhui999:审批通过时,不允许删除;
216   -
  223 + // 1.3 审批通过时,不允许删除
  224 + if (ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
  225 + throw exception(RECEIVABLE_DELETE_FAIL_IS_APPROVE);
  226 + }
217 227 // 2. 删除
218 228 receivableMapper.deleteById(id);
219 229 // 3. 删除数据权限
... ... @@ -221,6 +231,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
221 231  
222 232 // 4. 记录操作日志上下文
223 233 LogRecordContext.putVariable("receivable", receivable);
  234 + LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
224 235 }
225 236  
226 237 @Override
... ... @@ -289,4 +300,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
289 300 return receivableMapper.selectReceivablePriceMapByContractId(contractIds);
290 301 }
291 302  
  303 + @Override
  304 + public List<CrmReceivableDO> getReceivableByContractId(Long contractId) {
  305 + return receivableMapper.selectList(CrmReceivableDO::getContractId, contractId);
  306 + }
  307 +
292 308 }
... ...