Commit dc695e3cdab93073ec4f2459a87b9d63395815ef

Authored by puhui999
2 parents 462a4ee8 5fbb4236

Merge remote-tracking branch 'yudao/develop' into develop

Showing 29 changed files with 465 additions and 313 deletions
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
... ... @@ -14,5 +14,6 @@ public interface DictTypeConstants {
14 14 String CRM_PRODUCT_UNIT = "crm_product_unit"; // CRM 产品单位
15 15 String CRM_PRODUCT_STATUS = "crm_product_status"; // CRM 产品状态
16 16 String CRM_FOLLOW_UP_TYPE = "crm_follow_up_type"; // CRM 跟进方式
  17 + String CRM_RECEIVABLE_RETURN_TYPE = "crm_receivable_return_type"; // CRM 回款方式
17 18  
18 19 }
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
... ... @@ -28,7 +28,6 @@ public interface ErrorCodeConstants {
28 28  
29 29 // ========== 联系人管理 1-020-003-000 ==========
30 30 ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
31   - ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode(1_020_003_001, "联系人商机关联不存在");
32 31 ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode(1_020_003_002, "联系人已关联合同,不能删除");
33 32 ErrorCode CONTACT_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_003_003, "更新联系人负责人失败");
34 33  
... ... @@ -38,10 +37,13 @@ public interface ErrorCodeConstants {
38 37 ErrorCode RECEIVABLE_DELETE_FAIL = new ErrorCode(1_020_004_002, "删除回款失败,原因: 被回款计划所使用,不允许删除");
39 38 ErrorCode RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_004_003, "回款提交审核失败,原因:回款没处在未提交状态");
40 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, "生成回款序列号重复,请重试");
  41 + ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态");
41 42  
42 43 // ========== 回款计划 1-020-005-000 ==========
43 44 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
44   - ErrorCode RECEIVABLE_PLAN_UPDATE_FAIL = new ErrorCode(1_020_006_000, "更想回款计划失败,原因:{}");
  45 + ErrorCode RECEIVABLE_PLAN_UPDATE_FAIL = new ErrorCode(1_020_006_000, "更想回款计划失败,原因:已经有对应的还款");
  46 + ErrorCode RECEIVABLE_PLAN_EXISTS_RECEIVABLE = new ErrorCode(1_020_006_001, "回款计划已经有对应的回款,不能使用");
45 47  
46 48 // ========== 客户管理 1_020_006_000 ==========
47 49 ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在");
... ... @@ -64,10 +66,8 @@ public interface ErrorCodeConstants {
64 66 // ========== 权限管理 1_020_007_000 ==========
65 67 ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
66 68 ErrorCode CRM_PERMISSION_DENIED = new ErrorCode(1_020_007_001, "{}操作失败,原因:没有权限");
67   - ErrorCode CRM_PERMISSION_MODEL_NOT_EXISTS = new ErrorCode(1_020_007_002, "{}不存在");
68 69 ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS = new ErrorCode(1_020_007_003, "{}操作失败,原因:转移对象已经是该负责人");
69 70 ErrorCode CRM_PERMISSION_DELETE_FAIL = new ErrorCode(1_020_007_004, "删除数据权限失败,原因:批量删除权限的时候,只能属于同一个 bizId 下");
70   - ErrorCode CRM_PERMISSION_DELETE_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_005, "删除数据权限失败,原因:存在负责人");
71 71 ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限");
72 72 ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人");
73 73 ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限");
... ... @@ -93,7 +93,6 @@ public interface ErrorCodeConstants {
93 93 ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_010_003, "商机状态不存在");
94 94  
95 95 // ========== 客户公海规则设置 1_020_012_000 ==========
96   - ErrorCode CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_020_012_000, "客户公海配置不存在或未启用");
97 96 ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_012_001, "客户限制配置不存在");
98 97  
99 98 // ========== 跟进记录 1_020_013_000 ==========
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 8 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
8 9 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
... ... @@ -24,6 +25,7 @@ import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
24 25 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
25 26 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
26 27 import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
  28 +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
27 29 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
28 30 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
29 31 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
... ... @@ -39,6 +41,7 @@ import org.springframework.validation.annotation.Validated;
39 41 import org.springframework.web.bind.annotation.*;
40 42  
41 43 import java.io.IOException;
  44 +import java.math.BigDecimal;
42 45 import java.util.Collections;
43 46 import java.util.List;
44 47 import java.util.Map;
... ... @@ -67,6 +70,8 @@ public class CrmContractController {
67 70 private CrmBusinessService businessService;
68 71 @Resource
69 72 private CrmProductService productService;
  73 + @Resource
  74 + private CrmReceivableService receivableService;
70 75  
71 76 @Resource
72 77 private AdminUserApi adminUserApi;
... ... @@ -174,12 +179,6 @@ public class CrmContractController {
174 179 return success(true);
175 180 }
176 181  
177   - /**
178   - * 构建详细的合同结果
179   - *
180   - * @param contractList 原始合同信息
181   - * @return 细的合同结果
182   - */
183 182 private List<CrmContractRespVO> buildContractDetailList(List<CrmContractDO> contractList) {
184 183 if (CollUtil.isEmpty(contractList)) {
185 184 return Collections.emptyList();
... ... @@ -197,6 +196,9 @@ public class CrmContractController {
197 196 // 1.4 获取商机
198 197 Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap(
199 198 convertSet(contractList, CrmContractDO::getBusinessId));
  199 + // 1.5 获得已回款金额
  200 + Map<Long, BigDecimal> receivablePriceMap = receivableService.getReceivablePriceMapByContractId(
  201 + convertSet(contractList, CrmContractDO::getId));
200 202 // 2. 拼接数据
201 203 return BeanUtils.toBean(contractList, CrmContractRespVO.class, contractVO -> {
202 204 // 2.1 设置客户信息
... ... @@ -212,6 +214,8 @@ public class CrmContractController {
212 214 findAndThen(contactMap, contractVO.getSignContactId(), contact -> contractVO.setSignContactName(contact.getName()));
213 215 // 2.4 设置商机信息
214 216 findAndThen(businessMap, contractVO.getBusinessId(), business -> contractVO.setBusinessName(business.getName()));
  217 + // 2.5 设置已回款金额
  218 + contractVO.setTotalReceivablePrice(receivablePriceMap.getOrDefault(contractVO.getId(), BigDecimal.ZERO));
215 219 });
216 220 }
217 221  
... ... @@ -229,13 +233,24 @@ public class CrmContractController {
229 233 return success(contractService.getRemindContractCount(getLoginUserId()));
230 234 }
231 235  
232   - @GetMapping("/list-all-simple-by-customer")
233   - @Operation(summary = "获得合同精简列表", description = "只包含有读权限的客户的合同,主要用于前端的下拉选项")
  236 + @GetMapping("/simple-list")
  237 + @Operation(summary = "获得合同精简列表", description = "只包含的合同,主要用于前端的下拉选项")
234 238 @Parameter(name = "customerId", description = "客户编号", required = true)
235 239 @PreAuthorize("@ss.hasPermission('crm:contract:query')")
236   - public CommonResult<List<CrmContractRespVO>> getListAllSimpleByCustomer(@RequestParam("customerId") Long customerId) {
237   - PageResult<CrmContractDO> result = contractService.getContractPageByCustomerId(new CrmContractPageReqVO().setCustomerId(customerId));
238   - return success(BeanUtils.toBean(result.getList(), CrmContractRespVO.class));
  240 + public CommonResult<List<CrmContractRespVO>> getContractSimpleList(@RequestParam("customerId") Long customerId) {
  241 + CrmContractPageReqVO pageReqVO = new CrmContractPageReqVO().setCustomerId(customerId);
  242 + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); // 不分页
  243 + PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageReqVO);
  244 + if (CollUtil.isEmpty(pageResult.getList())) {
  245 + return success(Collections.emptyList());
  246 + }
  247 + // 拼接数据
  248 + Map<Long, BigDecimal> receivablePriceMap = receivableService.getReceivablePriceMapByContractId(
  249 + convertSet(pageResult.getList(), CrmContractDO::getId));
  250 + return success(convertList(pageResult.getList(), contract -> new CrmContractRespVO() // 只返回 id、name 等精简字段
  251 + .setId(contract.getId()).setName(contract.getName()).setAuditStatus(contract.getAuditStatus())
  252 + .setTotalPrice(contract.getTotalPrice())
  253 + .setTotalReceivablePrice(receivablePriceMap.getOrDefault(contract.getId(), BigDecimal.ZERO))));
239 254 }
240 255  
241 256 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
... ... @@ -88,6 +88,10 @@ public class CrmContractRespVO {
88 88 @ExcelProperty("合同金额")
89 89 private BigDecimal totalPrice;
90 90  
  91 + @Schema(description = "已回款金额", example = "5617")
  92 + @ExcelProperty("已回款金额")
  93 + private BigDecimal totalReceivablePrice;
  94 +
91 95 @Schema(description = "客户签约人编号", example = "18546")
92 96 private Long signContactId;
93 97 @Schema(description = "客户签约人", example = "小豆")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java
... ... @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 6 import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogPageReqVO;
7   -import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogV2RespVO;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogRespVO;
8 8 import cn.iocoder.yudao.module.crm.enums.LogRecordConstants;
9 9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
10 10 import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
... ... @@ -54,11 +54,11 @@ public class CrmOperateLogController {
54 54 @GetMapping("/page")
55 55 @Operation(summary = "获得操作日志")
56 56 @PreAuthorize("@ss.hasPermission('crm:operate-log:query')")
57   - public CommonResult<PageResult<CrmOperateLogV2RespVO>> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
  57 + public CommonResult<PageResult<CrmOperateLogRespVO>> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
58 58 OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO();
59 59 reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页,需要分页需注释
60 60 reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId());
61   - return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogV2RespVO.class));
  61 + return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogRespVO.class));
