Commit cd1e73cc3f493d04e9696ae87c72cb5920327fde

Authored by dhb52
2 parents d23dfd42 63f322cb

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

# Conflicts:
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
Showing 57 changed files with 1002 additions and 575 deletions
.image/common/erp-feature.png 0 → 100644

45.9 KB

README.md
@@ -128,6 +128,8 @@ @@ -128,6 +128,8 @@
128 * 数据报表 128 * 数据报表
129 * 商城系统 129 * 商城系统
130 * 微信公众号 130 * 微信公众号
  131 +* ERP 系统
  132 +* CRM 系统
131 133
132 > 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。 134 > 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
133 > 135 >
@@ -247,23 +249,31 @@ @@ -247,23 +249,31 @@
247 | 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 | 249 | 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 |
248 | 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 | 250 | 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 |
249 251
  252 +### ERP 系统
  253 +
  254 +![功能图](/.image/common/erp-feature.png)
  255 +
  256 +演示地址:<https://doc.iocoder.cn/erp-preview/>
  257 +
250 ## 🐨 技术栈 258 ## 🐨 技术栈
251 259
252 ### 模块 260 ### 模块
253 261
254 -| 项目 | 说明 |  
255 -|--------------------------------------------------------------------------|--------------------|  
256 -| `yudao-dependencies` | Maven 依赖版本管理 |  
257 -| `yudao-framework` | Java 框架拓展 |  
258 -| `yudao-server` | 管理后台 + 用户 APP 的服务端 |  
259 -| `yudao-module-system` | 系统功能的 Module 模块 |  
260 -| `yudao-module-member` | 会员中心的 Module 模块 |  
261 -| `yudao-module-infra` | 基础设施的 Module 模块 |  
262 -| `yudao-module-bpm` | 工作流程的 Module 模块 |  
263 -| `yudao-module-pay` | 支付系统的 Module 模块 |  
264 -| `yudao-module-mall` | 商城系统的 Module 模块 |  
265 -| `yudao-module-mp` | 微信公众号的 Module 模块 |  
266 -| `yudao-module-report` | 大屏报表 Module 模块 | 262 +| 项目 | 说明 |
  263 +|-----------------------|--------------------|
  264 +| `yudao-dependencies` | Maven 依赖版本管理 |
  265 +| `yudao-framework` | Java 框架拓展 |
  266 +| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
  267 +| `yudao-module-system` | 系统功能的 Module 模块 |
  268 +| `yudao-module-member` | 会员中心的 Module 模块 |
  269 +| `yudao-module-infra` | 基础设施的 Module 模块 |
  270 +| `yudao-module-bpm` | 工作流程的 Module 模块 |
  271 +| `yudao-module-pay` | 支付系统的 Module 模块 |
  272 +| `yudao-module-mall` | 商城系统的 Module 模块 |
  273 +| `yudao-module-erp` | ERP 系统的 Module 模块 |
  274 +| `yudao-module-crm` | CRM 系统的 Module 模块 |
  275 +| `yudao-module-mp` | 微信公众号的 Module 模块 |
  276 +| `yudao-module-report` | 大屏报表 Module 模块 |
267 277
268 ### 框架 278 ### 框架
269 279
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java
1 package cn.iocoder.yudao.framework.file.core.client; 1 package cn.iocoder.yudao.framework.file.core.client;
2 2
  3 +import cn.iocoder.yudao.framework.file.core.client.s3.FilePresignedUrlRespDTO;
  4 +
3 /** 5 /**
4 * 文件客户端 6 * 文件客户端
5 * 7 *
@@ -18,11 +20,11 @@ public interface FileClient { @@ -18,11 +20,11 @@ public interface FileClient {
18 * 上传文件 20 * 上传文件
19 * 21 *
20 * @param content 文件流 22 * @param content 文件流
21 - * @param path 相对路径 23 + * @param path 相对路径
22 * @return 完整路径,即 HTTP 访问地址 24 * @return 完整路径,即 HTTP 访问地址
23 * @throws Exception 上传文件时,抛出 Exception 异常 25 * @throws Exception 上传文件时,抛出 Exception 异常
24 */ 26 */
25 - String upload(byte[] content, String path, String type) throws Exception; 27 + String upload(byte[] content, String path, String type) throws Exception;
26 28
27 /** 29 /**
28 * 删除文件 30 * 删除文件
@@ -40,4 +42,14 @@ public interface FileClient { @@ -40,4 +42,14 @@ public interface FileClient {
40 */ 42 */
41 byte[] getContent(String path) throws Exception; 43 byte[] getContent(String path) throws Exception;
42 44
  45 + /**
  46 + * 获得文件预签名地址
  47 + *
  48 + * @param path 相对路径
  49 + * @return 文件预签名地址
  50 + */
  51 + default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
  52 + throw new UnsupportedOperationException("不支持的操作");
  53 + }
  54 +
43 } 55 }
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/FilePresignedUrlRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.file.core.client.s3;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * 文件预签名地址 Response DTO
  9 + *
  10 + * @author owen
  11 + */
  12 +@Data
  13 +@AllArgsConstructor
  14 +@NoArgsConstructor
  15 +public class FilePresignedUrlRespDTO {
  16 +
  17 + /**
  18 + * 文件上传 URL(用于上传)
  19 + *
  20 + * 例如说:
  21 + */
  22 + private String uploadUrl;
  23 +
  24 + /**
  25 + * 文件 URL(用于读取、下载等)
  26 + */
  27 + private String url;
  28 +
  29 +}
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java
@@ -5,8 +5,10 @@ import cn.hutool.core.util.StrUtil; @@ -5,8 +5,10 @@ import cn.hutool.core.util.StrUtil;
5 import cn.hutool.http.HttpUtil; 5 import cn.hutool.http.HttpUtil;
6 import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient; 6 import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient;
7 import io.minio.*; 7 import io.minio.*;
  8 +import io.minio.http.Method;
8 9
9 import java.io.ByteArrayInputStream; 10 import java.io.ByteArrayInputStream;
  11 +import java.util.concurrent.TimeUnit;
10 12
11 import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN; 13 import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
12 import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT; 14 import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT;
@@ -117,4 +119,16 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; { @@ -117,4 +119,16 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
117 return IoUtil.readBytes(response); 119 return IoUtil.readBytes(response);
118 } 120 }
119 121
  122 + @Override
  123 + public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
  124 + String uploadUrl = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
  125 + .method(Method.PUT)
  126 + .bucket(config.getBucket())
  127 + .object(path)
  128 + .expiry(10, TimeUnit.MINUTES) // 过期时间(秒数)取值范围:1 秒 ~ 7 天
  129 + .build()
  130 + );
  131 + return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path);
  132 + }
  133 +
