diff --git a/.image/common/erp-feature.png b/.image/common/erp-feature.png
new file mode 100644
index 000000000..d30b30eed
Binary files /dev/null and b/.image/common/erp-feature.png differ
diff --git a/README.md b/README.md
index 99aa50650..1ec492f83 100644
--- a/README.md
+++ b/README.md
@@ -128,6 +128,8 @@
* 数据报表
* 商城系统
* 微信公众号
+* ERP 系统
+* CRM 系统
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
>
@@ -247,23 +249,31 @@
| 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 |
| 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 |
+### ERP 系统
+
+
+
+演示地址:
+
## 🐨 技术栈
### 模块
-| 项目 | 说明 |
-|--------------------------------------------------------------------------|--------------------|
-| `yudao-dependencies` | Maven 依赖版本管理 |
-| `yudao-framework` | Java 框架拓展 |
-| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
-| `yudao-module-system` | 系统功能的 Module 模块 |
-| `yudao-module-member` | 会员中心的 Module 模块 |
-| `yudao-module-infra` | 基础设施的 Module 模块 |
-| `yudao-module-bpm` | 工作流程的 Module 模块 |
-| `yudao-module-pay` | 支付系统的 Module 模块 |
-| `yudao-module-mall` | 商城系统的 Module 模块 |
-| `yudao-module-mp` | 微信公众号的 Module 模块 |
-| `yudao-module-report` | 大屏报表 Module 模块 |
+| 项目 | 说明 |
+|-----------------------|--------------------|
+| `yudao-dependencies` | Maven 依赖版本管理 |
+| `yudao-framework` | Java 框架拓展 |
+| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
+| `yudao-module-system` | 系统功能的 Module 模块 |
+| `yudao-module-member` | 会员中心的 Module 模块 |
+| `yudao-module-infra` | 基础设施的 Module 模块 |
+| `yudao-module-bpm` | 工作流程的 Module 模块 |
+| `yudao-module-pay` | 支付系统的 Module 模块 |
+| `yudao-module-mall` | 商城系统的 Module 模块 |
+| `yudao-module-erp` | ERP 系统的 Module 模块 |
+| `yudao-module-crm` | CRM 系统的 Module 模块 |
+| `yudao-module-mp` | 微信公众号的 Module 模块 |
+| `yudao-module-report` | 大屏报表 Module 模块 |
### 框架
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java
index fb576d508..2944ca72c 100644
--- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java
@@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.file.core.client;
+import cn.iocoder.yudao.framework.file.core.client.s3.FilePresignedUrlRespDTO;
+
/**
* 文件客户端
*
@@ -18,11 +20,11 @@ public interface FileClient {
* 上传文件
*
* @param content 文件流
- * @param path 相对路径
+ * @param path 相对路径
* @return 完整路径,即 HTTP 访问地址
* @throws Exception 上传文件时,抛出 Exception 异常
*/
- String upload(byte[] content, String path, String type) throws Exception;
+ String upload(byte[] content, String path, String type) throws Exception;
/**
* 删除文件
@@ -40,4 +42,14 @@ public interface FileClient {
*/
byte[] getContent(String path) throws Exception;
+ /**
+ * 获得文件预签名地址
+ *
+ * @param path 相对路径
+ * @return 文件预签名地址
+ */
+ default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
+ throw new UnsupportedOperationException("不支持的操作");
+ }
+
}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/FilePresignedUrlRespDTO.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/FilePresignedUrlRespDTO.java
new file mode 100644
index 000000000..6048494ed
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/FilePresignedUrlRespDTO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.framework.file.core.client.s3;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文件预签名地址 Response DTO
+ *
+ * @author owen
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class FilePresignedUrlRespDTO {
+
+ /**
+ * 文件上传 URL(用于上传)
+ *
+ * 例如说:
+ */
+ private String uploadUrl;
+
+ /**
+ * 文件 URL(用于读取、下载等)
+ */
+ private String url;
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java
index 49238f8f9..e7b470bad 100644
--- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java
+++ b/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;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient;
import io.minio.*;
+import io.minio.http.Method;
import java.io.ByteArrayInputStream;
+import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT;
@@ -117,4 +119,16 @@ public class S3FileClient extends AbstractFileClient {
return IoUtil.readBytes(response);
}
+ @Override
+ public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
+ String uploadUrl = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
+ .method(Method.PUT)
+ .bucket(config.getBucket())
+ .object(path)
+ .expiry(10, TimeUnit.MINUTES) // 过期时间(秒数)取值范围:1 秒 ~ 7 天
+ .build()
+ );
+ return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path);
+ }
+
}
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
index 19aaf046c..028ac8d71 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
+++ b/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 {
if (isNotPresent("cn.iocoder.yudao.module.trade.framework.web.config.TradeWebConfiguration")) {
System.out.println("[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
}
+ // ERP 系统
+ if (isNotPresent("cn.iocoder.yudao.module.erp.framework.web.config.ErpWebConfiguration")) {
+ System.out.println("[ERP 系统 yudao-module-erp - 已禁用][参考 https://doc.iocoder.cn/erp/build/ 开启]");
+ }
// 支付平台
if (isNotPresent("cn.iocoder.yudao.module.pay.framework.pay.config.PayConfiguration")) {
System.out.println("[支付系统 yudao-module-pay - 已禁用][参考 https://doc.iocoder.cn/pay/build/ 开启]");
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
index c26628b9a..c3a8f0c62 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
+++ b/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 {
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
}
- // 5. 支付平台
+ // 5. ERP 系统
+ if (message.contains("erp_")) {
+ log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(),
+ "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
+ }
+ // 6. 支付平台
if (message.contains("pay_")) {
log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
index d22e87bed..98a66d2c9 100644
--- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
+++ b/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 {
String CRM_CONTRACT_DELETE_SUCCESS = "删除了合同【{{#contractName}}】";
String CRM_CONTRACT_TRANSFER_SUB_TYPE = "转移合同";
String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
+ String CRM_CONTRACT_SUBMIT_SUB_TYPE = "提交合同审批";
+ String CRM_CONTRACT_SUBMIT_SUCCESS = "提交合同【{{#contractName}}】审批成功";
// ======================= CRM_PRODUCT 产品 =======================
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java
index a188232ef..21463aed0 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java
+++ b/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 {
return success(rankingService.getReceivablePriceRank(rankingReqVO));
}
+ @GetMapping("/get-contract-count-rank")
+ @Operation(summary = "获得签约合同数量排行榜")
+ @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
+ public CommonResult> getContractCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+ return success(rankingService.getContractCountRank(rankingReqVO));
+ }
+
+ @GetMapping("/get-product-sales-rank")
+ @Operation(summary = "获得产品销量排行榜")
+ @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
+ public CommonResult> getProductSalesRank(@Valid CrmBiRankReqVO rankingReqVO) {
+ return success(rankingService.getProductSalesRank(rankingReqVO));
+ }
+
+ @GetMapping("/get-customer-count-rank")
+ @Operation(summary = "获得新增客户数排行榜")
+ @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
+ public CommonResult> getCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+ return success(rankingService.getCustomerCountRank(rankingReqVO));
+ }
+
+ @GetMapping("/get-contacts-count-rank")
+ @Operation(summary = "获得新增联系人数排行榜")
+ @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
+ public CommonResult> getContactsCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+ return success(rankingService.getContactsCountRank(rankingReqVO));
+ }
+
+ @GetMapping("/get-follow-count-rank")
+ @Operation(summary = "获得跟进次数排行榜")
+ @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
+ public CommonResult> getFollowCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+ return success(rankingService.getFollowCountRank(rankingReqVO));
+ }
+
+ @GetMapping("/get-follow-customer-count-rank")
+ @Operation(summary = "获得跟进客户数排行榜")
+ @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
+ public CommonResult> getFollowCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+ return success(rankingService.getFollowCustomerCountRank(rankingReqVO));
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java
index 925fa4953..337590044 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java
+++ b/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
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;
-import cn.iocoder.yudao.module.crm.convert.businessstatus.CrmBusinessStatusConvert;
-import cn.iocoder.yudao.module.crm.convert.businessstatustype.CrmBusinessStatusTypeConvert;
+import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusConvert;
+import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusTypeConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
index 672450c4a..0be6264eb 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
@@ -1,18 +1,18 @@
package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;
import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus;
import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction;
import com.mzt.logapi.starter.annotation.DiffLogField;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
import lombok.Data;
+import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
-import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -58,7 +58,6 @@ public class CrmBusinessSaveReqVO {
@DiffLogField(name = "商机金额")
private Integer price;
- // TODO @lzxhqs:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题
@Schema(description = "整单折扣")
@DiffLogField(name = "整单折扣")
private Integer discountPercent;
@@ -75,11 +74,30 @@ public class CrmBusinessSaveReqVO {
@InEnum(CrmBizEndStatus.class)
private Integer endStatus;
- // TODO @lzxhqs:不设置默认 new ArrayList<>();一般 pojo 不设置默认值哈
- @Schema(description = "商机产品列表")
- private List products = new ArrayList<>();
-
@Schema(description = "联系人编号", example = "110")
private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段
+ // TODO @puhui999:传递 items 就行啦;
+ @Schema(description = "产品列表")
+ private List productItems;
+
+ @Schema(description = "产品列表")
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class CrmBusinessProductItem {
+
+ @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
+ @NotNull(message = "产品编号不能为空")
+ private Long id;
+
+ @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
+ @NotNull(message = "产品数量不能为空")
+ private Integer count;
+
+ @Schema(description = "产品折扣")
+ private Integer discountPercent;
+
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java
deleted file mode 100644
index 4804768a5..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-// TODO @lzxhqs:这个类,如果没用到,可以考虑删除哈
-@Schema(description = "管理后台 - 商机产品分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmBusinessProductPageReqVO extends PageParam {
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java
deleted file mode 100644
index d4996816f..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;
-
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Schema(description = "管理后台 - 商机产品关联 Response VO")
-@Data
-@ExcelIgnoreUnannotated
-public class CrmBusinessProductRespVO {
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java
deleted file mode 100644
index f28f0f350..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotEmpty;
-import jakarta.validation.constraints.NotNull;
-import lombok.Data;
-
-import java.math.BigDecimal;
-
-@Schema(description = "管理后台 - CRM 商机产品关联表 创建/更新 Request VO")
-@Data
-public class CrmBusinessProductSaveReqVO {
-
- @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
- private Long id;
-
- @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
- @NotNull(message = "商机编号不能为空")
- private Long businessId;
-
- @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
- @NotNull(message = "产品编号不能为空")
- private Long productId;
-
- @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
- @NotNull(message = "产品单价不能为空")
- private BigDecimal price;
-
- @Schema(description = "销售价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
- @NotNull(message = "销售价格不能为空")
- private BigDecimal salesPrice;
-
- @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
- @NotNull(message = "数量不能为空")
- private BigDecimal num;
-
- @Schema(description = "折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
- @NotNull(message = "折扣不能为空")
- private BigDecimal discount;
-
- @Schema(description = "小计(折扣后价格)", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
- @NotNull(message = "小计(折扣后价格)不能为空")
- private BigDecimal subtotal;
-
- @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
- @NotEmpty(message = "单位不能为空")
- private String unit;
-
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
index 8de96587e..c283f4acd 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
+++ b/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;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
-import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
-import cn.iocoder.yudao.module.crm.service.business.CrmBusinessProductService;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
@@ -30,7 +32,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
-import org.springframework.context.annotation.Lazy;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -43,9 +44,9 @@ import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
-import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static java.util.Collections.singletonList;
@Tag(name = "管理后台 - CRM 合同")
@RestController
@@ -62,11 +63,7 @@ public class CrmContractController {
@Resource
private CrmBusinessService businessService;
@Resource
- @Lazy
- private CrmBusinessProductService businessProductService;
- @Resource
private CrmProductService productService;
-
@Resource
private AdminUserApi adminUserApi;
@@ -105,22 +102,9 @@ public class CrmContractController {
return success(null);
}
- // 2.1 拼接合同信息
- List respVOList = buildContractDetailList(Collections.singletonList(contract));
- // 2.2 拼接产品信息
- // TODO @puhui999:下面这块也可以搞到 convert 里哈;可以在 ContractDetailList 加个开关,是不是查询商品信息;ps:jdk21 的方法不太能去用,因为 jdk8 项目要兼容;
- CrmContractRespVO respVO = respVOList.get(0);
- List businessProductList = businessProductService.getBusinessProductListByContractId(id);
- Map businessProductMap = convertMap(businessProductList, CrmBusinessProductDO::getProductId);
- List productList = productService.getProductListByIds(convertSet(businessProductList, CrmBusinessProductDO::getProductId));
- respVO.setProductItems(convertList(productList, product -> {
- CrmContractRespVO.CrmContractProductItemRespVO productItemRespVO = BeanUtils.toBean(product, CrmContractRespVO.CrmContractProductItemRespVO.class);
- findAndThen(businessProductMap, product.getId(), businessProduct -> {
- productItemRespVO.setCount(businessProduct.getCount()).setDiscountPercent(businessProduct.getDiscountPercent());
- });
- return productItemRespVO;
- }));
- return success(respVO);
+ // 2. 拼接合同信息
+ List respVOList = buildContractDetailList(singletonList(contract));
+ return success(respVOList.get(0));
}
@GetMapping("/page")
@@ -147,8 +131,24 @@ public class CrmContractController {
HttpServletResponse response) throws IOException {
PageResult pageResult = contractService.getContractPage(exportReqVO, getLoginUserId());
// 导出 Excel
- ExcelUtils.write(response, "合同.xls", "数据", CrmContractExcelVO.class,
- BeanUtils.toBean(pageResult.getList(), CrmContractExcelVO.class));
+ ExcelUtils.write(response, "合同.xls", "数据", CrmContractRespVO.class,
+ BeanUtils.toBean(pageResult.getList(), CrmContractRespVO.class));
+ }
+
+ @PutMapping("/transfer")
+ @Operation(summary = "合同转移")
+ @PreAuthorize("@ss.hasPermission('crm:contract:update')")
+ public CommonResult transferContract(@Valid @RequestBody CrmContractTransferReqVO reqVO) {
+ contractService.transferContract(reqVO, getLoginUserId());
+ return success(true);
+ }
+
+ @PutMapping("/submit")
+ @Operation(summary = "提交合同审批")
+ @PreAuthorize("@ss.hasPermission('crm:contract:update')")
+ public CommonResult submitContract(@RequestParam("id") Long id) {
+ contractService.submitContract(id, getLoginUserId());
+ return success(true);
}
/**
@@ -173,23 +173,15 @@ public class CrmContractController {
// 4. 获取商机
Map businessMap = convertMap(businessService.getBusinessList(convertSet(contractList,
CrmContractDO::getBusinessId)), CrmBusinessDO::getId);
- return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap);
- }
-
- @PutMapping("/transfer")
- @Operation(summary = "合同转移")
- @PreAuthorize("@ss.hasPermission('crm:contract:update')")
- public CommonResult transferContract(@Valid @RequestBody CrmContractTransferReqVO reqVO) {
- contractService.transferContract(reqVO, getLoginUserId());
- return success(true);
- }
-
- @PutMapping("/submit")
- @Operation(summary = "提交合同审批")
- @PreAuthorize("@ss.hasPermission('crm:contract:update')")
- public CommonResult submitContract(@RequestParam("id") Long id) {
- contractService.submitContract(id, getLoginUserId());
- return success(true);
+ // 5. 获取合同关联的商品
+ Map contractProductMap = null;
+ List productList = null;
+ if (contractList.size() == 1) {
+ List contractProductList = contractService.getContractProductListByContractId(contractList.get(0).getId());
+ contractProductMap = convertMap(contractProductList, CrmContractProductDO::getProductId);
+ productList = productService.getProductListByIds(convertSet(contractProductList, CrmContractProductDO::getProductId));
+ }
+ return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap, contractProductMap, productList);
}
@GetMapping("/check-contract-count")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractExcelVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractExcelVO.java
deleted file mode 100644
index 841e56426..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractExcelVO.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
-
-import com.alibaba.excel.annotation.ExcelProperty;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-// TODO @puhui999:合并到 RespVO 里哈;
-/**
- * CRM 合同 Excel VO
- *
- * @author dhb52
- */
-@Data
-public class CrmContractExcelVO {
-
- @ExcelProperty("合同编号")
- private Long id;
-
- @ExcelProperty("合同名称")
- private String name;
-
- @ExcelProperty("客户编号")
- private Long customerId;
-
- @ExcelProperty("商机编号")
- private Long businessId;
-
- @ExcelProperty("工作流编号")
- private Long processInstanceId;
-
- @ExcelProperty("下单日期")
- private LocalDateTime orderDate;
-
- @ExcelProperty("负责人的用户编号")
- private Long ownerUserId;
-
- @ExcelProperty("合同编号")
- private String no;
-
- @ExcelProperty("开始时间")
- private LocalDateTime startTime;
-
- @ExcelProperty("结束时间")
- private LocalDateTime endTime;
-
- @ExcelProperty("合同金额")
- private Integer price;
-
- @ExcelProperty("整单折扣")
- private Integer discountPercent;
-
- @ExcelProperty("产品总金额")
- private Integer productPrice;
-
- @ExcelProperty("联系人编号")
- private Long contactId;
-
- @ExcelProperty("公司签约人")
- private Long signUserId;
-
- @ExcelProperty("最后跟进时间")
- private LocalDateTime contactLastTime;
-
- @ExcelProperty("备注")
- private String remark;
-
- @ExcelProperty("创建时间")
- private LocalDateTime createTime;
-
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java
index 70529198a..c61a64ccf 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java
+++ b/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;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -14,7 +15,13 @@ import lombok.ToString;
@ToString(callSuper = true)
public class CrmContractPageReqVO extends PageParam {
+ /**
+ * 过期类型 - 即将过期
+ */
public static final Integer EXPIRY_TYPE_ABOUT_TO_EXPIRE = 1;
+ /**
+ * 过期类型 - 已过期
+ */
public static final Integer EXPIRY_TYPE_EXPIRED = 2;
@Schema(description = "合同编号", example = "XYZ008")
@@ -34,6 +41,7 @@ public class CrmContractPageReqVO extends PageParam {
private Integer sceneType; // 场景类型,为 null 时则表示全部
@Schema(description = "审批状态", example = "20")
+ @InEnum(CrmAuditStatusEnum.class)
private Integer auditStatus;
@Schema(description = "过期类型", example = "1")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java
index 6b60afc2a..da6239414 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java
+++ b/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 {
@Schema(description = "产品列表")
private List productItems;
+ // TODO @puhui999:可以直接叫 Item
@Schema(description = "产品列表")
@Data
@NoArgsConstructor
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
index 83e6274a0..51eb7c3e6 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
+++ b/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;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
@@ -31,7 +30,6 @@ import org.mapstruct.ap.internal.util.Collections;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
-import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.LocalDateTime;
@@ -253,19 +251,13 @@ public class CrmCustomerController {
ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list);
}
- // TODO @puhui999:updateSupport 要不改成前端必须传递;哈哈哈,代码排版看着有点乱;
- // TODO @puhui999:加一个选择负责人;允许空,空就进入公海;
@PostMapping("/import")
@Operation(summary = "导入客户")
- @Parameters({
- @Parameter(name = "file", description = "Excel 文件", required = true),
- @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
- })
@PreAuthorize("@ss.hasPermission('system:customer:import')")
- public CommonResult importExcel(@RequestParam("file") MultipartFile file, @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport)
+ public CommonResult importExcel(@Valid @RequestBody CrmCustomerImportReqVO importReqVO)
throws Exception {
- List list = ExcelUtils.read(file, CrmCustomerImportExcelVO.class);
- return success(customerService.importCustomerList(list, updateSupport, getLoginUserId()));
+ List list = ExcelUtils.read(importReqVO.getFile(), CrmCustomerImportExcelVO.class);
+ return success(customerService.importCustomerList(list, importReqVO));
}
@PutMapping("/transfer")
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java
new file mode 100644
index 000000000..a396dc50b
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Builder;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+@Schema(description = "管理后台 - 客户导入 Request VO")
+@Data
+@Builder
+public class CrmCustomerImportReqVO {
+
+ @Schema(description = "Excel 文件", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "Excel 文件不能为空")
+ private MultipartFile file;
+
+ @Schema(description = "是否支持更新", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "是否支持更新不能为空")
+ private Boolean updateSupport;
+
+ @Schema(description = "负责人", example = "1")
+ private Long ownerUserId; // 为 null 则客户进入公海
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java
index a041fa030..3675fba1f 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java
+++ b/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;
@ToString(callSuper = true)
public class CrmReceivablePlanPageReqVO extends PageParam {
- // 待回款
- public final static Integer REMIND_NEEDED = 1;
- // 已逾期
- public final static Integer REMIND_EXPIRED = 2;
- // 已回款
- public final static Integer REMIND_RECEIVED = 3;
+ /**
+ * 提醒类型 - 待回款
+ */
+ public final static Integer REMIND_TYPE_NEEDED = 1;
+ /**
+ * 提醒类型 - 已逾期
+ */
+ public final static Integer REMIND_TYPE_EXPIRED = 2;
+ /**
+ * 提醒类型 - 已回款
+ */
+ public final static Integer REMIND_TYPE_RECEIVED = 3;
@Schema(description = "客户编号", example = "18026")
private Long customerId;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java
index d47ca83f0..e1fe83087 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java
+++ b/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;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -28,6 +29,7 @@ public class CrmReceivablePageReqVO extends PageParam {
private Integer sceneType; // 场景类型,为 null 时则表示全部
@Schema(description = "审批状态", example = "20")
+ @InEnum(CrmAuditStatusEnum.class)
private Integer auditStatus;
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java
similarity index 92%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java
index df2532b27..52186e3d9 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.convert.businessstatus;
+package cn.iocoder.yudao.module.crm.convert.business;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java
similarity index 96%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java
index be203b580..4876fb537 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.convert.businessstatustype;
+package cn.iocoder.yudao.module.crm.convert.business;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java
deleted file mode 100644
index 2fcd54d84..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.crm.convert.businessproduct;
-
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-// TODO @lzxhqs:看看是不是用 BeanUtils 替代了
-/**
- * @author lzxhqs
- * @version 1.0
- * @title CrmBusinessProductConvert
- * @description
- * @create 2024/1/12
- */
-@Mapper
-public interface CrmBusinessProductConvert {
- CrmBusinessProductConvert INSTANCE = Mappers.getMapper(CrmBusinessProductConvert.class);
-
- CrmBusinessProductDO convert(CrmBusinessProductSaveReqVO product);
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
index c9247e6a5..0d2e49934 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
@@ -1,12 +1,16 @@
package cn.iocoder.yudao.module.crm.convert.contract;
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.mapstruct.Mapper;
@@ -34,7 +38,8 @@ public interface CrmContractConvert {
default List convertList(List contractList, Map userMap,
List customerList, Map contactMap,
- Map businessMap) {
+ Map businessMap, Map contractProductMap,
+ List productList) {
List respVOList = BeanUtils.toBean(contractList, CrmContractRespVO.class);
// 拼接关联字段
Map customerMap = convertMap(customerList, CrmCustomerDO::getId);
@@ -46,7 +51,20 @@ public interface CrmContractConvert {
findAndThen(contactMap, contract.getContactId(), contact -> contract.setContactName(contact.getName()));
findAndThen(businessMap, contract.getBusinessId(), business -> contract.setBusinessName(business.getName()));
});
+ if (CollUtil.isNotEmpty(respVOList) && respVOList.size() == 1) {
+ setContractRespVOProductItems(respVOList.get(0), contractProductMap, productList);
+ }
return respVOList;
}
+ default void setContractRespVOProductItems(CrmContractRespVO respVO, Map contractProductMap,
+ List productList) {
+ respVO.setProductItems(CollectionUtils.convertList(productList, product -> {
+ CrmContractRespVO.CrmContractProductItemRespVO productItemRespVO = BeanUtils.toBean(product, CrmContractRespVO.CrmContractProductItemRespVO.class);
+ findAndThen(contractProductMap, product.getId(), contractProduct ->
+ productItemRespVO.setCount(contractProduct.getCount()).setDiscountPercent(contractProduct.getDiscountPercent()));
+ return productItemRespVO;
+ }));
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
index 77c5ba779..79d6a2a7b 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
@@ -1,9 +1,7 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.business;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
-import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -41,19 +39,10 @@ public class CrmBusinessProductDO extends BaseDO {
* 关联 {@link CrmProductDO#getId()}
*/
private Long productId;
- // TODO 芋艿:需要在看下 CRM
- /**
- * 合同编号
- *
- * 关联 {@link CrmContractDO#getId()}
- */
- private Long contractId;
-
/**
* 产品单价
*/
private Integer price;
-
/**
* 销售价格, 单位:分
*/
@@ -71,11 +60,4 @@ public class CrmBusinessProductDO extends BaseDO {
*/
private Integer totalPrice;
- /**
- * 单位
- *
- * 字典 {@link DictTypeConstants#CRM_PRODUCT_UNIT}
- */
- private Integer unit;
-
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java
new file mode 100644
index 000000000..bc977c78f
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 合同产品关联表 DO
+ *
+ * @author HUIHUI
+ */
+@TableName("crm_contract_product")
+@KeySequence("crm_contract_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmContractProductDO extends BaseDO {
+
+ /**
+ * 主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 产品编号
+ *
+ * 关联 {@link CrmProductDO#getId()}
+ */
+ private Long productId;
+ /**
+ * 合同编号
+ *
+ * 关联 {@link CrmContractDO#getId()}
+ */
+ private Long contractId;
+ /**
+ * 产品单价
+ */
+ private Integer price;
+ /**
+ * 销售价格, 单位:分
+ */
+ private Integer salesPrice;
+ /**
+ * 数量
+ */
+ private Integer count;
+ /**
+ * 折扣
+ */
+ private Integer discountPercent;
+ /**
+ * 总计价格(折扣后价格)
+ *
+ * TODO @puhui999:可以写下计算公式哈;
+ */
+ private Integer totalPrice;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/package-info.java
deleted file mode 100644
index a981b5dfc..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * 合同
- */
-package cn.iocoder.yudao.module.crm.dal.dataobject.contract;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java
index 91b7a191b..9b71df7b6 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java
+++ b/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 {
*/
List selectReceivablePriceRank(CrmBiRankReqVO rankReqVO);
+ /**
+ * 查询签约合同数量排行榜
+ *
+ * @param rankReqVO 参数
+ * @return 签约合同数量排行榜
+ */
+ List selectContractCountRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 查询产品销量排行榜
+ *
+ * @param rankReqVO 参数
+ * @return 产品销量排行榜
+ */
+ List selectProductSalesRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 查询新增客户数排行榜
+ *
+ * @param rankReqVO 参数
+ * @return 新增客户数排行榜
+ */
+ List selectCustomerCountRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 查询联系人数量排行榜
+ *
+ * @param rankReqVO 参数
+ * @return 联系人数量排行榜
+ */
+ List selectContactsCountRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 查询跟进次数排行榜
+ *
+ * @param rankReqVO 参数
+ * @return 跟进次数排行榜
+ */
+ List selectFollowCountRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 查询跟进客户数排行榜
+ *
+ * @param rankReqVO 参数
+ * @return 跟进客户数排行榜
+ */
+ List selectFollowCustomerCountRank(CrmBiRankReqVO rankReqVO);
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java
index 1730067fe..2d1471c73 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java
+++ b/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;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
/**
* 商机产品 Mapper
*
@@ -13,12 +16,18 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CrmBusinessProductMapper extends BaseMapperX {
- default void deleteByBusinessId(Long id) { // TODO @lzxhqs:第一个方法,和类之间最好空一行;
- delete(CrmBusinessProductDO::getBusinessId, id);
+ // TODO @puhui999:用不到的方法,看看是不是删除哈
+ default void deleteByBusinessId(Long getBusinessId) { // TODO @lzxhqs:第一个方法,和类之间最好空一行;
+ delete(CrmBusinessProductDO::getBusinessId, getBusinessId);
}
- default CrmBusinessProductDO selectByBusinessId(Long id) {
- return selectOne(CrmBusinessProductDO::getBusinessId, id);
+ default CrmBusinessProductDO selectByBusinessId(Long getBusinessId) {
+ return selectOne(CrmBusinessProductDO::getBusinessId, getBusinessId);
+ }
+
+ default List selectListByBusinessId(Long businessId) {
+ // TODO @puhui999:可以简化,selectList(CrmBusinessProductDO::getBusinessId, businessId)
+ return selectList(new LambdaQueryWrapperX().eq(CrmBusinessProductDO::getBusinessId, businessId));
}
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
index 3f27400d1..7b4a7712e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
+++ b/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 {
// Backlog: 即将到期的合同
LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
- if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) {
- // 即将到期
+ if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) { // 即将到期
// TODO: @芋艿 需要配置 提前提醒天数
int REMIND_DAYS = 20;
query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus())
.between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(REMIND_DAYS));
- } else if (CrmContractPageReqVO.EXPIRY_TYPE_EXPIRED.equals(pageReqVO.getExpiryType())) {
- // 已到期
+ } else if (CrmContractPageReqVO.EXPIRY_TYPE_EXPIRED.equals(pageReqVO.getExpiryType())) { // 已到期
query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus())
.lt(CrmContractDO::getEndTime, endOfToday);
}
-
return selectJoinPage(pageReqVO, CrmContractDO.class, query);
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java
new file mode 100644
index 000000000..814024125
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.contract;
+
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 合同产品 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface CrmContractProductMapper extends BaseMapperX {
+
+ // TODO @puhui999:用不到的方法,看看是不是删除哈
+ default void deleteByContractId(Long contractId) { // TODO @lzxhqs:第一个方法,和类之间最好空一行;
+ delete(CrmContractProductDO::getContractId, contractId);
+ }
+
+ default CrmContractProductDO selectByContractId(Long contractId) {
+ return selectOne(CrmContractProductDO::getContractId, contractId);
+ }
+
+ default List selectListByContractId(Long contractId) {
+ return selectList(new LambdaQueryWrapperX().eq(CrmContractProductDO::getContractId, contractId));
+ }
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java
index 8a7fa600b..30a07eec2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java
+++ b/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;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
@@ -10,9 +9,6 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
import org.apache.ibatis.annotations.Mapper;
-import java.util.Collection;
-import java.util.List;
-
/**
* CRM 产品 Mapper
*
@@ -38,9 +34,4 @@ public interface CrmProductMapper extends BaseMapperX {
return selectOne(CrmProductDO::getNo, no);
}
- // TODO @puhui999:selectBatchIds
- default List selectListByIds(Collection ids) {
- return selectList(new LambdaQueryWrapperX().in(CrmProductDO::getId, ids));
- }
-
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
index 780d611cb..14c0a4ed9 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
+++ b/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 getReceivablePriceRank(CrmBiRankReqVO rankReqVO);
+ /**
+ * 获得签约合同数量排行榜
+ *
+ * @param rankReqVO 排行参数
+ * @return 签约合同数量排行榜
+ */
+ List getContractCountRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 获得产品销量排行榜
+ *
+ * @param rankReqVO 排行参数
+ * @return 产品销量排行榜
+ */
+ List getProductSalesRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 获得新增客户数排行榜
+ *
+ * @param rankReqVO 排行参数
+ * @return 新增客户数排行榜
+ */
+ List getCustomerCountRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 获得联系人数量排行榜
+ *
+ * @param rankReqVO 排行参数
+ * @return 联系人数量排行榜
+ */
+ List getContactsCountRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 获得跟进次数排行榜
+ *
+ * @param rankReqVO 排行参数
+ * @return 跟进次数排行榜
+ */
+ List getFollowCountRank(CrmBiRankReqVO rankReqVO);
+
+ /**
+ * 获得跟进客户数排行榜
+ *
+ * @param rankReqVO 排行参数
+ * @return 跟进客户数排行榜
+ */
+ List getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO);
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java
index 84f47ddc9..60e1b4ecb 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java
+++ b/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 {
return getRank(rankReqVO, biRankingMapper::selectReceivablePriceRank);
}
+ @Override
+ public List getContractCountRank(CrmBiRankReqVO rankReqVO) {
+ return getRank(rankReqVO, biRankingMapper::selectContractCountRank);
+ }
+
+ @Override
+ public List getProductSalesRank(CrmBiRankReqVO rankReqVO) {
+ return getRank(rankReqVO, biRankingMapper::selectProductSalesRank);
+ }
+
+ @Override
+ public List getCustomerCountRank(CrmBiRankReqVO rankReqVO) {
+ return getRank(rankReqVO, biRankingMapper::selectCustomerCountRank);
+ }
+
+ @Override
+ public List getContactsCountRank(CrmBiRankReqVO rankReqVO) {
+ return getRank(rankReqVO, biRankingMapper::selectContactsCountRank);
+ }
+
+ @Override
+ public List getFollowCountRank(CrmBiRankReqVO rankReqVO) {
+ return getRank(rankReqVO, biRankingMapper::selectFollowCountRank);
+ }
+
+ @Override
+ public List getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO) {
+ return getRank(rankReqVO, biRankingMapper::selectFollowCustomerCountRank);
+ }
+
/**
* 获得排行版数据
*
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductService.java
deleted file mode 100644
index a68ac37b9..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductService.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package cn.iocoder.yudao.module.crm.service.business;
-
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
-
-import java.util.List;
-
-/**
- * 商机产品关联表 Service 接口
- *
- * @author lzxhqs
- */
-public interface CrmBusinessProductService {
-
- /**
- * 批量新增商机产品关联数据
- *
- * @param list 商机产品集合
- */
- void createBusinessProductBatch(List list);
-
- /**
- * 批量更新商机产品表
- *
- * @param list 商机产品数据集合
- */
- void updateBusinessProductBatch(List list);
-
- /**
- * 批量删除
- *
- * @param list 需要删除的商机产品集合
- */
- void deleteBusinessProductBatch(List list);
-
- /**
- * 根据商机编号,删除商机产品关联数据
- *
- * @param businessId 商机id
- */
- void deleteBusinessProductByBusinessId(Long businessId);
-
- /**
- * 根据商机编号,获取商机产品关联数据集合
- *
- * @param businessId 商机编号
- */
- List getBusinessProductListByBusinessId(Long businessId);
-
- /**
- * 根据合同编号,获得合同关联的商品列表
- *
- * @param contractId 合同编号
- * @return 关联的商品列表
- */
- List getBusinessProductListByContractId(Long contractId);
-
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductServiceImpl.java
deleted file mode 100644
index 0762d4555..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductServiceImpl.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package cn.iocoder.yudao.module.crm.service.business;
-
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
-import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper;
-import jakarta.annotation.Resource;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import java.util.List;
-
-/**
- * 商机产品关联表 Service 实现类
- *
- * @author lzxhqs
- */
-@Service
-@Validated
-public class CrmBusinessProductServiceImpl implements CrmBusinessProductService {
-
- @Resource
- private CrmBusinessProductMapper businessProductMapper;
-
- @Override
- public void createBusinessProductBatch(List list) {
- businessProductMapper.insertBatch(list);
- }
-
- @Override
- public void updateBusinessProductBatch(List list) {
- businessProductMapper.updateBatch(list);
- }
-
- // TODO @puhui999:这个方法,可以直接调用 deleteList 方法,然后传递 ids 就好了;
- @Override
- public void deleteBusinessProductBatch(List list) {
- businessProductMapper.deleteBatchIds(CollectionUtils.convertList(list, CrmBusinessProductDO::getId));
- }
-
- @Override
- public void deleteBusinessProductByBusinessId(Long businessId) {
- businessProductMapper.deleteByBusinessId(businessId);
- }
-
- @Override
- public List getBusinessProductListByContractId(Long contractId) {
- return businessProductMapper.selectList(CrmBusinessProductDO::getContractId, contractId);
- }
-
- @Override
- public List getBusinessProductListByBusinessId(Long businessId) {
- return businessProductMapper.selectList(CrmBusinessProductDO::getBusinessId, businessId);
- }
-
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
index fde5551c1..683070d02 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
+++ b/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
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
import jakarta.validation.Valid;
@@ -50,6 +51,21 @@ public interface CrmBusinessService {
*/
void deleteBusiness(Long id);
+ /**
+ * 商机转移
+ *
+ * @param reqVO 请求
+ * @param userId 用户编号
+ */
+ void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId);
+
+ /**
+ * 更新商机关联商品
+ *
+ * @param updateProductReqBO 请求
+ */
+ void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO);
+
/**
* 获得商机
*
@@ -105,14 +121,6 @@ public interface CrmBusinessService {
*/
PageResult getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO);
- /**
- * 商机转移
- *
- * @param reqVO 请求
- * @param userId 用户编号
- */
- void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId);
-
/**
* 获取关联客户的商机数量
*
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
index df7348cfe..535578fd2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
+++ b/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;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;
import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
-import cn.iocoder.yudao.module.crm.convert.businessproduct.CrmBusinessProductConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
+import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
+import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
@@ -35,11 +38,12 @@ import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_CONTRACT_EXISTS;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
/**
@@ -53,9 +57,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
@Resource
private CrmBusinessMapper businessMapper;
-
@Resource
- private CrmBusinessProductService businessProductService;
+ private CrmBusinessProductMapper businessProductMapper;
+
@Resource
@Lazy // 延迟加载,避免循环依赖
private CrmContractService contractService;
@@ -63,6 +67,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
private CrmPermissionService permissionService;
@Resource
private CrmContactBusinessService contactBusinessService;
+ @Resource
+ private CrmProductService productService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -71,14 +77,17 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
createReqVO.setId(null);
// 1. 插入商机
- CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class)
- .setOwnerUserId(userId);
+ CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class).setOwnerUserId(userId);
businessMapper.insert(business);
- // TODO 商机待定:插入商机与产品的关联表;校验商品存在
- if (CollUtil.isNotEmpty(createReqVO.getProducts())) {
- createBusinessProducts(createReqVO.getProducts(), business.getId(), false);
+ // 1.2 插入商机关联商品
+ if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话
+ List productList = buildBusinessProductList(createReqVO.getProductItems(), business.getId());
+ businessProductMapper.insertBatch(productList);
+ // 更新合同商品总金额
+ businessMapper.updateById(new CrmBusinessDO().setId(business.getId()).setProductPrice(
+ getSumValue(productList, CrmBusinessProductDO::getTotalPrice, Integer::sum)));
}
- // TODO 商机待定:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表
+ // TODO @puhui999:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表
createContactBusiness(business.getId(), createReqVO.getContactId());
// 2. 创建数据权限
@@ -86,19 +95,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
.setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
- // 4. 记录操作日志上下文
+ // 3. 记录操作日志上下文
LogRecordContext.putVariable("business", business);
return business.getId();
}
// TODO @lzxhqs:CrmContactBusinessService 调用这个;这样逻辑才能收敛哈;
- /**
- * @param businessId 商机id
- * @param contactId 联系人id
- * @throws
- * @description 联系人与商机的关联
- * @author lzxhqs
- */
private void createContactBusiness(Long businessId, Long contactId) {
CrmContactBusinessDO contactBusiness = new CrmContactBusinessDO();
contactBusiness.setBusinessId(businessId);
@@ -106,37 +108,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
contactBusinessService.insert(contactBusiness);
}
- // TODO @lzxhqs:这个方法注释格式不对;删除@description,然后把 插入商机产品关联表 作为方法注释;
- /**
- * 插入商机产品关联表
- *
- * @param products 产品集合
- * @param businessId 商机id
- * @param updateFlag 更新标识 true 代表更新
- * @author lzxhqs
- */
- private void createBusinessProducts(List products, Long businessId, Boolean updateFlag) {
- List list = CollectionUtils.convertList(products, product ->
- CrmBusinessProductConvert.INSTANCE.convert(product).setBusinessId(businessId));
- if (Boolean.TRUE.equals(updateFlag)) {
-// 根据商机 id从商机产品关联表中获取已存在的数据集合
- List oldProducts = businessProductService.getBusinessProductListByBusinessId(businessId);
- List> diffList = CollectionUtils.diffList(oldProducts, list, (oldValue, newValue) ->
- ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId()));
- if (CollUtil.isNotEmpty(diffList.getFirst())) {
- businessProductService.createBusinessProductBatch(diffList.getFirst());
- }
- if (CollUtil.isNotEmpty(diffList.get(1))) {
- businessProductService.updateBusinessProductBatch(diffList.get(1));
- }
- if (CollUtil.isNotEmpty(diffList.get(2))) {
- businessProductService.deleteBusinessProductBatch(diffList.get(2));
- }
- } else {
- businessProductService.createBusinessProductBatch(list);
- }
- }
-
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
@@ -146,16 +117,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
// 1. 校验存在
CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
- // 2. 更新商机
+ // 2.1 更新商机
CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class);
businessMapper.updateById(updateObj);
- // TODO 商机待定:插入商机与产品的关联表;校验商品存在
- // TODO @lzxhqs:createBusinessProducts 可以抽成两个方法,一个新增;一个修改,修改需要把 businessProductService.deleteByBusinessId(updateReqVO.getId()); 一起处理进去;
- if (CollUtil.isNotEmpty(updateReqVO.getProducts())) {
- createBusinessProducts(updateReqVO.getProducts(), updateReqVO.getId(), true);
- } else {
- businessProductService.deleteBusinessProductByBusinessId(updateReqVO.getId());
- }
+ // 2.2 更新商机关联商品
+ List productList = buildBusinessProductList(updateReqVO.getProductItems(), updateObj.getId());
+ updateBusinessProduct(productList, updateObj.getId());
// TODO @商机待定:如果状态发生变化,插入商机状态变更记录表
// 3. 记录操作日志上下文
@@ -188,6 +155,44 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
LogRecordContext.putVariable("businessName", business.getName());
}
+ private void updateBusinessProduct(List newProductList, Long businessId) {
+ List oldProducts = businessProductMapper.selectListByBusinessId(businessId);
+ List> diffList = CollectionUtils.diffList(oldProducts, newProductList, (oldValue, newValue) -> {
+ boolean condition = ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId());
+ if (condition) {
+ newValue.setId(oldValue.getId()); // 更新需要原始编号
+ }
+ return condition;
+ });
+ if (CollUtil.isNotEmpty(diffList.get(0))) {
+ businessProductMapper.insertBatch(diffList.get(0));
+ }
+ if (CollUtil.isNotEmpty(diffList.get(1))) {
+ businessProductMapper.updateBatch(diffList.get(1));
+ }
+ if (CollUtil.isNotEmpty(diffList.get(2))) {
+ businessProductMapper.deleteBatchIds(convertSet(diffList.get(2), CrmBusinessProductDO::getId));
+ }
+ }
+
+ private List buildBusinessProductList(List productItems,
+ Long businessId) {
+ // 校验商品存在
+ Set productIds = convertSet(productItems, CrmBusinessSaveReqVO.CrmBusinessProductItem::getId);
+ List productList = productService.getProductList(productIds);
+ if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) {
+ throw exception(PRODUCT_NOT_EXISTS);
+ }
+ Map productMap = convertMap(productList, CrmProductDO::getId);
+ return convertList(productItems, productItem -> {
+ CrmProductDO product = productMap.get(productItem.getId());
+ return BeanUtils.toBean(product, CrmBusinessProductDO.class)
+ .setId(null).setProductId(productItem.getId()).setBusinessId(businessId)
+ .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent())
+ .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent()));
+ });
+ }
+
/**
* 删除校验合同是关联合同
*
@@ -228,6 +233,14 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
LogRecordContext.putVariable("business", business);
}
+ @Override
+ public void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO) {
+ // 更新商机关联商品
+ List productList = buildBusinessProductList(
+ BeanUtils.toBean(updateProductReqBO.getProductItems(), CrmBusinessSaveReqVO.CrmBusinessProductItem.class), updateProductReqBO.getId());
+ updateBusinessProduct(productList, updateProductReqBO.getId());
+ }
+
//======================= 查询相关 =======================
@Override
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java
new file mode 100644
index 000000000..34b2fa381
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.crm.service.business.bo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 更新商机商品 Update Req BO
+ *
+ * @author HUIHUI
+ */
+@Data
+public class CrmBusinessUpdateProductReqBO {
+
+ /**
+ * 商机编号
+ */
+ @NotNull(message = "商机编号不能为空")
+ private Long id;
+
+ // TODO @芋艿:再想想
+ @NotEmpty(message = "产品列表不能为空")
+ private List productItems;
+
+ @Schema(description = "产品列表")
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class CrmBusinessProductItem {
+
+ @Schema(description = "产品编号", example = "20529")
+ @NotNull(message = "产品编号不能为空")
+ private Long id;
+
+ @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
+ @NotNull(message = "产品数量不能为空")
+ private Integer count;
+
+ @Schema(description = "产品折扣")
+ private Integer discountPercent;
+
+ }
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
index 4c7feb4a9..2a1c1924a 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
+++ b/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;
import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractProductMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
-import cn.iocoder.yudao.module.crm.service.business.CrmBusinessProductService;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
+import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
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
@Validated
public class CrmContractServiceImpl implements CrmContractService {
- public static final String CONTRACT_APPROVE = "contract-approve"; // 合同审批流程标识
+ /**
+ * BPM 合同审批流程标识
+ */
+ public static final String CONTRACT_APPROVE = "contract-approve";
@Resource
private CrmContractMapper contractMapper;
+ @Resource
+ private CrmContractProductMapper contractProductMapper;
@Resource
private CrmPermissionService crmPermissionService;
@Resource
- private CrmBusinessProductService businessProductService;
- @Resource
private CrmProductService productService;
@Resource
private CrmCustomerService customerService;
@@ -89,11 +94,18 @@ public class CrmContractServiceImpl implements CrmContractService {
// 1.1 插入合同
CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null);
contractMapper.insert(contract);
- // 1.2 插入商机关联商品
+ // 1.2 插入合同关联商品
if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话
- List businessProduct = convertBusinessProductList(createReqVO, contract.getId());
- businessProductService.createBusinessProductBatch(businessProduct);
+ List productList = convertContractProductList(createReqVO, contract.getId());
+ contractProductMapper.insertBatch(productList);
// 更新合同商品总金额
+ contractMapper.updateById(new CrmContractDO().setId(contract.getId()).setProductPrice(
+ getSumValue(productList, CrmContractProductDO::getTotalPrice, Integer::sum)));
+ // 如果存在合同关联了商机则更新商机商品关联
+ if (contract.getBusinessId() != null) {
+ businessService.updateBusinessProduct(new CrmBusinessUpdateProductReqBO().setId(contract.getBusinessId())
+ .setProductItems(BeanUtils.toBean(createReqVO.getProductItems(), CrmBusinessUpdateProductReqBO.CrmBusinessProductItem.class)));
+ }
}
// 2. 创建数据权限
@@ -137,29 +149,29 @@ public class CrmContractServiceImpl implements CrmContractService {
if (CollUtil.isEmpty(updateReqVO.getProductItems())) {
return;
}
- List newProductList = convertBusinessProductList(updateReqVO, contractId);
- List oldProductList = businessProductService.getBusinessProductListByContractId(contractId);
- List> diffList = diffList(oldProductList, newProductList, (oldObj, newObj) -> {
- if (ObjUtil.notEqual(oldObj.getProductId(), newObj.getProductId())) {
- return false;
+ List newProductList = convertContractProductList(updateReqVO, contractId);
+ List oldProductList = contractProductMapper.selectListByContractId(contractId);
+ List> diffList = diffList(oldProductList, newProductList, (oldObj, newObj) -> {
+ boolean match = ObjUtil.equal(oldObj.getProductId(), newObj.getProductId());
+ if (match) {
+ newObj.setId(oldObj.getId()); // 设置一下老的编号更新时需要使用
}
- newObj.setId(oldObj.getId()); // 设置一下老的编号更新时需要使用
- return true;
+ return match;
});
- if (CollUtil.isNotEmpty(diffList.getFirst())) {
- businessProductService.createBusinessProductBatch(diffList.getFirst());
+ if (CollUtil.isNotEmpty(diffList.get(0))) {
+ contractProductMapper.insertBatch(diffList.get(0));
}
if (CollUtil.isNotEmpty(diffList.get(1))) {
- businessProductService.updateBusinessProductBatch(diffList.get(1));
+ contractProductMapper.updateBatch(diffList.get(1));
}
if (CollUtil.isNotEmpty(diffList.get(2))) {
- businessProductService.deleteBusinessProductBatch(diffList.get(2));
+ contractProductMapper.deleteBatchIds(convertList(diffList.get(2), CrmContractProductDO::getId));
}
}
// TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
- private List convertBusinessProductList(CrmContractSaveReqVO reqVO, Long contractId) {
+ private List convertContractProductList(CrmContractSaveReqVO reqVO, Long contractId) {
// 校验商品存在
Set productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId);
List productList = productService.getProductList(productIds);
@@ -169,8 +181,8 @@ public class CrmContractServiceImpl implements CrmContractService {
Map productMap = convertMap(productList, CrmProductDO::getId);
return convertList(reqVO.getProductItems(), productItem -> {
CrmProductDO product = productMap.get(productItem.getId());
- return BeanUtils.toBean(product, CrmBusinessProductDO.class)
- .setId(null).setBusinessId(reqVO.getBusinessId()).setProductId(productItem.getId()).setContractId(contractId)
+ return BeanUtils.toBean(product, CrmContractProductDO.class)
+ .setId(null).setProductId(productItem.getId()).setContractId(contractId)
.setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent())
.setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent()));
});
@@ -248,7 +260,8 @@ public class CrmContractServiceImpl implements CrmContractService {
@Override
@Transactional(rollbackFor = Exception.class)
- // TODO @puhui999:操作日志;
+ @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_SUBMIT_SUB_TYPE, bizNo = "{{#id}}",
+ success = CRM_CONTRACT_SUBMIT_SUCCESS)
public void submitContract(Long id, Long userId) {
// 1. 校验合同是否在审批
CrmContractDO contract = validateContractExists(id);
@@ -263,15 +276,43 @@ public class CrmContractServiceImpl implements CrmContractService {
// 3. 更新合同工作流编号
contractMapper.updateById(new CrmContractDO().setId(id).setProcessInstanceId(processInstanceId)
.setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
+
+ // 3. 记录日志
+ LogRecordContext.putVariable("contractName", contract.getName());
}
@Override
public void updateContractAuditStatus(BpmResultListenerRespDTO event) {
- // TODO @puhui999:可能要判断下状态是否符合预期
+ // 判断下状态是否符合预期
+ if (!isEndResult(event.getResult())) {
+ return;
+ }
+ // 状态转换
+ if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.APPROVE.getResult())) {
+ event.setResult(CrmAuditStatusEnum.APPROVE.getStatus());
+ }
+ if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.REJECT.getResult())) {
+ event.setResult(CrmAuditStatusEnum.REJECT.getStatus());
+ }
+ if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult())) {
+ event.setResult(CrmAuditStatusEnum.CANCEL.getStatus());
+ }
+ // 更新合同状态
contractMapper.updateById(new CrmContractDO().setId(Long.parseLong(event.getBusinessKey()))
.setAuditStatus(event.getResult()));
}
+ /**
+ * 判断该结果是否处于 End 最终结果
+ *
+ * @param result 结果
+ * @return 是否
+ */
+ public static boolean isEndResult(Integer result) {
+ return ObjectUtils.equalsAny(result, BpmProcessInstanceResultEnum.APPROVE.getResult(),
+ BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult());
+ }
+
//======================= 查询相关 =======================
@Override
@@ -313,6 +354,12 @@ public class CrmContractServiceImpl implements CrmContractService {
public Long getContractCountByBusinessId(Long businessId) {
return contractMapper.selectCountByBusinessId(businessId);
}
+
+ @Override
+ public List getContractProductListByContractId(Long contactId) {
+ return contractProductMapper.selectListByContractId(contactId);
+ }
+
// TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒;
@Override
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java
index dc278f337..c5ee407bd 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java
@@ -1,11 +1,7 @@
package cn.iocoder.yudao.module.crm.service.contract.listener;
-import cn.hutool.core.util.ObjUtil;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.bpm.api.listener.BpmResultListenerApi;
import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;
-import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
-import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractServiceImpl;
import jakarta.annotation.Resource;
@@ -30,31 +26,7 @@ public class CrmContractResultListener implements BpmResultListenerApi {
@Override
public void onEvent(BpmResultListenerRespDTO event) {
- boolean currentTaskFinish = isEndResult(event.getResult());
- if (!currentTaskFinish) {
- return;
- }
- if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.APPROVE.getResult())) {
- event.setResult(CrmAuditStatusEnum.APPROVE.getStatus());
- }
- if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.REJECT.getResult())) {
- event.setResult(CrmAuditStatusEnum.REJECT.getStatus());
- }
- if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult())) {
- event.setResult(CrmAuditStatusEnum.CANCEL.getStatus());
- }
contractService.updateContractAuditStatus(event);
}
- /**
- * 判断该结果是否处于 End 最终结果
- *
- * @param result 结果
- * @return 是否
- */
- public static boolean isEndResult(Integer result) {
- return ObjectUtils.equalsAny(result, BpmProcessInstanceResultEnum.APPROVE.getResult(),
- BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult());
- }
-
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
index 31553f9c0..5485cd637 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
+++ b/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 {
* 批量导入客户
*
* @param importCustomers 导入客户列表
- * @param isUpdateSupport 是否支持更新
- * @param userId 用户编号
+ * @param importReqVO 请求
* @return 导入结果
*/
- CrmCustomerImportRespVO importCustomerList(List importCustomers, Boolean isUpdateSupport, Long userId);
+ CrmCustomerImportRespVO importCustomerList(List importCustomers, CrmCustomerImportReqVO importReqVO);
// ==================== 公海相关操作 ====================
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
index 26d144ea7..bd2fc421c 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
+++ b/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 {
/**
* 初始化客户的通用字段
*
- * @param customer 客户信息
+ * @param customer 客户信息
* @param ownerUserId 负责人编号
* @return 客户信息 DO
*/
@@ -244,8 +244,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
}
@Override
- public CrmCustomerImportRespVO importCustomerList(List importCustomers,
- Boolean isUpdateSupport, Long userId) {
+ public CrmCustomerImportRespVO importCustomerList(List importCustomers, CrmCustomerImportReqVO importReqVO) {
if (CollUtil.isEmpty(importCustomers)) {
throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY);
}
@@ -264,19 +263,21 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName());
if (existCustomer == null) {
// 1.1 插入客户信息
- CrmCustomerDO customer = initCustomer(importCustomer, userId);
+ CrmCustomerDO customer = initCustomer(importCustomer, importReqVO.getOwnerUserId());
customerMapper.insert(customer);
respVO.getCreateCustomerNames().add(importCustomer.getName());
// 1.2 创建数据权限
- permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
- .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
+ if (importReqVO.getOwnerUserId() != null) {
+ permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
+ .setBizId(customer.getId()).setUserId(importReqVO.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
+ }
// 1.3 记录操作日志
getSelf().importCustomerLog(customer, false);
return;
}
// 情况二:如果存在,判断是否允许更新
- if (!isUpdateSupport) {
+ if (!importReqVO.getUpdateSupport()) {
respVO.getFailureCustomerNames().put(importCustomer.getName(),
StrUtil.format(CUSTOMER_NAME_EXISTS.getMsg(), importCustomer.getName()));
return;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
index 55f8a3593..95205524e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
+++ b/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 {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
- return productMapper.selectListByIds(ids);
+ return productMapper.selectBatchIds(ids);
}
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml
index ef90bb564..10030e0a7 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml
@@ -9,10 +9,11 @@
WHERE deleted = 0
AND audit_status = 20
and owner_user_id in
-
- #{userId}
-
- AND order_date between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
+
+ #{userId}
+
+ AND order_date between #{times[0],javaType=java.time.LocalDateTime} and
+ #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
@@ -22,12 +23,104 @@
FROM crm_receivable
WHERE deleted = 0
AND audit_status = 20
- and owner_user_id in
-
- #{userId}
-
- AND return_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
+ AND owner_user_id in
+
+ #{userId}
+
+ AND return_time between #{times[0],javaType=java.time.LocalDateTime} and
+ #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/package-info.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/package-info.java
new file mode 100644
index 000000000..af7bc123c
--- /dev/null
+++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 属于 erp 模块的 framework 封装
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.erp.framework;
diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/config/ErpWebConfiguration.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/config/ErpWebConfiguration.java
new file mode 100644
index 000000000..f87e04047
--- /dev/null
+++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/config/ErpWebConfiguration.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.erp.framework.web.config;
+
+import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * erp 模块的 web 组件的 Configuration
+ *
+ * @author 芋道源码
+ */
+@Configuration(proxyBeanMethods = false)
+public class ErpWebConfiguration {
+
+ /**
+ * erp 模块的 API 分组
+ */
+ @Bean
+ public GroupedOpenApi tradeGroupedOpenApi() {
+ return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("erp");
+ }
+
+}
diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/package-info.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/package-info.java
new file mode 100644
index 000000000..70f70c035
--- /dev/null
+++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * trade 模块的 web 配置
+ */
+package cn.iocoder.yudao.module.erp.framework.web;
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
index 3d3cd7ed6..7e1dea2e8 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
+++ b/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;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
-import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
-import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO;
-import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileUploadReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.service.file.FileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.annotation.security.PermitAll;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -23,12 +26,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
-import jakarta.annotation.Resource;
-import jakarta.annotation.security.PermitAll;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.Valid;
-
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 文件存储")
@@ -42,7 +39,7 @@ public class FileController {
private FileService fileService;
@PostMapping("/upload")
- @Operation(summary = "上传文件")
+ @Operation(summary = "上传文件", description = "模式一:后端上传文件")
@OperateLog(logArgs = false) // 上传文件,没有记录操作日志的必要
public CommonResult uploadFile(FileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile();
@@ -50,6 +47,18 @@ public class FileController {
return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
}
+ @GetMapping("/presigned-url")
+ @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
+ public CommonResult getFilePresignedUrl(@RequestParam("path") String path) throws Exception {
+ return success(fileService.getFilePresignedUrl(path));
+ }
+
+ @PostMapping("/create")
+ @Operation(summary = "创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件")
+ public CommonResult createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
+ return success(fileService.createFile(createReqVO));
+ }
+
@DeleteMapping("/delete")
@Operation(summary = "删除文件")
@Parameter(name = "id", description = "编号", required = true)
@@ -62,7 +71,7 @@ public class FileController {
@GetMapping("/{configId}/get/**")
@PermitAll
@Operation(summary = "下载文件")
- @Parameter(name = "configId", description = "配置编号", required = true)
+ @Parameter(name = "configId", description = "配置编号", required = true)
public void getFileContent(HttpServletRequest request,
HttpServletResponse response,
@PathVariable("configId") Long configId) throws Exception {
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java
new file mode 100644
index 000000000..5daa3972e
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 文件创建 Request VO")
+@Data
+public class FileCreateReqVO {
+
+ @NotNull(message = "文件配置编号不能为空")
+ @Schema(description = "文件配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
+ private Long configId;
+
+ @NotNull(message = "文件路径不能为空")
+ @Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg")
+ private String path;
+
+ @NotNull(message = "原文件名不能为空")
+ @Schema(description = "原文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg")
+ private String name;
+
+ @NotNull(message = "文件 URL不能为空")
+ @Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
+ private String url;
+
+ @Schema(description = "文件 MIME 类型", example = "application/octet-stream")
+ private String type;
+
+ @Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer size;
+
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java
new file mode 100644
index 000000000..926133ebc
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Schema(description = "管理后台 - 文件预签名地址 Response VO")
+@Data
+public class FilePresignedUrlRespVO {
+
+ @Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
+ private Long configId;
+
+ @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")
+ private String uploadUrl;
+
+ /**
+ * 为什么要返回 url 字段?
+ *
+ * 前端上传完文件后,需要使用该 URL 进行访问
+ */
+ @Schema(description = "文件访问 URL", requiredMode = Schema.RequiredMode.REQUIRED,
+ example = "https://test.yudao.iocoder.cn/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png")
+ private String url;
+
+}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java
index 24baf4218..3ca9a2419 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java
@@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.infra.service.file;
-import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
/**
@@ -22,13 +24,21 @@ public interface FileService {
/**
* 保存文件,并返回文件的访问路径
*
- * @param name 文件名称
- * @param path 文件路径
+ * @param name 文件名称
+ * @param path 文件路径
* @param content 文件内容
* @return 文件路径
*/
String createFile(String name, String path, byte[] content);
+ /**
+ * 创建文件
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createFile(FileCreateReqVO createReqVO);
+
/**
* 删除文件
*
@@ -40,9 +50,17 @@ public interface FileService {
* 获得文件内容
*
* @param configId 配置编号
- * @param path 文件路径
+ * @param path 文件路径
* @return 文件内容
*/
byte[] getFileContent(Long configId, String path) throws Exception;
+ /**
+ * 生成文件预签名地址信息
+ *
+ * @param path 文件路径
+ * @return 预签名地址信息
+ */
+ FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception;
+
}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
index 82ba6d4ff..f7c4b0b8e 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
+++ b/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;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.file.core.client.FileClient;
+import cn.iocoder.yudao.framework.file.core.client.s3.FilePresignedUrlRespDTO;
import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils;
+import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
+import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
-import jakarta.annotation.Resource;
-
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
@@ -66,6 +69,13 @@ public class FileServiceImpl implements FileService {
return url;
}
+ @Override
+ public Long createFile(FileCreateReqVO createReqVO) {
+ FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);
+ fileMapper.insert(file);
+ return file.getId();
+ }
+
@Override
public void deleteFile(Long id) throws Exception {
// 校验存在
@@ -95,4 +105,12 @@ public class FileServiceImpl implements FileService {
return client.getContent(path);
}
+ @Override
+ public FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception {
+ FileClient fileClient = fileConfigService.getMasterFileClient();
+ FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path);
+ return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class,
+ object -> object.setConfigId(fileClient.getId()));
+ }
+
}
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index ff17b298d..4ead0bba8 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -88,11 +88,11 @@
-
- cn.iocoder.boot
- yudao-module-crm-biz
- ${revision}
-
+
+
+
+
+
diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java
index ab626b1e7..7aba89997 100644
--- a/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java
+++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java
@@ -35,6 +35,12 @@ public class DefaultController {
"[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
}
+ @RequestMapping("/admin-api/erp/**")
+ public CommonResult erp404() {
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(),
+ "[ERP 模块 yudao-module-erp - 已禁用][参考 https://doc.iocoder.cn/erp/build/ 开启]");
+ }
+
@RequestMapping(value = {"/admin-api/report/**"})
public CommonResult report404() {
return CommonResult.error(NOT_IMPLEMENTED.getCode(),