Commit a531ba29022e5f219eff75aaa74c2c1e758d1757

Authored by YunaiV
1 parent 5fbb4236

✨ CRM:增加回款超过合同的退款金额校验

yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -39,6 +39,7 @@ public interface ErrorCodeConstants { @@ -39,6 +39,7 @@ public interface ErrorCodeConstants {
39 ErrorCode RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_004_004, "更新回款审核状态失败,原因:回款不是审核中状态"); 39 ErrorCode RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_004_004, "更新回款审核状态失败,原因:回款不是审核中状态");
40 ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试"); 40 ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试");
41 ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态"); 41 ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态");
  42 + ErrorCode RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_004_007, "创建回款失败,原因:回款金额超出合同金额,目前剩余可退:{} 元");
42 43
43 // ========== 回款计划 1-020-005-000 ========== 44 // ========== 回款计划 1-020-005-000 ==========
44 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在"); 45 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
@@ -78,6 +78,12 @@ public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> { @@ -78,6 +78,12 @@ public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> {
78 return selectCount(query); 78 return selectCount(query);
79 } 79 }
80 80
  81 + default List<CrmReceivableDO> selectListByContractIdAndStatus(Long contractId, Collection<Integer> auditStatuses) {
  82 + return selectList(new LambdaQueryWrapperX<CrmReceivableDO>()
  83 + .eq(CrmReceivableDO::getContractId, contractId)
  84 + .in(CrmReceivableDO::getAuditStatus, auditStatuses));
  85 + }
  86 +
81 default Map<Long, BigDecimal> selectReceivablePriceMapByContractId(Collection<Long> contractIds) { 87 default Map<Long, BigDecimal> selectReceivablePriceMapByContractId(Collection<Long> contractIds) {
82 if (CollUtil.isEmpty(contractIds)) { 88 if (CollUtil.isEmpty(contractIds)) {
83 return Collections.emptyMap(); 89 return Collections.emptyMap();
@@ -85,7 +91,8 @@ public interface CrmReceivableMapper extends BaseMapperX&lt;CrmReceivableDO&gt; { @@ -85,7 +91,8 @@ public interface CrmReceivableMapper extends BaseMapperX&lt;CrmReceivableDO&gt; {
85 // SQL sum 查询 91 // SQL sum 查询
86 List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmReceivableDO>() 92 List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmReceivableDO>()
87 .select("contract_id, SUM(price) AS total_price") 93 .select("contract_id, SUM(price) AS total_price")
88 - .eq("audit_status", CrmAuditStatusEnum.APPROVE.getStatus()) 94 + .in("audit_status", CrmAuditStatusEnum.DRAFT.getStatus(), // 草稿 + 审批中 + 审批通过
  95 + CrmAuditStatusEnum.PROCESS, CrmAuditStatusEnum.APPROVE.getStatus())
89 .groupBy("contract_id") 96 .groupBy("contract_id")
90 .in("contract_id", contractIds)); 97 .in("contract_id", contractIds));
91 // 获得金额 98 // 获得金额
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
@@ -6,6 +6,7 @@ import cn.hutool.core.lang.Assert; @@ -6,6 +6,7 @@ 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.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
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.framework.common.util.object.ObjectUtils; 11 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
11 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; 12 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
@@ -36,6 +37,7 @@ import org.springframework.transaction.annotation.Transactional; @@ -36,6 +37,7 @@ import org.springframework.transaction.annotation.Transactional;
36 import org.springframework.validation.annotation.Validated; 37 import org.springframework.validation.annotation.Validated;
37 38
38 import java.math.BigDecimal; 39 import java.math.BigDecimal;
  40 +import java.util.Arrays;
39 import java.util.Collection; 41 import java.util.Collection;
40 import java.util.List; 42 import java.util.List;
41 import java.util.Map; 43 import java.util.Map;
@@ -85,18 +87,21 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -85,18 +87,21 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
85 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}", 87 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}",
86 success = CRM_RECEIVABLE_CREATE_SUCCESS) 88 success = CRM_RECEIVABLE_CREATE_SUCCESS)
87 public Long createReceivable(CrmReceivableSaveReqVO createReqVO) { 89 public Long createReceivable(CrmReceivableSaveReqVO createReqVO) {
88 - // 1.1 校验关联数据存在 90 + // 1.1 校验可回款金额超过上限
  91 + validateReceivablePriceExceedsLimit(createReqVO);
  92 + // 1.2 校验关联数据存在
89 validateRelationDataExists(createReqVO); 93 validateRelationDataExists(createReqVO);
90 - // 1.2 生成回款编号 94 + // 1.3 生成回款编号
91 String no = noRedisDAO.generate(CrmNoRedisDAO.RECEIVABLE_PREFIX); 95 String no = noRedisDAO.generate(CrmNoRedisDAO.RECEIVABLE_PREFIX);
92 if (receivableMapper.selectByNo(no) != null) { 96 if (receivableMapper.selectByNo(no) != null) {
93 throw exception(RECEIVABLE_NO_EXISTS); 97 throw exception(RECEIVABLE_NO_EXISTS);
94 } 98 }
95 99
96 - // 2. 插入回款 100 + // 2.1 插入回款
97 CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class) 101 CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class)
98 .setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus()); 102 .setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
99 receivableMapper.insert(receivable); 103 receivableMapper.insert(receivable);
  104 + // 2.2
