Forráskód Böngészése

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

puhui999 1 éve
szülő
commit
b9ffb7833c
48 módosított fájl, 650 hozzáadás és 333 törlés
  1. 2 0
      yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/enums/ExcelColumn.java
  2. 23 11
      yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java
  3. 6 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
  4. 0 9
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http
  5. 0 9
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
  6. 17 9
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
  7. 22 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusiness2ReqVO.java
  8. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java
  9. 44 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractConfigController.java
  10. 21 13
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
  11. 16 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigRespVO.java
  12. 33 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigSaveReqVO.java
  13. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractPageReqVO.java
  14. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
  15. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java
  16. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractTransferReqVO.java
  17. 24 11
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java
  18. 12 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java
  19. 30 10
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java
  20. 9 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.http
  21. 24 24
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.java
  22. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java
  23. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java
  24. 0 56
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java
  25. 33 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractConfigDO.java
  26. 0 10
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
  27. 6 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactBusinessMapper.java
  28. 0 8
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java
  29. 20 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractConfigMapper.java
  30. 20 13
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
  31. 13 13
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankingMapper.java
  32. 9 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
  33. 0 8
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
  34. 17 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java
  35. 39 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java
  36. 0 9
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
  37. 0 8
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
  38. 28 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigService.java
  39. 56 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigServiceImpl.java
  40. 18 7
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
  41. 31 13
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
  42. 3 12
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java
  43. 4 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
  44. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
  45. 5 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
  46. 13 13
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingService.java
  47. 28 28
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingServiceImpl.java
  48. 10 10
      yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/enums/ExcelColumn.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.excel.core.enums;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+// TODO @puhui999:列表有办法通过 field name 么?主要考虑一个点,可能导入模版的顺序可能会变
 /**
  * Excel 列名枚举
  * 默认枚举 26 列列名如果有需求更多的列名请自行补充
@@ -12,6 +13,7 @@ import lombok.Getter;
 @Getter
 @AllArgsConstructor
 public enum ExcelColumn {
+
     A(0), B(1), C(2), D(3), E(4), F(5), G(6), H(7), I(8),
     J(9), K(10), L(11), M(12), N(13), O(14), P(15), Q(16),
     R(17), S(18), T(19), U(20), V(21), W(22), X(23), Y(24),

+ 23 - 11
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java

@@ -23,9 +23,20 @@ import java.util.stream.Collectors;
  */
 public class SelectSheetWriteHandler implements SheetWriteHandler {
 
+    /**
+     * 数据起始行从 0 开始
+     *
+     * 约定:本项目第一行有标题所以从 1 开始如果您的 Excel 有多行标题请自行更改
+     */
+    public static final int FIRST_ROW = 1;
+    /**
+     * 下拉列需要创建下拉框的行数,默认两千行如需更多请自行调整
+     */
+    public static final int LAST_ROW = 2000;
+
     private static final String DICT_SHEET_NAME = "字典sheet";
-    public static final int FIRST_ROW = 1; // 数据起始行从 0 开始,本项目第一行有标题所以从 1 开始如果您的 Excel 有多行标题请自行更改
-    public static final int LAST_ROW = 2000; // 下拉列需要创建下拉框的行数,默认两千行如需更多请自行调整
+
+    // TODO @puhui999:Map<ExcelColumn, List<String>> 可以么?之前用 keyvalue 的原因,返回给前端,无法用 linkedhashmap,默认 key 会乱序
     private final List<KeyValue<ExcelColumn, List<String>>> selectMap;
 
     public SelectSheetWriteHandler(List<KeyValue<ExcelColumn, List<String>>> selectMap) {
@@ -48,32 +59,32 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
             return;
         }
 
-        // 1.1 获取相应操作对象
+        // 1. 获取相应操作对象
         DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper(); // 需要设置下拉框的 sheet 页的数据验证助手
         Workbook workbook = writeWorkbookHolder.getWorkbook(); // 获得工作簿
 
-        // 1.2 创建数据字典的 sheet 页
+        // 2. 创建数据字典的 sheet 页
         Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME);
         for (KeyValue<ExcelColumn, List<String>> keyValue : selectMap) {
-            int rowLen = keyValue.getValue().size();
-            // 设置字典 sheet 页的值 每一列一个字典项
-            for (int i = 0; i < rowLen; i++) {
+            int rowLength = keyValue.getValue().size();
+            // 2.1 设置字典 sheet 页的值 每一列一个字典项
+            for (int i = 0; i < rowLength; i++) {
                 Row row = dictSheet.getRow(i);
                 if (row == null) {
                     row = dictSheet.createRow(i);
                 }
                 row.createCell(keyValue.getKey().getColNum()).setCellValue(keyValue.getValue().get(i));
             }
-            // 1.3 设置单元格下拉选择
-            setColSelect(writeSheetHolder, workbook, helper, keyValue);
+            // 2.2 设置单元格下拉选择
+            setColumnSelect(writeSheetHolder, workbook, helper, keyValue);
         }
     }
 
     /**
      * 设置单元格下拉选择
      */
-    private static void setColSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper,
-                                     KeyValue<ExcelColumn, List<String>> keyValue) {
+    private static void setColumnSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper,
+                                        KeyValue<ExcelColumn, List<String>> keyValue) {
         // 1.1 创建可被其他单元格引用的名称
         Name name = workbook.createName();
         String excelColumn = keyValue.getKey().name();
@@ -81,6 +92,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
         String refers = DICT_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + keyValue.getValue().size();
         name.setNameName("dict" + keyValue.getKey()); // 设置名称的名字
         name.setRefersToFormula(refers); // 设置公式
+
         // 2.1 设置约束
         DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); // 设置引用约束
         // 设置下拉单元格的首行、末行、首列、末列

+ 6 - 0
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java

@@ -96,6 +96,12 @@ public interface LogRecordConstants {
     String CRM_BUSINESS_UPDATE_STATUS_SUB_TYPE = "更新商机状态";
     String CRM_BUSINESS_UPDATE_STATUS_SUCCESS = "更新了商机【{{#businessName}}】的状态从【{{#oldStatusName}}】变更为了【{{#newStatusName}}】";
 
+    // ======================= CRM_CONTRACT_CONFIG 合同配置 =======================
+
+    String CRM_CONTRACT_CONFIG_TYPE = "CRM 合同配置";
+    String CRM_CONTRACT_CONFIG_SUB_TYPE = "{{#isPoolConfigUpdate ? '更新合同配置' : '创建合同配置'}}";
+    String CRM_CONTRACT_CONFIG_SUCCESS = "{{#isPoolConfigUpdate ? '更新了合同配置' : '创建了合同配置'}}";
+
     // ======================= CRM_CONTRACT 合同 =======================
 
     String CRM_CONTRACT_TYPE = "CRM 合同";

+ 0 - 9
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http

@@ -1,9 +0,0 @@
-### 合同金额排行榜
-GET {{baseUrl}}/crm/bi-rank/get-contract-price-rank?deptId=100&times[0]=2022-12-12 00:00:00&times[1]=2024-12-12 23:59:59
-Authorization: Bearer {{token}}
-tenant-id: {{adminTenentId}}
-
-### 回款金额排行榜
-GET {{baseUrl}}/crm/bi-rank/get-receivable-price-rank?deptId=100&times[0]=2022-12-12 00:00:00&times[1]=2024-12-12 23:59:59
-Authorization: Bearer {{token}}
-tenant-id: {{adminTenentId}}

+ 0 - 9
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java

@@ -126,15 +126,6 @@ public class CrmBusinessController {
         return businessVO;
     }
 
-    // TODO 芋艿:处理下
-    @GetMapping("/list-by-ids")
-    @Operation(summary = "获得商机列表")
-    @Parameter(name = "ids", description = "编号", required = true, example = "[1024]")
-    @PreAuthorize("@ss.hasPermission('crm:business:query')")
-    public CommonResult<List<CrmBusinessRespVO>> getContactListByIds(@RequestParam("ids") List<Long> ids) {
-        return success(BeanUtils.toBean(businessService.getBusinessList(ids, getLoginUserId()), CrmBusinessRespVO.class));
-    }
-
     @GetMapping("/simple-all-list")
     @Operation(summary = "获得联系人的精简列表")
     @PreAuthorize("@ss.hasPermission('crm:contact:query')")

+ 17 - 9
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java

@@ -105,15 +105,6 @@ public class CrmContactController {
         return buildContactDetailList(singletonList(contact)).get(0);
     }
 
-    @GetMapping("/list-by-ids")
-    @Operation(summary = "获得联系人列表")
-    @Parameter(name = "ids", description = "编号", required = true, example = "[1024]")
-    @PreAuthorize("@ss.hasPermission('crm:contact:query')")
-    public CommonResult<List<CrmContactRespVO>> getContactListByIds(@RequestParam("ids") List<Long> ids) {
-        List<CrmContactDO> list = contactService.getContactListByIds(ids, getLoginUserId());
-        return success(BeanUtils.toBean(list, CrmContactRespVO.class));
-    }
-
     @GetMapping("/simple-all-list")
     @Operation(summary = "获得联系人的精简列表")
     @PreAuthorize("@ss.hasPermission('crm:contact:query')")
@@ -208,6 +199,15 @@ public class CrmContactController {
         return success(true);
     }
 
+
+    @PostMapping("/create-business-list2")
+    @Operation(summary = "创建联系人与商机的关联")
+    @PreAuthorize("@ss.hasPermission('crm:contact:create-business')")
+    public CommonResult<Boolean> createContactBusinessList2(@Valid @RequestBody CrmContactBusiness2ReqVO createReqVO) {
+        contactBusinessLinkService.createContactBusinessList2(createReqVO);
+        return success(true);
+    }
+
     @DeleteMapping("/delete-business-list")
     @Operation(summary = "删除联系人与联系人的关联")
     @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
@@ -216,4 +216,12 @@ public class CrmContactController {
         return success(true);
     }
 
+    @DeleteMapping("/delete-business-list2")
+    @Operation(summary = "删除联系人与联系人的关联")
+    @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
+    public CommonResult<Boolean> deleteContactBusinessList(@Valid @RequestBody CrmContactBusiness2ReqVO deleteReqVO) {
+        contactBusinessLinkService.deleteContactBusinessList2(deleteReqVO);
+        return success(true);
+    }
+
 }