120 } 134 }
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
@@ -46,6 +46,10 @@ public class BannerApplicationRunner implements ApplicationRunner { @@ -46,6 +46,10 @@ public class BannerApplicationRunner implements ApplicationRunner {
46 if (isNotPresent("cn.iocoder.yudao.module.trade.framework.web.config.TradeWebConfiguration")) { 46 if (isNotPresent("cn.iocoder.yudao.module.trade.framework.web.config.TradeWebConfiguration")) {
47 System.out.println("[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]"); 47 System.out.println("[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
48 } 48 }
  49 + // ERP 系统
  50 + if (isNotPresent("cn.iocoder.yudao.module.erp.framework.web.config.ErpWebConfiguration")) {
  51 + System.out.println("[ERP 系统 yudao-module-erp - 已禁用][参考 https://doc.iocoder.cn/erp/build/ 开启]");
  52 + }
49 // 支付平台 53 // 支付平台
50 if (isNotPresent("cn.iocoder.yudao.module.pay.framework.pay.config.PayConfiguration")) { 54 if (isNotPresent("cn.iocoder.yudao.module.pay.framework.pay.config.PayConfiguration")) {
51 System.out.println("[支付系统 yudao-module-pay - 已禁用][参考 https://doc.iocoder.cn/pay/build/ 开启]"); 55 System.out.println("[支付系统 yudao-module-pay - 已禁用][参考 https://doc.iocoder.cn/pay/build/ 开启]");
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -313,7 +313,13 @@ public class GlobalExceptionHandler { @@ -313,7 +313,13 @@ public class GlobalExceptionHandler {
313 return CommonResult.error(NOT_IMPLEMENTED.getCode(), 313 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
314 "[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]"); 314 "[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
315 } 315 }
316 - // 5. 支付平台 316 + // 5. ERP 系统
  317 + if (message.contains("erp_")) {
  318 + log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
  319 + return CommonResult.error(NOT_IMPLEMENTED.getCode(),
  320 + "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
  321 + }
  322 + // 6. 支付平台
317 if (message.contains("pay_")) { 323 if (message.contains("pay_")) {
318 log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]"); 324 log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
319 return CommonResult.error(NOT_IMPLEMENTED.getCode(), 325 return CommonResult.error(NOT_IMPLEMENTED.getCode(),
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
@@ -93,6 +93,8 @@ public interface LogRecordConstants { @@ -93,6 +93,8 @@ public interface LogRecordConstants {
93 String CRM_CONTRACT_DELETE_SUCCESS = "删除了合同【{{#contractName}}】"; 93 String CRM_CONTRACT_DELETE_SUCCESS = "删除了合同【{{#contractName}}】";
94 String CRM_CONTRACT_TRANSFER_SUB_TYPE = "转移合同"; 94 String CRM_CONTRACT_TRANSFER_SUB_TYPE = "转移合同";
95 String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; 95 String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
  96 + String CRM_CONTRACT_SUBMIT_SUB_TYPE = "提交合同审批";
  97 + String CRM_CONTRACT_SUBMIT_SUCCESS = "提交合同【{{#contractName}}】审批成功";
96 98
97 // ======================= CRM_PRODUCT 产品 ======================= 99 // ======================= CRM_PRODUCT 产品 =======================
98 100
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java
@@ -42,4 +42,46 @@ public class CrmBiRankController { @@ -42,4 +42,46 @@ public class CrmBiRankController {
42 return success(rankingService.getReceivablePriceRank(rankingReqVO)); 42 return success(rankingService.getReceivablePriceRank(rankingReqVO));
43 } 43 }
44 44
  45 + @GetMapping("/get-contract-count-rank")
  46 + @Operation(summary = "获得签约合同数量排行榜")
  47 + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
  48 + public CommonResult<List<CrmBiRanKRespVO>> getContractCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
  49 + return success(rankingService.getContractCountRank(rankingReqVO));
  50 + }
  51 +
  52 + @GetMapping("/get-product-sales-rank")
  53 + @Operation(summary = "获得产品销量排行榜")
  54 + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
  55 + public CommonResult<List<CrmBiRanKRespVO>> getProductSalesRank(@Valid CrmBiRankReqVO rankingReqVO) {
  56 + return success(rankingService.getProductSalesRank(rankingReqVO));
  57 + }
  58 +
  59 + @GetMapping("/get-customer-count-rank")
  60 + @Operation(summary = "获得新增客户数排行榜")
  61 + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
  62 + public CommonResult<List<CrmBiRanKRespVO>> getCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
  63 + return success(rankingService.getCustomerCountRank(rankingReqVO));
  64 + }
  65 +
  66 + @GetMapping("/get-contacts-count-rank")
  67 + @Operation(summary = "获得新增联系人数排行榜")
  68 + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
  69 + public CommonResult<List<CrmBiRanKRespVO>> getContactsCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
  70 + return success(rankingService.getContactsCountRank(rankingReqVO));
  71 + }
  72 +
  73 + @GetMapping("/get-follow-count-rank")
  74 + @Operation(summary = "获得跟进次数排行榜")
  75 + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
  76 + public CommonResult<List<CrmBiRanKRespVO>> getFollowCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
  77 + return success(rankingService.getFollowCountRank(rankingReqVO));
  78 + }
  79 +
  80 + @GetMapping("/get-follow-customer-count-rank")
  81 + @Operation(summary = "获得跟进客户数排行榜")
  82 + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
  83 + public CommonResult<List<CrmBiRanKRespVO>> getFollowCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
  84 + return success(rankingService.getFollowCustomerCountRank(rankingReqVO));
  85 + }
  86 +
45 } 87 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java
@@ -14,8 +14,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusiness @@ -14,8 +14,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusiness
14 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; 14 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
15 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO; 15 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;
16 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; 16 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;
17 -import cn.iocoder.yudao.module.crm.convert.businessstatus.CrmBusinessStatusConvert;  
18 -import cn.iocoder.yudao.module.crm.convert.businessstatustype.CrmBusinessStatusTypeConvert; 17 +import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusConvert;
  18 +import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusTypeConvert;
19 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; 19 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
20 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; 20 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
21 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService; 21 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; 1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
2 2
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;  
5 import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus; 4 import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus;
6 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction; 5 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction;
7 import com.mzt.logapi.starter.annotation.DiffLogField; 6 import com.mzt.logapi.starter.annotation.DiffLogField;
8 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
9 import jakarta.validation.constraints.NotNull; 8 import jakarta.validation.constraints.NotNull;
  9 +import lombok.AllArgsConstructor;
10 import lombok.Data; 10 import lombok.Data;
  11 +import lombok.NoArgsConstructor;
11 import org.springframework.format.annotation.DateTimeFormat; 12 import org.springframework.format.annotation.DateTimeFormat;
12 13
13 import java.math.BigDecimal; 14 import java.math.BigDecimal;
14 import java.time.LocalDateTime; 15 import java.time.LocalDateTime;
15 -import java.util.ArrayList;  
16 import java.util.List; 16 import java.util.List;
17 17
18 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; 18 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -58,7 +58,6 @@ public class CrmBusinessSaveReqVO { @@ -58,7 +58,6 @@ public class CrmBusinessSaveReqVO {
58 @DiffLogField(name = "商机金额") 58 @DiffLogField(name = "商机金额")
59 private Integer price; 59 private Integer price;
60 60
61 - // TODO @lzxhqs:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题  
62 @Schema(description = "整单折扣") 61 @Schema(description = "整单折扣")
63 @DiffLogField(name = "整单折扣") 62 @DiffLogField(name = "整单折扣")
64 private Integer discountPercent; 63 private Integer discountPercent;
@@ -75,11 +74,30 @@ public class CrmBusinessSaveReqVO { @@ -75,11 +74,30 @@ public class CrmBusinessSaveReqVO {
75 @InEnum(CrmBizEndStatus.class) 74 @InEnum(CrmBizEndStatus.class)
76 private Integer endStatus; 75 private Integer endStatus;
77 76
78 - // TODO @lzxhqs:不设置默认 new ArrayList<>();一般 pojo 不设置默认值哈  
79 - @Schema(description = "商机产品列表")  
80 - private List<CrmBusinessProductSaveReqVO> products = new ArrayList<>();  
81 -  
82 @Schema(description = "联系人编号", example = "110") 77 @Schema(description = "联系人编号", example = "110")
83 private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段 78 private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段
84 79
  80 + // TODO @puhui999:传递 items 就行啦;
  81 + @Schema(description = "产品列表")
  82 + private List<CrmBusinessProductItem> productItems;
  83 +
  84 + @Schema(description = "产品列表")
  85 + @Data
  86 + @NoArgsConstructor
  87 + @AllArgsConstructor
  88 + public static class CrmBusinessProductItem {
  89 +
  90 + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
  91 + @NotNull(message = "产品编号不能为空")
  92 + private Long id;
  93 +
  94 + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
  95 + @NotNull(message = "产品数量不能为空")
  96 + private Integer count;
  97 +
  98 + @Schema(description = "产品折扣")
  99 + private Integer discountPercent;
  100 +
  101 + }
  102 +
85 } 103 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
4 -import io.swagger.v3.oas.annotations.media.Schema;  
5 -import lombok.Data;  
6 -import lombok.EqualsAndHashCode;  
7 -import lombok.ToString;  
8 -  
9 -// TODO @lzxhqs:这个类,如果没用到,可以考虑删除哈  
10 -@Schema(description = "管理后台 - 商机产品分页 Request VO")  
11 -@Data  
12 -@EqualsAndHashCode(callSuper = true)  
13 -@ToString(callSuper = true)  
14 -public class CrmBusinessProductPageReqVO extends PageParam {  
15 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;  
2 -  
3 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
4 -import io.swagger.v3.oas.annotations.media.Schema;  
5 -import lombok.Data;  
6 -  
7 -@Schema(description = "管理后台 - 商机产品关联 Response VO")  
8 -@Data  
9 -@ExcelIgnoreUnannotated  
10 -public class CrmBusinessProductRespVO {  
11 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import jakarta.validation.constraints.NotEmpty;  
5 -import jakarta.validation.constraints.NotNull;  
6 -import lombok.Data;  
7 -  
8 -import java.math.BigDecimal;  
9 -  
10 -@Schema(description = "管理后台 - CRM 商机产品关联表 创建/更新 Request VO")  
11 -@Data  
12 -public class CrmBusinessProductSaveReqVO {  
13 -  
14 - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")  
15 - private Long id;  
16 -  
17 - @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")  
18 - @NotNull(message = "商机编号不能为空")  
19 - private Long businessId;  
20 -  
21 - @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")  
22 - @NotNull(message = "产品编号不能为空")  
23 - private Long productId;  
24 -  
25 - @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")  
26 - @NotNull(message = "产品单价不能为空")  
27 - private BigDecimal price;  
28 -  
29 - @Schema(description = "销售价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")  
30 - @NotNull(message = "销售价格不能为空")  
31 - private BigDecimal salesPrice;  
32 -  
33 - @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")  
34 - @NotNull(message = "数量不能为空")  
35 - private BigDecimal num;  
36 -  
37 - @Schema(description = "折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")  
38 - @NotNull(message = "折扣不能为空")  
39 - private BigDecimal discount;  
40 -  
41 - @Schema(description = "小计(折扣后价格)", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")  
42 - @NotNull(message = "小计(折扣后价格)不能为空")  
43 - private BigDecimal subtotal;  
44 -  
45 - @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")  
46 - @NotEmpty(message = "单位不能为空")  
47 - private String unit;  
48 -  
49 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -8,15 +8,17 @@ import cn.iocoder.yudao.framework.common.util.number.NumberUtils; @@ -8,15 +8,17 @@ import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
11 -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*; 11 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
  12 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO;
  13 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
  14 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
12 import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; 15 import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
13 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
14 -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;  
15 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; 17 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
16 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 18 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
  19 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
17 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 20 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
18 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; 21 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
19 -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessProductService;  
20 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; 22 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
21 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; 23 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
22 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; 24 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
@@ -30,7 +32,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; @@ -30,7 +32,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
30 import jakarta.annotation.Resource; 32 import jakarta.annotation.Resource;
31 import jakarta.servlet.http.HttpServletResponse; 33 import jakarta.servlet.http.HttpServletResponse;
32 import jakarta.validation.Valid; 34 import jakarta.validation.Valid;
33 -import org.springframework.context.annotation.Lazy;  
34 import org.springframework.security.access.prepost.PreAuthorize; 35 import org.springframework.security.access.prepost.PreAuthorize;
35 import org.springframework.validation.annotation.Validated; 36 import org.springframework.validation.annotation.Validated;
36 import org.springframework.web.bind.annotation.*; 37 import org.springframework.web.bind.annotation.*;
@@ -43,9 +44,9 @@ import java.util.stream.Stream; @@ -43,9 +44,9 @@ import java.util.stream.Stream;
43 44
44 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 45 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
45 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 46 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
46 -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;  
47 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 47 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
48 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 48 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  49 +import static java.util.Collections.singletonList;
49 50
50 @Tag(name = "管理后台 - CRM 合同") 51 @Tag(name = "管理后台 - CRM 合同")
51 @RestController 52 @RestController
@@ -62,11 +63,7 @@ public class CrmContractController { @@ -62,11 +63,7 @@ public class CrmContractController {
62 @Resource 63 @Resource
63 private CrmBusinessService businessService; 64 private CrmBusinessService businessService;
64 @Resource 65 @Resource
65 - @Lazy  
66 - private CrmBusinessProductService businessProductService;  
67 - @Resource  
68 private CrmProductService productService; 66 private CrmProductService productService;
69 -  
70 @Resource 67 @Resource
71 private AdminUserApi adminUserApi; 68 private AdminUserApi adminUserApi;
72 69
@@ -105,22 +102,9 @@ public class CrmContractController { @@ -105,22 +102,9 @@ public class CrmContractController {
105 return success(null); 102 return success(null);
106 } 103 }
107 104
108 - // 2.1 拼接合同信息  
109 - List<CrmContractRespVO> respVOList = buildContractDetailList(Collections.singletonList(contract));  
110 - // 2.2 拼接产品信息  
111 - // TODO @puhui999:下面这块也可以搞到 convert 里哈;可以在 ContractDetailList 加个开关,是不是查询商品信息;ps:jdk21 的方法不太能去用,因为 jdk8 项目要兼容;  
112 - CrmContractRespVO respVO = respVOList.get(0);  
113 - List<CrmBusinessProductDO> businessProductList = businessProductService.getBusinessProductListByContractId(id);  
114 - Map<Long, CrmBusinessProductDO> businessProductMap = convertMap(businessProductList, CrmBusinessProductDO::getProductId);  
115 - List<CrmProductDO> productList = productService.getProductListByIds(convertSet(businessProductList, CrmBusinessProductDO::getProductId));  
116 - respVO.setProductItems(convertList(productList, product -> {  
117 - CrmContractRespVO.CrmContractProductItemRespVO productItemRespVO = BeanUtils.toBean(product, CrmContractRespVO.CrmContractProductItemRespVO.class);  
118 - findAndThen(businessProductMap, product.getId(), businessProduct -> {  
119 - productItemRespVO.setCount(businessProduct.getCount()).setDiscountPercent(businessProduct.getDiscountPercent());  
120 - });  
121 - return productItemRespVO;  
122 - }));  
123 - return success(respVO); 105 + // 2. 拼接合同信息
  106 + List<CrmContractRespVO> respVOList = buildContractDetailList(singletonList(contract));
  107 + return success(respVOList.get(0));
124 } 108 }
125 109
126 @GetMapping("/page") 110 @GetMapping("/page")
@@ -147,8 +131,24 @@ public class CrmContractController { @@ -147,8 +131,24 @@ public class CrmContractController {
147 HttpServletResponse response) throws IOException { 131 HttpServletResponse response) throws IOException {
148 PageResult<CrmContractDO> pageResult = contractService.getContractPage(exportReqVO, getLoginUserId()); 132 PageResult<CrmContractDO> pageResult = contractService.getContractPage(exportReqVO, getLoginUserId());
149 // 导出 Excel 133 // 导出 Excel
150 - ExcelUtils.write(response, "合同.xls", "数据", CrmContractExcelVO.class,  
151 - BeanUtils.toBean(pageResult.getList(), CrmContractExcelVO.class)); 134 + ExcelUtils.write(response, "合同.xls", "数据", CrmContractRespVO.class,
  135 + BeanUtils.toBean(pageResult.getList(), CrmContractRespVO.class));
  136 + }
  137 +
  138 + @PutMapping("/transfer")
  139 + @Operation(summary = "合同转移")
  140 + @PreAuthorize("@ss.hasPermission('crm:contract:update')")
  141 + public CommonResult<Boolean> transferContract(@Valid @RequestBody CrmContractTransferReqVO reqVO) {
  142 + contractService.transferContract(reqVO, getLoginUserId());
  143 + return success(true);
  144 + }
  145 +
  146 + @PutMapping("/submit")
  147 + @Operation(summary = "提交合同审批")
  148 + @PreAuthorize("@ss.hasPermission('crm:contract:update')")
  149 + public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {
  150 + contractService.submitContract(id, getLoginUserId());
  151 + return success(true);
152 } 152 }
153 153
154 /** 154 /**
@@ -173,23 +173,15 @@ public class CrmContractController { @@ -173,23 +173,15 @@ public class CrmContractController {
173 // 4. 获取商机 173 // 4. 获取商机
174 Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList(convertSet(contractList, 174 Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList(convertSet(contractList,
175 CrmContractDO::getBusinessId)), CrmBusinessDO::getId); 175 CrmContractDO::getBusinessId)), CrmBusinessDO::getId);
176 - return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap);  
177 - }  
178 -  
179 - @PutMapping("/transfer")  
180 - @Operation(summary = "合同转移")  
181 - @PreAuthorize("@ss.hasPermission('crm:contract:update')")  
182 - public CommonResult<Boolean> transferContract(@Valid @RequestBody CrmContractTransferReqVO reqVO) {  
183 - contractService.transferContract(reqVO, getLoginUserId());  
184 - return success(true);  
185 - }  
186 -  
187 - @PutMapping("/submit")  
188 - @Operation(summary = "提交合同审批")  
189 - @PreAuthorize("@ss.hasPermission('crm:contract:update')")  
190 - public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {  
191 - contractService.submitContract(id, getLoginUserId());  
192 - return success(true); 176 + // 5. 获取合同关联的商品
  177 + Map<Long, CrmContractProductDO> contractProductMap = null;
  178 + List<CrmProductDO> productList = null;
  179 + if (contractList.size() == 1) {
  180 + List<CrmContractProductDO> contractProductList = contractService.getContractProductListByContractId(contractList.get(0).getId());
  181 + contractProductMap = convertMap(contractProductList, CrmContractProductDO::getProductId);
  182 + productList = productService.getProductListByIds(convertSet(contractProductList, CrmContractProductDO::getProductId));
  183 + }
  184 + return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap, contractProductMap, productList);
193 } 185 }
194 186
195 @GetMapping("/check-contract-count") 187 @GetMapping("/check-contract-count")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractExcelVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;  
2 -  
3 -import com.alibaba.excel.annotation.ExcelProperty;  
4 -import lombok.Data;  
5 -  
6 -import java.time.LocalDateTime;  
7 -  
8 -// TODO @puhui999:合并到 RespVO 里哈;  
9 -/**  
10 - * CRM 合同 Excel VO  
11 - *  
12 - * @author dhb52  
13 - */  
14 -@Data  
15 -public class CrmContractExcelVO {  
16 -  
17 - @ExcelProperty("合同编号")  
18 - private Long id;  
19 -  
20 - @ExcelProperty("合同名称")  
21 - private String name;  
22 -  
23 - @ExcelProperty("客户编号")  
24 - private Long customerId;  
25 -  
26 - @ExcelProperty("商机编号")  
27 - private Long businessId;  
28 -  
29 - @ExcelProperty("工作流编号")  
30 - private Long processInstanceId;  
31 -  
32 - @ExcelProperty("下单日期")  
33 - private LocalDateTime orderDate;  
34 -  
35 - @ExcelProperty("负责人的用户编号")  
36 - private Long ownerUserId;  
37 -  
38 - @ExcelProperty("合同编号")  
39 - private String no;  
40 -  
41 - @ExcelProperty("开始时间")  
42 - private LocalDateTime startTime;  
43 -  
44 - @ExcelProperty("结束时间")  
45 - private LocalDateTime endTime;  
46 -  
47 - @ExcelProperty("合同金额")  
48 - private Integer price;  
49 -  
50 - @ExcelProperty("整单折扣")  
51 - private Integer discountPercent;  
52 -  
53 - @ExcelProperty("产品总金额")  
54 - private Integer productPrice;  
55 -  
56 - @ExcelProperty("联系人编号")  
57 - private Long contactId;  
58 -  
59 - @ExcelProperty("公司签约人")  
60 - private Long signUserId;  
61 -  
62 - @ExcelProperty("最后跟进时间")  
63 - private LocalDateTime contactLastTime;  
64 -  
65 - @ExcelProperty("备注")  
66 - private String remark;  
67 -  
68 - @ExcelProperty("创建时间")  
69 - private LocalDateTime createTime;  
70 -  
71 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 import cn.iocoder.yudao.framework.common.validation.InEnum; 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
5 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; 6 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
6 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 8 import lombok.Data;
@@ -14,7 +15,13 @@ import lombok.ToString; @@ -14,7 +15,13 @@ import lombok.ToString;
14 @ToString(callSuper = true) 15 @ToString(callSuper = true)
15 public class CrmContractPageReqVO extends PageParam { 16 public class CrmContractPageReqVO extends PageParam {
16 17
  18 + /**
  19 + * 过期类型 - 即将过期
  20 + */
17 public static final Integer EXPIRY_TYPE_ABOUT_TO_EXPIRE = 1; 21 public static final Integer EXPIRY_TYPE_ABOUT_TO_EXPIRE = 1;
  22 + /**
  23 + * 过期类型 - 已过期
  24 + */
18 public static final Integer EXPIRY_TYPE_EXPIRED = 2; 25 public static final Integer EXPIRY_TYPE_EXPIRED = 2;
19 26
20 @Schema(description = "合同编号", example = "XYZ008") 27 @Schema(description = "合同编号", example = "XYZ008")
@@ -34,6 +41,7 @@ public class CrmContractPageReqVO extends PageParam { @@ -34,6 +41,7 @@ public class CrmContractPageReqVO extends PageParam {
34 private Integer sceneType; // 场景类型,为 null 时则表示全部 41 private Integer sceneType; // 场景类型,为 null 时则表示全部
35 42
36 @Schema(description = "审批状态", example = "20") 43 @Schema(description = "审批状态", example = "20")
  44 + @InEnum(CrmAuditStatusEnum.class)
37 private Integer auditStatus; 45 private Integer auditStatus;
38 46
39 @Schema(description = "过期类型", example = "1") 47 @Schema(description = "过期类型", example = "1")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java
@@ -132,6 +132,7 @@ public class CrmContractRespVO { @@ -132,6 +132,7 @@ public class CrmContractRespVO {
132 @Schema(description = "产品列表") 132 @Schema(description = "产品列表")
133 private List<CrmContractProductItemRespVO> productItems; 133 private List<CrmContractProductItemRespVO> productItems;
134 134
  135 + // TODO @puhui999:可以直接叫 Item
135 @Schema(description = "产品列表") 136 @Schema(description = "产品列表")
136 @Data 137 @Data
137 @NoArgsConstructor 138 @NoArgsConstructor
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -22,7 +22,6 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; @@ -22,7 +22,6 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
22 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 22 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
23 import io.swagger.v3.oas.annotations.Operation; 23 import io.swagger.v3.oas.annotations.Operation;
24 import io.swagger.v3.oas.annotations.Parameter; 24 import io.swagger.v3.oas.annotations.Parameter;
25 -import io.swagger.v3.oas.annotations.Parameters;  
26 import io.swagger.v3.oas.annotations.tags.Tag; 25 import io.swagger.v3.oas.annotations.tags.Tag;
27 import jakarta.annotation.Resource; 26 import jakarta.annotation.Resource;
28 import jakarta.servlet.http.HttpServletResponse; 27 import jakarta.servlet.http.HttpServletResponse;
@@ -31,7 +30,6 @@ import org.mapstruct.ap.internal.util.Collections; @@ -31,7 +30,6 @@ import org.mapstruct.ap.internal.util.Collections;
31 import org.springframework.security.access.prepost.PreAuthorize; 30 import org.springframework.security.access.prepost.PreAuthorize;
32 import org.springframework.validation.annotation.Validated; 31 import org.springframework.validation.annotation.Validated;
33 import org.springframework.web.bind.annotation.*; 32 import org.springframework.web.bind.annotation.*;
34 -import org.springframework.web.multipart.MultipartFile;  
35 33
36 import java.io.IOException; 34 import java.io.IOException;
37 import java.time.LocalDateTime; 35 import java.time.LocalDateTime;
@@ -253,19 +251,13 @@ public class CrmCustomerController { @@ -253,19 +251,13 @@ public class CrmCustomerController {
253 ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list); 251 ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list);
254 } 252 }
255 253
256 - // TODO @puhui999:updateSupport 要不改成前端必须传递;哈哈哈,代码排版看着有点乱;  
257 - // TODO @puhui999:加一个选择负责人;允许空,空就进入公海;  
258 @PostMapping("/import") 254 @PostMapping("/import")
259 @Operation(summary = "导入客户") 255 @Operation(summary = "导入客户")
260 - @Parameters({  
261 - @Parameter(name = "file", description = "Excel 文件", required = true),  
262 - @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")  
263 - })  
264 @PreAuthorize("@ss.hasPermission('system:customer:import')") 256 @PreAuthorize("@ss.hasPermission('system:customer:import')")
265 - public CommonResult<CrmCustomerImportRespVO> importExcel(@RequestParam("file") MultipartFile file, @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) 257 + public CommonResult<CrmCustomerImportRespVO> importExcel(@Valid @RequestBody CrmCustomerImportReqVO importReqVO)
266 throws Exception { 258 throws Exception {
267 - List<CrmCustomerImportExcelVO> list = ExcelUtils.read(file, CrmCustomerImportExcelVO.class);  
268 - return success(customerService.importCustomerList(list, updateSupport, getLoginUserId())); 259 + List<CrmCustomerImportExcelVO> list = ExcelUtils.read(importReqVO.getFile(), CrmCustomerImportExcelVO.class);
  260 + return success(customerService.importCustomerList(list, importReqVO));
269 } 261 }
270 262
271 @PutMapping("/transfer") 263 @PutMapping("/transfer")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Builder;
  6 +import lombok.Data;
  7 +import org.springframework.web.multipart.MultipartFile;
  8 +
  9 +@Schema(description = "管理后台 - 客户导入 Request VO")
  10 +@Data
  11 +@Builder
  12 +public class CrmCustomerImportReqVO {
  13 +
  14 + @Schema(description = "Excel 文件", requiredMode = Schema.RequiredMode.REQUIRED)
  15 + @NotNull(message = "Excel 文件不能为空")
  16 + private MultipartFile file;
  17 +
  18 + @Schema(description = "是否支持更新", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  19 + @NotNull(message = "是否支持更新不能为空")
  20 + private Boolean updateSupport;
  21 +
  22 + @Schema(description = "负责人", example = "1")
  23 + private Long ownerUserId; // 为 null 则客户进入公海
  24 +
  25 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java
@@ -14,12 +14,18 @@ import lombok.ToString; @@ -14,12 +14,18 @@ import lombok.ToString;
14 @ToString(callSuper = true) 14 @ToString(callSuper = true)
15 public class CrmReceivablePlanPageReqVO extends PageParam { 15 public class CrmReceivablePlanPageReqVO extends PageParam {
16 16
17 - // 待回款  
18 - public final static Integer REMIND_NEEDED = 1;  
19 - // 已逾期  
20 - public final static Integer REMIND_EXPIRED = 2;  
21 - // 已回款  
22 - public final static Integer REMIND_RECEIVED = 3; 17 + /**
  18 + * 提醒类型 - 待回款
  19 + */
  20 + public final static Integer REMIND_TYPE_NEEDED = 1;
  21 + /**
  22 + * 提醒类型 - 已逾期
  23 + */
  24 + public final static Integer REMIND_TYPE_EXPIRED = 2;
  25 + /**
  26 + * 提醒类型 - 已回款
  27 + */
  28 + public final static Integer REMIND_TYPE_RECEIVED = 3;
23 29
24 @Schema(description = "客户编号", example = "18026") 30 @Schema(description = "客户编号", example = "18026")
25 private Long customerId; 31 private Long customerId;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 import cn.iocoder.yudao.framework.common.validation.InEnum; 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
5 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; 6 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
6 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 8 import lombok.Data;
@@ -28,6 +29,7 @@ public class CrmReceivablePageReqVO extends PageParam { @@ -28,6 +29,7 @@ public class CrmReceivablePageReqVO extends PageParam {
28 private Integer sceneType; // 场景类型,为 null 时则表示全部 29 private Integer sceneType; // 场景类型,为 null 时则表示全部
29 30
30 @Schema(description = "审批状态", example = "20") 31 @Schema(description = "审批状态", example = "20")
  32 + @InEnum(CrmAuditStatusEnum.class)
31 private Integer auditStatus; 33 private Integer auditStatus;
32 34
33 } 35 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java
1 -package cn.iocoder.yudao.module.crm.convert.businessstatus; 1 +package cn.iocoder.yudao.module.crm.convert.business;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO; 4 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java
1 -package cn.iocoder.yudao.module.crm.convert.businessstatustype; 1 +package cn.iocoder.yudao.module.crm.convert.business;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO; 4 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java deleted
1 -package cn.iocoder.yudao.module.crm.convert.businessproduct;  
2 -  
3 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;  
4 -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;  
5 -import org.mapstruct.Mapper;  
6 -import org.mapstruct.factory.Mappers;  
7 -  
8 -// TODO @lzxhqs:看看是不是用 BeanUtils 替代了  
9 -/**  
10 - * @author lzxhqs  
11 - * @version 1.0  
12 - * @title CrmBusinessProductConvert  
13 - * @description  
14 - * @create 2024/1/12  
15 - */  
16 -@Mapper  
17 -public interface CrmBusinessProductConvert {  
18 - CrmBusinessProductConvert INSTANCE = Mappers.getMapper(CrmBusinessProductConvert.class);  
19 -  
20 - CrmBusinessProductDO convert(CrmBusinessProductSaveReqVO product);  
21 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
1 package cn.iocoder.yudao.module.crm.convert.contract; 1 package cn.iocoder.yudao.module.crm.convert.contract;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO; 6 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO;
5 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; 7 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
6 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; 8 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
7 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; 9 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
8 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 10 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
  11 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
9 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 12 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
  13 +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
10 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; 14 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
11 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 15 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
12 import org.mapstruct.Mapper; 16 import org.mapstruct.Mapper;
@@ -34,7 +38,8 @@ public interface CrmContractConvert { @@ -34,7 +38,8 @@ public interface CrmContractConvert {
34 38
35 default List<CrmContractRespVO> convertList(List<CrmContractDO> contractList, Map<Long, AdminUserRespDTO> userMap, 39 default List<CrmContractRespVO> convertList(List<CrmContractDO> contractList, Map<Long, AdminUserRespDTO> userMap,
36 List<CrmCustomerDO> customerList, Map<Long, CrmContactDO> contactMap, 40 List<CrmCustomerDO> customerList, Map<Long, CrmContactDO> contactMap,
37 - Map<Long, CrmBusinessDO> businessMap) { 41 + Map<Long, CrmBusinessDO> businessMap, Map<Long, CrmContractProductDO> contractProductMap,
  42 + List<CrmProductDO> productList) {
38 List<CrmContractRespVO> respVOList = BeanUtils.toBean(contractList, CrmContractRespVO.class); 43 List<CrmContractRespVO> respVOList = BeanUtils.toBean(contractList, CrmContractRespVO.class);
39 // 拼接关联字段 44 // 拼接关联字段
40 Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId); 45 Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
@@ -46,7 +51,20 @@ public interface CrmContractConvert { @@ -46,7 +51,20 @@ public interface CrmContractConvert {
46 findAndThen(contactMap, contract.getContactId(), contact -> contract.setContactName(contact.getName())); 51 findAndThen(contactMap, contract.getContactId(), contact -> contract.setContactName(contact.getName()));
47 findAndThen(businessMap, contract.getBusinessId(), business -> contract.setBusinessName(business.getName())); 52 findAndThen(businessMap, contract.getBusinessId(), business -> contract.setBusinessName(business.getName()));
48 }); 53 });
  54 + if (CollUtil.isNotEmpty(respVOList) && respVOList.size() == 1) {
  55 + setContractRespVOProductItems(respVOList.get(0), contractProductMap, productList);
  56 + }
49 return respVOList; 57 return respVOList;
50 } 58 }
51 59
  60 + default void setContractRespVOProductItems(CrmContractRespVO respVO, Map<Long, CrmContractProductDO> contractProductMap,
  61 + List<CrmProductDO> productList) {
  62 + respVO.setProductItems(CollectionUtils.convertList(productList, product -> {
  63 + CrmContractRespVO.CrmContractProductItemRespVO productItemRespVO = BeanUtils.toBean(product, CrmContractRespVO.CrmContractProductItemRespVO.class);
  64 + findAndThen(contractProductMap, product.getId(), contractProduct ->
  65 + productItemRespVO.setCount(contractProduct.getCount()).setDiscountPercent(contractProduct.getDiscountPercent()));
  66 + return productItemRespVO;
  67 + }));
  68 + }
  69 +
52 } 70 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
1 package cn.iocoder.yudao.module.crm.dal.dataobject.business; 1 package cn.iocoder.yudao.module.crm.dal.dataobject.business;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 -import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;  
5 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; 4 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
6 -import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;  
7 import com.baomidou.mybatisplus.annotation.KeySequence; 5 import com.baomidou.mybatisplus.annotation.KeySequence;
8 import com.baomidou.mybatisplus.annotation.TableId; 6 import com.baomidou.mybatisplus.annotation.TableId;
9 import com.baomidou.mybatisplus.annotation.TableName; 7 import com.baomidou.mybatisplus.annotation.TableName;
@@ -41,19 +39,10 @@ public class CrmBusinessProductDO extends BaseDO { @@ -41,19 +39,10 @@ public class CrmBusinessProductDO extends BaseDO {
41 * 关联 {@link CrmProductDO#getId()} 39 * 关联 {@link CrmProductDO#getId()}
42 */ 40 */
43 private Long productId; 41 private Long productId;
44 - // TODO 芋艿:需要在看下 CRM  
45 - /**  
46 - * 合同编号  
47 - *  
48 - * 关联 {@link CrmContractDO#getId()}  
49 - */  
50 - private Long contractId;  
51 -  
52 /** 42 /**
53 * 产品单价 43 * 产品单价
54 */ 44 */
55 private Integer price; 45 private Integer price;
56 -  
57 /** 46 /**
58 * 销售价格, 单位:分 47 * 销售价格, 单位:分
59 */ 48 */
@@ -71,11 +60,4 @@ public class CrmBusinessProductDO extends BaseDO { @@ -71,11 +60,4 @@ public class CrmBusinessProductDO extends BaseDO {
71 */ 60 */
72 private Integer totalPrice; 61 private Integer totalPrice;
73 62
74 - /**  
75 - * 单位  
76 - *  
77 - * 字典 {@link DictTypeConstants#CRM_PRODUCT_UNIT}  
78 - */  
79 - private Integer unit;  
80 -  
81 } 63 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.dal.dataobject.contract;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
  5 +import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableId;
  7 +import com.baomidou.mybatisplus.annotation.TableName;
  8 +import lombok.*;
  9 +
  10 +/**
  11 + * 合同产品关联表 DO
  12 + *
  13 + * @author HUIHUI
  14 + */
  15 +@TableName("crm_contract_product")
  16 +@KeySequence("crm_contract_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  17 +@Data
  18 +@EqualsAndHashCode(callSuper = true)
  19 +@ToString(callSuper = true)
  20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class CrmContractProductDO extends BaseDO {
  24 +
  25 + /**
  26 + * 主键
  27 + */
  28 + @TableId
  29 + private Long id;
  30 + /**
  31 + * 产品编号
  32 + *
  33 + * 关联 {@link CrmProductDO#getId()}
  34 + */
  35 + private Long productId;
  36 + /**
  37 + * 合同编号
  38 + *
  39 + * 关联 {@link CrmContractDO#getId()}
  40 + */
  41 + private Long contractId;
  42 + /**
  43 + * 产品单价
  44 + */
  45 + private Integer price;
  46 + /**
  47 + * 销售价格, 单位:分
  48 + */
  49 + private Integer salesPrice;
  50 + /**
  51 + * 数量
  52 + */
  53 + private Integer count;
  54 + /**
  55 + * 折扣
  56 + */
  57 + private Integer discountPercent;
  58 + /**
  59 + * 总计价格(折扣后价格)
  60 + *
  61 + * TODO @puhui999:可以写下计算公式哈;
  62 + */
  63 + private Integer totalPrice;
  64 +
  65 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java
@@ -30,4 +30,52 @@ public interface CrmBiRankingMapper { @@ -30,4 +30,52 @@ public interface CrmBiRankingMapper {
30 */ 30 */
31 List<CrmBiRanKRespVO> selectReceivablePriceRank(CrmBiRankReqVO rankReqVO); 31 List<CrmBiRanKRespVO> selectReceivablePriceRank(CrmBiRankReqVO rankReqVO);
32 32
  33 + /**
  34 + * 查询签约合同数量排行榜
  35 + *
  36 + * @param rankReqVO 参数
  37 + * @return 签约合同数量排行榜
  38 + */
  39 + List<CrmBiRanKRespVO> selectContractCountRank(CrmBiRankReqVO rankReqVO);
  40 +
  41 + /**
  42 + * 查询产品销量排行榜
  43 + *
  44 + * @param rankReqVO 参数
  45 + * @return 产品销量排行榜
  46 + */
  47 + List<CrmBiRanKRespVO> selectProductSalesRank(CrmBiRankReqVO rankReqVO);
  48 +
  49 + /**
  50 + * 查询新增客户数排行榜
  51 + *
  52 + * @param rankReqVO 参数
  53 + * @return 新增客户数排行榜
  54 + */
  55 + List<CrmBiRanKRespVO> selectCustomerCountRank(CrmBiRankReqVO rankReqVO);
  56 +
  57 + /**
  58 + * 查询联系人数量排行榜
  59 + *
  60 + * @param rankReqVO 参数
  61 + * @return 联系人数量排行榜
  62 + */
  63 + List<CrmBiRanKRespVO> selectContactsCountRank(CrmBiRankReqVO rankReqVO);
  64 +
  65 + /**
  66 + * 查询跟进次数排行榜
  67 + *
  68 + * @param rankReqVO 参数
  69 + * @return 跟进次数排行榜
  70 + */
  71 + List<CrmBiRanKRespVO> selectFollowCountRank(CrmBiRankReqVO rankReqVO);
  72 +
  73 + /**
  74 + * 查询跟进客户数排行榜
  75 + *
  76 + * @param rankReqVO 参数
  77 + * @return 跟进客户数排行榜
  78 + */
  79 + List<CrmBiRanKRespVO> selectFollowCustomerCountRank(CrmBiRankReqVO rankReqVO);
  80 +
33 } 81 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java
@@ -2,9 +2,12 @@ package cn.iocoder.yudao.module.crm.dal.mysql.business; @@ -2,9 +2,12 @@ package cn.iocoder.yudao.module.crm.dal.mysql.business;
2 2
3 3
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; 6 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
6 import org.apache.ibatis.annotations.Mapper; 7 import org.apache.ibatis.annotations.Mapper;
7 8
  9 +import java.util.List;
  10 +
8 /** 11 /**
9 * 商机产品 Mapper 12 * 商机产品 Mapper
10 * 13 *
@@ -13,12 +16,18 @@ import org.apache.ibatis.annotations.Mapper; @@ -13,12 +16,18 @@ import org.apache.ibatis.annotations.Mapper;
13 @Mapper 16 @Mapper
14 public interface CrmBusinessProductMapper extends BaseMapperX<CrmBusinessProductDO> { 17 public interface CrmBusinessProductMapper extends BaseMapperX<CrmBusinessProductDO> {
15 18
16 - default void deleteByBusinessId(Long id) { // TODO @lzxhqs:第一个方法,和类之间最好空一行;  
17 - delete(CrmBusinessProductDO::getBusinessId, id); 19 + // TODO @puhui999:用不到的方法,看看是不是删除哈
  20 + default void deleteByBusinessId(Long getBusinessId) { // TODO @lzxhqs:第一个方法,和类之间最好空一行;
  21 + delete(CrmBusinessProductDO::getBusinessId, getBusinessId);
  22 + }
  23 +
  24 + default CrmBusinessProductDO selectByBusinessId(Long getBusinessId) {
  25 + return selectOne(CrmBusinessProductDO::getBusinessId, getBusinessId);
18 } 26 }
19 27
20 - default CrmBusinessProductDO selectByBusinessId(Long id) {  
21 - return selectOne(CrmBusinessProductDO::getBusinessId, id); 28 + default List<CrmBusinessProductDO> selectListByBusinessId(Long businessId) {
  29 + // TODO @puhui999:可以简化,selectList(CrmBusinessProductDO::getBusinessId, businessId)
  30 + return selectList(new LambdaQueryWrapperX<CrmBusinessProductDO>().eq(CrmBusinessProductDO::getBusinessId, businessId));
22 } 31 }
23 32
24 } 33 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
@@ -59,18 +59,15 @@ public interface CrmContractMapper extends BaseMapperX&lt;CrmContractDO&gt; { @@ -59,18 +59,15 @@ public interface CrmContractMapper extends BaseMapperX&lt;CrmContractDO&gt; {
59 // Backlog: 即将到期的合同 59 // Backlog: 即将到期的合同
60 LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); 60 LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
61 LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); 61 LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
62 - if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) {  
63 - // 即将到期 62 + if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) { // 即将到期
64 // TODO: @芋艿 需要配置 提前提醒天数 63 // TODO: @芋艿 需要配置 提前提醒天数
65 int REMIND_DAYS = 20; 64 int REMIND_DAYS = 20;
66 query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus()) 65 query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus())
67 .between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(REMIND_DAYS)); 66 .between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(REMIND_DAYS));
68 - } else if (CrmContractPageReqVO.EXPIRY_TYPE_EXPIRED.equals(pageReqVO.getExpiryType())) {  
69 - // 已到期 67 + } else if (CrmContractPageReqVO.EXPIRY_TYPE_EXPIRED.equals(pageReqVO.getExpiryType())) { // 已到期
70 query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus()) 68 query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus())
71 .lt(CrmContractDO::getEndTime, endOfToday); 69 .lt(CrmContractDO::getEndTime, endOfToday);
72 } 70 }
73 -  
74 return selectJoinPage(pageReqVO, CrmContractDO.class, query); 71 return selectJoinPage(pageReqVO, CrmContractDO.class, query);
75 } 72 }
76 73
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.dal.mysql.contract;
  2 +
  3 +
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
  7 +import org.apache.ibatis.annotations.Mapper;
  8 +
  9 +import java.util.List;
  10 +
  11 +/**
  12 + * 合同产品 Mapper
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Mapper
  17 +public interface CrmContractProductMapper extends BaseMapperX<CrmContractProductDO> {
  18 +
  19 + // TODO @puhui999:用不到的方法,看看是不是删除哈
  20 + default void deleteByContractId(Long contractId) { // TODO @lzxhqs:第一个方法,和类之间最好空一行;
  21 + delete(CrmContractProductDO::getContractId, contractId);
  22 + }
  23 +
  24 + default CrmContractProductDO selectByContractId(Long contractId) {
  25 + return selectOne(CrmContractProductDO::getContractId, contractId);
  26 + }
  27 +
  28 + default List<CrmContractProductDO> selectListByContractId(Long contractId) {
  29 + return selectList(new LambdaQueryWrapperX<CrmContractProductDO>().eq(CrmContractProductDO::getContractId, contractId));
  30 + }
  31 +
  32 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.crm.dal.mysql.product; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.crm.dal.mysql.product;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;  
6 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
7 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; 6 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
8 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; 7 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
@@ -10,9 +9,6 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; @@ -10,9 +9,6 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
10 import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; 9 import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
11 import org.apache.ibatis.annotations.Mapper; 10 import org.apache.ibatis.annotations.Mapper;
12 11
13 -import java.util.Collection;  
14 -import java.util.List;  
15 -  
16 /** 12 /**
17 * CRM 产品 Mapper 13 * CRM 产品 Mapper
18 * 14 *
@@ -38,9 +34,4 @@ public interface CrmProductMapper extends BaseMapperX&lt;CrmProductDO&gt; { @@ -38,9 +34,4 @@ public interface CrmProductMapper extends BaseMapperX&lt;CrmProductDO&gt; {
38 return selectOne(CrmProductDO::getNo, no); 34 return selectOne(CrmProductDO::getNo, no);
39 } 35 }
40 36
41 - // TODO @puhui999:selectBatchIds  
42 - default List<CrmProductDO> selectListByIds(Collection<Long> ids) {  
43 - return selectList(new LambdaQueryWrapperX<CrmProductDO>().in(CrmProductDO::getId, ids));  
44 - }  
45 -  
46 } 37 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
@@ -52,23 +52,21 @@ public interface CrmReceivablePlanMapper extends BaseMapperX&lt;CrmReceivablePlanDO @@ -52,23 +52,21 @@ public interface CrmReceivablePlanMapper extends BaseMapperX&lt;CrmReceivablePlanDO
52 // Backlog: 回款提醒类型 52 // Backlog: 回款提醒类型
53 LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); 53 LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
54 LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); 54 LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
55 - if (CrmReceivablePlanPageReqVO.REMIND_NEEDED.equals(pageReqVO.getRemindType())) {  
56 - // 待回款 55 + if (CrmReceivablePlanPageReqVO.REMIND_TYPE_NEEDED.equals(pageReqVO.getRemindType())) { // 待回款
57 query.isNull(CrmReceivablePlanDO::getReceivableId) 56 query.isNull(CrmReceivablePlanDO::getReceivableId)
58 .gt(CrmReceivablePlanDO::getReturnTime, beginOfToday) 57 .gt(CrmReceivablePlanDO::getReturnTime, beginOfToday)
  58 + // TODO @dhb52:这里看看怎么改成不要使用 to_days
59 .apply("to_days(return_time) <= to_days(now())+ remind_days"); 59 .apply("to_days(return_time) <= to_days(now())+ remind_days");
60 - } else if (CrmReceivablePlanPageReqVO.REMIND_EXPIRED.equals(pageReqVO.getRemindType())) {  
61 - // 已逾期 60 + } else if (CrmReceivablePlanPageReqVO.REMIND_TYPE_EXPIRED.equals(pageReqVO.getRemindType())) { // 已逾期
62 query.isNull(CrmReceivablePlanDO::getReceivableId) 61 query.isNull(CrmReceivablePlanDO::getReceivableId)
63 .lt(CrmReceivablePlanDO::getReturnTime, endOfToday); 62 .lt(CrmReceivablePlanDO::getReturnTime, endOfToday);
64 - } else if (CrmReceivablePlanPageReqVO.REMIND_RECEIVED.equals(pageReqVO.getRemindType())) {  
65 - // 已回款 63 + } else if (CrmReceivablePlanPageReqVO.REMIND_TYPE_RECEIVED.equals(pageReqVO.getRemindType())) { // 已回款
66 query.isNotNull(CrmReceivablePlanDO::getReceivableId) 64 query.isNotNull(CrmReceivablePlanDO::getReceivableId)
67 .gt(CrmReceivablePlanDO::getReturnTime, beginOfToday) 65 .gt(CrmReceivablePlanDO::getReturnTime, beginOfToday)
  66 + // TODO @dhb52:这里看看怎么改成不要使用 to_days
68 .apply("to_days(return_time) <= to_days(now()) + remind_days"); 67 .apply("to_days(return_time) <= to_days(now()) + remind_days");
69 } 68 }
70 69
71 -  
72 return selectJoinPage(pageReqVO, CrmReceivablePlanDO.class, query); 70 return selectJoinPage(pageReqVO, CrmReceivablePlanDO.class, query);
73 } 71 }
74 72
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingService.java
@@ -29,4 +29,52 @@ public interface CrmBiRankingService { @@ -29,4 +29,52 @@ public interface CrmBiRankingService {
29 */ 29 */
30 List<CrmBiRanKRespVO> getReceivablePriceRank(CrmBiRankReqVO rankReqVO); 30 List<CrmBiRanKRespVO> getReceivablePriceRank(CrmBiRankReqVO rankReqVO);
31 31
  32 + /**
  33 + * 获得签约合同数量排行榜
  34 + *
  35 + * @param rankReqVO 排行参数
  36 + * @return 签约合同数量排行榜
  37 + */
  38 + List<CrmBiRanKRespVO> getContractCountRank(CrmBiRankReqVO rankReqVO);
  39 +
  40 + /**
  41 + * 获得产品销量排行榜
  42 + *
  43 + * @param rankReqVO 排行参数
  44 + * @return 产品销量排行榜
  45 + */
  46 + List<CrmBiRanKRespVO> getProductSalesRank(CrmBiRankReqVO rankReqVO);
  47 +
  48 + /**
  49 + * 获得新增客户数排行榜
  50 + *
  51 + * @param rankReqVO 排行参数
  52 + * @return 新增客户数排行榜
  53 + */
  54 + List<CrmBiRanKRespVO> getCustomerCountRank(CrmBiRankReqVO rankReqVO);
  55 +
  56 + /**
  57 + * 获得联系人数量排行榜
  58 + *
  59 + * @param rankReqVO 排行参数
  60 + * @return 联系人数量排行榜
  61 + */
  62 + List<CrmBiRanKRespVO> getContactsCountRank(CrmBiRankReqVO rankReqVO);
  63 +
  64 + /**
  65 + * 获得跟进次数排行榜
  66 + *
  67 + * @param rankReqVO 排行参数
  68 + * @return 跟进次数排行榜
  69 + */
  70 + List<CrmBiRanKRespVO> getFollowCountRank(CrmBiRankReqVO rankReqVO);
  71 +
  72 + /**
  73 + * 获得跟进客户数排行榜
  74 + *
  75 + * @param rankReqVO 排行参数
  76 + * @return 跟进客户数排行榜
  77 + */
  78 + List<CrmBiRanKRespVO> getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO);
  79 +
32 } 80 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java
@@ -49,6 +49,36 @@ public class CrmBiRankingServiceImpl implements CrmBiRankingService { @@ -49,6 +49,36 @@ public class CrmBiRankingServiceImpl implements CrmBiRankingService {
49 return getRank(rankReqVO, biRankingMapper::selectReceivablePriceRank); 49 return getRank(rankReqVO, biRankingMapper::selectReceivablePriceRank);
50 } 50 }
51 51
  52 + @Override
  53 + public List<CrmBiRanKRespVO> getContractCountRank(CrmBiRankReqVO rankReqVO) {
  54 + return getRank(rankReqVO, biRankingMapper::selectContractCountRank);
  55 + }
  56 +
  57 + @Override
  58 + public List<CrmBiRanKRespVO> getProductSalesRank(CrmBiRankReqVO rankReqVO) {
  59 + return getRank(rankReqVO, biRankingMapper::selectProductSalesRank);
  60 + }
  61 +
  62 + @Override
  63 + public List<CrmBiRanKRespVO> getCustomerCountRank(CrmBiRankReqVO rankReqVO) {
  64 + return getRank(rankReqVO, biRankingMapper::selectCustomerCountRank);
  65 + }
  66 +
  67 + @Override
  68 + public List<CrmBiRanKRespVO> getContactsCountRank(CrmBiRankReqVO rankReqVO) {
  69 + return getRank(rankReqVO, biRankingMapper::selectContactsCountRank);
  70 + }
  71 +
  72 + @Override
  73 + public List<CrmBiRanKRespVO> getFollowCountRank(CrmBiRankReqVO rankReqVO) {
  74 + return getRank(rankReqVO, biRankingMapper::selectFollowCountRank);
  75 + }
  76 +
  77 + @Override
  78 + public List<CrmBiRanKRespVO> getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO) {
  79 + return getRank(rankReqVO, biRankingMapper::selectFollowCustomerCountRank);
  80 + }
  81 +
52 /** 82 /**
53 * 获得排行版数据 83 * 获得排行版数据
54 * 84 *
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductService.java deleted
1 -package cn.iocoder.yudao.module.crm.service.business;  
2 -  
3 -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;  
4 -  
5 -import java.util.List;  
6 -  
7 -/**  
8 - * 商机产品关联表 Service 接口  
9 - *  
10 - * @author lzxhqs  
11 - */  
12 -public interface CrmBusinessProductService {  
13 -  
14 - /**  
15 - * 批量新增商机产品关联数据  
16 - *  
17 - * @param list 商机产品集合  
18 - */  
19 - void createBusinessProductBatch(List<CrmBusinessProductDO> list);  
20 -  
21 - /**  
22 - * 批量更新商机产品表  
23 - *  
24 - * @param list 商机产品数据集合  
25 - */  
26 - void updateBusinessProductBatch(List<CrmBusinessProductDO> list);  
27 -  
28 - /**  
29 - * 批量删除  
30 - *  
31 - * @param list 需要删除的商机产品集合  
32 - */  
33 - void deleteBusinessProductBatch(List<CrmBusinessProductDO> list);  
34 -  
35 - /**  
36 - * 根据商机编号,删除商机产品关联数据  
37 - *  
38 - * @param businessId 商机id  
39 - */  
40 - void deleteBusinessProductByBusinessId(Long businessId);  
41 -  
42 - /**  
43 - * 根据商机编号,获取商机产品关联数据集合  
44 - *  
45 - * @param businessId 商机编号  
46 - */  
47 - List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId);  
48 -  
49 - /**  
50 - * 根据合同编号,获得合同关联的商品列表  
51 - *  
52 - * @param contractId 合同编号  
53 - * @return 关联的商品列表  
54 - */  
55 - List<CrmBusinessProductDO> getBusinessProductListByContractId(Long contractId);  
56 -  
57 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductServiceImpl.java deleted
1 -package cn.iocoder.yudao.module.crm.service.business;  
2 -  
3 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
4 -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;  
5 -import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper;  
6 -import jakarta.annotation.Resource;  
7 -import org.springframework.stereotype.Service;  
8 -import org.springframework.validation.annotation.Validated;  
9 -  
10 -import java.util.List;  
11 -  
12 -/**  
13 - * 商机产品关联表 Service 实现类  
14 - *  
15 - * @author lzxhqs  
16 - */  
17 -@Service  
18 -@Validated  
19 -public class CrmBusinessProductServiceImpl implements CrmBusinessProductService {  
20 -  
21 - @Resource  
22 - private CrmBusinessProductMapper businessProductMapper;  
23 -  
24 - @Override  
25 - public void createBusinessProductBatch(List<CrmBusinessProductDO> list) {  
26 - businessProductMapper.insertBatch(list);  
27 - }  
28 -  
29 - @Override  
30 - public void updateBusinessProductBatch(List<CrmBusinessProductDO> list) {  
31 - businessProductMapper.updateBatch(list);  
32 - }  
33 -  
34 - // TODO @puhui999:这个方法,可以直接调用 deleteList 方法,然后传递 ids 就好了;  
35 - @Override  
36 - public void deleteBusinessProductBatch(List<CrmBusinessProductDO> list) {  
37 - businessProductMapper.deleteBatchIds(CollectionUtils.convertList(list, CrmBusinessProductDO::getId));  
38 - }  
39 -  
40 - @Override  
41 - public void deleteBusinessProductByBusinessId(Long businessId) {  
42 - businessProductMapper.deleteByBusinessId(businessId);  
43 - }  
44 -  
45 - @Override  
46 - public List<CrmBusinessProductDO> getBusinessProductListByContractId(Long contractId) {  
47 - return businessProductMapper.selectList(CrmBusinessProductDO::getContractId, contractId);  
48 - }  
49 -  
50 - @Override  
51 - public List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId) {  
52 - return businessProductMapper.selectList(CrmBusinessProductDO::getBusinessId, businessId);  
53 - }  
54 -  
55 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
@@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
7 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; 7 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
8 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; 8 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
9 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 9 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
  10 +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO;
10 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; 11 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
11 import jakarta.validation.Valid; 12 import jakarta.validation.Valid;
12 13
@@ -51,6 +52,21 @@ public interface CrmBusinessService { @@ -51,6 +52,21 @@ public interface CrmBusinessService {
51 void deleteBusiness(Long id); 52 void deleteBusiness(Long id);
52 53
53 /** 54 /**
  55 + * 商机转移
  56 + *
  57 + * @param reqVO 请求
  58 + * @param userId 用户编号
  59 + */
  60 + void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId);
  61 +
  62 + /**
  63 + * 更新商机关联商品
  64 + *
  65 + * @param updateProductReqBO 请求
  66 + */
  67 + void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO);
  68 +
  69 + /**
54 * 获得商机 70 * 获得商机
55 * 71 *
56 * @param id 编号 72 * @param id 编号
@@ -106,14 +122,6 @@ public interface CrmBusinessService { @@ -106,14 +122,6 @@ public interface CrmBusinessService {
106 PageResult<CrmBusinessDO> getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO); 122 PageResult<CrmBusinessDO> getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO);
107 123
108 /** 124 /**
109 - * 商机转移  
110 - *  
111 - * @param reqVO 请求  
112 - * @param userId 用户编号  
113 - */  
114 - void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId);  
115 -  
116 - /**  
117 * 获取关联客户的商机数量 125 * 获取关联客户的商机数量
118 * 126 *
119 * @param customerId 客户编号 127 * @param customerId 客户编号
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -5,25 +5,28 @@ import cn.hutool.core.collection.ListUtil; @@ -5,25 +5,28 @@ import cn.hutool.core.collection.ListUtil;
5 import cn.hutool.core.util.ObjectUtil; 5 import cn.hutool.core.util.ObjectUtil;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  8 +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; 10 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
10 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; 11 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
11 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; 12 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
12 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;  
13 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; 13 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
14 -import cn.iocoder.yudao.module.crm.convert.businessproduct.CrmBusinessProductConvert;  
15 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; 14 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
16 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
17 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
  17 +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
18 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper; 18 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
  19 +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper;
19 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 20 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
20 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 21 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
21 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; 22 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
  23 +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO;
22 import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService; 24 import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
23 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; 25 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
24 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; 26 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
25 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; 27 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
26 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; 28 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
  29 +import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
27 import com.mzt.logapi.context.LogRecordContext; 30 import com.mzt.logapi.context.LogRecordContext;
28 import com.mzt.logapi.service.impl.DiffParseFunction; 31 import com.mzt.logapi.service.impl.DiffParseFunction;
29 import com.mzt.logapi.starter.annotation.LogRecord; 32 import com.mzt.logapi.starter.annotation.LogRecord;
@@ -35,11 +38,12 @@ import org.springframework.validation.annotation.Validated; @@ -35,11 +38,12 @@ import org.springframework.validation.annotation.Validated;
35 38
36 import java.util.Collection; 39 import java.util.Collection;
37 import java.util.List; 40 import java.util.List;
  41 +import java.util.Map;
  42 +import java.util.Set;
38 43
39 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 44 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
40 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;  
41 -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_CONTRACT_EXISTS;  
42 -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS; 45 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  46 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
43 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; 47 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
44 48
45 /** 49 /**
@@ -53,9 +57,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -53,9 +57,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
53 57
54 @Resource 58 @Resource
55 private CrmBusinessMapper businessMapper; 59 private CrmBusinessMapper businessMapper;
56 -  
57 @Resource 60 @Resource
58 - private CrmBusinessProductService businessProductService; 61 + private CrmBusinessProductMapper businessProductMapper;
  62 +
59 @Resource 63 @Resource
60 @Lazy // 延迟加载,避免循环依赖 64 @Lazy // 延迟加载,避免循环依赖
61 private CrmContractService contractService; 65 private CrmContractService contractService;
@@ -63,6 +67,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -63,6 +67,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
63 private CrmPermissionService permissionService; 67 private CrmPermissionService permissionService;
64 @Resource 68 @Resource
65 private CrmContactBusinessService contactBusinessService; 69 private CrmContactBusinessService contactBusinessService;
  70 + @Resource
  71 + private CrmProductService productService;
66 72
67 @Override 73 @Override
68 @Transactional(rollbackFor = Exception.class) 74 @Transactional(rollbackFor = Exception.class)
@@ -71,14 +77,17 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -71,14 +77,17 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
71 public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) { 77 public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
72 createReqVO.setId(null); 78 createReqVO.setId(null);
73 // 1. 插入商机 79 // 1. 插入商机
74 - CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class)  
75 - .setOwnerUserId(userId); 80 + CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class).setOwnerUserId(userId);
76 businessMapper.insert(business); 81 businessMapper.insert(business);
77 - // TODO 商机待定:插入商机与产品的关联表;校验商品存在  
78 - if (CollUtil.isNotEmpty(createReqVO.getProducts())) {  
79 - createBusinessProducts(createReqVO.getProducts(), business.getId(), false); 82 + // 1.2 插入商机关联商品
  83 + if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话
  84 + List<CrmBusinessProductDO> productList = buildBusinessProductList(createReqVO.getProductItems(), business.getId());
  85 + businessProductMapper.insertBatch(productList);
  86 + // 更新合同商品总金额
  87 + businessMapper.updateById(new CrmBusinessDO().setId(business.getId()).setProductPrice(
  88 + getSumValue(productList, CrmBusinessProductDO::getTotalPrice, Integer::sum)));
80 } 89 }
81 - // TODO 商机待定:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表 90 + // TODO @puhui999:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表
82 createContactBusiness(business.getId(), createReqVO.getContactId()); 91 createContactBusiness(business.getId(), createReqVO.getContactId());
83 92
84 // 2. 创建数据权限 93 // 2. 创建数据权限
@@ -86,19 +95,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -86,19 +95,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
86 permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()) 95 permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
87 .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); 96 .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
88 97
89 - // 4. 记录操作日志上下文 98 + // 3. 记录操作日志上下文
90 LogRecordContext.putVariable("business", business); 99 LogRecordContext.putVariable("business", business);
91 return business.getId(); 100 return business.getId();
92 } 101 }
93 102
94 // TODO @lzxhqs:CrmContactBusinessService 调用这个;这样逻辑才能收敛哈; 103 // TODO @lzxhqs:CrmContactBusinessService 调用这个;这样逻辑才能收敛哈;
95 - /**  
96 - * @param businessId 商机id  
97 - * @param contactId 联系人id  
98 - * @throws  
99 - * @description 联系人与商机的关联  
100 - * @author lzxhqs  
101 - */  
102 private void createContactBusiness(Long businessId, Long contactId) { 104 private void createContactBusiness(Long businessId, Long contactId) {
103 CrmContactBusinessDO contactBusiness = new CrmContactBusinessDO(); 105 CrmContactBusinessDO contactBusiness = new CrmContactBusinessDO();
104 contactBusiness.setBusinessId(businessId); 106 contactBusiness.setBusinessId(businessId);
@@ -106,37 +108,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -106,37 +108,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
106 contactBusinessService.insert(contactBusiness); 108 contactBusinessService.insert(contactBusiness);
107 } 109 }
108 110
109 - // TODO @lzxhqs:这个方法注释格式不对;删除@description,然后把 插入商机产品关联表 作为方法注释;  
110 - /**  
111 - * 插入商机产品关联表  
112 - *  
113 - * @param products 产品集合  
114 - * @param businessId 商机id  
115 - * @param updateFlag 更新标识 true 代表更新  
116 - * @author lzxhqs  
117 - */  
118 - private void createBusinessProducts(List<CrmBusinessProductSaveReqVO> products, Long businessId, Boolean updateFlag) {  
119 - List<CrmBusinessProductDO> list = CollectionUtils.convertList(products, product ->  
120 - CrmBusinessProductConvert.INSTANCE.convert(product).setBusinessId(businessId));  
121 - if (Boolean.TRUE.equals(updateFlag)) {  
122 -// 根据商机 id从商机产品关联表中获取已存在的数据集合  
123 - List<CrmBusinessProductDO> oldProducts = businessProductService.getBusinessProductListByBusinessId(businessId);  
124 - List<List<CrmBusinessProductDO>> diffList = CollectionUtils.diffList(oldProducts, list, (oldValue, newValue) ->  
125 - ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId()));  
126 - if (CollUtil.isNotEmpty(diffList.getFirst())) {  
127 - businessProductService.createBusinessProductBatch(diffList.getFirst());  
128 - }  
129 - if (CollUtil.isNotEmpty(diffList.get(1))) {  
130 - businessProductService.updateBusinessProductBatch(diffList.get(1));  
131 - }  
132 - if (CollUtil.isNotEmpty(diffList.get(2))) {  
133 - businessProductService.deleteBusinessProductBatch(diffList.get(2));  
134 - }  
135 - } else {  
136 - businessProductService.createBusinessProductBatch(list);  
137 - }  
138 - }  
139 -  
140 @Override 111 @Override
141 @Transactional(rollbackFor = Exception.class) 112 @Transactional(rollbackFor = Exception.class)
142 @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", 113 @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
@@ -146,16 +117,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -146,16 +117,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
146 // 1. 校验存在 117 // 1. 校验存在
147 CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId()); 118 CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
148 119
149 - // 2. 更新商机 120 + // 2.1 更新商机
150 CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class); 121 CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class);
151 businessMapper.updateById(updateObj); 122 businessMapper.updateById(updateObj);
152 - // TODO 商机待定:插入商机与产品的关联表;校验商品存在  
153 - // TODO @lzxhqs:createBusinessProducts 可以抽成两个方法,一个新增;一个修改,修改需要把 businessProductService.deleteByBusinessId(updateReqVO.getId()); 一起处理进去;  
154 - if (CollUtil.isNotEmpty(updateReqVO.getProducts())) {  
155 - createBusinessProducts(updateReqVO.getProducts(), updateReqVO.getId(), true);  
156 - } else {  
157 - businessProductService.deleteBusinessProductByBusinessId(updateReqVO.getId());  
158 - } 123 + // 2.2 更新商机关联商品
  124 + List<CrmBusinessProductDO> productList = buildBusinessProductList(updateReqVO.getProductItems(), updateObj.getId());
  125 + updateBusinessProduct(productList, updateObj.getId());
159 126
160 // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表 127 // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表
161 // 3. 记录操作日志上下文 128 // 3. 记录操作日志上下文
@@ -188,6 +155,44 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -188,6 +155,44 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
188 LogRecordContext.putVariable("businessName", business.getName()); 155 LogRecordContext.putVariable("businessName", business.getName());
189 } 156 }
190 157
  158 + private void updateBusinessProduct(List<CrmBusinessProductDO> newProductList, Long businessId) {
  159 + List<CrmBusinessProductDO> oldProducts = businessProductMapper.selectListByBusinessId(businessId);
  160 + List<List<CrmBusinessProductDO>> diffList = CollectionUtils.diffList(oldProducts, newProductList, (oldValue, newValue) -> {
  161 + boolean condition = ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId());
  162 + if (condition) {
  163 + newValue.setId(oldValue.getId()); // 更新需要原始编号
  164 + }
  165 + return condition;
  166 + });
  167 + if (CollUtil.isNotEmpty(diffList.get(0))) {
  168 + businessProductMapper.insertBatch(diffList.get(0));
  169 + }
  170 + if (CollUtil.isNotEmpty(diffList.get(1))) {
  171 + businessProductMapper.updateBatch(diffList.get(1));
  172 + }
  173 + if (CollUtil.isNotEmpty(diffList.get(2))) {
  174 + businessProductMapper.deleteBatchIds(convertSet(diffList.get(2), CrmBusinessProductDO::getId));
  175 + }
  176 + }
  177 +
  178 + private List<CrmBusinessProductDO> buildBusinessProductList(List<CrmBusinessSaveReqVO.CrmBusinessProductItem> productItems,
  179 + Long businessId) {
  180 + // 校验商品存在
  181 + Set<Long> productIds = convertSet(productItems, CrmBusinessSaveReqVO.CrmBusinessProductItem::getId);
  182 + List<CrmProductDO> productList = productService.getProductList(productIds);
  183 + if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) {
  184 + throw exception(PRODUCT_NOT_EXISTS);
  185 + }
  186 + Map<Long, CrmProductDO> productMap = convertMap(productList, CrmProductDO::getId);
  187 + return convertList(productItems, productItem -> {
  188 + CrmProductDO product = productMap.get(productItem.getId());
  189 + return BeanUtils.toBean(product, CrmBusinessProductDO.class)
  190 + .setId(null).setProductId(productItem.getId()).setBusinessId(businessId)
  191 + .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent())
  192 + .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent()));
  193 + });
  194 + }
  195 +
191 /** 196 /**
192 * 删除校验合同是关联合同 197 * 删除校验合同是关联合同
193 * 198 *
@@ -228,6 +233,14 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -228,6 +233,14 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
228 LogRecordContext.putVariable("business", business); 233 LogRecordContext.putVariable("business", business);
229 } 234 }
230 235
  236 + @Override
  237 + public void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO) {
  238 + // 更新商机关联商品
  239 + List<CrmBusinessProductDO> productList = buildBusinessProductList(
  240 + BeanUtils.toBean(updateProductReqBO.getProductItems(), CrmBusinessSaveReqVO.CrmBusinessProductItem.class), updateProductReqBO.getId());
  241 + updateBusinessProduct(productList, updateProductReqBO.getId());
  242 + }
  243 +
231 //======================= 查询相关 ======================= 244 //======================= 查询相关 =======================
232 245
233 @Override 246 @Override
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.business.bo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.AllArgsConstructor;
  7 +import lombok.Data;
  8 +import lombok.NoArgsConstructor;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * 更新商机商品 Update Req BO
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +@Data
  18 +public class CrmBusinessUpdateProductReqBO {
  19 +
  20 + /**
  21 + * 商机编号
  22 + */
  23 + @NotNull(message = "商机编号不能为空")
  24 + private Long id;
  25 +
  26 + // TODO @芋艿:再想想
  27 + @NotEmpty(message = "产品列表不能为空")
  28 + private List<CrmBusinessProductItem> productItems;
  29 +
  30 + @Schema(description = "产品列表")
  31 + @Data
  32 + @NoArgsConstructor
  33 + @AllArgsConstructor
  34 + public static class CrmBusinessProductItem {
  35 +
  36 + @Schema(description = "产品编号", example = "20529")
  37 + @NotNull(message = "产品编号不能为空")
  38 + private Long id;
  39 +
  40 + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
  41 + @NotNull(message = "产品数量不能为空")
  42 + private Integer count;
  43 +
  44 + @Schema(description = "产品折扣")
  45 + private Integer discountPercent;
  46 +
  47 + }
  48 +
  49 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -11,20 +11,22 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; @@ -11,20 +11,22 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
11 import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO; 11 import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;
12 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; 12 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
13 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; 13 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
  14 +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
14 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; 15 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
15 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; 16 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
16 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; 17 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
17 import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; 18 import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
18 -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;  
19 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 19 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
  20 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
20 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; 21 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
21 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; 22 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
  23 +import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractProductMapper;
22 import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; 24 import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
23 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 25 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
24 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 26 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
25 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; 27 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
26 -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessProductService;  
27 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; 28 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
  29 +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO;
28 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 30 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
29 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; 31 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
30 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; 32 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
@@ -59,16 +61,19 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_E @@ -59,16 +61,19 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_E
59 @Validated 61 @Validated
60 public class CrmContractServiceImpl implements CrmContractService { 62 public class CrmContractServiceImpl implements CrmContractService {
61 63
62 - public static final String CONTRACT_APPROVE = "contract-approve"; // 合同审批流程标识 64 + /**
  65 + * BPM 合同审批流程标识
  66 + */
  67 + public static final String CONTRACT_APPROVE = "contract-approve";
63 68
64 @Resource 69 @Resource
65 private CrmContractMapper contractMapper; 70 private CrmContractMapper contractMapper;
  71 + @Resource
  72 + private CrmContractProductMapper contractProductMapper;
66 73
67 @Resource 74 @Resource
68 private CrmPermissionService crmPermissionService; 75 private CrmPermissionService crmPermissionService;
69 @Resource 76 @Resource
70 - private CrmBusinessProductService businessProductService;  
71 - @Resource  
72 private CrmProductService productService; 77 private CrmProductService productService;
73 @Resource 78 @Resource
74 private CrmCustomerService customerService; 79 private CrmCustomerService customerService;
@@ -89,11 +94,18 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -89,11 +94,18 @@ public class CrmContractServiceImpl implements CrmContractService {
89 // 1.1 插入合同 94 // 1.1 插入合同
90 CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null); 95 CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null);
91 contractMapper.insert(contract); 96 contractMapper.insert(contract);
92 - // 1.2 插入商机关联商品 97 + // 1.2 插入合同关联商品
93 if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话 98 if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话
94 - List<CrmBusinessProductDO> businessProduct = convertBusinessProductList(createReqVO, contract.getId());  
95 - businessProductService.createBusinessProductBatch(businessProduct); 99 + List<CrmContractProductDO> productList = convertContractProductList(createReqVO, contract.getId());
  100 + contractProductMapper.insertBatch(productList);
96 // 更新合同商品总金额 101 // 更新合同商品总金额
  102 + contractMapper.updateById(new CrmContractDO().setId(contract.getId()).setProductPrice(
  103 + getSumValue(productList, CrmContractProductDO::getTotalPrice, Integer::sum)));
  104 + // 如果存在合同关联了商机则更新商机商品关联
  105 + if (contract.getBusinessId() != null) {
  106 + businessService.updateBusinessProduct(new CrmBusinessUpdateProductReqBO().setId(contract.getBusinessId())
  107 + .setProductItems(BeanUtils.toBean(createReqVO.getProductItems(), CrmBusinessUpdateProductReqBO.CrmBusinessProductItem.class)));
  108 + }
97 } 109 }
98 110
99 // 2. 创建数据权限 111 // 2. 创建数据权限
@@ -137,29 +149,29 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -137,29 +149,29 @@ public class CrmContractServiceImpl implements CrmContractService {
137 if (CollUtil.isEmpty(updateReqVO.getProductItems())) { 149 if (CollUtil.isEmpty(updateReqVO.getProductItems())) {
138 return; 150 return;
139 } 151 }
140 - List<CrmBusinessProductDO> newProductList = convertBusinessProductList(updateReqVO, contractId);  
141 - List<CrmBusinessProductDO> oldProductList = businessProductService.getBusinessProductListByContractId(contractId);  
142 - List<List<CrmBusinessProductDO>> diffList = diffList(oldProductList, newProductList, (oldObj, newObj) -> {  
143 - if (ObjUtil.notEqual(oldObj.getProductId(), newObj.getProductId())) {  
144 - return false; 152 + List<CrmContractProductDO> newProductList = convertContractProductList(updateReqVO, contractId);
  153 + List<CrmContractProductDO> oldProductList = contractProductMapper.selectListByContractId(contractId);
  154 + List<List<CrmContractProductDO>> diffList = diffList(oldProductList, newProductList, (oldObj, newObj) -> {
  155 + boolean match = ObjUtil.equal(oldObj.getProductId(), newObj.getProductId());
  156 + if (match) {
  157 + newObj.setId(oldObj.getId()); // 设置一下老的编号更新时需要使用
145 } 158 }
146 - newObj.setId(oldObj.getId()); // 设置一下老的编号更新时需要使用  
147 - return true; 159 + return match;
148 }); 160 });
149 - if (CollUtil.isNotEmpty(diffList.getFirst())) {  
150 - businessProductService.createBusinessProductBatch(diffList.getFirst()); 161 + if (CollUtil.isNotEmpty(diffList.get(0))) {
  162 + contractProductMapper.insertBatch(diffList.get(0));
151 } 163 }
152 if (CollUtil.isNotEmpty(diffList.get(1))) { 164 if (CollUtil.isNotEmpty(diffList.get(1))) {
153 - businessProductService.updateBusinessProductBatch(diffList.get(1)); 165 + contractProductMapper.updateBatch(diffList.get(1));
154 } 166 }
155 if (CollUtil.isNotEmpty(diffList.get(2))) { 167 if (CollUtil.isNotEmpty(diffList.get(2))) {
156 - businessProductService.deleteBusinessProductBatch(diffList.get(2)); 168 + contractProductMapper.deleteBatchIds(convertList(diffList.get(2), CrmContractProductDO::getId));
157 } 169 }
158 } 170 }
159 171
160 // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum 172 // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
161 173
162 - private List<CrmBusinessProductDO> convertBusinessProductList(CrmContractSaveReqVO reqVO, Long contractId) { 174 + private List<CrmContractProductDO> convertContractProductList(CrmContractSaveReqVO reqVO, Long contractId) {
163 // 校验商品存在 175 // 校验商品存在
164 Set<Long> productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId); 176 Set<Long> productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId);
165 List<CrmProductDO> productList = productService.getProductList(productIds); 177 List<CrmProductDO> productList = productService.getProductList(productIds);
@@ -169,8 +181,8 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -169,8 +181,8 @@ public class CrmContractServiceImpl implements CrmContractService {
169 Map<Long, CrmProductDO> productMap = convertMap(productList, CrmProductDO::getId); 181 Map<Long, CrmProductDO> productMap = convertMap(productList, CrmProductDO::getId);
170 return convertList(reqVO.getProductItems(), productItem -> { 182 return convertList(reqVO.getProductItems(), productItem -> {
171 CrmProductDO product = productMap.get(productItem.getId()); 183 CrmProductDO product = productMap.get(productItem.getId());
172 - return BeanUtils.toBean(product, CrmBusinessProductDO.class)  
173 - .setId(null).setBusinessId(reqVO.getBusinessId()).setProductId(productItem.getId()).setContractId(contractId) 184 + return BeanUtils.toBean(product, CrmContractProductDO.class)
  185 + .setId(null).setProductId(productItem.getId()).setContractId(contractId)
174 .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent()) 186 .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent())
175 .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent())); 187 .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent()));
176 }); 188 });
@@ -248,7 +260,8 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -248,7 +260,8 @@ public class CrmContractServiceImpl implements CrmContractService {
248 260
249 @Override 261 @Override
250 @Transactional(rollbackFor = Exception.class) 262 @Transactional(rollbackFor = Exception.class)
251 - // TODO @puhui999:操作日志; 263 + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_SUBMIT_SUB_TYPE, bizNo = "{{#id}}",
  264 + success = CRM_CONTRACT_SUBMIT_SUCCESS)
252 public void submitContract(Long id, Long userId) { 265 public void submitContract(Long id, Long userId) {
253 // 1. 校验合同是否在审批 266 // 1. 校验合同是否在审批
254 CrmContractDO contract = validateContractExists(id); 267 CrmContractDO contract = validateContractExists(id);
@@ -263,15 +276,43 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -263,15 +276,43 @@ public class CrmContractServiceImpl implements CrmContractService {
263 // 3. 更新合同工作流编号 276 // 3. 更新合同工作流编号
264 contractMapper.updateById(new CrmContractDO().setId(id).setProcessInstanceId(processInstanceId) 277 contractMapper.updateById(new CrmContractDO().setId(id).setProcessInstanceId(processInstanceId)
265 .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); 278 .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
  279 +
  280 + // 3. 记录日志
  281 + LogRecordContext.putVariable("contractName", contract.getName());
266 } 282 }
267 283
268 @Override 284 @Override
269 public void updateContractAuditStatus(BpmResultListenerRespDTO event) { 285 public void updateContractAuditStatus(BpmResultListenerRespDTO event) {
270 - // TODO @puhui999:可能要判断下状态是否符合预期 286 + // 判断下状态是否符合预期
  287 + if (!isEndResult(event.getResult())) {
  288 + return;
  289 + }
  290 + // 状态转换
  291 + if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.APPROVE.getResult())) {
  292 + event.setResult(CrmAuditStatusEnum.APPROVE.getStatus());
  293 + }
  294 + if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.REJECT.getResult())) {
  295 + event.setResult(CrmAuditStatusEnum.REJECT.getStatus());
  296 + }
  297 + if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult())) {
  298 + event.setResult(CrmAuditStatusEnum.CANCEL.getStatus());
  299 + }
  300 + // 更新合同状态
271 contractMapper.updateById(new CrmContractDO().setId(Long.parseLong(event.getBusinessKey())) 301 contractMapper.updateById(new CrmContractDO().setId(Long.parseLong(event.getBusinessKey()))
272 .setAuditStatus(event.getResult())); 302 .setAuditStatus(event.getResult()));
273 } 303 }
274 304
  305 + /**
  306 + * 判断该结果是否处于 End 最终结果
  307 + *
  308 + * @param result 结果
  309 + * @return 是否
  310 + */
  311 + public static boolean isEndResult(Integer result) {
  312 + return ObjectUtils.equalsAny(result, BpmProcessInstanceResultEnum.APPROVE.getResult(),
  313 + BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult());
  314 + }
  315 +
275 //======================= 查询相关 ======================= 316 //======================= 查询相关 =======================
276 317
277 @Override 318 @Override
@@ -313,6 +354,12 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -313,6 +354,12 @@ public class CrmContractServiceImpl implements CrmContractService {
313 public Long getContractCountByBusinessId(Long businessId) { 354 public Long getContractCountByBusinessId(Long businessId) {
314 return contractMapper.selectCountByBusinessId(businessId); 355 return contractMapper.selectCountByBusinessId(businessId);
315 } 356 }
  357 +
  358 + @Override
  359 + public List<CrmContractProductDO> getContractProductListByContractId(Long contactId) {
  360 + return contractProductMapper.selectListByContractId(contactId);
  361 + }
  362 +
316 // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒; 363 // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒;
317 364
318 @Override 365 @Override
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java
1 package cn.iocoder.yudao.module.crm.service.contract.listener; 1 package cn.iocoder.yudao.module.crm.service.contract.listener;
2 2
3 -import cn.hutool.core.util.ObjUtil;  
4 -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;  
5 import cn.iocoder.yudao.module.bpm.api.listener.BpmResultListenerApi; 3 import cn.iocoder.yudao.module.bpm.api.listener.BpmResultListenerApi;
6 import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO; 4 import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;
7 -import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;  
8 -import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;  
9 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; 5 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
10 import cn.iocoder.yudao.module.crm.service.contract.CrmContractServiceImpl; 6 import cn.iocoder.yudao.module.crm.service.contract.CrmContractServiceImpl;
11 import jakarta.annotation.Resource; 7 import jakarta.annotation.Resource;
@@ -30,31 +26,7 @@ public class CrmContractResultListener implements BpmResultListenerApi { @@ -30,31 +26,7 @@ public class CrmContractResultListener implements BpmResultListenerApi {
30 26
31 @Override 27 @Override
32 public void onEvent(BpmResultListenerRespDTO event) { 28 public void onEvent(BpmResultListenerRespDTO event) {
33 - boolean currentTaskFinish = isEndResult(event.getResult());  
34 - if (!currentTaskFinish) {  
35 - return;  
36 - }  
37 - if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.APPROVE.getResult())) {  
38 - event.setResult(CrmAuditStatusEnum.APPROVE.getStatus());  
39 - }  
40 - if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.REJECT.getResult())) {  
41 - event.setResult(CrmAuditStatusEnum.REJECT.getStatus());  
42 - }  
43 - if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult())) {  
44 - event.setResult(CrmAuditStatusEnum.CANCEL.getStatus());  
45 - }  
46 contractService.updateContractAuditStatus(event); 29 contractService.updateContractAuditStatus(event);
47 } 30 }
48 31
49 - /**  
50 - * 判断该结果是否处于 End 最终结果  
51 - *  
52 - * @param result 结果  
53 - * @return 是否  
54 - */  
55 - public static boolean isEndResult(Integer result) {  
56 - return ObjectUtils.equalsAny(result, BpmProcessInstanceResultEnum.APPROVE.getResult(),  
57 - BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult());  
58 - }  
59 -  
60 } 32 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
@@ -110,11 +110,10 @@ public interface CrmCustomerService { @@ -110,11 +110,10 @@ public interface CrmCustomerService {
110 * 批量导入客户 110 * 批量导入客户
111 * 111 *
112 * @param importCustomers 导入客户列表 112 * @param importCustomers 导入客户列表
113 - * @param isUpdateSupport 是否支持更新  
114 - * @param userId 用户编号 113 + * @param importReqVO 请求
115 * @return 导入结果 114 * @return 导入结果
116 */ 115 */
117 - CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, Boolean isUpdateSupport, Long userId); 116 + CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, CrmCustomerImportReqVO importReqVO);
118 117
119 // ==================== 公海相关操作 ==================== 118 // ==================== 公海相关操作 ====================
120 119
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -108,7 +108,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @@ -108,7 +108,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
108 /** 108 /**
109 * 初始化客户的通用字段 109 * 初始化客户的通用字段
110 * 110 *
111 - * @param customer 客户信息 111 + * @param customer 客户信息
112 * @param ownerUserId 负责人编号 112 * @param ownerUserId 负责人编号
113 * @return 客户信息 DO 113 * @return 客户信息 DO
114 */ 114 */
@@ -244,8 +244,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @@ -244,8 +244,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
244 } 244 }
245 245
246 @Override 246 @Override
247 - public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers,  
248 - Boolean isUpdateSupport, Long userId) { 247 + public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, CrmCustomerImportReqVO importReqVO) {
249 if (CollUtil.isEmpty(importCustomers)) { 248 if (CollUtil.isEmpty(importCustomers)) {
250 throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY); 249 throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY);
251 } 250 }
@@ -264,19 +263,21 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @@ -264,19 +263,21 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
264 CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName()); 263 CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName());
265 if (existCustomer == null) { 264 if (existCustomer == null) {
266 // 1.1 插入客户信息 265 // 1.1 插入客户信息
267 - CrmCustomerDO customer = initCustomer(importCustomer, userId); 266 + CrmCustomerDO customer = initCustomer(importCustomer, importReqVO.getOwnerUserId());
268 customerMapper.insert(customer); 267 customerMapper.insert(customer);
269 respVO.getCreateCustomerNames().add(importCustomer.getName()); 268 respVO.getCreateCustomerNames().add(importCustomer.getName());
270 // 1.2 创建数据权限 269 // 1.2 创建数据权限
271 - permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())  
272 - .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 270 + if (importReqVO.getOwnerUserId() != null) {
  271 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
  272 + .setBizId(customer.getId()).setUserId(importReqVO.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
  273 + }
273 // 1.3 记录操作日志 274 // 1.3 记录操作日志
274 getSelf().importCustomerLog(customer, false); 275 getSelf().importCustomerLog(customer, false);
275 return; 276 return;
276 } 277 }
277 278
278 // 情况二:如果存在,判断是否允许更新 279 // 情况二:如果存在,判断是否允许更新
279 - if (!isUpdateSupport) { 280 + if (!importReqVO.getUpdateSupport()) {
280 respVO.getFailureCustomerNames().put(importCustomer.getName(), 281 respVO.getFailureCustomerNames().put(importCustomer.getName(),
281 StrUtil.format(CUSTOMER_NAME_EXISTS.getMsg(), importCustomer.getName())); 282 StrUtil.format(CUSTOMER_NAME_EXISTS.getMsg(), importCustomer.getName()));
282 return; 283 return;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
@@ -160,7 +160,7 @@ public class CrmProductServiceImpl implements CrmProductService { @@ -160,7 +160,7 @@ public class CrmProductServiceImpl implements CrmProductService {
160 if (CollUtil.isEmpty(ids)) { 160 if (CollUtil.isEmpty(ids)) {
161 return Collections.emptyList(); 161 return Collections.emptyList();
162 } 162 }
163 - return productMapper.selectListByIds(ids); 163 + return productMapper.selectBatchIds(ids);
164 } 164 }
165 165
166 } 166 }
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml
@@ -9,10 +9,11 @@ @@ -9,10 +9,11 @@
9 WHERE deleted = 0 9 WHERE deleted = 0
10 AND audit_status = 20 10 AND audit_status = 20
11 and owner_user_id in 11 and owner_user_id in
12 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
13 - #{userId}  
14 - </foreach>  
15 - AND order_date between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime} 12 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  13 + #{userId}
  14 + </foreach>
  15 + AND order_date between #{times[0],javaType=java.time.LocalDateTime} and
  16 + #{times[1],javaType=java.time.LocalDateTime}