100 105
101 // 3. 创建数据权限 106 // 3. 创建数据权限
102 permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType()) 107 permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType())
@@ -113,6 +118,22 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -113,6 +118,22 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
113 return receivable.getId(); 118 return receivable.getId();
114 } 119 }
115 120
  121 + private void validateReceivablePriceExceedsLimit(CrmReceivableSaveReqVO reqVO) {
  122 + // 1. 计算剩余可退款金额,不包括 reqVO 自身
  123 + CrmContractDO contract = contractService.validateContract(reqVO.getContractId());
  124 + List<CrmReceivableDO> receivables = receivableMapper.selectListByContractIdAndStatus(reqVO.getContractId(),
  125 + Arrays.asList(CrmAuditStatusEnum.APPROVE.getStatus(), CrmAuditStatusEnum.PROCESS.getStatus()));
  126 + if (reqVO.getId() != null) {
  127 + receivables.removeIf(receivable -> ObjectUtil.equal(receivable.getId(), reqVO.getId()));
  128 + }
  129 + BigDecimal notReceivablePrice = contract.getTotalPrice().subtract(
  130 + CollectionUtils.getSumValue(receivables, CrmReceivableDO::getPrice, BigDecimal::add, BigDecimal.ZERO));
  131 + // 2. 校验金额是否超过
  132 + if (reqVO.getPrice().compareTo(notReceivablePrice) > 0) {
  133 + throw exception(RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT, notReceivablePrice);
  134 + }
  135 + }
  136 +
116 private void validateRelationDataExists(CrmReceivableSaveReqVO reqVO) { 137 private void validateRelationDataExists(CrmReceivableSaveReqVO reqVO) {
117 if (reqVO.getOwnerUserId() != null) { 138 if (reqVO.getOwnerUserId() != null) {
118 adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在 139 adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在
@@ -144,9 +165,11 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -144,9 +165,11 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
144 public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) { 165 public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) {
145 Assert.notNull(updateReqVO.getId(), "回款编号不能为空"); 166 Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
146 updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段 167 updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
147 - // 1.1 校验存在 168 + // 1.1 校验可回款金额超过上限
  169 + validateReceivablePriceExceedsLimit(updateReqVO);
  170 + // 1.2 校验存在
148 CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId()); 171 CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());
149 - // 1.2 只有草稿、审批中,可以编辑; 172 + // 1.3 只有草稿、审批中,可以编辑;
150 if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), 173 if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
151 CrmAuditStatusEnum.PROCESS.getStatus())) { 174 CrmAuditStatusEnum.PROCESS.getStatus())) {
152 throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED); 175 throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
@@ -189,6 +212,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -189,6 +212,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
189 if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) { 212 if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
190 throw exception(RECEIVABLE_DELETE_FAIL); 213 throw exception(RECEIVABLE_DELETE_FAIL);
191 } 214 }
  215 + // TODO @芋艿:审批通过时,不允许删除;
192 216
193 // 2. 删除 217 // 2. 删除
194 receivableMapper.deleteById(id); 218 receivableMapper.deleteById(id);