+ 22 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusiness2ReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 【商机关联联系人】用于关联,取消关联的操作
+@Data
+public class CrmContactBusiness2ReqVO {
+
+    @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
+    @NotNull(message="商机不能为空")
+    private Long businessId;
+
+    @Schema(description = "联系人编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
+    @NotEmpty(message="联系人数组不能为空")
+    private List<Long> contactIds;
+
+}

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java

@@ -7,7 +7,7 @@ import lombok.Data;
 
 import java.util.List;
 
-@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 用于关联,取消关联的操作
+@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 【联系人关联商机】用于关联,取消关联的操作
 @Data
 public class CrmContactBusinessReqVO {
 

+ 44 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractConfigController.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contract;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
+import cn.iocoder.yudao.module.crm.service.contract.CrmContractConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - CRM 合同配置")
+@RestController
+@RequestMapping("/crm/contract-config")
+@Validated
+public class CrmContractConfigController {
+
+    @Resource
+    private CrmContractConfigService contractConfigService;
+
+    @GetMapping("/get")
+    @Operation(summary = "获取合同配置")
+    @PreAuthorize("@ss.hasPermission('crm:contract-config:query')")
+    public CommonResult<CrmContractConfigRespVO> getCustomerPoolConfig() {
+        CrmContractConfigDO config = contractConfigService.getContractConfig();
+        return success(BeanUtils.toBean(config, CrmContractConfigRespVO.class));
+    }
+
+    @PutMapping("/save")
+    @Operation(summary = "更新合同配置")
+    @PreAuthorize("@ss.hasPermission('crm:contract-config:update')")
+    public CommonResult<Boolean> saveCustomerPoolConfig(@Valid @RequestBody CrmContractConfigSaveReqVO updateReqVO) {
+        contractConfigService.saveContractConfig(updateReqVO);
+        return success(true);
+    }
+
+}

+ 21 - 13
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java

@@ -9,10 +9,10 @@ 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.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.controller.admin.contract.vo.contract.CrmContractPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.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;
@@ -138,6 +138,14 @@ public class CrmContractController {
         return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList())));
     }
 
+    @GetMapping("/page-by-business")
+    @Operation(summary = "获得合同分页,基于指定商机")
+    public CommonResult<PageResult<CrmContractRespVO>> getContractPageByBusiness(@Valid CrmContractPageReqVO pageVO) {
+        Assert.notNull(pageVO.getBusinessId(), "商机编号不能为空");
+        PageResult<CrmContractDO> pageResult = contractService.getContractPageByBusinessId(pageVO);
+        return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList())));
+    }
+
     @GetMapping("/export-excel")
     @Operation(summary = "导出合同 Excel")
     @PreAuthorize("@ss.hasPermission('crm:contract:export')")
@@ -187,8 +195,8 @@ public class CrmContractController {
         Map<Long, CrmContactDO> contactMap = convertMap(contactService.getContactList(convertSet(contractList,
                 CrmContractDO::getSignContactId)), CrmContactDO::getId);
         // 1.4 获取商机
-        Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList(convertSet(contractList,
-                CrmContractDO::getBusinessId)), CrmBusinessDO::getId);
+        Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap(
+                convertSet(contractList, CrmContractDO::getBusinessId));
         // 2. 拼接数据
         return BeanUtils.toBean(contractList, CrmContractRespVO.class, contractVO -> {
             // 2.1 设置客户信息
@@ -207,18 +215,18 @@ public class CrmContractController {
         });
     }
 
-    @GetMapping("/check-contract-count")
+    @GetMapping("/audit-count")
     @Operation(summary = "获得待审核合同数量")
     @PreAuthorize("@ss.hasPermission('crm:contract:query')")
-    public CommonResult<Long> getCheckContractCount() {
-        return success(contractService.getCheckContractCount(getLoginUserId()));
+    public CommonResult<Long> getAuditContractCount() {
+        return success(contractService.getAuditContractCount(getLoginUserId()));
     }
 
-    @GetMapping("/end-contract-count")
-    @Operation(summary = "获得即将到期的合同数量")
+    @GetMapping("/remind-count")
+    @Operation(summary = "获得即将到期(提醒)的合同数量")
     @PreAuthorize("@ss.hasPermission('crm:contract:query')")
-    public CommonResult<Long> getEndContractCount() {
-        return success(contractService.getEndContractCount(getLoginUserId()));
+    public CommonResult<Long> getRemindContractCount() {
+        return success(contractService.getRemindContractCount(getLoginUserId()));
     }
 
     @GetMapping("/list-all-simple-by-customer")

+ 16 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigRespVO.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - CRM 合同配置 Response VO")
+@Data
+public class CrmContractConfigRespVO {
+
+    @Schema(description = "是否开启提前提醒", example = "true")
+    private Boolean notifyEnabled;
+
+    @Schema(description = "提前提醒天数", example = "2")
+    private Integer notifyDays;
+
+}

+ 33 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigSaveReqVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config;
+
+import cn.hutool.core.util.BooleanUtil;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.mzt.logapi.starter.annotation.DiffLogField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.AssertTrue;
+import lombok.Data;
+
+import java.util.Objects;
+
+@Schema(description = "管理后台 - CRM 合同配置 Request VO")
+@Data
+public class CrmContractConfigSaveReqVO {
+
+    @Schema(description = "是否开启提前提醒", example = "true")
+    @DiffLogField(name = "是否开启提前提醒")
+    private Boolean notifyEnabled;
+
+    @Schema(description = "提前提醒天数", example = "2")
+    @DiffLogField(name = "提前提醒天数")
+    private Integer notifyDays;
+
+    @AssertTrue(message = "提前提醒天数不能为空")
+    @JsonIgnore
+    public boolean isNotifyDaysValid() {
+        if (!BooleanUtil.isTrue(getNotifyEnabled())) {
+            return true;
+        }
+        return Objects.nonNull(getNotifyDays());
+    }
+
+}

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractPageReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.validation.InEnum;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
 
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
 
 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmBusinessParseFunction;
 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmContactParseFunction;

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractTransferReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
 
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;

+ 24 - 11
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordRespVO;
 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO;
@@ -13,6 +14,8 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
 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.followup.CrmFollowUpRecordService;
+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.tags.Tag;
@@ -26,7 +29,7 @@ import java.util.ArrayList;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
@@ -44,6 +47,9 @@ public class CrmFollowUpRecordController {
     @Resource
     private CrmBusinessService businessService;
 
+    @Resource
+    private AdminUserApi adminUserApi;
+
     @PostMapping("/create")
     @Operation(summary = "创建跟进记录")
     @PreAuthorize("@ss.hasPermission('crm:follow-up-record:create')")
@@ -74,17 +80,24 @@ public class CrmFollowUpRecordController {
     @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')")
     public CommonResult<PageResult<CrmFollowUpRecordRespVO>> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) {
         PageResult<CrmFollowUpRecordDO> pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO);
-        /// 拼接数据
-        Map<Long, CrmContactDO> contactMap = convertMap(contactService.getContactList(
-                convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream())), CrmContactDO::getId);
-        Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList(
-                convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream())), CrmBusinessDO::getId);
+        // 1.1 查询联系人和商机
+        Map<Long, CrmContactDO> contactMap = contactService.getContactMap(
+                convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream()));
+        Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap(
+                convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream()));
+        // 1.2 查询用户
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSet(pageResult.getList(), item -> Long.valueOf(item.getCreator())));
+        // 2. 拼接数据
         PageResult<CrmFollowUpRecordRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmFollowUpRecordRespVO.class, record -> {
-            record.setContactNames(new ArrayList<>()).setBusinessNames(new ArrayList<>());
-            record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id,
-                    contact -> record.getContactNames().add(contact.getName())));
-            record.getContactIds().forEach(id -> MapUtils.findAndThen(businessMap, id,
-                    business -> record.getBusinessNames().add(business.getName())));
+            // 2.1 设置联系人和商机信息
+            record.setBusinesses(new ArrayList<>()).setContacts(new ArrayList<>());
+            record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id, contact ->
+                    record.getContacts().add(new CrmBusinessRespVO().setId(contact.getId()).setName(contact.getName()))));
+            record.getContactIds().forEach(id -> MapUtils.findAndThen(businessMap, id, business ->
+                    record.getBusinesses().add(new CrmBusinessRespVO().setId(business.getId()).setName(business.getName()))));
+            // 2.2 设置用户信息
+            MapUtils.findAndThen(userMap, Long.valueOf(record.getCreator()), user -> record.setCreatorName(user.getNickname()));
         });
         return success(voPageResult);
     }