16 GROUP BY owner_user_id 17 GROUP BY owner_user_id
17 </select> 18 </select>
18 19
@@ -22,12 +23,104 @@ @@ -22,12 +23,104 @@
22 FROM crm_receivable 23 FROM crm_receivable
23 WHERE deleted = 0 24 WHERE deleted = 0
24 AND audit_status = 20 25 AND audit_status = 20
25 - and owner_user_id in  
26 - <foreach collection="userIds" item="userId" open="(" close=")" separator=",">  
27 - #{userId}  
28 - </foreach>  
29 - AND return_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime} 26 + AND owner_user_id in
  27 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  28 + #{userId}
  29 + </foreach>
  30 + AND return_time between #{times[0],javaType=java.time.LocalDateTime} and
  31 + #{times[1],javaType=java.time.LocalDateTime}
  32 + GROUP BY owner_user_id
  33 + </select>
  34 +
  35 + <select id="selectContractCountRank"
  36 + resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
  37 + SELECT COUNT(1) AS count, owner_user_id
  38 + FROM crm_contract
  39 + WHERE deleted = 0
  40 + AND audit_status = 20
  41 + AND owner_user_id in
  42 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  43 + #{userId}
  44 + </foreach>
  45 + AND order_date between #{times[0],javaType=java.time.LocalDateTime} and
  46 + #{times[1],javaType=java.time.LocalDateTime}