62 62 }
63 63  
64 64 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogRespVO.java
... ... @@ -6,10 +6,10 @@ import lombok.Data;
6 6  
7 7 import java.time.LocalDateTime;
8 8  
9   -@Schema(description = "管理后台 - CRM 跟进 Response VO")
  9 +@Schema(description = "管理后台 - CRM 操作日志 Response VO")
10 10 @Data
11 11 @ExcelIgnoreUnannotated
12   -public class CrmOperateLogV2RespVO {
  12 +public class CrmOperateLogRespVO {
13 13  
14 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
15 15 private Long id;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
... ... @@ -4,20 +4,23 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
10 11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  12 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
11 13 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
12 14 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
13 15 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
14   -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert;
15 16 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 17 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
17 18 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
18 19 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
19 20 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
20 21 import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
  22 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  23 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
21 24 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
22 25 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
23 26 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -31,14 +34,15 @@ import org.springframework.validation.annotation.Validated;
31 34 import org.springframework.web.bind.annotation.*;
32 35  
33 36 import java.io.IOException;
  37 +import java.util.Collections;
34 38 import java.util.List;
35 39 import java.util.Map;
36 40 import java.util.stream.Stream;
37 41  
38 42 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
39 43 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
40   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
41   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  45 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
42 46 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
43 47 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
44 48  
... ... @@ -57,6 +61,8 @@ public class CrmReceivableController {
57 61  
58 62 @Resource
59 63 private AdminUserApi adminUserApi;
  64 + @Resource
  65 + private DeptApi deptApi;
60 66  
61 67 @PostMapping("/create")
62 68 @Operation(summary = "创建回款")
... ... @@ -88,7 +94,14 @@ public class CrmReceivableController {
88 94 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
89 95 public CommonResult<CrmReceivableRespVO> getReceivable(@RequestParam("id") Long id) {
90 96 CrmReceivableDO receivable = receivableService.getReceivable(id);
91   - return success(BeanUtils.toBean(receivable, CrmReceivableRespVO.class));
  97 + return success(buildReceivableDetail(receivable));
  98 + }
  99 +
  100 + private CrmReceivableRespVO buildReceivableDetail(CrmReceivableDO receivable) {
  101 + if (receivable == null) {
  102 + return null;
  103 + }
  104 + return buildReceivableDetailList(Collections.singletonList(receivable)).get(0);
92 105 }
93 106  
94 107 @GetMapping("/page")
... ... @@ -96,7 +109,7 @@ public class CrmReceivableController {
96 109 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
97 110 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) {
98 111 PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId());
99   - return success(buildReceivableDetailPage(pageResult));
  112 + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
100 113 }
101 114  
102 115 @GetMapping("/page-by-customer")
... ... @@ -104,7 +117,7 @@ public class CrmReceivableController {
104 117 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) {
105 118 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
106 119 PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO);
107   - return success(buildReceivableDetailPage(pageResult));
  120 + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
108 121 }
109 122  
110 123 @GetMapping("/export-excel")
... ... @@ -114,36 +127,43 @@ public class CrmReceivableController {
114 127 public void exportReceivableExcel(@Valid CrmReceivablePageReqVO exportReqVO,
115 128 HttpServletResponse response) throws IOException {
116 129 exportReqVO.setPageSize(PAGE_SIZE_NONE);
117   - PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId());
  130 + List<CrmReceivableDO> list = receivableService.getReceivablePage(exportReqVO, getLoginUserId()).getList();
118 131 // 导出 Excel
119 132 ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class,
120   - buildReceivableDetailPage(pageResult).getList());
  133 + buildReceivableDetailList(list));
121 134 }
122 135  
123   - /**
124   - * 构建详细的回款分页结果
125   - *
126   - * @param pageResult 简单的回款分页结果
127   - * @return 详细的回款分页结果
128   - */
129   - private PageResult<CrmReceivableRespVO> buildReceivableDetailPage(PageResult<CrmReceivableDO> pageResult) {
130   - List<CrmReceivableDO> receivableList = pageResult.getList();
  136 + private List<CrmReceivableRespVO> buildReceivableDetailList(List<CrmReceivableDO> receivableList) {
131 137 if (CollUtil.isEmpty(receivableList)) {
132   - return PageResult.empty(pageResult.getTotal());
  138 + return Collections.emptyList();
133 139 }
134   - // 1. 获取客户列表
135   - List<CrmCustomerDO> customerList = customerService.getCustomerList(
  140 + // 1.1 获取客户列表
  141 + Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
136 142 convertSet(receivableList, CrmReceivableDO::getCustomerId));
137   - // 2. 获取创建人、负责人列表
  143 + // 1.2 获取创建人、负责人列表
138 144 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList,
139 145 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
140   - // 3. 获得合同列表
141   - List<CrmContractDO> contractList = contractService.getContractList(
  146 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  147 + // 1.3 获得合同列表
  148 + Map<Long, CrmContractDO> contractMap = contractService.getContractMap(
142 149 convertSet(receivableList, CrmReceivableDO::getContractId));
143   - return CrmReceivableConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList);
  150 + // 2. 拼接结果
  151 + return BeanUtils.toBean(receivableList, CrmReceivableRespVO.class, (receivableVO) -> {
  152 + // 2.1 拼接客户名称
  153 + findAndThen(customerMap, receivableVO.getCustomerId(), customer -> receivableVO.setCustomerName(customer.getName()));
  154 + // 2.2 拼接负责人、创建人名称
  155 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(receivableVO.getCreator()),
  156 + user -> receivableVO.setCreatorName(user.getNickname()));
  157 + MapUtils.findAndThen(userMap, receivableVO.getOwnerUserId(), user -> {
  158 + receivableVO.setOwnerUserName(user.getNickname());
  159 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> receivableVO.setOwnerUserDeptName(dept.getName()));
  160 + });
  161 + // 2.3 拼接合同信息
  162 + findAndThen(contractMap, receivableVO.getContractId(), contract ->
  163 + receivableVO.setContract(BeanUtils.toBean(contract, CrmContractRespVO.class)));
  164 + });