+ 12 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java

@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.module.crm.controller.admin.followup.vo;
 
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -36,19 +38,26 @@ public class CrmFollowUpRecordRespVO {
 
     @Schema(description = "关联的商机编号数组")
     private List<Long> businessIds;
-    @Schema(description = "关联的商机名称数组")
-    private List<String> businessNames;
+    @Schema(description = "关联的商机数组")
+    private List<CrmBusinessRespVO> businesses;
 
     @Schema(description = "关联的联系人编号数组")
     private List<Long> contactIds;
     @Schema(description = "关联的联系人名称数组")
-    private List<String> contactNames;
+    private List<CrmBusinessRespVO> contacts;
 
     @Schema(description = "图片")
     private List<String> picUrls;
     @Schema(description = "附件")
     private List<String> fileUrls;
 
+    @Schema(description = "创建人", example = "1024")
+    @ExcelProperty("创建人")
+    private String creator;
+    @Schema(description = "创建人名字", example = "芋道源码")
+    @ExcelProperty("创建人名字")
+    private String creatorName;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

+ 30 - 10
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java

@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.permission;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO;
 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
-import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
@@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.google.common.collect.Multimaps;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.Parameters;
@@ -29,11 +30,16 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.*;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 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.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
 @Tag(name = "管理后台 - CRM 数据权限")
@@ -98,18 +104,32 @@ public class CrmPermissionController {
     @PreAuthorize("@ss.hasPermission('crm:permission:query')")
     public CommonResult<List<CrmPermissionRespVO>> getPermissionList(@RequestParam("bizType") Integer bizType,
                                                                      @RequestParam("bizId") Long bizId) {
-        List<CrmPermissionDO> permission = permissionService.getPermissionListByBiz(bizType, bizId);
-        if (CollUtil.isEmpty(permission)) {
+        List<CrmPermissionDO> permissions = permissionService.getPermissionListByBiz(bizType, bizId);
+        if (CollUtil.isEmpty(permissions)) {
             return success(Collections.emptyList());
         }
 
+        // 查询相关数据
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSet(permissions, CrmPermissionDO::getUserId));
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+        Map<Long, PostRespDTO> postMap = postApi.getPostMap(
+                convertSetByFlatMap(userMap.values(), AdminUserRespDTO::getPostIds,
+                        item -> item != null ? item.stream() : Stream.empty()));
         // 拼接数据
-        List<AdminUserRespDTO> userList = adminUserApi.getUserList(convertSet(permission, CrmPermissionDO::getUserId));
-        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId));
-        Set<Long> postIds = CollectionUtils.convertSetByFlatMap(userList, AdminUserRespDTO::getPostIds,
-                item -> item != null ? item.stream() : Stream.empty());
-        Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
-        return success(CrmPermissionConvert.INSTANCE.convert(permission, userList, deptMap, postMap));
+        return success(CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> {
+            findAndThen(userMap, item.getUserId(), user -> {
+                item.setNickname(user.getNickname());
+                findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName()));
+                if (CollUtil.isEmpty(user.getPostIds())) {
+                    item.setPostNames(Collections.emptySet());
+                    return;
+                }
+                List<PostRespDTO> postList = MapUtils.getList(Multimaps.forMap(postMap), user.getPostIds());
+                item.setPostNames(CollectionUtils.convertSet(postList, PostRespDTO::getName));
+            });
+            return item;
+        }));
     }
 
 }

+ 9 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.http

@@ -0,0 +1,9 @@
+### 合同金额排行榜
+GET {{baseUrl}}/crm/statistics-rank/get-contract-price-rank?deptId=100&times[0]=2022-12-12 00:00:00&times[1]=2024-12-12 23:59:59
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}
+
+### 回款金额排行榜
+GET {{baseUrl}}/crm/statistics-rank/get-receivable-price-rank?deptId=100&times[0]=2022-12-12 00:00:00&times[1]=2024-12-12 23:59:59
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenentId}}

+ 24 - 24
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.java

@@ -1,9 +1,9 @@
-package cn.iocoder.yudao.module.crm.controller.admin.bi;
+package cn.iocoder.yudao.module.crm.controller.admin.statistics;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO;
-import cn.iocoder.yudao.module.crm.service.bi.CrmBiRankingService;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;
+import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsRankingService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
@@ -19,68 +19,68 @@ import java.util.List;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
 
-@Tag(name = "管理后台 - CRM BI 排行榜")
+@Tag(name = "管理后台 - CRM 排行榜统计")
 @RestController
-@RequestMapping("/crm/bi-rank")
+@RequestMapping("/crm/statistics-rank")
 @Validated
-public class CrmBiRankController {
+public class CrmStatisticsRankController {
 
     @Resource
-    private CrmBiRankingService rankingService;
+    private CrmStatisticsRankingService rankingService;
 
     @GetMapping("/get-contract-price-rank")
     @Operation(summary = "获得合同金额排行榜")
-    @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
-    public CommonResult<List<CrmBiRanKRespVO>> getContractPriceRank(@Valid CrmBiRankReqVO rankingReqVO) {
+    @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
+    public CommonResult<List<CrmStatisticsRanKRespVO>> getContractPriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
         return success(rankingService.getContractPriceRank(rankingReqVO));
     }
 
     @GetMapping("/get-receivable-price-rank")
     @Operation(summary = "获得回款金额排行榜")
-    @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
-    public CommonResult<List<CrmBiRanKRespVO>> getReceivablePriceRank(@Valid CrmBiRankReqVO rankingReqVO) {
+    @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
+    public CommonResult<List<CrmStatisticsRanKRespVO>> getReceivablePriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
         return success(rankingService.getReceivablePriceRank(rankingReqVO));
     }
 
     @GetMapping("/get-contract-count-rank")
     @Operation(summary = "获得签约合同数量排行榜")
-    @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
-    public CommonResult<List<CrmBiRanKRespVO>> getContractCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+    @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
+    public CommonResult<List<CrmStatisticsRanKRespVO>> getContractCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
         return success(rankingService.getContractCountRank(rankingReqVO));
     }
 
     @GetMapping("/get-product-sales-rank")
     @Operation(summary = "获得产品销量排行榜")
-    @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
-    public CommonResult<List<CrmBiRanKRespVO>> getProductSalesRank(@Valid CrmBiRankReqVO rankingReqVO) {
+    @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
+    public CommonResult<List<CrmStatisticsRanKRespVO>> getProductSalesRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
         return success(rankingService.getProductSalesRank(rankingReqVO));
     }
 
     @GetMapping("/get-customer-count-rank")
     @Operation(summary = "获得新增客户数排行榜")
-    @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
-    public CommonResult<List<CrmBiRanKRespVO>> getCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+    @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
+    public CommonResult<List<CrmStatisticsRanKRespVO>> getCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
         return success(rankingService.getCustomerCountRank(rankingReqVO));
     }
 
     @GetMapping("/get-contacts-count-rank")
     @Operation(summary = "获得新增联系人数排行榜")