30 GROUP BY owner_user_id 47 GROUP BY owner_user_id
31 </select> 48 </select>
32 49
  50 + <!-- TODO 待定 这里是否需要关联 crm_contract_product 表,计算销售额 -->
  51 + <select id="selectProductSalesRank"
  52 + resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
  53 + SELECT COUNT(1) AS count, owner_user_id
  54 + FROM crm_contract
  55 + WHERE deleted = 0
  56 + AND audit_status = 20
  57 + AND owner_user_id in
  58 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  59 + #{userId}
  60 + </foreach>
  61 + AND order_date between #{times[0],javaType=java.time.LocalDateTime} and
  62 + #{times[1],javaType=java.time.LocalDateTime}
  63 + GROUP BY owner_user_id
  64 + </select>
  65 +
  66 + <select id="selectCustomerCountRank"
  67 + resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
  68 + SELECT COUNT(1) AS count, owner_user_id
  69 + FROM crm_customer
  70 + WHERE deleted = 0
  71 + AND owner_user_id in
  72 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  73 + #{userId}
  74 + </foreach>
  75 + AND create_time between #{times[0],javaType=java.time.LocalDateTime} and
  76 + #{times[1],javaType=java.time.LocalDateTime}
  77 + GROUP BY owner_user_id
  78 + </select>
  79 +
  80 + <select id="selectContactsCountRank"
  81 + resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
  82 + SELECT COUNT(1) AS count, owner_user_id
  83 + FROM crm_contact
  84 + WHERE deleted = 0
  85 + AND owner_user_id in
  86 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  87 + #{userId}
  88 + </foreach>
  89 + AND create_time between #{times[0],javaType=java.time.LocalDateTime} and
  90 + #{times[1],javaType=java.time.LocalDateTime}
  91 + GROUP BY owner_user_id
  92 + </select>
  93 +
  94 + <select id="selectFollowCountRank"
  95 + resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
  96 + SELECT COUNT(1) AS count, cc.owner_user_id
  97 + FROM crm_follow_up_record AS cfur
  98 + LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
  99 + WHERE cfur.deleted = 0
  100 + AND cc.deleted = 0
  101 + AND cc.owner_user_id in
  102 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  103 + #{userId}
  104 + </foreach>
  105 + AND cfur.create_time between #{times[0],javaType=java.time.LocalDateTime} and
  106 + #{times[1],javaType=java.time.LocalDateTime}
  107 + GROUP BY cc.owner_user_id
  108 + </select>
  109 +
  110 + <select id="selectFollowCustomerCountRank"
  111 + resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
  112 + SELECT COUNT(DISTINCT cc.id) AS count, cc.owner_user_id
  113 + FROM crm_follow_up_record AS cfur
  114 + LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
  115 + WHERE cfur.deleted = 0
  116 + AND cc.deleted = 0
  117 + AND cc.owner_user_id in
  118 + <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
  119 + #{userId}
  120 + </foreach>
  121 + AND cfur.create_time between #{times[0],javaType=java.time.LocalDateTime} and
  122 + #{times[1],javaType=java.time.LocalDateTime}
  123 + GROUP BY cc.owner_user_id
  124 + </select>
  125 +
33 </mapper> 126 </mapper>
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/package-info.java 0 → 100644
  1 +/**
  2 + * 属于 erp 模块的 framework 封装
  3 + *
  4 + * @author 芋道源码
  5 + */
  6 +package cn.iocoder.yudao.module.erp.framework;
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/config/ErpWebConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.erp.framework.web.config;
  2 +
  3 +import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
  4 +import org.springdoc.core.models.GroupedOpenApi;
  5 +import org.springframework.context.annotation.Bean;
  6 +import org.springframework.context.annotation.Configuration;
  7 +
  8 +/**
  9 + * erp 模块的 web 组件的 Configuration
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Configuration(proxyBeanMethods = false)
  14 +public class ErpWebConfiguration {
  15 +
  16 + /**
  17 + * erp 模块的 API 分组
  18 + */
  19 + @Bean
  20 + public GroupedOpenApi tradeGroupedOpenApi() {
  21 + return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("erp");
  22 + }
  23 +
  24 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/package-info.java renamed to yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/package-info.java
1 /** 1 /**
2 - * 合同 2 + * trade 模块的 web 配置
3 */ 3 */
4 -package cn.iocoder.yudao.module.crm.dal.dataobject.contract; 4 +package cn.iocoder.yudao.module.erp.framework.web;
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
@@ -8,14 +8,17 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -8,14 +8,17 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 9 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
11 -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;  
12 -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO;  
13 -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileUploadReqVO; 11 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
14 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; 12 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
15 import cn.iocoder.yudao.module.infra.service.file.FileService; 13 import cn.iocoder.yudao.module.infra.service.file.FileService;
16 import io.swagger.v3.oas.annotations.Operation; 14 import io.swagger.v3.oas.annotations.Operation;
17 import io.swagger.v3.oas.annotations.Parameter; 15 import io.swagger.v3.oas.annotations.Parameter;
18 import io.swagger.v3.oas.annotations.tags.Tag; 16 import io.swagger.v3.oas.annotations.tags.Tag;
  17 +import jakarta.annotation.Resource;
  18 +import jakarta.annotation.security.PermitAll;
  19 +import jakarta.servlet.http.HttpServletRequest;
  20 +import jakarta.servlet.http.HttpServletResponse;
  21 +import jakarta.validation.Valid;
19 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
20 import org.springframework.http.HttpStatus; 23 import org.springframework.http.HttpStatus;
21 import org.springframework.security.access.prepost.PreAuthorize; 24 import org.springframework.security.access.prepost.PreAuthorize;
@@ -23,12 +26,6 @@ import org.springframework.validation.annotation.Validated; @@ -23,12 +26,6 @@ import org.springframework.validation.annotation.Validated;
23 import org.springframework.web.bind.annotation.*; 26 import org.springframework.web.bind.annotation.*;
24 import org.springframework.web.multipart.MultipartFile; 27 import org.springframework.web.multipart.MultipartFile;
25 28
26 -import jakarta.annotation.Resource;  
27 -import jakarta.annotation.security.PermitAll;  
28 -import jakarta.servlet.http.HttpServletRequest;  
29 -import jakarta.servlet.http.HttpServletResponse;  
30 -import jakarta.validation.Valid;  
31 -  
32 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
33 30
34 @Tag(name = "管理后台 - 文件存储") 31 @Tag(name = "管理后台 - 文件存储")
@@ -42,7 +39,7 @@ public class FileController { @@ -42,7 +39,7 @@ public class FileController {
42 private FileService fileService; 39 private FileService fileService;
43 40
44 @PostMapping("/upload") 41 @PostMapping("/upload")
45 - @Operation(summary = "上传文件") 42 + @Operation(summary = "上传文件", description = "模式一:后端上传文件")
46 @OperateLog(logArgs = false) // 上传文件,没有记录操作日志的必要 43 @OperateLog(logArgs = false) // 上传文件,没有记录操作日志的必要
47 public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception { 44 public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception {
48 MultipartFile file = uploadReqVO.getFile(); 45 MultipartFile file = uploadReqVO.getFile();
@@ -50,6 +47,18 @@ public class FileController { @@ -50,6 +47,18 @@ public class FileController {
50 return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()))); 47 return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
51 } 48 }
52 49
  50 + @GetMapping("/presigned-url")
  51 + @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
  52 + public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("path") String path) throws Exception {
  53 + return success(fileService.getFilePresignedUrl(path));
  54 + }
  55 +
  56 + @PostMapping("/create")
  57 + @Operation(summary = "创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件")
  58 + public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
  59 + return success(fileService.createFile(createReqVO));
  60 + }
  61 +