144 165 }
145 166  
146   -
147 167 @GetMapping("/check-receivables-count")
148 168 @Operation(summary = "获得待审核回款数量")
149 169 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
... ... @@ -11,7 +11,7 @@ import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
11 11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
12 12 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
13 13 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO;
14   -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert;
  14 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
15 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
17 17 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
... ... @@ -34,14 +34,15 @@ import org.springframework.validation.annotation.Validated;
34 34 import org.springframework.web.bind.annotation.*;
35 35  
36 36 import java.io.IOException;
  37 +import java.util.Collections;
37 38 import java.util.List;
38 39 import java.util.Map;
39 40 import java.util.stream.Stream;
40 41  
41 42 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42 43 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
43   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
44   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  45 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
45 46 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
46 47 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
47 48  
... ... @@ -93,7 +94,14 @@ public class CrmReceivablePlanController {
93 94 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
94 95 public CommonResult<CrmReceivablePlanRespVO> getReceivablePlan(@RequestParam("id") Long id) {
95 96 CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id);
96   - return success(BeanUtils.toBean(receivablePlan, CrmReceivablePlanRespVO.class));
  97 + return success(buildReceivablePlanDetail(receivablePlan));
  98 + }
  99 +
  100 + private CrmReceivablePlanRespVO buildReceivablePlanDetail(CrmReceivablePlanDO receivablePlan) {
  101 + if (receivablePlan == null) {
  102 + return null;
  103 + }
  104 + return buildReceivableDetailList(Collections.singletonList(receivablePlan)).get(0);
97 105 }
98 106  
99 107 @GetMapping("/page")
... ... @@ -101,7 +109,7 @@ public class CrmReceivablePlanController {
101 109 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
102 110 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPage(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
103 111 PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO, getLoginUserId());
104   - return success(convertDetailReceivablePlanPage(pageResult));
  112 + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
105 113 }
106 114  
107 115 @GetMapping("/page-by-customer")
... ... @@ -109,10 +117,9 @@ public class CrmReceivablePlanController {
109 117 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPageByCustomer(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
110 118 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
111 119 PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO);
112   - return success(convertDetailReceivablePlanPage(pageResult));
  120 + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
113 121 }
114 122  
115   - // TODO 芋艿:后面在优化导出
116 123 @GetMapping("/export-excel")
117 124 @Operation(summary = "导出回款计划 Excel")
118 125 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:export')")
... ... @@ -120,39 +127,42 @@ public class CrmReceivablePlanController {
120 127 public void exportReceivablePlanExcel(@Valid CrmReceivablePlanPageReqVO exportReqVO,
121 128 HttpServletResponse response) throws IOException {
122 129 exportReqVO.setPageSize(PAGE_SIZE_NONE);
123   - PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId());
  130 + List<CrmReceivablePlanDO> list = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId()).getList();
124 131 // 导出 Excel
125 132 ExcelUtils.write(response, "回款计划.xls", "数据", CrmReceivablePlanRespVO.class,
126   - convertDetailReceivablePlanPage(pageResult).getList());
  133 + buildReceivableDetailList(list));
127 134 }
128 135  
129   - /**
130   - * 构建详细的回款计划分页结果
131   - *
132   - * @param pageResult 简单的回款计划分页结果
133   - * @return 详细的回款计划分页结果
134   - */
135   - private PageResult<CrmReceivablePlanRespVO> convertDetailReceivablePlanPage(PageResult<CrmReceivablePlanDO> pageResult) {
136   - List<CrmReceivablePlanDO> receivablePlanList = pageResult.getList();
  136 + private List<CrmReceivablePlanRespVO> buildReceivableDetailList(List<CrmReceivablePlanDO> receivablePlanList) {
137 137 if (CollUtil.isEmpty(receivablePlanList)) {
138   - return PageResult.empty(pageResult.getTotal());
  138 + return Collections.emptyList();
139 139 }
140   - // 1. 获取客户列表
141   - List<CrmCustomerDO> customerList = customerService.getCustomerList(
  140 + // 1.1 获取客户 Map
  141 + Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
142 142 convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId));
143   - // 2. 获取创建人、负责人列表
  143 + // 1.2 获取创建人、负责人列表
144 144 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList,
145 145 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
146   - // 3. 获得合同列表
147   - List<CrmContractDO> contractList = contractService.getContractList(
  146 + // 1.3 获得合同 Map
  147 + Map<Long, CrmContractDO> contractMap = contractService.getContractMap(
148 148 convertSet(receivablePlanList, CrmReceivablePlanDO::getContractId));
149   - // 4. 获得还款列表
150   - List<CrmReceivableDO> receivableList = receivableService.getReceivableList(
  149 + // 1.4 获得回款 Map
  150 + Map<Long, CrmReceivableDO> receivableMap = receivableService.getReceivableMap(
151 151 convertSet(receivablePlanList, CrmReceivablePlanDO::getReceivableId));
152   - return CrmReceivablePlanConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList, receivableList);
  152 + // 2. 拼接数据
  153 + return BeanUtils.toBean(receivablePlanList, CrmReceivablePlanRespVO.class, (receivablePlanVO) -> {
  154 + // 2.1 拼接客户信息
  155 + findAndThen(customerMap, receivablePlanVO.getCustomerId(), customer -> receivablePlanVO.setCustomerName(customer.getName()));
  156 + // 2.2 拼接用户信息
  157 + findAndThen(userMap, receivablePlanVO.getOwnerUserId(), user -> receivablePlanVO.setOwnerUserName(user.getNickname()));
  158 + findAndThen(userMap, Long.parseLong(receivablePlanVO.getCreator()), user -> receivablePlanVO.setCreatorName(user.getNickname()));
  159 + // 2.3 拼接合同信息
  160 + findAndThen(contractMap, receivablePlanVO.getContractId(), contract -> receivablePlanVO.setContractNo(contract.getNo()));
  161 + // 2.4 拼接回款信息
  162 + receivablePlanVO.setReceivable(BeanUtils.toBean(receivableMap.get(receivablePlanVO.getReceivableId()), CrmReceivableRespVO.class));
  163 + });
153 164 }
154 165  
155   -
156 166 @GetMapping("/remind-receivable-plan-count")
157 167 @Operation(summary = "获得待回款提醒数量")
158 168 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
... ... @@ -160,18 +170,21 @@ public class CrmReceivablePlanController {
160 170 return success(receivablePlanService.getRemindReceivablePlanCount(getLoginUserId()));
161 171 }
162 172  
163   - @GetMapping("/list-all-simple-by-customer")
  173 + @GetMapping("/simple-list")
164 174 @Operation(summary = "获得回款计划精简列表", description = "获得回款计划精简列表,主要用于前端的下拉选项")
165 175 @Parameters({
166 176 @Parameter(name = "customerId", description = "客户编号", required = true),
167 177 @Parameter(name = "contractId", description = "合同编号", required = true)
168 178 })
169 179 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
170   - public CommonResult<List<CrmReceivablePlanRespVO>> getListAllSimpleByCustomer(@RequestParam("customerId") Long customerId,
171   - @RequestParam("contractId") Long contractId) {
172   - PageResult<CrmReceivablePlanDO> result = receivablePlanService.getReceivablePlanPageByCustomerId(
173   - new CrmReceivablePlanPageReqVO().setCustomerId(customerId).setContractId(contractId));
174   - return success(BeanUtils.toBean(result.getList(), CrmReceivablePlanRespVO.class));
  180 + public CommonResult<List<CrmReceivablePlanRespVO>> getReceivablePlanSimpleList(@RequestParam("customerId") Long customerId,
  181 + @RequestParam("contractId") Long contractId) {
  182 + CrmReceivablePlanPageReqVO pageReqVO = new CrmReceivablePlanPageReqVO().setCustomerId(customerId).setContractId(contractId);
  183 + pageReqVO.setPageNo(PAGE_SIZE_NONE);
  184 + PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO);
  185 + return success(convertList(pageResult.getList(), receivablePlan -> new CrmReceivablePlanRespVO() // 只返回 id、period 等信息
  186 + .setId(receivablePlan.getId()).setPeriod(receivablePlan.getPeriod()).setReceivableId(receivablePlan.getReceivableId())
  187 + .setPrice(receivablePlan.getPrice()).setReturnType(receivablePlan.getReturnType())));
175 188 }
176 189  
177 190 }
... ...
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 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
  4 +import com.alibaba.excel.annotation.ExcelProperty;
3 5 import io.swagger.v3.oas.annotations.media.Schema;
4 6 import lombok.Data;
5 7  
... ... @@ -35,17 +37,16 @@ public class CrmReceivablePlanRespVO {
35 37 @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
36 38 private LocalDateTime returnTime;
37 39  
38   - @Schema(description = "回款方式", example = "1")
39   - private Integer returnType; // 来自 Receivable 的 returnType 字段
  40 + @Schema(description = "计划回款方式", example = "1")
  41 + private Integer returnType;
40 42  
41 43 @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
42 44 private BigDecimal price;
43 45  
44 46 @Schema(description = "回款编号", example = "19852")
45 47 private Long receivableId;
46   -
47   - @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
48   - private Boolean finishStatus;
  48 + @Schema(description = "回款信息")
  49 + private CrmReceivableRespVO receivable;
49 50  
50 51 @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
51 52 private Integer remindDays;
... ... @@ -57,12 +58,18 @@ public class CrmReceivablePlanRespVO {
57 58 private String remark;
58 59  
59 60 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  61 + @ExcelProperty("创建时间")
60 62 private LocalDateTime createTime;
61 63  
62   - @Schema(description = "创建人", example = "25682")
63   - private String creator;
  64 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  65 + @ExcelProperty("更新时间")
  66 + private LocalDateTime updateTime;
64 67  
65   - @Schema(description = "创建人名字", example = "test")
  68 + @Schema(description = "创建人", example = "1024")
  69 + @ExcelProperty("创建人")
  70 + private String creator;
  71 + @Schema(description = "创建人名字", example = "芋道源码")
  72 + @ExcelProperty("创建人名字")
66 73 private String creatorName;
67 74  
68 75 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanSaveReqVO.java
... ... @@ -14,9 +14,8 @@ public class CrmReceivablePlanSaveReqVO {
14 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
15 15 private Long id;
16 16  
17   - @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
18   - @NotNull(message = "客户编号不能为空")
19   - private Long customerId;
  17 + @Schema(description = "客户编号", hidden = true, example = "2")
  18 + private Long customerId; // 该字段不通过前端传递,而是 contractId 查询出来设置进去
20 19  
21 20 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
22 21 @NotNull(message = "合同编号不能为空")
... ... @@ -37,10 +36,6 @@ public class CrmReceivablePlanSaveReqVO {
37 36 @NotNull(message = "计划回款金额不能为空")
38 37 private BigDecimal price;
39 38  
40   - @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
41   - @NotNull(message = "提醒日期不能为空")
42   - private LocalDateTime remindTime;
43   -
44 39 @Schema(description = "提前几天提醒", example = "1")
45 40 private Integer remindDays;
46 41  
... ...
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 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
  4 +import com.alibaba.excel.annotation.ExcelProperty;
3 5 import io.swagger.v3.oas.annotations.media.Schema;
4 6 import lombok.Data;
5 7  
... ... @@ -31,12 +33,30 @@ public class CrmReceivableRespVO {
31 33  
32 34 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
33 35 private Long customerId;
  36 + @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
  37 + private String customerName;
34 38  
35 39 @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
36 40 private Long contractId;
  41 + @Schema(description = "合同信息")
  42 + private CrmContractRespVO contract;
37 43  
38   - @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  44 + @Schema(description = "负责人的用户编号", example = "25682")
  45 + @ExcelProperty("负责人的用户编号")
39 46 private Long ownerUserId;
  47 + @Schema(description = "负责人名字", example = "25682")
  48 + @ExcelProperty("负责人名字")
  49 + private String ownerUserName;
  50 + @Schema(description = "负责人部门")
  51 + @ExcelProperty("负责人部门")
  52 + private String ownerUserDeptName;
  53 +
  54 + @Schema(description = "工作流编号", example = "1043")
  55 + @ExcelProperty("工作流编号")
  56 + private String processInstanceId;
  57 +
  58 + @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  59 + private Integer auditStatus;
40 60  
41 61 @Schema(description = "备注", example = "备注")
42 62 private String remark;
... ... @@ -44,21 +64,11 @@ public class CrmReceivableRespVO {
44 64 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
45 65 private LocalDateTime createTime;
46 66  
47   - @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
48   - private String customerName;
49   -
50   - @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
51   - private Integer auditStatus;
52   -
53   - @Schema(description = "合同编号", example = "Q110")
54   - private String contractNo;
55   -
56   - @Schema(description = "负责人", example = "test")
57   - private String ownerUserName;
  67 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  68 + private LocalDateTime updateTime;
58 69  
59 70 @Schema(description = "创建人", example = "25682")
60 71 private String creator;
61   -
62 72 @Schema(description = "创建人名字", example = "test")
63 73 private String creatorName;
64 74  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableSaveReqVO.java
... ... @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
2 2  
3 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableReturnTypeEnum;
  5 +import cn.iocoder.yudao.module.crm.framework.operatelog.core.*;
  6 +import com.mzt.logapi.starter.annotation.DiffLogField;
5 7 import io.swagger.v3.oas.annotations.media.Schema;
6 8 import jakarta.validation.constraints.NotNull;
7 9 import lombok.Data;
... ... @@ -16,37 +18,41 @@ public class CrmReceivableSaveReqVO {
16 18 @Schema(description = "编号", example = "25787")
17 19 private Long id;
18 20  
19   - @Schema(description = "回款编号", example = "31177")
20   - private String no;
  21 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  22 + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
  23 + @NotNull(message = "负责人编号不能为空")
  24 + private Long ownerUserId;
21 25  
22   - @Schema(description = "回款计划编号", example = "1024")
23   - private Long planId; // 不是通过回款计划创建的回款没有回款计划编号
  26 + @Schema(description = "客户编号", example = "2")
  27 + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
  28 + private Long customerId; // 该字段不通过前端传递,而是 contractId 查询出来设置进去
  29 +
  30 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  31 + @DiffLogField(name = "合同", function = CrmContractParseFunction.NAME)
  32 + @NotNull(message = "合同编号不能为空")
  33 + private Long contractId;
  34 +
  35 + @Schema(description = "回款计划编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  36 + @DiffLogField(name = "合同", function = CrmReceivablePlanParseFunction.NAME)
  37 + private Long planId;
24 38  
25 39 @Schema(description = "回款方式", example = "2")
  40 + @DiffLogField(name = "回款方式", function = CrmReceivableReturnTypeParseFunction.NAME)
26 41 @InEnum(CrmReceivableReturnTypeEnum.class)
27 42 private Integer returnType;
28 43  
29 44 @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  45 + @DiffLogField(name = "回款金额")
30 46 @NotNull(message = "回款金额不能为空")
31 47 private BigDecimal price;
32 48  
33   - @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
34   - @NotNull(message = "计划回款日期不能为空")
  49 + @Schema(description = "回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  50 + @NotNull(message = "回款日期不能为空")
  51 + @DiffLogField(name = "回款日期")
35 52 private LocalDateTime returnTime;
36 53  
37   - @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
38   - @NotNull(message = "客户编号不能为空")
39   - private Long customerId;
40   -
41   - @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
42   - @NotNull(message = "合同编号不能为空")
43   - private Long contractId;
44   -
45   - @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
46   - @NotNull(message = "负责人编号不能为空")
47   - private Long ownerUserId;
48   -
49 54 @Schema(description = "备注", example = "备注")
  55 + @DiffLogField(name = "备注")
50 56 private String remark;
51 57  
52 58 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java deleted
1   -package cn.iocoder.yudao.module.crm.convert.receivable;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
6   -import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
7   -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
8   -import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
9   -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
10   -import org.mapstruct.Mapper;
11   -import org.mapstruct.factory.Mappers;
12   -
13   -import java.util.List;
14   -import java.util.Map;
15   -
16   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
17   -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
18   -
19   -/**
20   - * 回款 Convert
21   - *
22   - * @author 赤焰
23   - */
24   -@Mapper
25   -public interface CrmReceivableConvert {
26   -
27   - CrmReceivableConvert INSTANCE = Mappers.getMapper(CrmReceivableConvert.class);
28   -
29   - default PageResult<CrmReceivableRespVO> convertPage(PageResult<CrmReceivableDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
30   - List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) {
31   - PageResult<CrmReceivableRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class);
32   - // 拼接关联字段
33   - Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
34   - Map<Long, CrmContractDO> contractMap = convertMap(contractList, CrmContractDO::getId);
35   - voPageResult.getList().forEach(receivable -> {
36   - findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname()));
37   - findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname()));
38   - findAndThen(customerMap, receivable.getCustomerId(), customer -> receivable.setCustomerName(customer.getName()));
39   - findAndThen(contractMap, receivable.getContractId(), contract -> receivable.setContractNo(contract.getNo()));
40   - });
41   - return voPageResult;
42   - }
43   -
44   -}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java deleted
1   -package cn.iocoder.yudao.module.crm.convert.receivable;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
6   -import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
7   -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
8   -import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
9   -import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
10   -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
11   -import org.mapstruct.Mapper;
12   -import org.mapstruct.factory.Mappers;
13   -
14   -import java.util.List;
15   -import java.util.Map;
16   -
17   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
18   -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
19   -
20   -/**
21   - * 回款计划 Convert
22   - *
23   - * @author 芋道源码
24   - */
25   -@Mapper
26   -public interface CrmReceivablePlanConvert {
27   -
28   - CrmReceivablePlanConvert INSTANCE = Mappers.getMapper(CrmReceivablePlanConvert.class);
29   -
30   - default PageResult<CrmReceivablePlanRespVO> convertPage(PageResult<CrmReceivablePlanDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
31   - List<CrmCustomerDO> customerList, List<CrmContractDO> contractList,
32   - List<CrmReceivableDO> receivableList) {
33   - PageResult<CrmReceivablePlanRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivablePlanRespVO.class);
34   - // 拼接关联字段
35   - Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
36   - Map<Long, CrmContractDO> contractMap = convertMap(contractList, CrmContractDO::getId);
37   - Map<Long, CrmReceivableDO> receivableMap = convertMap(receivableList, CrmReceivableDO::getId);
38   - voPageResult.getList().forEach(receivablePlan -> {
39   - findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname()));
40   - findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname()));
41   - findAndThen(customerMap, receivablePlan.getCustomerId(), customer -> receivablePlan.setCustomerName(customer.getName()));
42   - findAndThen(contractMap, receivablePlan.getContractId(), contract -> receivablePlan.setContractNo(contract.getNo()));
43   - findAndThen(receivableMap, receivablePlan.getReceivableId(), receivable -> receivablePlan.setReturnType(receivable.getReturnType()));
44   - });
45   - return voPageResult;
46   - }
47   -
48   -}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java
... ... @@ -39,15 +39,21 @@ public class CrmReceivableDO extends BaseDO {
39 39 */
40 40 private String no;
41 41 /**
42   - * 回款计划编号,关联 {@link CrmReceivablePlanDO#getId()}
  42 + * 回款计划编号
  43 + *
  44 + * 关联 {@link CrmReceivablePlanDO#getId()},非必须
43 45 */
44 46 private Long planId;
45 47 /**
46   - * 客户编号,关联 {@link CrmCustomerDO#getId()}
  48 + * 客户编号
  49 + *
  50 + * 关联 {@link CrmCustomerDO#getId()}
47 51 */
48 52 private Long customerId;
49 53 /**
50   - * 合同编号,关联 {@link CrmContractDO#getId()}
  54 + * 合同编号
  55 + *
  56 + * 关联 {@link CrmContractDO#getId()}
51 57 */
52 58 private Long contractId;
53 59 /**
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java
... ... @@ -61,7 +61,7 @@ public class CrmReceivablePlanDO extends BaseDO {
61 61 */
62 62 private LocalDateTime returnTime;
63 63 /**
64   - * 回款类型
  64 + * 计划回款类型
65 65 *
66 66 * 枚举 {@link CrmReceivableReturnTypeEnum}
67 67 */
... ... @@ -75,10 +75,6 @@ public class CrmReceivablePlanDO extends BaseDO {
75 75 * 回款编号,关联 {@link CrmReceivableDO#getId()}
76 76 */
77 77 private Long receivableId;
78   - /**
79   - * 完成状态
80   - */
81   - private Boolean finishStatus;
82 78  
83 79 /**
84 80 * 提前几天提醒
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/package-info.java deleted
1   -/**
2   - * 回款
3   - */
4   -package cn.iocoder.yudao.module.crm.dal.dataobject.receivable;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
1 1 package cn.iocoder.yudao.module.crm.dal.mysql.receivable;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
... ... @@ -11,10 +12,16 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
11 12 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
12 13 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
13 14 import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
  15 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
14 16 import org.apache.ibatis.annotations.Mapper;
15 17  
  18 +import java.math.BigDecimal;
16 19 import java.util.Collection;
  20 +import java.util.Collections;
17 21 import java.util.List;
  22 +import java.util.Map;
  23 +
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
18 25  
19 26 /**
20 27 * 回款 Mapper
... ... @@ -24,6 +31,10 @@ import java.util.List;
24 31 @Mapper
25 32 public interface CrmReceivableMapper extends BaseMapperX<CrmReceivableDO> {
26 33  
  34 + default CrmReceivableDO selectByNo(String no) {
  35 + return selectOne(CrmReceivableDO::getNo, no);
  36 + }
  37 +
27 38 default PageResult<CrmReceivableDO> selectPageByCustomerId(CrmReceivablePageReqVO reqVO) {
28 39 return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivableDO>()
29 40 .eq(CrmReceivableDO::getCustomerId, reqVO.getCustomerId()) // 必须传递
... ... @@ -67,4 +78,18 @@ public interface CrmReceivableMapper extends BaseMapperX&lt;CrmReceivableDO&gt; {
67 78 return selectCount(query);
68 79 }
69 80  
  81 + default Map<Long, BigDecimal> selectReceivablePriceMapByContractId(Collection<Long> contractIds) {
  82 + if (CollUtil.isEmpty(contractIds)) {
  83 + return Collections.emptyMap();
  84 + }
  85 + // SQL sum 查询
  86 + List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmReceivableDO>()
  87 + .select("contract_id, SUM(price) AS total_price")
  88 + .eq("audit_status", CrmAuditStatusEnum.APPROVE.getStatus())
  89 + .groupBy("contract_id")
  90 + .in("contract_id", contractIds));
  91 + // 获得金额
  92 + return convertMap(result, obj -> (Long) obj.get("contract_id"), obj -> (BigDecimal) obj.get("total_price"));
  93 + }
  94 +
70 95 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
... ... @@ -25,6 +25,13 @@ import java.util.Objects;
25 25 @Mapper
26 26 public interface CrmReceivablePlanMapper extends BaseMapperX<CrmReceivablePlanDO> {
27 27  
  28 + default CrmReceivablePlanDO selectMaxPeriodByContractId(Long contractId) {
  29 + return selectOne(new MPJLambdaWrapperX<CrmReceivablePlanDO>()
  30 + .eq(CrmReceivablePlanDO::getContractId, contractId)
  31 + .orderByDesc(CrmReceivablePlanDO::getPeriod)
  32 + .last("LIMIT 1"));
  33 + }
  34 +
28 35 default PageResult<CrmReceivablePlanDO> selectPageByCustomerId(CrmReceivablePlanPageReqVO reqVO) {
29 36 MPJLambdaWrapperX<CrmReceivablePlanDO> query = new MPJLambdaWrapperX<>();
30 37 if (Objects.nonNull(reqVO.getContractNo())) { // 根据合同编号检索
... ... @@ -47,7 +54,6 @@ public interface CrmReceivablePlanMapper extends BaseMapperX&lt;CrmReceivablePlanDO
47 54 .eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId())
48 55 .eqIfPresent(CrmReceivablePlanDO::getContractId, pageReqVO.getContractId())
49 56 .orderByDesc(CrmReceivablePlanDO::getPeriod);
50   -
51 57 if (Objects.nonNull(pageReqVO.getContractNo())) { // 根据合同编号检索
52 58 query.innerJoin(CrmContractDO.class, on -> on.like(CrmContractDO::getNo, pageReqVO.getContractNo())
53 59 .eq(CrmContractDO::getId, CrmReceivablePlanDO::getContractId));
... ... @@ -67,9 +73,7 @@ public interface CrmReceivablePlanMapper extends BaseMapperX&lt;CrmReceivablePlanDO
67 73 } else if (CrmReceivablePlanPageReqVO.REMIND_TYPE_RECEIVED.equals(pageReqVO.getRemindType())) { // 已回款
68 74 query.isNotNull(CrmReceivablePlanDO::getReceivableId)
69 75 .between(CrmReceivablePlanDO::getReturnTime, beginOfToday, endOfToday.plusDays(REMIND_DAYS));
70   - ;
71 76 }
72   -
73 77 return selectJoinPage(pageReqVO, CrmReceivablePlanDO.class, query);
74 78 }
75 79  
... ... @@ -93,6 +97,9 @@ public interface CrmReceivablePlanMapper extends BaseMapperX&lt;CrmReceivablePlanDO
93 97 LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
94 98 query.isNull(CrmReceivablePlanDO::getReceivableId)
95 99 .between(CrmReceivablePlanDO::getReturnTime, beginOfToday, endOfToday.plusDays(REMIND_DAYS));
  100 + // TODO return_time 小于现在;
  101 + // TODO 未回款
  102 + // TODO remind_time 大于现在;
96 103 return selectCount(query);
97 104 }
98 105  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java
... ... @@ -25,7 +25,7 @@ public class CrmNoRedisDAO {
25 25 public static final String CONTRACT_NO_PREFIX = "HT";
26 26  
27 27 /**
28   - * 还款 {@link cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO}
  28 + * 回款 {@link cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO}
29 29 */
30 30 public static final String RECEIVABLE_PREFIX = "HK";
31 31  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmReceivablePlanParseFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.core;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
  5 +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivablePlanService;
  6 +import com.mzt.logapi.service.IParseFunction;
  7 +import jakarta.annotation.Resource;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.springframework.stereotype.Component;
  10 +
  11 +/**
  12 + * CRM 回款计划的 {@link IParseFunction} 实现类
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class CrmReceivablePlanParseFunction implements IParseFunction {
  19 +
  20 + public static final String NAME = "getReceivablePlanServiceById";
  21 +
  22 + @Resource
  23 + private CrmReceivablePlanService receivablePlanService;
  24 +
  25 + @Override
  26 + public boolean executeBefore() {
  27 + return true; // 先转换值后对比
  28 + }
  29 +
  30 + @Override
  31 + public String functionName() {
  32 + return NAME;
  33 + }
  34 +
  35 + @Override
  36 + public String apply(Object value) {
  37 + if (StrUtil.isEmptyIfStr(value)) {
  38 + return "";
  39 + }
  40 + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(Long.parseLong(value.toString()));
  41 + return receivablePlan == null ? "" : receivablePlan.getPeriod().toString();
  42 + }
  43 +
  44 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmReceivableReturnTypeParseFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.framework.operatelog.core;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
  5 +import com.mzt.logapi.service.IParseFunction;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_RECEIVABLE_RETURN_TYPE;
  10 +
  11 +/**
  12 + * CRM 回款方式的 {@link IParseFunction} 实现类
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Slf4j
  17 +@Component
  18 +public class CrmReceivableReturnTypeParseFunction implements IParseFunction {
  19 +
  20 + public static final String NAME = "getReceivableReturnType";
  21 +
  22 + @Override
  23 + public boolean executeBefore() {
  24 + return true; // 先转换值后对比
  25 + }
  26 +
  27 + @Override
  28 + public String functionName() {
  29 + return NAME;
  30 + }
  31 +
  32 + @Override
  33 + public String apply(Object value) {
  34 + if (StrUtil.isEmptyIfStr(value)) {
  35 + return "";
  36 + }
  37 + return DictFrameworkUtils.getDictDataLabel(CRM_RECEIVABLE_RETURN_TYPE, value.toString());
  38 + }
  39 +
  40 +}
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
... ... @@ -13,6 +13,9 @@ import jakarta.validation.Valid;
13 13 import java.time.LocalDateTime;
14 14 import java.util.Collection;
15 15 import java.util.List;
  16 +import java.util.Map;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
16 19  
17 20 /**
18 21 * CRM 合同 Service 接口
... ... @@ -86,6 +89,14 @@ public interface CrmContractService {
86 89 CrmContractDO getContract(Long id);
87 90  
88 91 /**
  92 + * 校验合同是否合法
  93 + *
  94 + * @param id 编号
  95 + * @return 合同
  96 + */
  97 + CrmContractDO validateContract(Long id);
  98 +
  99 + /**
89 100 * 获得合同列表
90 101 *
91 102 * @param ids 编号
... ... @@ -94,6 +105,16 @@ public interface CrmContractService {
94 105 List<CrmContractDO> getContractList(Collection<Long> ids);
95 106  
96 107 /**
  108 + * 获得合同 Map
  109 + *
  110 + * @param ids 编号
  111 + * @return 合同 Map
  112 + */
  113 + default Map<Long, CrmContractDO> getContractMap(Collection<Long> ids) {
  114 + return convertMap(getContractList(ids), CrmContractDO::getId);
  115 + }
  116 +
  117 + /**
97 118 * 获得合同分页
98 119 *
99 120 * 数据权限:基于 {@link CrmContractDO} 读取
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
... ... @@ -324,6 +324,11 @@ public class CrmContractServiceImpl implements CrmContractService {
324 324 }
325 325  
326 326 @Override
  327 + public CrmContractDO validateContract(Long id) {
  328 + return validateContractExists(id);
  329 + }
  330 +
  331 + @Override
327 332 public List<CrmContractDO> getContractList(Collection<Long> ids) {
328 333 if (CollUtil.isEmpty(ids)) {
329 334 return ListUtil.empty();
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java
... ... @@ -38,7 +38,7 @@ public interface CrmReceivablePlanService {
38 38 * @param id 编号
39 39 * @param receivableId 回款编号
40 40 */
41   - void updateReceivableId(Long id, Long receivableId);
  41 + void updateReceivablePlanReceivableId(Long id, Long receivableId);
42 42  
43 43 /**
44 44 * 删除回款计划
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
... ... @@ -2,20 +2,17 @@ package cn.iocoder.yudao.module.crm.service.receivable;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.ListUtil;
5   -import cn.hutool.core.util.ObjectUtil;
6 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 7 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
9 8 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO;
10 9 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
11   -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
12 10 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
13 11 import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper;
14 12 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
15 13 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
16 14 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
17 15 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
18   -import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
19 16 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
20 17 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
21 18 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
... ... @@ -33,7 +30,8 @@ import java.util.List;
33 30 import java.util.Objects;
34 31  
35 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
36   -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
  33 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_PLAN_NOT_EXISTS;
  34 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_PLAN_UPDATE_FAIL;
37 35 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
38 36  
39 37 /**
... ... @@ -54,8 +52,6 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
54 52 @Resource
55 53 private CrmContractService contractService;
56 54 @Resource
57   - private CrmCustomerService customerService;
58   - @Resource
59 55 private CrmPermissionService permissionService;
60 56  
61 57 @Resource
... ... @@ -66,15 +62,16 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
66 62 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE, bizNo = "{{#receivablePlan.id}}",
67 63 success = CRM_RECEIVABLE_PLAN_CREATE_SUCCESS)
68 64 public Long createReceivablePlan(CrmReceivablePlanSaveReqVO createReqVO) {
69   - // 1.1 校验关联数据是否存在
  65 + // 1. 校验关联数据是否存在
70 66 validateRelationDataExists(createReqVO);
71   - // 1.2 查验关联合同回款数量
72   - Long count = receivableService.getReceivableCountByContractId(createReqVO.getContractId());
73   - int period = (int) (count + 1);
74 67  
75   - // 2. 插入还款计划
76   - CrmReceivablePlanDO receivablePlan = BeanUtils.toBean(createReqVO, CrmReceivablePlanDO.class)
77   - .setPeriod(period).setFinishStatus(false);
  68 + // 2. 插入回款计划
  69 + CrmReceivablePlanDO maxPeriodReceivablePlan = receivablePlanMapper.selectMaxPeriodByContractId(createReqVO.getContractId());
  70 + int period = maxPeriodReceivablePlan == null ? 1 : maxPeriodReceivablePlan.getPeriod() + 1;
  71 + CrmReceivablePlanDO receivablePlan = BeanUtils.toBean(createReqVO, CrmReceivablePlanDO.class).setPeriod(period);
  72 + if (createReqVO.getReturnTime() != null && createReqVO.getRemindDays() != null) {
  73 + receivablePlan.setRemindTime(createReqVO.getReturnTime().minusDays(createReqVO.getRemindDays()));
  74 + }
78 75 receivablePlanMapper.insert(receivablePlan);
79 76  
80 77 // 3. 创建数据权限
... ... @@ -93,15 +90,21 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
93 90 success = CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS)
94 91 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
95 92 public void updateReceivablePlan(CrmReceivablePlanSaveReqVO updateReqVO) {
96   - // 1. 校验存在
  93 + updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null); // 防止修改这些字段
  94 + // 1.1 校验存在
97 95 validateRelationDataExists(updateReqVO);
  96 + // 1.2 校验关联数据是否存在
98 97 CrmReceivablePlanDO oldReceivablePlan = validateReceivablePlanExists(updateReqVO.getId());
99   - if (Objects.nonNull(oldReceivablePlan.getReceivableId())) { // 如果已经有对应的还款,则不允许编辑;
100   - throw exception(RECEIVABLE_PLAN_UPDATE_FAIL, "已经有对应的还款");
  98 + // 1.3 如果已经有对应的回款,则不允许编辑
  99 + if (Objects.nonNull(oldReceivablePlan.getReceivableId())) {
  100 + throw exception(RECEIVABLE_PLAN_UPDATE_FAIL);
101 101 }
102 102  
103   - // 2. 更新
  103 + // 2. 更新回款计划
104 104 CrmReceivablePlanDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivablePlanDO.class);
  105 + if (updateReqVO.getReturnTime() != null && updateReqVO.getRemindDays() != null) {
  106 + updateObj.setRemindTime(updateReqVO.getReturnTime().minusDays(updateReqVO.getRemindDays()));
  107 + }
105 108 receivablePlanMapper.updateById(updateObj);
106 109  
107 110 // 3. 记录操作日志上下文
... ... @@ -109,24 +112,24 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
109 112 LogRecordContext.putVariable("receivablePlan", oldReceivablePlan);
110 113 }
111 114  
  115 + private void validateRelationDataExists(CrmReceivablePlanSaveReqVO reqVO) {
  116 + // 校验负责人存在
  117 + if (reqVO.getOwnerUserId() != null) {
  118 + adminUserApi.validateUser(reqVO.getOwnerUserId());
  119 + }
  120 + // 校验合同存在
  121 + if (reqVO.getContractId() != null) {
  122 + CrmContractDO contract = contractService.getContract(reqVO.getContractId());
  123 + reqVO.setCustomerId(contract.getCustomerId());
  124 + }
  125 + }
  126 +
112 127 @Override
113   - public void updateReceivableId(Long id, Long receivableId) {
  128 + public void updateReceivablePlanReceivableId(Long id, Long receivableId) {
114 129 // 校验存在
115 130 validateReceivablePlanExists(id);
116 131 // 更新回款计划
117   - receivablePlanMapper.updateById(new CrmReceivablePlanDO().setReceivableId(receivableId).setFinishStatus(true));
118   - }
119   -
120   - private void validateRelationDataExists(CrmReceivablePlanSaveReqVO reqVO) {
121   - adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在
122   - CrmContractDO contract = contractService.getContract(reqVO.getContractId());
123   - if (ObjectUtil.isNull(contract)) { // 合同不存在
124   - throw exception(CONTRACT_NOT_EXISTS);
125   - }
126   - CrmCustomerDO customer = customerService.getCustomer(reqVO.getCustomerId());
127   - if (ObjectUtil.isNull(customer)) { // 客户不存在
128   - throw exception(CUSTOMER_NOT_EXISTS);
129   - }
  132 + receivablePlanMapper.updateById(new CrmReceivablePlanDO().setId(id).setReceivableId(receivableId));
130 133 }
131 134  
132 135 @Override
... ... @@ -135,13 +138,15 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
135 138 success = CRM_RECEIVABLE_PLAN_DELETE_SUCCESS)
136 139 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
137 140 public void deleteReceivablePlan(Long id) {
138   - // 校验存在
  141 + // 1. 校验存在
139 142 CrmReceivablePlanDO receivablePlan = validateReceivablePlanExists(id);
140   - // 删除
  143 +
  144 + // 2. 删除
141 145 receivablePlanMapper.deleteById(id);
142   - // 删除数据权限
  146 + // 3. 删除数据权限
143 147 permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
144   - // 记录操作日志上下文
  148 +
  149 + // 4. 记录操作日志上下文
145 150 LogRecordContext.putVariable("receivablePlan", receivablePlan);
146 151 }
147 152  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
... ... @@ -7,8 +7,12 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
7 7 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
8 8 import jakarta.validation.Valid;
9 9  
  10 +import java.math.BigDecimal;
10 11 import java.util.Collection;
11 12 import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
12 16  
13 17 /**
14 18 * CRM 回款 Service 接口
... ... @@ -72,6 +76,16 @@ public interface CrmReceivableService {
72 76 List<CrmReceivableDO> getReceivableList(Collection<Long> ids);
73 77  
74 78 /**
  79 + * 获得回款 Map
  80 + *
  81 + * @param ids 编号
  82 + * @return 回款 Map
  83 + */
  84 + default Map<Long, CrmReceivableDO> getReceivableMap(Collection<Long> ids) {
  85 + return convertMap(getReceivableList(ids), CrmReceivableDO::getId);
  86 + }
  87 +
  88 + /**
75 89 * 获得回款分页
76 90 *
77 91 * 数据权限:基于 {@link CrmReceivableDO} 读取
... ... @@ -101,11 +115,11 @@ public interface CrmReceivableService {
101 115 Long getCheckReceivablesCount(Long userId);
102 116  
103 117 /**
104   - * 根据合同编号,获取合同关联的回款数量
  118 + * 获得合同已回款金额 Map
105 119 *
106   - * @param contractId 合同编号
107   - * @return 数量
  120 + * @param contractIds 合同编号
  121 + * @return 回款金额 Map
108 122 */
109   - Long getReceivableCountByContractId(Long contractId);
  123 + Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds);
110 124  
111 125 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
... ... @@ -13,16 +13,15 @@ import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
13 13 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
14 14 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
15 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16   -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
17 16 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
18 17 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
19 18 import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper;
  19 +import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO;
20 20 import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
21 21 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
22 22 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
23 23 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
24 24 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
25   -import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
26 25 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
27 26 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
28 27 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
... ... @@ -36,9 +35,10 @@ import org.springframework.stereotype.Service;
36 35 import org.springframework.transaction.annotation.Transactional;
37 36 import org.springframework.validation.annotation.Validated;
38 37  
  38 +import java.math.BigDecimal;
39 39 import java.util.Collection;
40 40 import java.util.List;
41   -import java.util.Objects;
  41 +import java.util.Map;
42 42  
43 43 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
44 44 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
... ... @@ -56,17 +56,18 @@ import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmRes
56 56 public class CrmReceivableServiceImpl implements CrmReceivableService {
57 57  
58 58 /**
59   - * BPM 回款审批流程标识
  59 + * BPM 合同审批流程标识
60 60 */
61   - public static final String RECEIVABLE_APPROVE = "receivable-approve";
  61 + public static final String BPM_PROCESS_DEFINITION_KEY = "crm-receivable-audit";
62 62  
63 63 @Resource
64 64 private CrmReceivableMapper receivableMapper;
65 65  
66 66 @Resource
67   - private CrmContractService contractService;
  67 + private CrmNoRedisDAO noRedisDAO;
  68 +
68 69 @Resource
69   - private CrmCustomerService customerService;
  70 + private CrmContractService contractService;
70 71 @Resource
71 72 @Lazy // 延迟加载,避免循环依赖
72 73 private CrmReceivablePlanService receivablePlanService;
... ... @@ -78,48 +79,63 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
78 79 @Resource
79 80 private BpmProcessInstanceApi bpmProcessInstanceApi;
80 81  
  82 + // TODO @puhui999:操作日志没记录上
81 83 @Override
82 84 @Transactional(rollbackFor = Exception.class)
83 85 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}",
84 86 success = CRM_RECEIVABLE_CREATE_SUCCESS)
85 87 public Long createReceivable(CrmReceivableSaveReqVO createReqVO) {
86   - // 1. 校验关联数据存在
  88 + // 1.1 校验关联数据存在
87 89 validateRelationDataExists(createReqVO);
88   - // 2. 插入还款
89   - CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
  90 + // 1.2 生成回款编号
  91 + String no = noRedisDAO.generate(CrmNoRedisDAO.RECEIVABLE_PREFIX);
  92 + if (receivableMapper.selectByNo(no) != null) {
  93 + throw exception(RECEIVABLE_NO_EXISTS);
  94 + }
  95 +
  96 + // 2. 插入回款
  97 + CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class)
  98 + .setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
90 99 receivableMapper.insert(receivable);
  100 +
91 101 // 3. 创建数据权限
92 102 permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType())
93   - .setBizId(receivable.getId()).setUserId(createReqVO.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
  103 + .setBizId(receivable.getId()).setUserId(createReqVO.getOwnerUserId())
  104 + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
  105 +
94 106 // 4. 更新关联的回款计划
95   - if (Objects.nonNull(createReqVO.getPlanId())) {
96   - receivablePlanService.updateReceivableId(receivable.getPlanId(), receivable.getId());
  107 + if (createReqVO.getPlanId() != null) {
  108 + receivablePlanService.updateReceivablePlanReceivableId(receivable.getPlanId(), receivable.getId());
97 109 }
  110 +
98 111 // 5. 记录操作日志上下文
99 112 LogRecordContext.putVariable("receivable", receivable);
100 113 return receivable.getId();
101 114 }
102 115  
103 116 private void validateRelationDataExists(CrmReceivableSaveReqVO reqVO) {
104   - adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在
105   - CrmContractDO contract = contractService.getContract(reqVO.getContractId());
106   - if (ObjectUtil.isNull(contract)) {
107   - throw exception(CONTRACT_NOT_EXISTS);
108   - }
109   - CrmCustomerDO customer = customerService.getCustomer(reqVO.getCustomerId());
110   - if (ObjectUtil.isNull(customer)) {
111   - throw exception(CUSTOMER_NOT_EXISTS);
  117 + if (reqVO.getOwnerUserId() != null) {
  118 + adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在
112 119 }
113   - if (Objects.isNull(reqVO.getPlanId())) { // 没有回款计划编号则不校验
114   - return;
  120 + if (reqVO.getContractId() != null) {
  121 + CrmContractDO contract = contractService.validateContract(reqVO.getContractId());
  122 + if (ObjectUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) {
  123 + throw exception(RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE);
  124 + }
  125 + reqVO.setCustomerId(contract.getCustomerId()); // 设置客户编号
115 126 }
116   - CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(reqVO.getPlanId());
117   - if (ObjectUtil.isNull(receivablePlan)) {
118   - throw exception(RECEIVABLE_PLAN_NOT_EXISTS);
  127 + if (reqVO.getPlanId() != null) {
  128 + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(reqVO.getPlanId());
  129 + if (receivablePlan == null) {
  130 + throw exception(RECEIVABLE_PLAN_NOT_EXISTS);
  131 + }
  132 + if (receivablePlan.getReceivableId() != null) {
  133 + throw exception(RECEIVABLE_PLAN_EXISTS_RECEIVABLE);
  134 + }
119 135 }
120   -
121 136 }
122 137  
  138 + // TODO @puhui999:操作日志没记录上
123 139 @Override
124 140 @Transactional(rollbackFor = Exception.class)
125 141 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
... ... @@ -127,6 +143,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
127 143 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
128 144 public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) {
129 145 Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
  146 + updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
130 147 // 1.1 校验存在
131 148 CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());
132 149 // 1.2 只有草稿、审批中,可以编辑;
... ... @@ -135,7 +152,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
135 152 throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
136 153 }
137 154  
138   - // 2. 更新还款
  155 + // 2. 更新回款
139 156 CrmReceivableDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivableDO.class);
140 157 receivableMapper.updateById(updateObj);
141 158  
... ... @@ -160,27 +177,25 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
160 177 receivableMapper.updateById(new CrmReceivableDO().setId(id).setAuditStatus(auditStatus));
161 178 }
162 179  
163   - // TODO @liuhongfeng:缺一个取消回款的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
164   -
165 180 @Override
166 181 @Transactional(rollbackFor = Exception.class)
167 182 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
168 183 success = CRM_RECEIVABLE_DELETE_SUCCESS)
169 184 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
170 185 public void deleteReceivable(Long id) {
171   - // 校验存在
  186 + // 1.1 校验存在
172 187 CrmReceivableDO receivable = validateReceivableExists(id);
173   - // 如果被 CrmReceivablePlanDO 所使用,则不允许删除
174   - if (Objects.nonNull(receivable.getPlanId()) && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
  188 + // 1.2 如果被 CrmReceivablePlanDO 所使用,则不允许删除
  189 + if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
175 190 throw exception(RECEIVABLE_DELETE_FAIL);
176 191 }
177   - // 删除
178   - receivableMapper.deleteById(id);
179 192  
180   - // 删除数据权限
  193 + // 2. 删除
  194 + receivableMapper.deleteById(id);
  195 + // 3. 删除数据权限
181 196 permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id);
182 197  
183   - // 记录操作日志上下文
  198 + // 4. 记录操作日志上下文
184 199 LogRecordContext.putVariable("receivable", receivable);
185 200 }
186 201  
... ... @@ -197,7 +212,7 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
197 212  
198 213 // 2. 创建回款审批流程实例
199 214 String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
200   - .setProcessDefinitionKey(RECEIVABLE_APPROVE).setBusinessKey(String.valueOf(id)));
  215 + .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY).setBusinessKey(String.valueOf(id)));
201 216  
202 217 // 3. 更新回款工作流编号
203 218 receivableMapper.updateById(new CrmReceivableDO().setId(id).setProcessInstanceId(processInstanceId)
... ... @@ -246,8 +261,8 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
246 261 }
247 262  
248 263 @Override
249   - public Long getReceivableCountByContractId(Long contractId) {
250   - return receivableMapper.selectCount(CrmReceivableDO::getContractId, contractId);
  264 + public Map<Long, BigDecimal> getReceivablePriceMapByContractId(Collection<Long> contractIds) {
  265 + return receivableMapper.selectReceivablePriceMapByContractId(contractIds);
251 266 }
252 267  
253 268 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/listener/CrmReceivableResultListener.java
... ... @@ -20,7 +20,7 @@ public class CrmReceivableResultListener extends BpmProcessInstanceResultEventLi
20 20  
21 21 @Override
22 22 public String getProcessDefinitionKey() {
23   - return CrmReceivableServiceImpl.RECEIVABLE_APPROVE;
  23 + return CrmReceivableServiceImpl.BPM_PROCESS_DEFINITION_KEY;
24 24 }
25 25  
26 26 @Override
... ...