-    @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
-    public CommonResult<List<CrmBiRanKRespVO>> getContactsCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+    @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
+    public CommonResult<List<CrmStatisticsRanKRespVO>> getContactsCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
         return success(rankingService.getContactsCountRank(rankingReqVO));
     }
 
     @GetMapping("/get-follow-count-rank")
     @Operation(summary = "获得跟进次数排行榜")
-    @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
-    public CommonResult<List<CrmBiRanKRespVO>> getFollowCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+    @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
+    public CommonResult<List<CrmStatisticsRanKRespVO>> getFollowCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
         return success(rankingService.getFollowCountRank(rankingReqVO));
     }
 
     @GetMapping("/get-follow-customer-count-rank")
     @Operation(summary = "获得跟进客户数排行榜")
-    @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
-    public CommonResult<List<CrmBiRanKRespVO>> getFollowCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) {
+    @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
+    public CommonResult<List<CrmStatisticsRanKRespVO>> getFollowCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
         return success(rankingService.getFollowCustomerCountRank(rankingReqVO));
     }
 

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRanKRespVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java

@@ -1,12 +1,12 @@
-package cn.iocoder.yudao.module.crm.controller.admin.bi.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 
-@Schema(description = "管理后台 - CRM BI 排行榜 Response VO")
+@Schema(description = "管理后台 - CRM BI 排行榜统计 Response VO")
 @Data
-public class CrmBiRanKRespVO {
+public class CrmStatisticsRanKRespVO {
 
     @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long ownerUserId;

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRankReqVO.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.controller.admin.bi.vo;
+package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotEmpty;
@@ -11,9 +11,9 @@ import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
-@Schema(description = "管理后台 - CRM BI 排行榜 Request VO")
+@Schema(description = "管理后台 - CRM 排行榜统计 Request VO")
 @Data
-public class CrmBiRankReqVO {
+public class CrmStatisticsRankReqVO {
 
     @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "部门 id 不能为空")

+ 0 - 56
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java

@@ -1,56 +0,0 @@
-package cn.iocoder.yudao.module.crm.convert.permission;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
-import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
-import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
-import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
-import com.google.common.collect.Multimaps;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
-
-/**
- * Crm 数据权限 Convert
- *
- * @author HUIHUI
- */
-@Mapper
-public interface CrmPermissionConvert {
-
-    CrmPermissionConvert INSTANCE = Mappers.getMapper(CrmPermissionConvert.class);
-
-    default List<CrmPermissionRespVO> convert(List<CrmPermissionDO> permissions, List<AdminUserRespDTO> userList,
-                                              Map<Long, DeptRespDTO> deptMap, Map<Long, PostRespDTO> postMap) {
-        Map<Long, AdminUserRespDTO> userMap = CollectionUtils.convertMap(userList, AdminUserRespDTO::getId);
-        return CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> {
-            findAndThen(userMap, item.getUserId(), user -> {
-                item.setNickname(user.getNickname());
-                findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName()));
-                if (CollUtil.isEmpty(user.getPostIds())) {
-                    item.setPostNames(Collections.emptySet());
-                    return;
-                }
-                List<PostRespDTO> postList = MapUtils.getList(Multimaps.forMap(postMap), user.getPostIds());
-                item.setPostNames(CollectionUtils.convertSet(postList, PostRespDTO::getName));
-            });
-            return item;
-        });
-    }
-
-    default List<CrmPermissionDO> convertList(CrmPermissionUpdateReqVO updateReqVO) {
-        return CollectionUtils.convertList(updateReqVO.getIds(),
-                id -> new CrmPermissionDO().setId(id).setLevel(updateReqVO.getLevel()));
-    }
-
-}

+ 33 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractConfigDO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.*;
+
+@TableName("crm_contract_config")
+@KeySequence("crm_contract_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmContractConfigDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 是否开启提前提醒
+     */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
+    private Boolean notifyEnabled;
+    /**
+     * 提前提醒天数
+     */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
+    private Integer notifyDays;
+
+}

+ 0 - 10
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java

@@ -12,7 +12,6 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.Collection;
-import java.util.List;
 
 /**
  * 商机 Mapper
@@ -54,15 +53,6 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
         return selectJoinPage(pageReqVO, CrmBusinessDO.class, query);
     }
 
-    default List<CrmBusinessDO> selectBatchIds(Collection<Long> ids, Long userId) {
-        MPJLambdaWrapperX<CrmBusinessDO> query = new MPJLambdaWrapperX<>();
-        // 拼接数据权限的查询条件
-        CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), ids, userId);
-        // 拼接自身的查询条件
-        query.selectAll(CrmBusinessDO.class).in(CrmBusinessDO::getId, ids).orderByDesc(CrmBusinessDO::getId);
-        return selectJoinList(CrmBusinessDO.class, query);
-    }
-
     default Long selectCountByStatusTypeId(Long statusTypeId) {
         return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId);
     }

+ 6 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactBusinessMapper.java

@@ -27,6 +27,12 @@ public interface CrmContactBusinessMapper extends BaseMapperX<CrmContactBusiness
                 .in(CrmContactBusinessDO::getBusinessId, businessIds));
     }
 
+    default void deleteByBusinessIdAndContactId(Long businessId, List<Long> contactIds) {
+        delete(new LambdaQueryWrapper<CrmContactBusinessDO>()
+                .eq(CrmContactBusinessDO::getBusinessId, businessId)
+                .in(CrmContactBusinessDO::getContactId, contactIds));
+    }
+
     default List<CrmContactBusinessDO> selectListByContactId(Long contactId) {
         return selectList(CrmContactBusinessDO::getContactId, contactId);
     }

+ 0 - 8
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java

@@ -69,14 +69,6 @@ public interface CrmContactMapper extends BaseMapperX<CrmContactDO> {
         return selectJoinPage(pageReqVO, CrmContactDO.class, query);
     }
 
-    default List<CrmContactDO> selectBatchIds(Collection<Long> ids, Long ownerUserId) {
-        MPJLambdaWrapperX<CrmContactDO> query = new MPJLambdaWrapperX<>();
-        // 拼接数据权限的查询条件
-        CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, ownerUserId);
-        query.selectAll(CrmContactDO.class).in(CrmContactDO::getId, ids).orderByDesc(CrmContactDO::getId);
-        return selectJoinList(CrmContactDO.class, query);
-    }
-
     default List<CrmContactDO> selectListByCustomerId(Long customerId) {
         return selectList(CrmContactDO::getCustomerId, customerId);
     }

+ 20 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractConfigMapper.java

@@ -0,0 +1,20 @@
+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.QueryWrapperX;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 合同配置 Mapper
+ *
+ * @author Wanwan
+ */
+@Mapper
+public interface CrmContractConfigMapper extends BaseMapperX<CrmContractConfigDO> {
+
+    default CrmContractConfigDO selectOne() {
+        return selectOne(new QueryWrapperX<CrmContractConfigDO>().limitN(1));
+    }
+
+}

+ 20 - 13
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java

@@ -5,7 +5,8 @@ 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.contract.vo.CrmContractPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
@@ -39,7 +40,17 @@ public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
                 .orderByDesc(CrmContractDO::getId));
     }
 
-    default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId) {
+    default PageResult<CrmContractDO> selectPageByBusinessId(CrmContractPageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<CrmContractDO>()
+                .eq(CrmContractDO::getBusinessId, pageReqVO.getBusinessId())
+                .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo())
+                .likeIfPresent(CrmContractDO::getName, pageReqVO.getName())
+                .eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId())
+                .eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId())
+                .orderByDesc(CrmContractDO::getId));
+    }
+
+    default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId, CrmContractConfigDO config) {
         MPJLambdaWrapperX<CrmContractDO> query = new MPJLambdaWrapperX<>();
         // 拼接数据权限的查询条件
         CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(),
@@ -57,10 +68,8 @@ public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
         LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
         LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
         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));