53 @DeleteMapping("/delete") 62 @DeleteMapping("/delete")
54 @Operation(summary = "删除文件") 63 @Operation(summary = "删除文件")
55 @Parameter(name = "id", description = "编号", required = true) 64 @Parameter(name = "id", description = "编号", required = true)
@@ -62,7 +71,7 @@ public class FileController { @@ -62,7 +71,7 @@ public class FileController {
62 @GetMapping("/{configId}/get/**") 71 @GetMapping("/{configId}/get/**")
63 @PermitAll 72 @PermitAll
64 @Operation(summary = "下载文件") 73 @Operation(summary = "下载文件")
65 - @Parameter(name = "configId", description = "配置编号", required = true) 74 + @Parameter(name = "configId", description = "配置编号", required = true)
66 public void getFileContent(HttpServletRequest request, 75 public void getFileContent(HttpServletRequest request,
67 HttpServletResponse response, 76 HttpServletResponse response,
68 @PathVariable("configId") Long configId) throws Exception { 77 @PathVariable("configId") Long configId) throws Exception {
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 文件创建 Request VO")
  8 +@Data
  9 +public class FileCreateReqVO {
  10 +
  11 + @NotNull(message = "文件配置编号不能为空")
  12 + @Schema(description = "文件配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
  13 + private Long configId;
  14 +
  15 + @NotNull(message = "文件路径不能为空")
  16 + @Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg")
  17 + private String path;
  18 +
  19 + @NotNull(message = "原文件名不能为空")
  20 + @Schema(description = "原文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg")
  21 + private String name;
  22 +
  23 + @NotNull(message = "文件 URL不能为空")
  24 + @Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
  25 + private String url;
  26 +
  27 + @Schema(description = "文件 MIME 类型", example = "application/octet-stream")
  28 + private String type;
  29 +
  30 + @Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED)
  31 + private Integer size;
  32 +
  33 +}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +@AllArgsConstructor
  9 +@NoArgsConstructor
  10 +@Schema(description = "管理后台 - 文件预签名地址 Response VO")
  11 +@Data
  12 +public class FilePresignedUrlRespVO {
  13 +
  14 + @Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
  15 + private Long configId;
  16 +
  17 + @Schema(description = "文件上传 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://s3.cn-south-1.qiniucs.com/ruoyi-vue-pro/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS%2F20240217%2Fcn-south-1%2Fs3%2Faws4_request&X-Amz-Date=20240217T123222Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host&X-Amz-Signature=a29f33770ab79bf523ccd4034d0752ac545f3c2a3b17baa1eb4e280cfdccfda5")
  18 + private String uploadUrl;
  19 +
  20 + /**
  21 + * 为什么要返回 url 字段?
  22 + *
  23 + * 前端上传完文件后,需要使用该 URL 进行访问
  24 + */
  25 + @Schema(description = "文件访问 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  26 + example = "https://test.yudao.iocoder.cn/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png")
  27 + private String url;
  28 +
  29 +}
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java
1 package cn.iocoder.yudao.module.infra.service.file; 1 package cn.iocoder.yudao.module.infra.service.file;
2 2
3 -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
  5 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
  6 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
5 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; 7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
6 8
7 /** 9 /**
@@ -22,14 +24,22 @@ public interface FileService { @@ -22,14 +24,22 @@ public interface FileService {
22 /** 24 /**
23 * 保存文件,并返回文件的访问路径 25 * 保存文件,并返回文件的访问路径
24 * 26 *
25 - * @param name 文件名称  
26 - * @param path 文件路径 27 + * @param name 文件名称
  28 + * @param path 文件路径
27 * @param content 文件内容 29 * @param content 文件内容
28 * @return 文件路径 30 * @return 文件路径
29 */ 31 */
30 String createFile(String name, String path, byte[] content); 32 String createFile(String name, String path, byte[] content);
31 33
32 /** 34 /**
  35 + * 创建文件
  36 + *
  37 + * @param createReqVO 创建信息
  38 + * @return 编号
  39 + */
  40 + Long createFile(FileCreateReqVO createReqVO);
  41 +
  42 + /**
33 * 删除文件 43 * 删除文件
34 * 44 *
35 * @param id 编号 45 * @param id 编号
@@ -40,9 +50,17 @@ public interface FileService { @@ -40,9 +50,17 @@ public interface FileService {
40 * 获得文件内容 50 * 获得文件内容
41 * 51 *
42 * @param configId 配置编号 52 * @param configId 配置编号
43 - * @param path 文件路径 53 + * @param path 文件路径
44 * @return 文件内容 54 * @return 文件内容
45 */ 55 */
46 byte[] getFileContent(Long configId, String path) throws Exception; 56 byte[] getFileContent(Long configId, String path) throws Exception;
47 57
  58 + /**
  59 + * 生成文件预签名地址信息
  60 + *
  61 + * @param path 文件路径
  62 + * @return 预签名地址信息
  63 + */
  64 + FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception;
  65 +
48 } 66 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
@@ -4,16 +4,19 @@ import cn.hutool.core.lang.Assert; @@ -4,16 +4,19 @@ import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.io.FileUtils; 6 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.framework.file.core.client.FileClient; 8 import cn.iocoder.yudao.framework.file.core.client.FileClient;
  9 +import cn.iocoder.yudao.framework.file.core.client.s3.FilePresignedUrlRespDTO;
8 import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils; 10 import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils;
  11 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
9 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; 12 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
  13 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
10 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; 14 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
11 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; 15 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
  16 +import jakarta.annotation.Resource;
12 import lombok.SneakyThrows; 17 import lombok.SneakyThrows;
13 import org.springframework.stereotype.Service; 18 import org.springframework.stereotype.Service;
14 19
15 -import jakarta.annotation.Resource;  
16 -  
17 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
18 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS; 21 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
19 22
@@ -67,6 +70,13 @@ public class FileServiceImpl implements FileService { @@ -67,6 +70,13 @@ public class FileServiceImpl implements FileService {
67 } 70 }
68 71
69 @Override 72 @Override
  73 + public Long createFile(FileCreateReqVO createReqVO) {
  74 + FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);
  75 + fileMapper.insert(file);
  76 + return file.getId();
  77 + }
  78 +
  79 + @Override
70 public void deleteFile(Long id) throws Exception { 80 public void deleteFile(Long id) throws Exception {
71 // 校验存在 81 // 校验存在
72 FileDO file = validateFileExists(id); 82 FileDO file = validateFileExists(id);
@@ -95,4 +105,12 @@ public class FileServiceImpl implements FileService { @@ -95,4 +105,12 @@ public class FileServiceImpl implements FileService {
95 return client.getContent(path); 105 return client.getContent(path);
96 } 106 }
97 107
  108 + @Override
  109 + public FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception {
  110 + FileClient fileClient = fileConfigService.getMasterFileClient();
  111 + FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path);
  112 + return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class,
  113 + object -> object.setConfigId(fileClient.getId()));
  114 + }
  115 +
98 } 116 }
yudao-server/pom.xml
@@ -88,11 +88,11 @@ @@ -88,11 +88,11 @@
88 <!-- </dependency>--> 88 <!-- </dependency>-->
89 89
90 <!-- CRM 相关模块。默认注释,保证编译速度 --> 90 <!-- CRM 相关模块。默认注释,保证编译速度 -->
91 - <dependency>  
92 - <groupId>cn.iocoder.boot</groupId>  
93 - <artifactId>yudao-module-crm-biz</artifactId>  
94 - <version>${revision}</version>  
95 - </dependency> 91 +<!-- <dependency>-->
  92 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  93 +<!-- <artifactId>yudao-module-crm-biz</artifactId>-->
  94 +<!-- <version>${revision}</version>-->
  95 +<!-- </dependency>-->
96 96
97 <!-- ERP 相关模块。默认注释,保证编译速度 --> 97 <!-- ERP 相关模块。默认注释,保证编译速度 -->
98 <dependency> 98 <dependency>
yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java
@@ -35,6 +35,12 @@ public class DefaultController { @@ -35,6 +35,12 @@ public class DefaultController {
35 "[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]"); 35 "[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
36 } 36 }
37 37
  38 + @RequestMapping("/admin-api/erp/**")
  39 + public CommonResult<Boolean> erp404() {
  40 + return CommonResult.error(NOT_IMPLEMENTED.getCode(),
  41 + "[ERP 模块 yudao-module-erp - 已禁用][参考 https://doc.iocoder.cn/erp/build/ 开启]");
  42 + }
  43 +
38 @RequestMapping(value = {"/admin-api/report/**"}) 44 @RequestMapping(value = {"/admin-api/report/**"})
39 public CommonResult<Boolean> report404() { 45 public CommonResult<Boolean> report404() {
40 return CommonResult.error(NOT_IMPLEMENTED.getCode(), 46 return CommonResult.error(NOT_IMPLEMENTED.getCode(),