+                    .between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(config.getNotifyDays()));
         } else if (CrmContractPageReqVO.EXPIRY_TYPE_EXPIRED.equals(pageReqVO.getExpiryType())) { // 已到期
             query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus())
                     .lt(CrmContractDO::getEndTime, endOfToday);
@@ -85,17 +94,17 @@ public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
         return selectCount(CrmContractDO::getBusinessId, businessId);
     }
 
-    default Long selectCheckContractCount(Long userId) {
+    default Long selectCountByAudit(Long userId) {
         MPJLambdaWrapperX<CrmContractDO> query = new MPJLambdaWrapperX<>();
         // 我负责的 + 非公海
         CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(),
                 CrmContractDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE);
-        // 未提交 or 审核不通过
-        query.in(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.DRAFT.getStatus(), CrmAuditStatusEnum.REJECT.getStatus());
+        // 未审核
+        query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.PROCESS.getStatus());
         return selectCount(query);
     }
 
-    default Long selectEndContractCount(Long userId) {
+    default Long selectCountByRemind(Long userId, CrmContractConfigDO config) {
         MPJLambdaWrapperX<CrmContractDO> query = new MPJLambdaWrapperX<>();
         // 我负责的 + 非公海
         CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(),
@@ -103,10 +112,8 @@ public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
         // 即将到期
         LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
         LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
-        // TODO: @dhb52 需要配置 提前提醒天数
-        int REMIND_DAYS = 20;
-        query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus())
-                .between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(REMIND_DAYS));
+        query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus()) // 必须审批通过!
+                .between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(config.getNotifyDays()));
         return selectCount(query);
     }
 

+ 13 - 13
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankingMapper.java

@@ -1,18 +1,18 @@
-package cn.iocoder.yudao.module.crm.dal.mysql.bi;
+package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
 
-import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
 
 /**
- * CRM BI 排行榜 Mapper
+ * CRM 排行榜统计 Mapper
  *
  * @author anhaohao
  */
 @Mapper
-public interface CrmBiRankingMapper {
+public interface CrmStatisticsRankingMapper {
 
     /**
      * 查询合同金额排行榜
@@ -20,7 +20,7 @@ public interface CrmBiRankingMapper {
      * @param rankReqVO 参数
      * @return 合同金额排行榜
      */
-    List<CrmBiRanKRespVO> selectContractPriceRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> selectContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 查询回款金额排行榜
@@ -28,7 +28,7 @@ public interface CrmBiRankingMapper {
      * @param rankReqVO 参数
      * @return 回款金额排行榜
      */
-    List<CrmBiRanKRespVO> selectReceivablePriceRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> selectReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 查询签约合同数量排行榜
@@ -36,7 +36,7 @@ public interface CrmBiRankingMapper {
      * @param rankReqVO 参数
      * @return 签约合同数量排行榜
      */
-    List<CrmBiRanKRespVO> selectContractCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> selectContractCountRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 查询产品销量排行榜
@@ -44,7 +44,7 @@ public interface CrmBiRankingMapper {
      * @param rankReqVO 参数
      * @return 产品销量排行榜
      */
-    List<CrmBiRanKRespVO> selectProductSalesRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> selectProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 查询新增客户数排行榜
@@ -52,7 +52,7 @@ public interface CrmBiRankingMapper {
      * @param rankReqVO 参数
      * @return 新增客户数排行榜
      */
-    List<CrmBiRanKRespVO> selectCustomerCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> selectCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 查询联系人数量排行榜
@@ -60,7 +60,7 @@ public interface CrmBiRankingMapper {
      * @param rankReqVO 参数
      * @return 联系人数量排行榜
      */
-    List<CrmBiRanKRespVO> selectContactsCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> selectContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 查询跟进次数排行榜
@@ -68,7 +68,7 @@ public interface CrmBiRankingMapper {
      * @param rankReqVO 参数
      * @return 跟进次数排行榜
      */
-    List<CrmBiRanKRespVO> selectFollowCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> selectFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 查询跟进客户数排行榜
@@ -76,6 +76,6 @@ public interface CrmBiRankingMapper {
      * @param rankReqVO 参数
      * @return 跟进客户数排行榜
      */
-    List<CrmBiRanKRespVO> selectFollowCustomerCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> selectFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
 
 }

+ 9 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java

@@ -16,6 +16,9 @@ import jakarta.validation.Valid;
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * 商机 Service 接口
@@ -101,15 +104,17 @@ public interface CrmBusinessService {
      * @param ids 编号
      * @return 商机列表
      */
-    List<CrmBusinessDO> getBusinessList(Collection<Long> ids, Long userId);
+    List<CrmBusinessDO> getBusinessList(Collection<Long> ids);
 
     /**
-     * 获得商机列表
+     * 获得商机 Map
      *
      * @param ids 编号
-     * @return 商机列表
+     * @return 商机 Map
      */
-    List<CrmBusinessDO> getBusinessList(Collection<Long> ids);
+    default Map<Long, CrmBusinessDO> getBusinessMap(Collection<Long> ids) {
+        return convertMap(getBusinessList(ids), CrmBusinessDO::getId);
+    }
 
     /**
      * 获得指定商机编号的产品列表

+ 0 - 8
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java

@@ -322,14 +322,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         return validateBusinessExists(id);
     }
 
-    @Override
-    public List<CrmBusinessDO> getBusinessList(Collection<Long> ids, Long userId) {
-        if (CollUtil.isEmpty(ids)) {
-            return ListUtil.empty();
-        }
-        return businessMapper.selectBatchIds(ids, userId);
-    }
-
     @Override
     public List<CrmBusinessDO> getBusinessList(Collection<Long> ids) {
         if (CollUtil.isEmpty(ids)) {

+ 17 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.crm.service.contact;
 
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusiness2ReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
 import jakarta.validation.Valid;
@@ -14,19 +15,33 @@ import java.util.List;
 public interface CrmContactBusinessService {
 
     /**
-     * 创建联系人与商机的关联
+     * 创建联系人与商机的关联【通过联系人,关联商机】
      *
      * @param createReqVO 创建信息
      */
     void createContactBusinessList(@Valid CrmContactBusinessReqVO createReqVO);
 
     /**
-     * 删除联系人与商机的关联
+     * 创建联系人与商机的关联【通过商机,关联联系人】
+     *
+     * @param createReqVO 创建信息
+     */
+    void createContactBusinessList2(@Valid CrmContactBusiness2ReqVO createReqVO);
+
+    /**
+     * 删除联系人与商机的关联【通过联系人,取关商机】
      *
      * @param deleteReqVO 删除信息
      */
     void deleteContactBusinessList(@Valid CrmContactBusinessReqVO deleteReqVO);
 
+    /**
+     * 删除联系人与商机的关联【通过商机,取关联系人】
+     *
+     * @param deleteReqVO 删除信息
+     */
+    void deleteContactBusinessList2(@Valid CrmContactBusiness2ReqVO deleteReqVO);
+
     /**
      * 删除联系人与商机的关联,基于联系人编号
      *

+ 39 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.crm.service.contact;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusiness2ReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
@@ -67,6 +68,32 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService
         }
     }
 
+    @Override
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#createReqVO.businessId", level = CrmPermissionLevelEnum.WRITE)
+    public void createContactBusinessList2(CrmContactBusiness2ReqVO createReqVO) {
+        CrmBusinessDO business = businessService.getBusiness(createReqVO.getBusinessId());
+        if (business == null) {
+            throw exception(BUSINESS_NOT_EXISTS);
+        }
+        // 遍历处理,考虑到一般数量不会太多,代码处理简单
+        List<CrmContactBusinessDO> saveDOList = new ArrayList<>();
+        createReqVO.getContactIds().forEach(contactId -> {
+            CrmContactDO contact = contactService.getContact(contactId);
+            if (contact == null) {
+                throw exception(CONTACT_NOT_EXISTS);
+            }
+            // 关联判重
+            if (contactBusinessMapper.selectByContactIdAndBusinessId(contactId, createReqVO.getBusinessId()) != null) {
+                return;
+            }
+            saveDOList.add(new CrmContactBusinessDO(null, contactId, createReqVO.getBusinessId()));
+        });
+        // 批量插入
+        if (CollUtil.isNotEmpty(saveDOList)) {
+            contactBusinessMapper.insertBatch(saveDOList);
+        }
+    }
+
     @Override
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#deleteReqVO.contactId", level = CrmPermissionLevelEnum.WRITE)
     public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) {
@@ -79,6 +106,18 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService
                 deleteReqVO.getContactId(), deleteReqVO.getBusinessIds());
     }
 
+    @Override
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#deleteReqVO.businessId", level = CrmPermissionLevelEnum.WRITE)
+    public void deleteContactBusinessList2(CrmContactBusiness2ReqVO deleteReqVO) {
+        CrmBusinessDO business = businessService.getBusiness(deleteReqVO.getBusinessId());
+        if (business == null) {
+            throw exception(BUSINESS_NOT_EXISTS);
+        }
+        // 直接删除
+        contactBusinessMapper.deleteByBusinessIdAndContactId(
+                deleteReqVO.getBusinessId(), deleteReqVO.getContactIds());
+    }
+
     @Override
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.WRITE)
     public void deleteContactBusinessByContactId(Long contactId) {

+ 0 - 9
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java

@@ -95,15 +95,6 @@ public interface CrmContactService {
      */
     void validateContact(Long id);
 
-    /**
-     * 获得联系人列表
-     *
-     * @param ids    编号
-     * @param userId 用户编号
-     * @return 联系人列表
-     */
-    List<CrmContactDO> getContactListByIds(Collection<Long> ids, Long userId);
-
     /**
      * 获得联系人列表
      *

+ 0 - 8
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java

@@ -257,14 +257,6 @@ public class CrmContactServiceImpl implements CrmContactService {
         validateContactExists(id);
     }
 
-    @Override
-    public List<CrmContactDO> getContactListByIds(Collection<Long> ids, Long userId) {
-        if (CollUtil.isEmpty(ids)) {
-            return ListUtil.empty();
-        }
-        return contactMapper.selectBatchIds(ids, userId);
-    }
-
     @Override
     public List<CrmContactDO> getContactList(Collection<Long> ids) {
         if (CollUtil.isEmpty(ids)) {

+ 28 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigService.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.crm.service.contract;
+
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
+import jakarta.validation.Valid;
+
+/**
+ * 合同配置 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface CrmContractConfigService {
+
+    /**
+     * 获得合同配置
+     *
+     * @return 合同配置
+     */
+    CrmContractConfigDO getContractConfig();
+
+    /**
+     * 保存合同配置
+     *
+     * @param saveReqVO 更新信息
+     */
+    void saveContractConfig(@Valid CrmContractConfigSaveReqVO saveReqVO);
+
+}

+ 56 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigServiceImpl.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.crm.service.contract;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractConfigMapper;
+import com.mzt.logapi.context.LogRecordContext;
+import com.mzt.logapi.starter.annotation.LogRecord;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Objects;
+
+import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
+
+/**
+ * 合同配置 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class CrmContractConfigServiceImpl implements CrmContractConfigService {
+
+    @Resource
+    private CrmContractConfigMapper contractConfigMapper;
+
+    @Override
+    public CrmContractConfigDO getContractConfig() {
+        return contractConfigMapper.selectOne();
+    }
+
+    @Override
+    @LogRecord(type = CRM_CONTRACT_CONFIG_TYPE, subType = CRM_CONTRACT_CONFIG_SUB_TYPE, bizNo = "{{#configId}}",
+            success = CRM_CONTRACT_CONFIG_SUCCESS)
+    public void saveContractConfig(CrmContractConfigSaveReqVO saveReqVO) {
+        // 1. 存在,则进行更新
+        CrmContractConfigDO dbConfig = getContractConfig();
+        CrmContractConfigDO config = BeanUtils.toBean(saveReqVO, CrmContractConfigDO.class);
+        if (Objects.nonNull(dbConfig)) {
+            contractConfigMapper.updateById(config.setId(dbConfig.getId()));
+            // 记录操作日志上下文
+            LogRecordContext.putVariable("isConfigUpdate", Boolean.TRUE);
+            LogRecordContext.putVariable("configId", config.getId());
+            return;
+        }
+
+        // 2. 不存在,则进行插入
+        contractConfigMapper.insert(config);
+        // 记录操作日志上下文
+        LogRecordContext.putVariable("isConfigUpdate", Boolean.FALSE);
+        LogRecordContext.putVariable("configId", config.getId());
+    }
+
+}

+ 18 - 7
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java

@@ -1,9 +1,10 @@
 package cn.iocoder.yudao.module.crm.service.contract;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-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.controller.admin.contract.vo.contract.CrmContractPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 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;
@@ -109,10 +110,20 @@ public interface CrmContractService {
      * 数据权限:基于 {@link CrmCustomerDO} 读取
      *
      * @param pageReqVO 分页查询
-     * @return 联系人分页
+     * @return 合同分页
      */
     PageResult<CrmContractDO> getContractPageByCustomerId(CrmContractPageReqVO pageReqVO);
 
+    /**
+     * 获得合同分页,基于指定商机
+     *
+     * 数据权限:基于 {@link CrmBusinessDO} 读取
+     *
+     * @param pageReqVO 分页查询
+     * @return 合同分页
+     */
+    PageResult<CrmContractDO> getContractPageByBusinessId(CrmContractPageReqVO pageReqVO);
+
     /**
      * 查询属于某个联系人的合同数量
      *
@@ -151,14 +162,14 @@ public interface CrmContractService {
      * @param userId 用户编号
      * @return 提醒数量
      */
-    Long getCheckContractCount(Long userId);
+    Long getAuditContractCount(Long userId);
 
     /**
-     * 获得即将到期的合同数量
+     * 获得即将到期(提醒)的合同数量
      *
      * @param userId 用户编号
      * @return 提醒数量
      */
-    Long getEndContractCount(Long userId);
+    Long getRemindContractCount(Long userId);
 
 }

+ 31 - 13
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java

@@ -10,9 +10,10 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
-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.controller.admin.contract.vo.contract.CrmContractPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
 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.mysql.contract.CrmContractMapper;
@@ -83,6 +84,8 @@ public class CrmContractServiceImpl implements CrmContractService {
     private CrmBusinessService businessService;
     @Resource
     private CrmContactService contactService;
+    @Resource
+    private CrmContractConfigService contractConfigService;
 
     @Resource
     private AdminUserApi adminUserApi;
@@ -172,8 +175,6 @@ public class CrmContractServiceImpl implements CrmContractService {
         }
     }
 
-    // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
-
     /**
      * 校验关联数据是否存在
      *
@@ -314,7 +315,7 @@ public class CrmContractServiceImpl implements CrmContractService {
         contractMapper.updateById(new CrmContractDO().setId(id).setAuditStatus(auditStatus));
     }
 
-    //======================= 查询相关 =======================
+    // ======================= 查询相关 =======================
 
     @Override
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.READ)
@@ -332,7 +333,16 @@ public class CrmContractServiceImpl implements CrmContractService {
 
     @Override
     public PageResult<CrmContractDO> getContractPage(CrmContractPageReqVO pageReqVO, Long userId) {
-        return contractMapper.selectPage(pageReqVO, userId);
+        // 1. 即将到期,需要查询合同配置
+        CrmContractConfigDO config = null;
+        if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) {
+            config = contractConfigService.getContractConfig();
+            if (config != null && Boolean.FALSE.equals(config.getNotifyEnabled())) {
+                config = null;
+            }
+        }
+        // 2. 查询分页
+        return contractMapper.selectPage(pageReqVO, userId, config);
     }
 
     @Override
@@ -341,6 +351,12 @@ public class CrmContractServiceImpl implements CrmContractService {
         return contractMapper.selectPageByCustomerId(pageReqVO);
     }
 
+    @Override
+    @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#pageReqVO.businessId", level = CrmPermissionLevelEnum.READ)
+    public PageResult<CrmContractDO> getContractPageByBusinessId(CrmContractPageReqVO pageReqVO) {
+        return contractMapper.selectPageByBusinessId(pageReqVO);
+    }
+
     @Override
     public Long getContractCountByContactId(Long contactId) {
         return contractMapper.selectCountByContactId(contactId);
@@ -361,16 +377,18 @@ public class CrmContractServiceImpl implements CrmContractService {
         return contractProductMapper.selectListByContractId(contactId);
     }
 
-    // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒;
-
     @Override
-    public Long getCheckContractCount(Long userId) {
-        return contractMapper.selectCheckContractCount(userId);
+    public Long getAuditContractCount(Long userId) {
+        return contractMapper.selectCountByAudit(userId);
     }
 
     @Override
-    public Long getEndContractCount(Long userId) {
-        return contractMapper.selectEndContractCount(userId);
+    public Long getRemindContractCount(Long userId) {
+        CrmContractConfigDO config = contractConfigService.getContractConfig();
+        if (config == null || Boolean.FALSE.equals(config.getNotifyEnabled())) {
+            return 0L;
+        }
+        return contractMapper.selectCountByRemind(userId, config);
     }
 
 }

+ 3 - 12
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java

@@ -26,26 +26,16 @@ public class CrmCustomerPoolConfigServiceImpl implements CrmCustomerPoolConfigSe
     @Resource
     private CrmCustomerPoolConfigMapper customerPoolConfigMapper;
 
-    /**
-     * 获得客户公海配置
-     *
-     * @return 客户公海配置
-     */
     @Override
     public CrmCustomerPoolConfigDO getCustomerPoolConfig() {
         return customerPoolConfigMapper.selectOne();
     }
 
-    /**
-     * 保存客户公海配置
-     *
-     * @param saveReqVO 更新信息
-     */
     @Override
     @LogRecord(type = CRM_CUSTOMER_POOL_CONFIG_TYPE, subType = CRM_CUSTOMER_POOL_CONFIG_SUB_TYPE, bizNo = "{{#poolConfigId}}",
             success = CRM_CUSTOMER_POOL_CONFIG_SUCCESS)
     public void saveCustomerPoolConfig(CrmCustomerPoolConfigSaveReqVO saveReqVO) {
-        // 存在,则进行更新
+        // 1. 存在,则进行更新
         CrmCustomerPoolConfigDO dbConfig = getCustomerPoolConfig();
         CrmCustomerPoolConfigDO poolConfig = BeanUtils.toBean(saveReqVO, CrmCustomerPoolConfigDO.class);
         if (Objects.nonNull(dbConfig)) {
@@ -55,7 +45,8 @@ public class CrmCustomerPoolConfigServiceImpl implements CrmCustomerPoolConfigSe
             LogRecordContext.putVariable("poolConfigId", poolConfig.getId());
             return;
         }
-        // 不存在,则进行插入
+
+        // 2. 不存在,则进行插入
         customerPoolConfigMapper.insert(poolConfig);
         // 记录操作日志上下文
         LogRecordContext.putVariable("isPoolConfigUpdate", Boolean.FALSE);

+ 4 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java

@@ -258,11 +258,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
     @Override
     public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers,
                                                       CrmCustomerImportReqVO importReqVO) {
+        // 校验非空
+        importCustomers = filterList(importCustomers, item -> Objects.nonNull(item.getName()));
         if (CollUtil.isEmpty(importCustomers)) {
             throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY);
         }
-        // 因为有下拉所以需要过滤掉空行
-        importCustomers = filterList(importCustomers, item -> Objects.nonNull(item.getName()));
+
+        // 逐条处理
         CrmCustomerImportRespVO respVO = CrmCustomerImportRespVO.builder().createCustomerNames(new ArrayList<>())
                 .updateCustomerNames(new ArrayList<>()).failureCustomerNames(new LinkedHashMap<>()).build();
         importCustomers.forEach(importCustomer -> {

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java

@@ -69,6 +69,9 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
         crmFollowUpRecordMapper.insert(record);
 
         // 2. 更新 bizId 对应的记录
+        if (ObjUtil.equal(CrmBizTypeEnum.CRM_CUSTOMER.getType(), record.getBizType())) { // 更新客户跟进信息
+            customerService.updateCustomerFollowUp(record.getBizId(), record.getNextTime(), record.getContent());
+        }
         if (ObjUtil.equal(CrmBizTypeEnum.CRM_BUSINESS.getType(), record.getBizType())) { // 更新商机跟进信息
             businessService.updateBusinessFollowUp(record.getBizId(), record.getNextTime(), record.getContent());
         }
@@ -81,9 +84,6 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
         if (ObjUtil.equal(CrmBizTypeEnum.CRM_CONTRACT.getType(), record.getBizType())) { // 更新合同跟进信息
             contractService.updateContractFollowUp(record.getBizId(), record.getNextTime(), record.getContent());
         }
-        if (ObjUtil.equal(CrmBizTypeEnum.CRM_CUSTOMER.getType(), record.getBizType())) { // 更新客户跟进信息
-            customerService.updateCustomerFollowUp(record.getBizId(), record.getNextTime(), record.getContent());
-        }
 
         // 3.1 更新 contactIds 对应的记录,只更新 nextTime
         if (CollUtil.isNotEmpty(createReqVO.getContactIds())) {

+ 5 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java

@@ -2,16 +2,16 @@ package cn.iocoder.yudao.module.crm.service.permission;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+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.permission.vo.CrmPermissionUpdateReqVO;
-import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
-import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
+import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
@@ -74,8 +74,9 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
         // 1. 校验存在
         validatePermissionExists(updateReqVO.getIds());
         // 2. 更新
-        List<CrmPermissionDO> updateDO = CrmPermissionConvert.INSTANCE.convertList(updateReqVO);
-        permissionMapper.updateBatch(updateDO);
+        List<CrmPermissionDO> updateList = CollectionUtils.convertList(updateReqVO.getIds(),
+                id -> new CrmPermissionDO().setId(id).setLevel(updateReqVO.getLevel()));
+        permissionMapper.updateBatch(updateList);
     }
 
     private void validatePermissionExists(Collection<Long> ids) {

+ 13 - 13
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingService.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingService.java

@@ -1,17 +1,17 @@
-package cn.iocoder.yudao.module.crm.service.bi;
+package cn.iocoder.yudao.module.crm.service.statistics;
 
 
-import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;
 
 import java.util.List;
 
 /**
- * CRM BI 排行榜 Service 接口
+ * CRM 排行榜统计 Service 接口
  *
  * @author anhaohao
  */
-public interface CrmBiRankingService {
+public interface CrmStatisticsRankingService {
 
     /**
      * 获得合同金额排行榜
@@ -19,7 +19,7 @@ public interface CrmBiRankingService {
      * @param rankReqVO 排行参数
      * @return 合同金额排行榜
      */
-    List<CrmBiRanKRespVO> getContractPriceRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 获得回款金额排行榜
@@ -27,7 +27,7 @@ public interface CrmBiRankingService {
      * @param rankReqVO 排行参数
      * @return 回款金额排行榜
      */
-    List<CrmBiRanKRespVO> getReceivablePriceRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 获得签约合同数量排行榜
@@ -35,7 +35,7 @@ public interface CrmBiRankingService {
      * @param rankReqVO 排行参数
      * @return 签约合同数量排行榜
      */
-    List<CrmBiRanKRespVO> getContractCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 获得产品销量排行榜
@@ -43,7 +43,7 @@ public interface CrmBiRankingService {
      * @param rankReqVO 排行参数
      * @return 产品销量排行榜
      */
-    List<CrmBiRanKRespVO> getProductSalesRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 获得新增客户数排行榜
@@ -51,7 +51,7 @@ public interface CrmBiRankingService {
      * @param rankReqVO 排行参数
      * @return 新增客户数排行榜
      */
-    List<CrmBiRanKRespVO> getCustomerCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 获得联系人数量排行榜
@@ -59,7 +59,7 @@ public interface CrmBiRankingService {
      * @param rankReqVO 排行参数
      * @return 联系人数量排行榜
      */
-    List<CrmBiRanKRespVO> getContactsCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 获得跟进次数排行榜
@@ -67,7 +67,7 @@ public interface CrmBiRankingService {
      * @param rankReqVO 排行参数
      * @return 跟进次数排行榜
      */
-    List<CrmBiRanKRespVO> getFollowCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO);
 
     /**
      * 获得跟进客户数排行榜
@@ -75,6 +75,6 @@ public interface CrmBiRankingService {
      * @param rankReqVO 排行参数
      * @return 跟进客户数排行榜
      */
-    List<CrmBiRanKRespVO> getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO);
+    List<CrmStatisticsRanKRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO);
 
 }

+ 28 - 28
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java → yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingServiceImpl.java

@@ -1,10 +1,10 @@
-package cn.iocoder.yudao.module.crm.service.bi;
+package cn.iocoder.yudao.module.crm.service.statistics;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO;
-import cn.iocoder.yudao.module.crm.dal.mysql.bi.CrmBiRankingMapper;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;
+import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsRankingMapper;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -23,16 +23,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 
 /**
- * CRM BI 排行榜 Service 实现类
+ * CRM 排行榜统计 Service 实现类
  *
  * @author anhaohao
  */
 @Service
 @Validated
-public class CrmBiRankingServiceImpl implements CrmBiRankingService {
+public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingService {
 
     @Resource
-    private CrmBiRankingMapper biRankingMapper;
+    private CrmStatisticsRankingMapper rankMapper;
 
     @Resource
     private AdminUserApi adminUserApi;
@@ -40,43 +40,43 @@ public class CrmBiRankingServiceImpl implements CrmBiRankingService {
     private DeptApi deptApi;
 
     @Override
-    public List<CrmBiRanKRespVO> getContractPriceRank(CrmBiRankReqVO rankReqVO) {
-        return getRank(rankReqVO, biRankingMapper::selectContractPriceRank);
+    public List<CrmStatisticsRanKRespVO> getContractPriceRank(CrmStatisticsRankReqVO rankReqVO) {
+        return getRank(rankReqVO, rankMapper::selectContractPriceRank);
     }
 
     @Override
-    public List<CrmBiRanKRespVO> getReceivablePriceRank(CrmBiRankReqVO rankReqVO) {
-        return getRank(rankReqVO, biRankingMapper::selectReceivablePriceRank);
+    public List<CrmStatisticsRanKRespVO> getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO) {
+        return getRank(rankReqVO, rankMapper::selectReceivablePriceRank);
     }
 
     @Override
-    public List<CrmBiRanKRespVO> getContractCountRank(CrmBiRankReqVO rankReqVO) {
-        return getRank(rankReqVO, biRankingMapper::selectContractCountRank);
+    public List<CrmStatisticsRanKRespVO> getContractCountRank(CrmStatisticsRankReqVO rankReqVO) {
+        return getRank(rankReqVO, rankMapper::selectContractCountRank);
     }
 
     @Override
-    public List<CrmBiRanKRespVO> getProductSalesRank(CrmBiRankReqVO rankReqVO) {
-        return getRank(rankReqVO, biRankingMapper::selectProductSalesRank);
+    public List<CrmStatisticsRanKRespVO> getProductSalesRank(CrmStatisticsRankReqVO rankReqVO) {
+        return getRank(rankReqVO, rankMapper::selectProductSalesRank);
     }
 
     @Override
-    public List<CrmBiRanKRespVO> getCustomerCountRank(CrmBiRankReqVO rankReqVO) {
-        return getRank(rankReqVO, biRankingMapper::selectCustomerCountRank);
+    public List<CrmStatisticsRanKRespVO> getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
+        return getRank(rankReqVO, rankMapper::selectCustomerCountRank);
     }
 
     @Override
-    public List<CrmBiRanKRespVO> getContactsCountRank(CrmBiRankReqVO rankReqVO) {
-        return getRank(rankReqVO, biRankingMapper::selectContactsCountRank);
+    public List<CrmStatisticsRanKRespVO> getContactsCountRank(CrmStatisticsRankReqVO rankReqVO) {
+        return getRank(rankReqVO, rankMapper::selectContactsCountRank);
     }
 
     @Override
-    public List<CrmBiRanKRespVO> getFollowCountRank(CrmBiRankReqVO rankReqVO) {
-        return getRank(rankReqVO, biRankingMapper::selectFollowCountRank);
+    public List<CrmStatisticsRanKRespVO> getFollowCountRank(CrmStatisticsRankReqVO rankReqVO) {
+        return getRank(rankReqVO, rankMapper::selectFollowCountRank);
     }
 
     @Override
-    public List<CrmBiRanKRespVO> getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO) {
-        return getRank(rankReqVO, biRankingMapper::selectFollowCustomerCountRank);
+    public List<CrmStatisticsRanKRespVO> getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) {
+        return getRank(rankReqVO, rankMapper::selectFollowCustomerCountRank);
     }
 
     /**
@@ -86,18 +86,18 @@ public class CrmBiRankingServiceImpl implements CrmBiRankingService {
      * @param rankFunction 排行榜方法
      * @return 排行版数据
      */
-    private List<CrmBiRanKRespVO> getRank(CrmBiRankReqVO rankReqVO, Function<CrmBiRankReqVO, List<CrmBiRanKRespVO>> rankFunction) {
+    private List<CrmStatisticsRanKRespVO> getRank(CrmStatisticsRankReqVO rankReqVO, Function<CrmStatisticsRankReqVO, List<CrmStatisticsRanKRespVO>> rankFunction) {
         // 1. 获得用户编号数组
         rankReqVO.setUserIds(getUserIds(rankReqVO.getDeptId()));
         if (CollUtil.isEmpty(rankReqVO.getUserIds())) {
             return Collections.emptyList();
         }
         // 2. 获得排行数据
-        List<CrmBiRanKRespVO> ranks = rankFunction.apply(rankReqVO);
+        List<CrmStatisticsRanKRespVO> ranks = rankFunction.apply(rankReqVO);
         if (CollUtil.isEmpty(ranks)) {
             return Collections.emptyList();
         }
-        ranks.sort(Comparator.comparing(CrmBiRanKRespVO::getCount).reversed());
+        ranks.sort(Comparator.comparing(CrmStatisticsRanKRespVO::getCount).reversed());
         // 3. 拼接用户信息
         appendUserInfo(ranks);
         return ranks;
@@ -108,8 +108,8 @@ public class CrmBiRankingServiceImpl implements CrmBiRankingService {
      *
      * @param ranks 排行榜数据
      */
-    private void appendUserInfo(List<CrmBiRanKRespVO> ranks) {
-        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(ranks, CrmBiRanKRespVO::getOwnerUserId));
+    private void appendUserInfo(List<CrmStatisticsRanKRespVO> ranks) {
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(ranks, CrmStatisticsRanKRespVO::getOwnerUserId));
         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
         ranks.forEach(rank -> MapUtils.findAndThen(userMap, rank.getOwnerUserId(), user -> {
             rank.setNickname(user.getNickname());

+ 10 - 10
yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml

@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.bi.CrmBiRankingMapper">
+<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsRankingMapper">
 
     <select id="selectContractPriceRank"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
-        SELECT IFNULL(SUM(price), 0) AS count, owner_user_id
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO">
+        SELECT IFNULL(SUM(total_price), 0) AS count, owner_user_id
         FROM crm_contract
         WHERE deleted = 0
         AND audit_status = 20
@@ -18,7 +18,7 @@
     </select>
 
     <select id="selectReceivablePriceRank"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO">
         SELECT IFNULL(SUM(price), 0) AS count, owner_user_id
         FROM crm_receivable
         WHERE deleted = 0
@@ -33,7 +33,7 @@
     </select>
 
     <select id="selectContractCountRank"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO">
         SELECT COUNT(1) AS count, owner_user_id
         FROM crm_contract
         WHERE deleted = 0
@@ -49,7 +49,7 @@
 
     <!-- TODO 待定 这里是否需要关联 crm_contract_product 表,计算销售额 -->
     <select id="selectProductSalesRank"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO">
         SELECT COUNT(1) AS count, owner_user_id
         FROM crm_contract
         WHERE deleted = 0
@@ -64,7 +64,7 @@
     </select>
 
     <select id="selectCustomerCountRank"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO">
         SELECT COUNT(1) AS count, owner_user_id
         FROM crm_customer
         WHERE deleted = 0
@@ -78,7 +78,7 @@
     </select>
 
     <select id="selectContactsCountRank"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO">
         SELECT COUNT(1) AS count, owner_user_id
         FROM crm_contact
         WHERE deleted = 0
@@ -92,7 +92,7 @@
     </select>
 
     <select id="selectFollowCountRank"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO">
         SELECT COUNT(1) AS count, cc.owner_user_id
         FROM crm_follow_up_record AS cfur
         LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
@@ -108,7 +108,7 @@
     </select>
 
     <select id="selectFollowCustomerCountRank"
-            resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
+            resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO">
         SELECT COUNT(DISTINCT cc.id) AS count, cc.owner_user_id
         FROM crm_follow_up_record AS cfur
         LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids)