lzxhqs 1 жил өмнө
parent
commit
510b4fd3de
77 өөрчлөгдсөн 999 нэмэгдсэн , 609 устгасан
  1. 0 7
      yudao-module-crm/yudao-module-crm-api/pom.xml
  2. 1 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
  3. 2 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
  4. 10 13
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
  5. 39 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/message/CrmContactStatusEnum.java
  6. 0 19
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/operatelog/CrmParseFunctionNameConstants.java
  7. 26 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
  8. 0 57
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessBaseVO.java
  9. 0 16
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessCreateReqVO.java
  10. 45 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
  11. 19 7
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
  12. 0 23
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessUpdateReqVO.java
  13. 2 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java
  14. 0 53
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueBaseVO.java
  15. 0 66
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExcelVO.java
  16. 60 7
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java
  17. 14 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
  18. 7 9
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java
  19. 9 8
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
  20. 0 37
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractRespVO.java
  21. 0 14
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractCreateReqVO.java
  22. 83 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java
  23. 35 15
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java
  24. 0 20
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractUpdateReqVO.java
  25. 8 6
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java
  26. 0 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java
  27. 4 5
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigSaveReqVO.java
  28. 26 21
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java
  29. 0 16
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordPageReqVO.java
  30. 10 14
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java
  31. 3 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java
  32. 41 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/CrmMessageController.java
  33. 28 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/vo/CrmTodayCustomerPageReqVO.java
  34. 3 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java
  35. 2 10
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java
  36. 2 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java
  37. 0 5
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java
  38. 0 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java
  39. 2 5
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java
  40. 5 16
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
  41. 2 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java
  42. 57 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
  43. 0 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/followup/CrmFollowUpRecordMapper.java
  44. 12 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java
  45. 44 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBusinessParseFunction.java
  46. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java
  47. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContractParseFunction.java
  48. 3 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerIndustryParseFunction.java
  49. 3 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerLevelParseFunction.java
  50. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java
  51. 1 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerSourceParseFunction.java
  52. 50 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java
  53. 38 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAreaParseFunction.java
  54. 39 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysBooleanParseFunction.java
  55. 45 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java
  56. 39 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysSexParseFunction.java
  57. 11 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
  58. 25 14
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
  59. 32 17
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
  60. 8 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
  61. 9 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
  62. 3 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
  63. 7 7
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
  64. 2 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
  65. 23 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageService.java
  66. 24 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageServiceImpl.java
  67. 2 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java
  68. 3 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
  69. 1 4
      yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java
  70. 4 5
      yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java
  71. 2 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
  72. 0 18
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/operatelog/SysParseFunctionNameConstants.java
  73. 3 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/AdminUserParseFunction.java
  74. 3 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/AreaParseFunction.java
  75. 3 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/BooleanParseFunction.java
  76. 3 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/DeptParseFunction.java
  77. 3 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/SexParseFunction.java

+ 0 - 7
yudao-module-crm/yudao-module-crm-api/pom.xml

@@ -28,13 +28,6 @@
             <artifactId>spring-boot-starter-validation</artifactId>
             <optional>true</optional>
         </dependency>
-        <!-- TODO @puhui999:api 之间,不直接引入哈;然后,logrecord function 在微服务下,这么跑会高不起来,所以每个服务自己写 function -->
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-system-api</artifactId>
-            <version>${revision}</version>
-            <optional>true</optional>
-        </dependency>
     </dependencies>
 
 </project>

+ 1 - 0
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java

@@ -13,5 +13,6 @@ public interface DictTypeConstants {
     String CRM_AUDIT_STATUS = "crm_audit_status"; // CRM 审批状态
     String CRM_PRODUCT_UNIT = "crm_product_unit"; // CRM 产品单位
     String CRM_PRODUCT_STATUS = "crm_product_status"; // CRM 产品状态
+    String CRM_FOLLOW_UP_TYPE = "crm_follow_up_type"; // 跟进方式
 
 }

+ 2 - 0
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java

@@ -14,6 +14,8 @@ public interface ErrorCodeConstants {
 
     // ========== 线索管理 1-020-001-000 ==========
     ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
+    ErrorCode CLUE_ANY_CLUE_NOT_EXISTS = new ErrorCode(1_020_001_001, "线索【{}】不存在");
+    ErrorCode CLUE_ANY_CLUE_ALREADY_TRANSLATED = new ErrorCode(1_020_001_002, "线索【{}】已经转化过了,请勿重复转化");
 
     // ========== 商机管理 1-020-002-000 ==========
     ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在");

+ 10 - 13
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java

@@ -1,8 +1,5 @@
 package cn.iocoder.yudao.module.crm.enums;
 
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.GET_CONTRACT_BY_ID;
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.GET_ADMIN_USER_BY_ID;
-
 /**
  * CRM 操作日志枚举
  * 目的:统一管理,也减少 Service 里各种“复杂”字符串
@@ -25,7 +22,7 @@ public interface LogRecordConstants {
     String CRM_CUSTOMER_DELETE_SUB_TYPE = "删除客户";
     String CRM_CUSTOMER_DELETE_SUCCESS = "删除了客户【{{#customerName}}】";
     String CRM_CUSTOMER_TRANSFER_SUB_TYPE = "转移客户";
-    String CRM_CUSTOMER_TRANSFER_SUCCESS = "将客户【{{#customer.name}}】的负责人从【{" + GET_ADMIN_USER_BY_ID + "{#customer.ownerUserId}}】变更为了【{" + GET_ADMIN_USER_BY_ID + "{#reqVO.newOwnerUserId}}】";
+    String CRM_CUSTOMER_TRANSFER_SUCCESS = "将客户【{{#customer.name}}】的负责人从【{getAdminUserById{#customer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
     String CRM_CUSTOMER_LOCK_SUB_TYPE = "{{#customer.lockStatus ? '解锁客户' : '锁定客户'}}";
     String CRM_CUSTOMER_LOCK_SUCCESS = "{{#customer.lockStatus ? '将客户【' + #customer.name + '】解锁' : '将客户【' + #customer.name + '】锁定'}}";
     String CRM_CUSTOMER_POOL_SUB_TYPE = "客户放入公海";
@@ -59,7 +56,7 @@ public interface LogRecordConstants {
     String CRM_CONTACT_DELETE_SUB_TYPE = "删除联系人";
     String CRM_CONTACT_DELETE_SUCCESS = "删除了联系人【{{#contactName}}】";
     String CRM_CONTACT_TRANSFER_SUB_TYPE = "转移联系人";
-    String CRM_CONTACT_TRANSFER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{" + GET_ADMIN_USER_BY_ID + "{#contact.ownerUserId}}】变更为了【{" + GET_ADMIN_USER_BY_ID + "{#reqVO.newOwnerUserId}}】";
+    String CRM_CONTACT_TRANSFER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
 
     // ======================= CRM_BUSINESS 商机 =======================
 
@@ -71,7 +68,7 @@ public interface LogRecordConstants {
     String CRM_BUSINESS_DELETE_SUB_TYPE = "删除商机";
     String CRM_BUSINESS_DELETE_SUCCESS = "删除了商机【{{#businessName}}】";
     String CRM_BUSINESS_TRANSFER_SUB_TYPE = "转移商机";
-    String CRM_BUSINESS_TRANSFER_SUCCESS = "将商机【{{#business.name}}】的负责人从【{" + GET_ADMIN_USER_BY_ID + "{#business.ownerUserId}}】变更为了【{" + GET_ADMIN_USER_BY_ID + "{#reqVO.newOwnerUserId}}】";
+    String CRM_BUSINESS_TRANSFER_SUCCESS = "将商机【{{#business.name}}】的负责人从【{getAdminUserById{#business.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
 
     // ======================= CRM_CONTRACT 合同 =======================
 
@@ -83,7 +80,7 @@ public interface LogRecordConstants {
     String CRM_CONTRACT_DELETE_SUB_TYPE = "删除合同";
     String CRM_CONTRACT_DELETE_SUCCESS = "删除了合同【{{#contractName}}】";
     String CRM_CONTRACT_TRANSFER_SUB_TYPE = "转移合同";
-    String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{" + GET_ADMIN_USER_BY_ID + "{#contract.ownerUserId}}】变更为了【{" + GET_ADMIN_USER_BY_ID + "{#reqVO.newOwnerUserId}}】";
+    String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
 
     // ======================= CRM_PRODUCT 产品 =======================
 
@@ -109,20 +106,20 @@ public interface LogRecordConstants {
 
     String CRM_RECEIVABLE_TYPE = "CRM 回款";
     String CRM_RECEIVABLE_CREATE_SUB_TYPE = "创建回款";
-    String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{" + GET_CONTRACT_BY_ID + "{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
+    String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
     String CRM_RECEIVABLE_UPDATE_SUB_TYPE = "更新回款";
-    String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{" + GET_CONTRACT_BY_ID + "{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}";
+    String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}";
     String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款";
-    String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{" + GET_CONTRACT_BY_ID + "{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
+    String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
 
     // ======================= CRM_RECEIVABLE_PLAN 回款计划 =======================
 
     String CRM_RECEIVABLE_PLAN_TYPE = "CRM 回款计划";
     String CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE = "创建回款计划";
-    String CRM_RECEIVABLE_PLAN_CREATE_SUCCESS = "创建了合同【{" + GET_CONTRACT_BY_ID + "{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划";
+    String CRM_RECEIVABLE_PLAN_CREATE_SUCCESS = "创建了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划";
     String CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE = "更新回款计划";
-    String CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS = "更新了合同【{" + GET_CONTRACT_BY_ID + "{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划: {_DIFF{#updateReqVO}}";
+    String CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划: {_DIFF{#updateReqVO}}";
     String CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE = "删除回款计划";
-    String CRM_RECEIVABLE_PLAN_DELETE_SUCCESS = "删除了合同【{" + GET_CONTRACT_BY_ID + "{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划";
+    String CRM_RECEIVABLE_PLAN_DELETE_SUCCESS = "删除了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划";
 
 }

+ 39 - 0
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/message/CrmContactStatusEnum.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.crm.enums.message;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * CRM 联系状态
+ *
+ * @author dhb52
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CrmContactStatusEnum implements IntArrayValuable {
+
+    NEEDED_TODAY(1, "今日需联系"),
+    EXPIRED(2, "已逾期"),
+    ALREADY_CONTACT(3, "已联系"),
+    ;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmContactStatusEnum::getType).toArray();
+
+    /**
+     * 状态
+     */
+    private final Integer type;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+}

+ 0 - 19
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/operatelog/CrmParseFunctionNameConstants.java

@@ -1,19 +0,0 @@
-package cn.iocoder.yudao.module.crm.enums.operatelog;
-
-/**
- * functionName 常量枚举
- * 方便别的模块调用
- *
- * @author HUIHUI
- */
-// TODO @puhui999:这个枚举,还是放在对应的 Function 里好。主要考虑,和 Function 实现可以更近一点哈
-public interface CrmParseFunctionNameConstants {
-
-    String GET_CONTACT_BY_ID = "getContactById"; // 获取联系人信息
-    String GET_CUSTOMER_BY_ID = "getCustomerById"; // 获取客户信息
-    String GET_CUSTOMER_INDUSTRY = "getCustomerIndustry"; // 获取客户行业信息
-    String GET_CUSTOMER_LEVEL = "getCustomerLevel"; // 获取客户级别
-    String GET_CUSTOMER_SOURCE = "getCustomerSource"; // 获取客户来源
-    String GET_CONTRACT_BY_ID = "getContractById"; // 获取合同信息
-
-}

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

@@ -3,9 +3,13 @@ package cn.iocoder.yudao.module.crm.controller.admin.business;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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.business.vo.business.*;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
@@ -31,6 +35,7 @@ import java.util.List;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -51,7 +56,6 @@ public class CrmBusinessController {
     @Resource
     private CrmBusinessStatusService businessStatusService;
 
-    // TODO @商机待定:CrmBusinessCreateReqVO、CrmBusinessUpdateReqVO、CrmBusinessRespVO 按照新的 VO 规范
     @PostMapping("/create")
     @Operation(summary = "创建商机")
     @PreAuthorize("@ss.hasPermission('crm:business:create')")
@@ -82,7 +86,26 @@ public class CrmBusinessController {
     @PreAuthorize("@ss.hasPermission('crm:business:query')")
     public CommonResult<CrmBusinessRespVO> getBusiness(@RequestParam("id") Long id) {
         CrmBusinessDO business = businessService.getBusiness(id);
-        return success(CrmBusinessConvert.INSTANCE.convert(business));
+        return success(BeanUtils.toBean(business, CrmBusinessRespVO.class));
+    }
+
+    @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')")
+    public CommonResult<List<CrmBusinessRespVO>> getSimpleContactList() {
+        CrmBusinessPageReqVO reqVO = new CrmBusinessPageReqVO();
+        reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
+        PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(reqVO, getLoginUserId());
+        return success(convertList(pageResult.getList(), business -> // 只返回 id、name 字段
+                new CrmBusinessRespVO().setId(business.getId()).setName(business.getName())));
     }
 
     @GetMapping("/page")

+ 0 - 57
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessBaseVO.java

@@ -1,57 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import jakarta.validation.constraints.NotNull;
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-/**
- * 商机 Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
-@Data
-public class CrmBusinessBaseVO {
-
-    @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
-    @NotNull(message = "商机名称不能为空")
-    private String name;
-
-    @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
-    @NotNull(message = "商机状态类型不能为空")
-    private Long statusTypeId;
-
-    @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotNull(message = "商机状态不能为空")
-    private Long statusId;
-
-    @Schema(description = "下次联系时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime contactNextTime;
-
-    @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
-    @NotNull(message = "客户不能为空")
-    private Long customerId;
-
-    @Schema(description = "预计成交日期")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime dealTime;
-
-    @Schema(description = "商机金额", example = "12371")
-    private Integer price;
-
-    // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题
-    @Schema(description = "整单折扣")
-    private Integer discountPercent;
-
-    @Schema(description = "产品总金额", example = "12025")
-    private BigDecimal productPrice;
-
-    @Schema(description = "备注", example = "随便")
-    private String remark;
-
-}

+ 0 - 16
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessCreateReqVO.java

@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - 商机创建 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmBusinessCreateReqVO extends CrmBusinessBaseVO {
-
-    // TODO @ljileo:新建的时候,应该可以传递添加的产品;
-
-}

+ 45 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java

@@ -1,18 +1,59 @@
 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
 @Schema(description = "管理后台 - 商机 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmBusinessRespVO extends CrmBusinessBaseVO {
+public class CrmBusinessRespVO {
 
     @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
     private Long id;
 
+    @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @NotNull(message = "商机名称不能为空")
+    private String name;
+
+    @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
+    @NotNull(message = "商机状态类型不能为空")
+    private Long statusTypeId;
+
+    @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "商机状态不能为空")
+    private Long statusId;
+
+    @Schema(description = "下次联系时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime contactNextTime;
+
+    @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
+    @NotNull(message = "客户不能为空")
+    private Long customerId;
+
+    @Schema(description = "预计成交日期")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime dealTime;
+
+    @Schema(description = "商机金额", example = "12371")
+    private Integer price;
+
+    // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题
+    @Schema(description = "整单折扣")
+    private Integer discountPercent;
+
+    @Schema(description = "产品总金额", example = "12025")
+    private BigDecimal productPrice;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

+ 19 - 7
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java

@@ -1,8 +1,10 @@
 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
 
 import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;
 import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction;
+import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
@@ -15,9 +17,7 @@ import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
-/**
- * @author lzxhqs
- */
+// TODO @ljileo:DiffLogField function 完善一下
 @Schema(description = "管理后台 - CRM 商机创建/更新 Request VO")
 @Data
 public class CrmBusinessSaveReqVO {
@@ -26,41 +26,52 @@ public class CrmBusinessSaveReqVO {
     private Long id;
 
     @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @DiffLogField(name = "商机名称")
     @NotNull(message = "商机名称不能为空")
     private String name;
 
     @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
+    @DiffLogField(name = "商机状态")
     @NotNull(message = "商机状态类型不能为空")
     private Long statusTypeId;
 
     @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @DiffLogField(name = "商机状态")
     @NotNull(message = "商机状态不能为空")
     private Long statusId;
 
     @Schema(description = "下次联系时间")
+    @DiffLogField(name = "下次联系时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime contactNextTime;
 
     @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
+    @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
     @NotNull(message = "客户不能为空")
     private Long customerId;
 
     @Schema(description = "预计成交日期")
+    @DiffLogField(name = "预计成交日期")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime dealTime;
 
     @Schema(description = "商机金额", example = "12371")
+    @DiffLogField(name = "商机金额")
     private Integer price;
 
+    // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题
     @Schema(description = "整单折扣")
+    @DiffLogField(name = "整单折扣")
     private Integer discountPercent;
 
     @Schema(description = "产品总金额", example = "12025")
+    @DiffLogField(name = "产品总金额")
     private BigDecimal productPrice;
 
     @Schema(description = "备注", example = "随便")
+    @DiffLogField(name = "备注")
     private String remark;
-
+    // TODO @ljileo:修改的时候,应该可以传递添加的产品;
     @Schema(description = "联系人编号", example = "110")
     @NotNull(message = "联系人编号不能为空")
     private Long contactId;
@@ -69,7 +80,8 @@ public class CrmBusinessSaveReqVO {
     @InEnum(CrmBizEndStatus.class)
     private Integer endStatus;
 
-    @Schema(description = "产品列表", example = "")
-    private List<CrmProductSaveReqVO> product = new ArrayList<>();
+    @Schema(description = "商机产品列表", example = "")
+    private List<CrmBusinessProductSaveReqVO> products = new ArrayList<>();
+
 
 }

+ 0 - 23
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessUpdateReqVO.java

@@ -1,23 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
-
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessBaseVO;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-import jakarta.validation.constraints.NotNull;
-
-@Schema(description = "管理后台 - 商机更新 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmBusinessUpdateReqVO extends CrmBusinessBaseVO {
-
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
-    @NotNull(message = "主键不能为空")
-    private Long id;
-
-    // TODO @ljileo:修改的时候,应该可以传递添加的产品;
-
-}

+ 2 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java

@@ -84,8 +84,8 @@ public class CrmClueController {
         pageReqVO.setPageSize(PAGE_SIZE_NONE);
         List<CrmClueDO> list = clueService.getCluePage(pageReqVO, getLoginUserId()).getList();
         // 导出 Excel
-        List<CrmClueExcelVO> datas = BeanUtils.toBean(list, CrmClueExcelVO.class);
-        ExcelUtils.write(response, "线索.xls", "数据", CrmClueExcelVO.class, datas);
+        List<CrmClueRespVO> datas = BeanUtils.toBean(list, CrmClueRespVO.class);
+        ExcelUtils.write(response, "线索.xls", "数据", CrmClueRespVO.class, datas);
     }
 
     @PutMapping("/transfer")

+ 0 - 53
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueBaseVO.java

@@ -1,53 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
-
-import cn.iocoder.yudao.framework.common.validation.Mobile;
-import cn.iocoder.yudao.framework.common.validation.Telephone;
-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotEmpty;
-import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-/**
- * 线索 Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
-@Data
-public class CrmClueBaseVO {
-
-    @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx")
-    @NotEmpty(message = "线索名称不能为空")
-    private String name;
-
-    @Schema(description = "客户 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520")
-    private Long customerId;
-
-    @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime contactNextTime;
-
-    @Schema(description = "电话", example = "18000000000")
-    @Telephone
-    private String telephone;
-
-    @Schema(description = "手机号", example = "18000000000")
-    @Mobile
-    private String mobile;
-
-    @Schema(description = "地址", example = "北京市海淀区")
-    private String address;
-
-    @Schema(description = "最后跟进时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime contactLastTime;
-
-    @Schema(description = "负责人编号")
-    private Long ownerUserId;
-
-    @Schema(description = "备注", example = "随便")
-    private String remark;
-
-}

+ 0 - 66
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueExcelVO.java

@@ -1,66 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
-
-import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import java.util.*;
-import java.time.LocalDateTime;
-import java.time.LocalDateTime;
-import java.time.LocalDateTime;
-import java.time.LocalDateTime;
-
-import com.alibaba.excel.annotation.ExcelProperty;
-import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
-import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
-
-/**
- * 线索 Excel VO
- *
- * @author Wanwan
- */
-@Data
-public class CrmClueExcelVO {
-
-    @ExcelProperty("编号")
-    private Long id;
-
-    @ExcelProperty(value = "转化状态", converter = DictConvert.class)
-    @DictFormat(DictTypeConstants.BOOLEAN_STRING)
-    private Boolean transformStatus;
-
-    @ExcelProperty(value = "跟进状态", converter = DictConvert.class)
-    @DictFormat(DictTypeConstants.BOOLEAN_STRING)
-    private Boolean followUpStatus;
-
-    @ExcelProperty("线索名称")
-    private String name;
-
-    // TODO 这里需要导出成客户名称
-    @ExcelProperty("客户id")
-    private Long customerId;
-
-    @ExcelProperty("下次联系时间")
-    private LocalDateTime contactNextTime;
-
-    @ExcelProperty("电话")
-    private String telephone;
-
-    @ExcelProperty("手机号")
-    private String mobile;
-
-    @ExcelProperty("地址")
-    private String address;
-
-    @ExcelProperty("负责人的用户编号")
-    private Long ownerUserId;
-
-    @ExcelProperty("最后跟进时间")
-    private LocalDateTime contactLastTime;
-
-    @ExcelProperty("备注")
-    private String remark;
-
-    @ExcelProperty("创建时间")
-    private LocalDateTime createTime;
-
-}

+ 60 - 7
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java

@@ -1,27 +1,80 @@
 package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
 
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
+import lombok.Data;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
 
-import jakarta.validation.constraints.NotNull;
 import java.time.LocalDateTime;
 
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
 @Schema(description = "管理后台 - 线索 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class CrmClueRespVO extends CrmClueBaseVO {
+@ExcelIgnoreUnannotated
+public class CrmClueRespVO {
 
     @Schema(description = "编号,主键自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "10969")
+    @ExcelProperty("编号")
     private Long id;
 
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
-
     @Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    @ExcelProperty(value = "转化状态", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.BOOLEAN_STRING)
     private Boolean transformStatus;
 
     @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    @ExcelProperty(value = "跟进状态", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.BOOLEAN_STRING)
     private Boolean followUpStatus;
 
+    @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx")
+    @ExcelProperty("线索名称")
+    private String name;
+
+    @Schema(description = "客户 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520")
+    // TODO 这里需要导出成客户名称
+    @ExcelProperty("客户id")
+    private Long customerId;
+
+    @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ExcelProperty("下次联系时间")
+    private LocalDateTime contactNextTime;
+
+    @Schema(description = "电话", example = "18000000000")
+    @ExcelProperty("电话")
+    private String telephone;
+
+    @Schema(description = "手机号", example = "18000000000")
+    @ExcelProperty("手机号")
+    private String mobile;
+
+    @Schema(description = "地址", example = "北京市海淀区")
+    @ExcelProperty("地址")
+    private String address;
+
+    @Schema(description = "负责人编号")
+    @ExcelProperty("负责人的用户编号")
+    private Long ownerUserId;
+
+    @Schema(description = "最后跟进时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ExcelProperty("最后跟进时间")
+    private LocalDateTime contactLastTime;
+
+    @Schema(description = "备注", example = "随便")
+    @ExcelProperty("备注")
+    private String remark;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
 }

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

@@ -113,12 +113,23 @@ public class CrmContactController {
         return success(CrmContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList));
     }
 
+    @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) {
+        return success(BeanUtils.toBean(contactService.getContactList(ids, getLoginUserId()), CrmContactRespVO.class));
+    }
+
     @GetMapping("/simple-all-list")
     @Operation(summary = "获得联系人的精简列表")
     @PreAuthorize("@ss.hasPermission('crm:contact:query')")
     public CommonResult<List<CrmContactRespVO>> getSimpleContactList() {
-        List<CrmContactDO> list = contactService.getContactList();
-        return success(convertList(list, contact -> // 只返回 id、name 字段
+        // TODO @puhui999:这种还是搞个 getContactList 方法好点哈;
+        CrmContactPageReqVO reqVO = new CrmContactPageReqVO();
+        reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
+        PageResult<CrmContactDO> pageResult = contactService.getContactPage(reqVO, getLoginUserId());
+        return success(convertList(pageResult.getList(), contact -> // 只返回 id、name 字段
                 new CrmContactRespVO().setId(contact.getId()).setName(contact.getName())));
     }
 
@@ -153,7 +164,7 @@ public class CrmContactController {
     @GetMapping("/operate-log-page")
     @Operation(summary = "获得客户操作日志")
     @PreAuthorize("@ss.hasPermission('crm:customer:query')")
-    public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(@RequestParam("bizId")Long bizId) {
+    public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(@RequestParam("bizId") Long bizId) {
         OperateLogV2PageReqDTO reqVO = new OperateLogV2PageReqDTO();
         reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
         reqVO.setBizType(CRM_CONTACT_TYPE);

+ 7 - 9
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
 
 import cn.iocoder.yudao.framework.common.validation.Mobile;
 import cn.iocoder.yudao.framework.common.validation.Telephone;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.*;
 import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.Email;
@@ -13,9 +14,6 @@ import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.GET_CONTACT_BY_ID;
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.GET_CUSTOMER_BY_ID;
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.*;
 
 @Schema(description = "管理后台 - CRM 联系人创建/更新 Request VO")
 @Data
@@ -30,11 +28,11 @@ public class CrmContactSaveReqVO {
     private String name;
 
     @Schema(description = "客户编号", example = "10795")
-    @DiffLogField(name = "姓名", function = GET_CUSTOMER_BY_ID)
+    @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
     private Long customerId;
 
     @Schema(description = "性别")
-    @DiffLogField(name = "性别", function = GET_SEX)
+    @DiffLogField(name = "性别", function = SysSexParseFunction.NAME)
     private Integer sex;
 
     @Schema(description = "职位")
@@ -42,11 +40,11 @@ public class CrmContactSaveReqVO {
     private String post;
 
     @Schema(description = "是否关键决策人")
-    @DiffLogField(name = "关键决策人", function = GET_BOOLEAN)
+    @DiffLogField(name = "关键决策人", function = SysBooleanParseFunction.NAME)
     private Boolean master;
 
     @Schema(description = "直属上级", example = "23457")
-    @DiffLogField(name = "直属上级", function = GET_CONTACT_BY_ID)
+    @DiffLogField(name = "直属上级", function = CrmContactParseFunction.NAME)
     private Long parentId;
 
     @Schema(description = "手机号", example = "1387171766")
@@ -73,7 +71,7 @@ public class CrmContactSaveReqVO {
     private String email;
 
     @Schema(description = "地区编号", example = "20158")
-    @DiffLogField(name = "所在地", function = GET_AREA)
+    @DiffLogField(name = "所在地", function = SysAreaParseFunction.NAME)
     private Integer areaId;
 
     @Schema(description = "地址")
@@ -86,7 +84,7 @@ public class CrmContactSaveReqVO {
 
     @Schema(description = "负责人用户编号", example = "14334")
     @NotNull(message = "负责人不能为空")
-    @DiffLogField(name = "负责人", function = GET_ADMIN_USER_BY_ID)
+    @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
     private Long ownerUserId;
 
     @Schema(description = "最后跟进时间")

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

@@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 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.*;
@@ -53,14 +54,14 @@ public class CrmContractController {
     @PostMapping("/create")
     @Operation(summary = "创建合同")
     @PreAuthorize("@ss.hasPermission('crm:contract:create')")
-    public CommonResult<Long> createContract(@Valid @RequestBody CrmContractCreateReqVO createReqVO) {
+    public CommonResult<Long> createContract(@Valid @RequestBody CrmContractSaveReqVO createReqVO) {
         return success(contractService.createContract(createReqVO, getLoginUserId()));
     }
 
     @PutMapping("/update")
     @Operation(summary = "更新合同")
     @PreAuthorize("@ss.hasPermission('crm:contract:update')")
-    public CommonResult<Boolean> updateContract(@Valid @RequestBody CrmContractUpdateReqVO updateReqVO) {
+    public CommonResult<Boolean> updateContract(@Valid @RequestBody CrmContractSaveReqVO updateReqVO) {
         contractService.updateContract(updateReqVO);
         return success(true);
     }
@@ -78,22 +79,22 @@ public class CrmContractController {
     @Operation(summary = "获得合同")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('crm:contract:query')")
-    public CommonResult<ContractRespVO> getContract(@RequestParam("id") Long id) {
+    public CommonResult<CrmContractRespVO> getContract(@RequestParam("id") Long id) {
         CrmContractDO contract = contractService.getContract(id);
-        return success(CrmContractConvert.INSTANCE.convert(contract));
+        return success(BeanUtils.toBean(contract, CrmContractRespVO.class));
     }
 
     @GetMapping("/page")
     @Operation(summary = "获得合同分页")
     @PreAuthorize("@ss.hasPermission('crm:contract:query')")
-    public CommonResult<PageResult<ContractRespVO>> getContractPage(@Valid CrmContractPageReqVO pageVO) {
+    public CommonResult<PageResult<CrmContractRespVO>> getContractPage(@Valid CrmContractPageReqVO pageVO) {
         PageResult<CrmContractDO> pageResult = contractService.getContractPage(pageVO, getLoginUserId());
         return success(buildContractDetailPage(pageResult));
     }
 
     @GetMapping("/page-by-customer")
     @Operation(summary = "获得联系人分页,基于指定客户")
-    public CommonResult<PageResult<ContractRespVO>> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) {
+    public CommonResult<PageResult<CrmContractRespVO>> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) {
         Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
         PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageVO);
         return success(buildContractDetailPage(pageResult));
@@ -108,7 +109,7 @@ public class CrmContractController {
         PageResult<CrmContractDO> pageResult = contractService.getContractPage(exportReqVO, getLoginUserId());
         // 导出 Excel
         ExcelUtils.write(response, "合同.xls", "数据", CrmContractExcelVO.class,
-                CrmContractConvert.INSTANCE.convertList02(pageResult.getList()));
+                BeanUtils.toBean(pageResult.getList(), CrmContractExcelVO.class));
     }
 
     /**
@@ -117,7 +118,7 @@ public class CrmContractController {
      * @param pageResult 简单的合同分页结果
      * @return 详细的合同分页结果
      */
-    private PageResult<ContractRespVO> buildContractDetailPage(PageResult<CrmContractDO> pageResult) {
+    private PageResult<CrmContractRespVO> buildContractDetailPage(PageResult<CrmContractDO> pageResult) {
         List<CrmContractDO> contactList = pageResult.getList();
         if (CollUtil.isEmpty(contactList)) {
             return PageResult.empty(pageResult.getTotal());

+ 0 - 37
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/ContractRespVO.java

@@ -1,37 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-import java.time.LocalDateTime;
-
-@Schema(description = "管理后台 - CRM 合同 Response VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class ContractRespVO extends CrmContractBaseVO {
-
-    @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
-    private Long id;
-
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
-
-    @Schema(description = "创建人", example = "25682")
-    private String creator;
-
-    @Schema(description = "创建人名字", example = "test")
-    private String creatorName;
-
-    @Schema(description = "客户名字", example = "test")
-    private String customerName;
-
-    @Schema(description = "负责人", example = "test")
-    private String ownerUserName;
-
-    @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
-    private Integer auditStatus;
-
-}

+ 0 - 14
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractCreateReqVO.java

@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - CRM 合同创建 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmContractCreateReqVO extends CrmContractBaseVO {
-
-}

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

@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+// TODO @puhui999:导出注解哈
+@Schema(description = "管理后台 - CRM 合同 Response VO")
+@Data
+public class CrmContractRespVO {
+
+    @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
+    private Long id;
+
+    @Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    private String name;
+
+    @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336")
+    private Long customerId;
+
+    @Schema(description = "商机编号", example = "10864")
+    private Long businessId;
+
+    @Schema(description = "工作流编号", example = "1043")
+    private Long processInstanceId;
+
+    @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime orderDate;
+
+    @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144")
+    private Long ownerUserId;
+
+    // TODO @芋艿:未来应该支持自动生成;
+    @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101")
+    private String no;
+
+    @Schema(description = "开始时间")
+    private LocalDateTime startTime;
+
+    @Schema(description = "结束时间")
+    private LocalDateTime endTime;
+
+    @Schema(description = "合同金额", example = "5617")
+    private Integer price;
+
+    @Schema(description = "整单折扣")
+    private Integer discountPercent;
+
+    @Schema(description = "产品总金额", example = "19510")
+    private Integer productPrice;
+
+    @Schema(description = "联系人编号", example = "18546")
+    private Long contactId;
+
+    @Schema(description = "公司签约人", example = "14036")
+    private Long signUserId;
+
+    @Schema(description = "最后跟进时间")
+    private LocalDateTime contactLastTime;
+
+    @Schema(description = "备注", example = "你猜")
+    private String remark;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "创建人", example = "25682")
+    private String creator;
+
+    @Schema(description = "创建人名字", example = "test")
+    private String creatorName;
+
+    @Schema(description = "客户名字", example = "test")
+    private String customerName;
+
+    @Schema(description = "负责人", example = "test")
+    private String ownerUserName;
+
+    @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    private Integer auditStatus;
+
+}

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

@@ -1,81 +1,101 @@
 package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
 
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmBusinessParseFunction;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmContactParseFunction;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction;
+import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 
-import jakarta.validation.constraints.NotNull;
 import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
-/**
- * 合同 Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
+@Schema(description = "管理后台 - CRM 合同创建/更新 Request VO")
 @Data
-public class CrmContractBaseVO {
+public class CrmContractSaveReqVO {
 
-    // TODO @dhb52:类似 no 字段的 example 要写xia 哈;
+    @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
+    private Long id;
 
     @Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @DiffLogField(name = "合同名称")
     @NotNull(message = "合同名称不能为空")
     private String name;
 
-    // TODO @dhb52:这个必须传递
-    @Schema(description = "客户编号", example = "18336")
+    @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336")
+    @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
+    @NotNull(message = "客户编号不能为空")
     private Long customerId;
 
     @Schema(description = "商机编号", example = "10864")
+    @DiffLogField(name = "商机", function = CrmBusinessParseFunction.NAME)
     private Long businessId;
 
     @Schema(description = "工作流编号", example = "1043")
+    @DiffLogField(name = "工作流编号")
     private Long processInstanceId;
 
-    // TODO @dhb52:这个必须传递
-    @Schema(description = "下单日期")
+    @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED)
+    @DiffLogField(name = "下单日期")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @NotNull(message = "下单日期不能为空")
     private LocalDateTime orderDate;
 
-    // TODO @dhb52:这个必须传递
-    @Schema(description = "负责人的用户编号", example = "17144")
+    @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144")
+    @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
+    @NotNull(message = "负责人不能为空")
     private Long ownerUserId;
 
     // TODO @芋艿:未来应该支持自动生成;
-    // TODO @dhb52:这个必须传递;
-    @Schema(description = "合同编号")
+    @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101")
+    @DiffLogField(name = "合同编号")
+    @NotNull(message = "合同编号不能为空")
     private String no;
 
     @Schema(description = "开始时间")
+    @DiffLogField(name = "开始时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime startTime;
 
     @Schema(description = "结束时间")
+    @DiffLogField(name = "结束时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime endTime;
 
     @Schema(description = "合同金额", example = "5617")
+    @DiffLogField(name = "合同金额")
     private Integer price;
 
     @Schema(description = "整单折扣")
+    @DiffLogField(name = "整单折扣")
     private Integer discountPercent;
 
     @Schema(description = "产品总金额", example = "19510")
+    @DiffLogField(name = "产品总金额")
     private Integer productPrice;
 
     @Schema(description = "联系人编号", example = "18546")
+    @DiffLogField(name = "联系人", function = CrmContactParseFunction.NAME)
     private Long contactId;
 
     @Schema(description = "公司签约人", example = "14036")
+    @DiffLogField(name = "公司签约人", function = SysAdminUserParseFunction.NAME)
     private Long signUserId;
 
     @Schema(description = "最后跟进时间")
+    @DiffLogField(name = "最后跟进时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime contactLastTime;
 
     @Schema(description = "备注", example = "你猜")
+    @DiffLogField(name = "备注")
     private String remark;
 
     // TODO @dhb52:增加一个 status 字段:具体有哪些值,你来枚举下;主要页面上有个【草稿】【提交审核】的流程,可以看看。然后要对接工作流,这块也可以看看,不确定的地方问我。
 
+
 }

+ 0 - 20
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractUpdateReqVO.java

@@ -1,20 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-import jakarta.validation.constraints.NotNull;
-
-@Schema(description = "管理后台 - CRM 合同更新 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmContractUpdateReqVO extends CrmContractBaseVO {
-
-    @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
-    @NotNull(message = "合同编号不能为空")
-    private Long id;
-
-}

+ 8 - 6
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java

@@ -5,6 +5,10 @@ import cn.iocoder.yudao.framework.common.validation.Mobile;
 import cn.iocoder.yudao.framework.common.validation.Telephone;
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerIndustryParseFunction;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerLevelParseFunction;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerSourceParseFunction;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAreaParseFunction;
 import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.Email;
@@ -17,8 +21,6 @@ import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.*;
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.GET_AREA;
 
 @Schema(description = "管理后台 - CRM 客户新增/修改 Request VO")
 @Data
@@ -33,17 +35,17 @@ public class CrmCustomerSaveReqVO {
     private String name;
 
     @Schema(description = "所属行业", example = "1")
-    @DiffLogField(name = "所属行业", function = GET_CUSTOMER_INDUSTRY)
+    @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME)
     @DictFormat(CRM_CUSTOMER_INDUSTRY)
     private Integer industryId;
 
     @Schema(description = "客户等级", example = "2")
-    @DiffLogField(name = "客户等级", function = GET_CUSTOMER_LEVEL)
+    @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME)
     @InEnum(CrmCustomerLevelEnum.class)
     private Integer level;
 
     @Schema(description = "客户来源", example = "3")
-    @DiffLogField(name = "客户来源", function = GET_CUSTOMER_SOURCE)
+    @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME)
     private Integer source;
 
     @Schema(description = "手机", example = "18000000000")
@@ -86,7 +88,7 @@ public class CrmCustomerSaveReqVO {
     private String remark;
 
     @Schema(description = "地区编号", example = "20158")
-    @DiffLogField(name = "地区编号", function = GET_AREA)
+    @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME)
     private Integer areaId;
 
     @Schema(description = "详细地址", example = "北京市海淀区")

+ 0 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java

@@ -28,6 +28,4 @@ public class CrmCustomerTransferReqVO {
     @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
     private Integer oldOwnerPermissionLevel;
 
-    // TODO @puhui999:联系人、商机、合同的转移
-
 }

+ 4 - 5
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigSaveReqVO.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig;
 
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction;
+import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysDeptParseFunction;
 import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;
@@ -7,9 +9,6 @@ import lombok.Data;
 
 import java.util.List;
 
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.GET_ADMIN_USER_BY_ID;
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.GET_DEPT_BY_ID;
-
 @Schema(description = "管理后台 - 客户限制配置创建/更新 Request VO")
 @Data
 public class CrmCustomerLimitConfigSaveReqVO {
@@ -23,11 +22,11 @@ public class CrmCustomerLimitConfigSaveReqVO {
     private Integer type;
 
     @Schema(description = "规则适用人群")
-    @DiffLogField(name = "规则适用人群", function = GET_ADMIN_USER_BY_ID)
+    @DiffLogField(name = "规则适用人群", function = SysAdminUserParseFunction.NAME)
     private List<Long> userIds;
 
     @Schema(description = "规则适用部门")
-    @DiffLogField(name = "规则适用部门", function = GET_DEPT_BY_ID)
+    @DiffLogField(name = "规则适用部门", function = SysDeptParseFunction.NAME)
     private List<Long> deptIds;
 
     @Schema(description = "数量上限", requiredMode = Schema.RequiredMode.REQUIRED, example = "28384")

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

@@ -1,31 +1,33 @@
 package cn.iocoder.yudao.module.crm.controller.admin.followup;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 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.framework.excel.core.util.ExcelUtils;
-import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 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;
+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.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 io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
-import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import java.io.IOException;
-import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
 
 
 @Tag(name = "管理后台 - 跟进记录")
@@ -36,6 +38,10 @@ public class CrmFollowUpRecordController {
 
     @Resource
     private CrmFollowUpRecordService crmFollowUpRecordService;
+    @Resource
+    private CrmContactService contactService;
+    @Resource
+    private CrmBusinessService businessService;
 
     @PostMapping("/create")
     @Operation(summary = "创建跟进记录")
@@ -75,20 +81,19 @@ public class CrmFollowUpRecordController {
     @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')")
     public CommonResult<PageResult<CrmFollowUpRecordRespVO>> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) {
         PageResult<CrmFollowUpRecordDO> pageResult = crmFollowUpRecordService.getFollowUpRecordPage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, CrmFollowUpRecordRespVO.class));
-    }
-
-    @GetMapping("/export-excel")
-    @Operation(summary = "导出跟进记录 Excel")
-    @PreAuthorize("@ss.hasPermission('crm:follow-up-record:export')")
-    @OperateLog(type = EXPORT)
-    public void exportFollowUpRecordExcel(@Valid CrmFollowUpRecordPageReqVO pageReqVO,
-                                          HttpServletResponse response) throws IOException {
-        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<CrmFollowUpRecordDO> list = crmFollowUpRecordService.getFollowUpRecordPage(pageReqVO).getList();
-        // 导出 Excel
-        ExcelUtils.write(response, "跟进记录.xls", "数据", CrmFollowUpRecordRespVO.class,
-                BeanUtils.toBean(list, CrmFollowUpRecordRespVO.class));
+        /// 拼接数据
+        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);
+        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())));
+        });
+        return success(voPageResult);
     }
 
 }

+ 0 - 16
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordPageReqVO.java

@@ -5,11 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
 @Schema(description = "管理后台 - 跟进记录分页 Request VO")
 @Data
@@ -23,15 +18,4 @@ public class CrmFollowUpRecordPageReqVO extends PageParam {
     @Schema(description = "数据编号", example = "5564")
     private Long bizId;
 
-    @Schema(description = "跟进类型", example = "2")
-    private Integer type;
-
-    @Schema(description = "下次联系时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] nextTime;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
-
 }

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

@@ -1,13 +1,14 @@
 package cn.iocoder.yudao.module.crm.controller.admin.followup.vo;
 
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
-import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_FOLLOW_UP_TYPE;
 
 @Schema(description = "管理后台 - 跟进记录 Response VO")
 @Data
@@ -15,40 +16,35 @@ import java.time.LocalDateTime;
 public class CrmFollowUpRecordRespVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28800")
-    @ExcelProperty("编号")
     private Long id;
 
     @Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    @ExcelProperty("数据类型")
     private Integer bizType;
 
     @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5564")
-    @ExcelProperty("数据编号")
     private Long bizId;
 
     @Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    @ExcelProperty(value = "跟进类型", converter = DictConvert.class)
-    @DictFormat("crm_follow_up_type") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
+    @DictFormat(CRM_FOLLOW_UP_TYPE)
     private Integer type;
 
     @Schema(description = "跟进内容", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("跟进内容")
     private String content;
 
     @Schema(description = "下次联系时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("下次联系时间")
     private LocalDateTime nextTime;
 
     @Schema(description = "关联的商机编号数组")
-    @ExcelProperty("关联的商机编号数组")
-    private String businessIds;
+    private List<Long> businessIds;
+    @Schema(description = "关联的商机名称数组")
+    private List<String> businessNames;
 
     @Schema(description = "关联的联系人编号数组")
-    @ExcelProperty("关联的联系人编号数组")
-    private String contactIds;
+    private List<Long> contactIds;
+    @Schema(description = "关联的联系人名称数组")
+    private List<String> contactNames;
 
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("创建时间")
     private LocalDateTime createTime;
 
 }

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

@@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "管理后台 - 跟进记录新增/修改 Request VO")
 @Data
@@ -35,9 +36,9 @@ public class CrmFollowUpRecordSaveReqVO {
     private LocalDateTime nextTime;
 
     @Schema(description = "关联的商机编号数组")
-    private String businessIds;
+    private List<Long> businessIds;
 
     @Schema(description = "关联的联系人编号数组")
-    private String contactIds;
+    private List<Long> contactIds;
 
 }

+ 41 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/CrmMessageController.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.crm.controller.admin.message;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.message.vo.CrmTodayCustomerPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.service.message.CrmMessageService;
+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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - CRM消息")
+@RestController
+@RequestMapping("/crm/message")
+@Validated
+public class CrmMessageController {
+
+    @Resource
+    private CrmMessageService crmMessageService;
+
+    // TODO 芋艿:未来可能合并到 CrmCustomerController
+    @GetMapping("/todayCustomer") // TODO @dbh52:【优先级低】url 使用中划线,项目规范。然后叫 today-customer-page,通过 page 体现出它是个分页接口
+    @Operation(summary = "今日需联系客户")
+    @PreAuthorize("@ss.hasPermission('crm:message:todayCustomer')")
+    public CommonResult<PageResult<CrmCustomerRespVO>> getTodayCustomerPage(@Valid CrmTodayCustomerPageReqVO pageReqVO) {
+        PageResult<CrmCustomerDO> pageResult = crmMessageService.getTodayCustomerPage(pageReqVO, getLoginUserId());
+        return success(BeanUtils.toBean(pageResult, CrmCustomerRespVO.class));
+    }
+
+}

+ 28 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/message/vo/CrmTodayCustomerPageReqVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.crm.controller.admin.message.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
+import cn.iocoder.yudao.module.crm.enums.message.CrmContactStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 今日需联系客户 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CrmTodayCustomerPageReqVO extends PageParam {
+
+    // TODO @dbh52:CrmContactStatusEnum 可以直接枚举三个 Integer;一般来说,枚举类尽量给数据模型用,这样枚举类少,更聚焦;这里的枚举,更多是专门给这个接口用的哈
+
+    @Schema(description = "联系状态", example = "1")
+    @InEnum(CrmContactStatusEnum.class)
+    private Integer contactStatus;
+
+    @Schema(description = "场景类型", example = "1")
+    @InEnum(CrmSceneTypeEnum.class)
+    private Integer sceneType;
+
+}

+ 3 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java

@@ -42,6 +42,7 @@ import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
 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.operatelog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_PRODUCT_TYPE;
 
 @Tag(name = "管理后台 - CRM 产品")
@@ -102,7 +103,7 @@ public class CrmProductController {
     @Operation(summary = "获得产品分页")
     @PreAuthorize("@ss.hasPermission('crm:product:query')")
     public CommonResult<PageResult<CrmProductRespVO>> getProductPage(@Valid CrmProductPageReqVO pageVO) {
-        PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO);
+        PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO, getLoginUserId());
         return success(new PageResult<>(getProductDetailList(pageResult.getList()), pageResult.getTotal()));
     }
 
@@ -113,7 +114,7 @@ public class CrmProductController {
     public void exportProductExcel(@Valid CrmProductPageReqVO exportReqVO,
                                    HttpServletResponse response) throws IOException {
         exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<CrmProductDO> list = productService.getProductPage(exportReqVO).getList();
+        List<CrmProductDO> list = productService.getProductPage(exportReqVO, getLoginUserId()).getList();
         // 导出 Excel
         ExcelUtils.write(response, "产品.xls", "数据", CrmProductRespVO.class,
                 getProductDetailList(list));

+ 2 - 10
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java

@@ -2,7 +2,8 @@ package cn.iocoder.yudao.module.crm.convert.business;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
@@ -27,15 +28,6 @@ public interface CrmBusinessConvert {
 
     CrmBusinessConvert INSTANCE = Mappers.getMapper(CrmBusinessConvert.class);
 
-    CrmBusinessDO convert(CrmBusinessSaveReqVO bean);
-
-    CrmBusinessDO convert(CrmBusinessUpdateReqVO bean);
-
-    CrmBusinessRespVO convert(CrmBusinessDO bean);
-    List<CrmBusinessRespVO> convert(List<CrmBusinessDO> bean);
-
-    List<CrmBusinessExcelVO> convertList02(List<CrmBusinessDO> list);
-
     @Mapping(target = "bizId", source = "reqVO.id")
     CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId);
 

+ 2 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.crm.convert.businessproduct;
 
-import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
@@ -16,5 +16,5 @@ import org.mapstruct.factory.Mappers;
 public interface CrmBusinessProductConvert {
     CrmBusinessProductConvert INSTANCE = Mappers.getMapper(CrmBusinessProductConvert.class);
 
-    CrmBusinessProductDO convert(CrmProductSaveReqVO product);
+    CrmBusinessProductDO convert(CrmBusinessProductSaveReqVO product);
 }

+ 0 - 5
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.crm.convert.businessstatus;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
@@ -19,10 +18,6 @@ public interface CrmBusinessStatusConvert {
 
     CrmBusinessStatusConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusConvert.class);
 
-    CrmBusinessStatusDO convert(CrmBusinessStatusSaveReqVO bean);
-
-    CrmBusinessStatusRespVO convert(CrmBusinessStatusDO bean);
-
     List<CrmBusinessStatusRespVO> convertList(List<CrmBusinessStatusDO> list);
 
     PageResult<CrmBusinessStatusRespVO> convertPage(PageResult<CrmBusinessStatusDO> page);

+ 0 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.crm.convert.businessstatustype;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@@ -25,7 +24,6 @@ public interface CrmBusinessStatusTypeConvert {
 
     CrmBusinessStatusTypeConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusTypeConvert.class);
 
-    CrmBusinessStatusTypeDO convert(CrmBusinessStatusTypeSaveReqVO bean);
 
     CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean);
 

+ 2 - 5
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java

@@ -4,7 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
@@ -31,10 +32,6 @@ public interface CrmContactConvert {
 
     CrmContactRespVO convert(CrmContactDO bean);
 
-    List<CrmContactRespVO> convertList(List<CrmContactDO> list);
-
-    PageResult<CrmContactRespVO> convertPage(PageResult<CrmContactDO> page);
-
     @Mapping(target = "bizId", source = "reqVO.id")
     CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId);
 

+ 5 - 16
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java

@@ -2,7 +2,8 @@ package cn.iocoder.yudao.module.crm.convert.contract;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
@@ -27,24 +28,12 @@ public interface CrmContractConvert {
 
     CrmContractConvert INSTANCE = Mappers.getMapper(CrmContractConvert.class);
 
-    CrmContractDO convert(CrmContractCreateReqVO bean);
-
-    CrmContractDO convert(CrmContractUpdateReqVO bean);
-
-    ContractRespVO convert(CrmContractDO bean);
-
-    List<ContractRespVO> convertList(List<CrmContractDO> list);
-
-    PageResult<ContractRespVO> convertPage(PageResult<CrmContractDO> page);
-
-    List<CrmContractExcelVO> convertList02(List<CrmContractDO> list);
-
     @Mapping(target = "bizId", source = "reqVO.id")
     CrmPermissionTransferReqBO convert(CrmContractTransferReqVO reqVO, Long userId);
 
-    default PageResult<ContractRespVO> convertPage(PageResult<CrmContractDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
-                                                     List<CrmCustomerDO> customerList) {
-        PageResult<ContractRespVO> voPageResult = BeanUtils.toBean(pageResult, ContractRespVO.class);
+    default PageResult<CrmContractRespVO> convertPage(PageResult<CrmContractDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
+                                                      List<CrmCustomerDO> customerList) {
+        PageResult<CrmContractRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmContractRespVO.class);
         // 拼接关联字段
         Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
         voPageResult.getList().forEach(contract -> {

+ 2 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
 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.enums.DictTypeConstants;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableField;
@@ -14,8 +15,6 @@ import lombok.*;
 import java.time.LocalDateTime;
 import java.util.List;
 
-// TODO @puhui999:界面:做成一个 list 列表,字段是 id、跟进人、跟进方式、跟进时间、跟进内容、下次联系时间、关联联系人、关联商机
-// TODO @puhui999:界面:记录时,弹窗,表单字段是跟进方式、跟进内容、下次联系时间、关联联系人、关联商机;其中关联联系人、关联商机,要做成对应的组件列。
 /**
  * 跟进记录 DO
  *
@@ -55,7 +54,7 @@ public class CrmFollowUpRecordDO extends BaseDO {
     /**
      * 跟进类型
      *
-     * TODO @puhui999:可以搞个数据字典,打电话、发短信、上门拜访、微信、邮箱、QQ
+     * 关联 {@link DictTypeConstants#CRM_FOLLOW_UP_TYPE} 字典
      */
     private Integer type;
     /**

+ 57 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java

@@ -5,13 +5,17 @@ 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.customer.vo.CrmCustomerPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.message.vo.CrmTodayCustomerPageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
+import cn.iocoder.yudao.module.crm.enums.message.CrmContactStatusEnum;
 import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.springframework.lang.Nullable;
 
+import java.time.LocalDate;
 import java.util.Collection;
 import java.util.List;
 
@@ -63,4 +67,57 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
         return selectJoinList(CrmCustomerDO.class, query);
     }
 
+    /**
+     * 待办事项 - 今日需联系客户
+     *
+     * @param pageReqVO 分页请求参数
+     * @param userId    当前用户ID
+     * @return 分页结果
+     */
+    default PageResult<CrmCustomerDO> selectTodayCustomerPage(CrmTodayCustomerPageReqVO pageReqVO, Long userId) {
+        MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
+        // 拼接数据权限的查询条件
+        CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
+                CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null);
+
+        query.selectAll(CrmCustomerDO.class)
+                .leftJoin(CrmFollowUpRecordDO.class, CrmFollowUpRecordDO::getBizId, CrmCustomerDO::getId)
+                .eq(CrmFollowUpRecordDO::getType, CrmBizTypeEnum.CRM_CUSTOMER.getType());
+
+        // 拼接自身的查询条件
+        // TODO @dbh52:这里不仅仅要获得 today、tomorrow。而是 today 要获取今天的 00:00:00 这种;
+        LocalDate today = LocalDate.now();
+        LocalDate tomorrow = today.plusDays(1);
+        LocalDate yesterday = today.minusDays(1);
+        if (pageReqVO.getContactStatus().equals(CrmContactStatusEnum.NEEDED_TODAY.getType())) {
+            // 今天需联系:
+            // 1.【客户】的【下一次联系时间】 是【今天】
+            // 2. 无法找到【今天】创建的【跟进】记录
+            query.between(CrmCustomerDO::getContactNextTime, today, tomorrow)
+                    // TODO @dbh52:是不是查询 CrmCustomerDO::contactLastTime < today?因为今天联系过,应该会更新该字段,减少链表查询;
+                    .between(CrmFollowUpRecordDO::getCreateTime, today, tomorrow)
+                    .isNull(CrmFollowUpRecordDO::getId);
+        } else if (pageReqVO.getContactStatus().equals(CrmContactStatusEnum.EXPIRED.getType())) {
+            // 已逾期:
+            // 1. 【客户】的【下一次联系时间】 <= 【昨天】
+            // 2. 无法找到【今天】创建的【跟进】记录
+            //  TODO @dbh52:是不是 contactNextTime 在当前时间之前,且 contactLastTime < contactNextTime?说白了,就是下次联系时间超过当前时间,且最后联系时间没去联系;
+            query.le(CrmCustomerDO::getContactNextTime, yesterday)
+                    .between(CrmFollowUpRecordDO::getCreateTime, today, tomorrow)
+                    .isNull(CrmFollowUpRecordDO::getId);
+        } else if (pageReqVO.getContactStatus().equals(CrmContactStatusEnum.ALREADY_CONTACT.getType())) {
+            // 已联系:
+            // 1.【客户】的【下一次联系时间】 是【今天】
+            // 2. 找到【今天】创建的【跟进】记录
+            query.between(CrmCustomerDO::getContactNextTime, today, tomorrow)
+                    // TODO @dbh52:contactLastTime 是今天
+                    .between(CrmFollowUpRecordDO::getCreateTime, today, tomorrow)
+                    .isNotNull(CrmFollowUpRecordDO::getId);
+        } else {
+            // TODO: 参数错误,是不是要兜一下底;直接抛出异常就好啦;
+        }
+
+        return selectJoinPage(pageReqVO, CrmCustomerDO.class, query);
+    }
+
 }

+ 0 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/followup/CrmFollowUpRecordMapper.java

@@ -19,9 +19,6 @@ public interface CrmFollowUpRecordMapper extends BaseMapperX<CrmFollowUpRecordDO
         return selectPage(reqVO, new LambdaQueryWrapperX<CrmFollowUpRecordDO>()
                 .eqIfPresent(CrmFollowUpRecordDO::getBizType, reqVO.getBizType())
                 .eqIfPresent(CrmFollowUpRecordDO::getBizId, reqVO.getBizId())
-                .eqIfPresent(CrmFollowUpRecordDO::getType, reqVO.getType())
-                .betweenIfPresent(CrmFollowUpRecordDO::getNextTime, reqVO.getNextTime())
-                .betweenIfPresent(CrmFollowUpRecordDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(CrmFollowUpRecordDO::getId));
     }
 

+ 12 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java

@@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.crm.dal.mysql.product;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
+import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
+import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -15,11 +17,17 @@ import org.apache.ibatis.annotations.Mapper;
 @Mapper
 public interface CrmProductMapper extends BaseMapperX<CrmProductDO> {
 
-    default PageResult<CrmProductDO> selectPage(CrmProductPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<CrmProductDO>()
+    default PageResult<CrmProductDO> selectPage(CrmProductPageReqVO reqVO, Long userId) {
+        MPJLambdaWrapperX<CrmProductDO> query = new MPJLambdaWrapperX<>();
+        // 拼接数据权限的查询条件
+        CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_PRODUCT.getType(),
+                CrmProductDO::getId, userId, null, Boolean.FALSE);
+        // 拼接自身的查询条件
+        query.selectAll(CrmProductDO.class)
                 .likeIfPresent(CrmProductDO::getName, reqVO.getName())
                 .eqIfPresent(CrmProductDO::getStatus, reqVO.getStatus())
-                .orderByDesc(CrmProductDO::getId));
+                .orderByDesc(CrmProductDO::getId);
+        return selectJoinPage(reqVO, CrmProductDO.class, query);
     }
 
     default CrmProductDO selectByNo(String no) {

+ 44 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBusinessParseFunction.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
+import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
+import com.mzt.logapi.service.IParseFunction;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * CRM 商机的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Component
+@Slf4j
+public class CrmBusinessParseFunction implements IParseFunction {
+
+    public static final String NAME = "getBusinessById";
+
+    @Resource
+    private CrmBusinessService businessService;
+
+    @Override
+    public boolean executeBefore() {
+        return true; // 先转换值后对比
+    }
+
+    @Override
+    public String functionName() {
+        return NAME;
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+        CrmBusinessDO businessDO = businessService.getBusiness(Long.parseLong(value.toString()));
+        return businessDO == null ? "" : businessDO.getName();
+    }
+
+}

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java

@@ -8,8 +8,6 @@ import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.GET_CONTACT_BY_ID;
-
 /**
  * CRM 联系人的 {@link IParseFunction} 实现类
  *
@@ -19,6 +17,8 @@ import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameC
 @Slf4j
 public class CrmContactParseFunction implements IParseFunction {
 
+    public static final String NAME = "getContactById";
+
     @Resource
     private CrmContactService contactService;
 
@@ -29,7 +29,7 @@ public class CrmContactParseFunction implements IParseFunction {
 
     @Override
     public String functionName() {
-        return GET_CONTACT_BY_ID;
+        return NAME;
     }
 
     @Override

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContractParseFunction.java

@@ -8,8 +8,6 @@ import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.GET_CONTRACT_BY_ID;
-
 /**
  * CRM 合同的 {@link IParseFunction} 实现类
  *
@@ -19,6 +17,8 @@ import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameC
 @Slf4j
 public class CrmContractParseFunction implements IParseFunction {
 
+    public static final String NAME = "getContractById";
+
     @Resource
     private CrmContractService contractService;
 
@@ -29,7 +29,7 @@ public class CrmContractParseFunction implements IParseFunction {
 
     @Override
     public String functionName() {
-        return GET_CONTRACT_BY_ID;
+        return NAME;
     }
 
     @Override

+ 3 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerIndustryParseFunction.java

@@ -7,7 +7,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.GET_CUSTOMER_INDUSTRY;
 
 /**
  * 行业的 {@link IParseFunction} 实现类
@@ -18,6 +17,8 @@ import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameC
 @Slf4j
 public class CrmCustomerIndustryParseFunction implements IParseFunction {
 
+    public static final String NAME = "getCustomerIndustry";
+
     @Override
     public boolean executeBefore() {
         return true; // 先转换值后对比
@@ -25,7 +26,7 @@ public class CrmCustomerIndustryParseFunction implements IParseFunction {
 
     @Override
     public String functionName() {
-        return GET_CUSTOMER_INDUSTRY;
+        return NAME;
     }
 
     @Override

+ 3 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerLevelParseFunction.java

@@ -7,7 +7,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL;
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.GET_CUSTOMER_LEVEL;
 
 /**
  * 客户等级的 {@link IParseFunction} 实现类
@@ -18,6 +17,8 @@ import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameC
 @Slf4j
 public class CrmCustomerLevelParseFunction implements IParseFunction {
 
+    public static final String NAME = "getCustomerLevel";
+
     @Override
     public boolean executeBefore() {
         return true; // 先转换值后对比
@@ -25,7 +26,7 @@ public class CrmCustomerLevelParseFunction implements IParseFunction {
 
     @Override
     public String functionName() {
-        return GET_CUSTOMER_LEVEL;
+        return NAME;
     }
 
     @Override

+ 3 - 3
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java

@@ -8,8 +8,6 @@ import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.GET_CUSTOMER_BY_ID;
-
 /**
  * CRM 客户的 {@link IParseFunction} 实现类
  *
@@ -19,6 +17,8 @@ import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameC
 @Slf4j
 public class CrmCustomerParseFunction implements IParseFunction {
 
+    public static final String NAME = "getCustomerById";
+
     @Resource
     private CrmCustomerService customerService;
 
@@ -29,7 +29,7 @@ public class CrmCustomerParseFunction implements IParseFunction {
 
     @Override
     public String functionName() {
-        return GET_CUSTOMER_BY_ID;
+        return NAME;
     }
 
     @Override

+ 1 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerSourceParseFunction.java

@@ -7,7 +7,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE;
-import static cn.iocoder.yudao.module.crm.enums.operatelog.CrmParseFunctionNameConstants.GET_CUSTOMER_SOURCE;
 
 /**
  * CRM 客户来源的 {@link IParseFunction} 实现类
@@ -27,7 +26,7 @@ public class CrmCustomerSourceParseFunction implements IParseFunction {
 
     @Override
     public String functionName() {
-        return GET_CUSTOMER_SOURCE;
+        return NAME;
     }
 
     @Override

+ 50 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.mzt.logapi.service.IParseFunction;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 管理员名字的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+@Component
+public class SysAdminUserParseFunction implements IParseFunction {
+
+    public static final String NAME = "getAdminUserById";
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @Override
+    public String functionName() {
+        return NAME;
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+
+        // 获取用户信息
+        AdminUserRespDTO user = adminUserApi.getUser(Long.parseLong(value.toString()));
+        if (user == null) {
+            log.warn("[apply][获取用户{{}}为空", value);
+            return "";
+        }
+        // 返回格式 芋道源码(13888888888)
+        String nickname = user.getNickname();
+        if (StrUtil.isEmpty(user.getMobile())) {
+            return nickname;
+        }
+        return StrUtil.format("{}({})", nickname, user.getMobile());
+    }
+
+}

+ 38 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAreaParseFunction.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
+import com.mzt.logapi.service.IParseFunction;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 地名的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+@Component
+public class SysAreaParseFunction implements IParseFunction {
+
+    public static final String NAME = "getArea";
+
+    @Override
+    public boolean executeBefore() {
+        return true; // 先转换值后对比
+    }
+
+    @Override
+    public String functionName() {
+        return NAME;
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+        return AreaUtils.format(Integer.parseInt(value.toString()));
+    }
+
+}

+ 39 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysBooleanParseFunction.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
+import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
+import com.mzt.logapi.service.IParseFunction;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 是否类型的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Component
+@Slf4j
+public class SysBooleanParseFunction implements IParseFunction {
+
+    public static final String NAME = "getBoolean";
+
+    @Override
+    public boolean executeBefore() {
+        return true; // 先转换值后对比
+    }
+
+    @Override
+    public String functionName() {
+        return NAME;
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+        return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BOOLEAN_STRING, value.toString());
+    }
+
+}

+ 45 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import com.mzt.logapi.service.IParseFunction;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 管理员名字的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+@Component
+public class SysDeptParseFunction implements IParseFunction {
+
+    public static final String NAME = "getDeptById";
+
+    @Resource
+    private DeptApi deptApi;
+
+    @Override
+    public String functionName() {
+        return NAME;
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+
+        // 获取部门信息
+        DeptRespDTO dept = deptApi.getDept(Long.parseLong(value.toString()));
+        if (dept == null) {
+            log.warn("[apply][获取部门{{}}为空", value);
+            return "";
+        }
+        return dept.getName();
+    }
+
+}

+ 39 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysSexParseFunction.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.crm.framework.operatelog.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
+import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
+import com.mzt.logapi.service.IParseFunction;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 行业的 {@link IParseFunction} 实现类
+ *
+ * @author HUIHUI
+ */
+@Component
+@Slf4j
+public class SysSexParseFunction implements IParseFunction {
+
+    public static final String NAME = "getSex";
+
+    @Override
+    public boolean executeBefore() {
+        return true; // 先转换值后对比
+    }
+
+    @Override
+    public String functionName() {
+        return NAME;
+    }
+
+    @Override
+    public String apply(Object value) {
+        if (StrUtil.isEmptyIfStr(value)) {
+            return "";
+        }
+        return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.USER_SEX, value.toString());
+    }
+
+}

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

@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.module.crm.service.business;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@@ -56,6 +58,14 @@ public interface CrmBusinessService {
      */
     List<CrmBusinessDO> getBusinessList(Collection<Long> ids, Long userId);
 
+    /**
+     * 获得商机列表
+     *
+     * @param ids 编号
+     * @return 商机列表
+     */
+    List<CrmBusinessDO> getBusinessList(Collection<Long> ids);
+
     /**
      * 获得商机分页
      *

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

@@ -4,7 +4,10 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
 import cn.iocoder.yudao.module.crm.convert.businessproduct.CrmBusinessProductConvert;
@@ -70,13 +73,15 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE, bizNo = "{{#business.id}}",
             success = CRM_BUSINESS_CREATE_SUCCESS)
     public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
+        createReqVO.setId(null);
         // 1. 插入商机
-        CrmBusinessDO business = CrmBusinessConvert.INSTANCE.convert(createReqVO);
+        CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class)
+                .setOwnerUserId(userId);
         businessMapper.insert(business);
         // TODO 商机待定:插入商机与产品的关联表;校验商品存在
         verifyCrmBusinessProduct(business.getId());
-        if (!createReqVO.getProduct().isEmpty()) {
-            createBusinessProducts(createReqVO.getProduct(), business.getId());
+        if (!createReqVO.getProducts().isEmpty()) {
+            createBusinessProducts(createReqVO.getProducts(), business.getId());
         }
         // TODO 商机待定:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表
         createContactBusiness(business.getId(), createReqVO.getContactId());
@@ -110,13 +115,11 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
      * @description 插入商机产品关联表
      * @author lzxhqs
      */
-    private void createBusinessProducts(List<CrmProductSaveReqVO> products, Long businessId) {
+    private void createBusinessProducts(List<CrmBusinessProductSaveReqVO> products, Long businessId) {
         List<CrmBusinessProductDO> list = new ArrayList<>();
-        for (CrmProductSaveReqVO product : products) {
-            CrmBusinessProductDO businessProductDO = new CrmBusinessProductDO();
-            businessProductDO.setBusinessId(businessId)
-                    .setProductId(product.getId())
-                    .setPrice(BigDecimal.valueOf(product.getPrice()));
+        for (CrmBusinessProductSaveReqVO product : products) {
+            CrmBusinessProductDO businessProductDO = CrmBusinessProductConvert.INSTANCE.convert(product);
+            businessProductDO.setBusinessId(businessId);
             list.add(businessProductDO);
         }
         businessProductMapper.insertBatch(list);
@@ -146,17 +149,17 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
 
         // 2. 更新商机
-        CrmBusinessDO updateObj = CrmBusinessConvert.INSTANCE.convert(updateReqVO);
+        CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class);
         businessMapper.updateById(updateObj);
         // TODO 商机待定:插入商机与产品的关联表;校验商品存在
         verifyCrmBusinessProduct(updateReqVO.getId());
-        if (!updateReqVO.getProduct().isEmpty()) {
-            createBusinessProducts(updateReqVO.getProduct(), updateReqVO.getId());
+        if (!updateReqVO.getProducts().isEmpty()) {
+            createBusinessProducts(updateReqVO.getProducts(), updateReqVO.getId());
         }
 
         // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表
         // 3. 记录操作日志上下文
-        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessUpdateReqVO.class));
+        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
         LogRecordContext.putVariable("businessName", oldBusiness.getName());
     }
 
@@ -237,6 +240,14 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         return businessMapper.selectBatchIds(ids, userId);
     }
 
+    @Override
+    public List<CrmBusinessDO> getBusinessList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return ListUtil.empty();
+        }
+        return businessMapper.selectBatchIds(ids);
+    }
+
     @Override
     public PageResult<CrmBusinessDO> getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
         return businessMapper.selectPage(pageReqVO, userId);

+ 32 - 17
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java

@@ -27,9 +27,12 @@ import org.springframework.validation.annotation.Validated;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS;
 
 /**
@@ -132,23 +135,35 @@ public class CrmClueServiceImpl implements CrmClueService {
     @Transactional(rollbackFor = Exception.class)
     public void translateCustomer(CrmClueTransformReqVO reqVO, Long userId) {
         // 校验线索都存在
-        List<CrmClueDO> clues = getClueList(reqVO.getIds(), userId);
-        if (CollUtil.isEmpty(clues)) {
-            throw exception(CLUE_NOT_EXISTS);
+        Set<Long> clueIds = reqVO.getIds();
+        List<CrmClueDO> clues = getClueList(clueIds, userId);
+        if (CollUtil.isEmpty(clues) || ObjectUtil.notEqual(clues.size(), clueIds.size())) {
+            clueIds.removeAll(convertSet(clues, CrmClueDO::getId));
+            // TODO @min:可以使用 StrUtil.join(",", clueIds) 简化这种常见操作
+            throw exception(CLUE_ANY_CLUE_NOT_EXISTS, clueIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
+        }
+
+        // 过滤出未转化的客户
+        // TODO @min:1)存在已经转化的,直接提示哈。避免操作的用户,以为都转化成功了;2)常见的过滤逻辑,可以使用 CollectionUtils.filterList()
+        List<CrmClueDO> unTransformClues = clues.stream()
+                .filter(clue -> ObjectUtil.notEqual(Boolean.TRUE, clue.getTransformStatus())).toList();
+        // 传入的线索中包含已经转化的情况,抛出业务异常
+        if (ObjectUtil.notEqual(clues.size(), unTransformClues.size())) {
+            // TODO @min:可以使用 StrUtil.join(",", clueIds) 简化这种常见操作
+            clueIds.removeAll(convertSet(unTransformClues, CrmClueDO::getId));
+            throw exception(CLUE_ANY_CLUE_ALREADY_TRANSLATED, clueIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
         }
-        // TODO @min:如果已经转化,则不能重复转化
-
-        // 遍历线索(过滤掉已转化的线索),创建对应的客户
-        clues.stream().filter(clue -> ObjectUtil.notEqual(Boolean.TRUE, clue.getTransformStatus()))
-                .forEach(clue -> {
-                    // 1. 创建客户
-                    CrmCustomerSaveReqVO customerSaveReqVO = BeanUtils.toBean(clue, CrmCustomerSaveReqVO.class).setId(null);
-                    Long customerId = customerService.createCustomer(customerSaveReqVO, userId);
-                    // TODO @puhui999:如果有跟进记录,需要一起转过去;
-                    // 2. 更新线索
-                    clueMapper.updateById(new CrmClueDO().setId(clue.getId())
-                            .setTransformStatus(Boolean.TRUE).setCustomerId(customerId));
-                });
+
+        // 遍历线索(未转化的线索),创建对应的客户
+        unTransformClues.forEach(clue -> {
+            // 1. 创建客户
+            CrmCustomerSaveReqVO customerSaveReqVO = BeanUtils.toBean(clue, CrmCustomerSaveReqVO.class).setId(null);
+            Long customerId = customerService.createCustomer(customerSaveReqVO, userId);
+            // TODO @puhui999:如果有跟进记录,需要一起转过去;
+            // 2. 更新线索
+            clueMapper.updateById(new CrmClueDO().setId(clue.getId())
+                    .setTransformStatus(Boolean.TRUE).setCustomerId(customerId));
+        });
     }
 
     private void validateRelationDataExists(CrmClueSaveReqVO reqVO) {

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

@@ -74,6 +74,14 @@ public interface CrmContactService {
      */
     List<CrmContactDO> getContactList(Collection<Long> ids, Long userId);
 
+    /**
+     * 获得联系人列表
+     *
+     * @param ids 编号
+     * @return 联系人列表
+     */
+    List<CrmContactDO> getContactList(Collection<Long> ids);
+
     /**
      * 获得联系人列表
      *

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

@@ -68,6 +68,7 @@ public class CrmContactServiceImpl implements CrmContactService {
     @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_CREATE_SUB_TYPE, bizNo = "{{#contact.id}}",
             success = CRM_CONTACT_CREATE_SUCCESS)
     public Long createContact(CrmContactSaveReqVO createReqVO, Long userId) {
+        createReqVO.setId(null);
         // 1. 校验
         validateRelationDataExists(createReqVO);
 
@@ -207,6 +208,14 @@ public class CrmContactServiceImpl implements CrmContactService {
         return contactMapper.selectBatchIds(ids, userId);
     }
 
+    @Override
+    public List<CrmContactDO> getContactList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return ListUtil.empty();
+        }
+        return contactMapper.selectBatchIds(ids);
+    }
+
     @Override
     public List<CrmContactDO> getContactList() {
         return contactMapper.selectList();

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

@@ -1,10 +1,9 @@
 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.CrmContractCreateReqVO;
 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.CrmContractUpdateReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import jakarta.validation.Valid;
@@ -26,14 +25,14 @@ public interface CrmContractService {
      * @param userId      用户编号
      * @return 编号
      */
-    Long createContract(@Valid CrmContractCreateReqVO createReqVO, Long userId);
+    Long createContract(@Valid CrmContractSaveReqVO createReqVO, Long userId);
 
     /**
      * 更新合同
      *
      * @param updateReqVO 更新信息
      */
-    void updateContract(@Valid CrmContractUpdateReqVO updateReqVO);
+    void updateContract(@Valid CrmContractSaveReqVO updateReqVO);
 
     /**
      * 删除合同

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

@@ -4,10 +4,9 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractCreateReqVO;
 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.CrmContractUpdateReqVO;
 import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
@@ -50,10 +49,11 @@ public class CrmContractServiceImpl implements CrmContractService {
     @Transactional(rollbackFor = Exception.class)
     @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_CREATE_SUB_TYPE, bizNo = "{{#contract.id}}",
             success = CRM_CONTRACT_CREATE_SUCCESS)
-    public Long createContract(CrmContractCreateReqVO createReqVO, Long userId) {
+    public Long createContract(CrmContractSaveReqVO createReqVO, Long userId) {
+        createReqVO.setId(null);
         // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO
         // 插入合同
-        CrmContractDO contract = CrmContractConvert.INSTANCE.convert(createReqVO);
+        CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class);
         contractMapper.insert(contract);
 
         // 创建数据权限
@@ -71,17 +71,17 @@ public class CrmContractServiceImpl implements CrmContractService {
     @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
             success = CRM_CONTRACT_UPDATE_SUCCESS)
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
-    public void updateContract(CrmContractUpdateReqVO updateReqVO) {
+    public void updateContract(CrmContractSaveReqVO updateReqVO) {
         // TODO @合同待定:只有草稿、审批中,可以编辑;
         // 校验存在
         CrmContractDO oldContract = validateContractExists(updateReqVO.getId());
         // 更新合同
-        CrmContractDO updateObj = CrmContractConvert.INSTANCE.convert(updateReqVO);
+        CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class);
         contractMapper.updateById(updateObj);
         // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO
 
         // 3. 记录操作日志上下文
-        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContract, CrmContractUpdateReqVO.class));
+        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContract, CrmContractSaveReqVO.class));
         LogRecordContext.putVariable("contractName", oldContract.getName());
     }
 

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

@@ -27,10 +27,10 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
 
     @Override
     public Long createFollowUpRecord(CrmFollowUpRecordSaveReqVO createReqVO) {
-        // 插入
         CrmFollowUpRecordDO followUpRecord = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class);
         crmFollowUpRecordMapper.insert(followUpRecord);
-        // 返回
+        // TODO @puhui999:需要更新 bizId 对应的记录;
+        // TODO @puhui999:需要更新 businessIds、contactIds 对应的记录;
         return followUpRecord.getId();
     }
 

+ 23 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageService.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.crm.service.message;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.message.vo.CrmTodayCustomerPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import jakarta.validation.Valid;
+
+/**
+ * CRM 代办消息 Service 接口
+ *
+ * @author dhb52
+ */
+public interface CrmMessageService {
+
+    /**
+     * TODO @dbh52:注释要写下
+     *
+     * @param pageReqVO
+     * @return
+     */
+    PageResult<CrmCustomerDO> getTodayCustomerPage(@Valid CrmTodayCustomerPageReqVO pageReqVO, Long userId);
+
+}

+ 24 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmMessageServiceImpl.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.crm.service.message;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.message.vo.CrmTodayCustomerPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+// TODO @dbh52:注释要写下
+@Component
+@Validated
+public class CrmMessageServiceImpl implements CrmMessageService {
+
+    @Resource
+    private CrmCustomerMapper customerMapper;
+
+    @Override
+    public PageResult<CrmCustomerDO> getTodayCustomerPage(CrmTodayCustomerPageReqVO pageReqVO, Long userId) {
+        return customerMapper.selectTodayCustomerPage(pageReqVO, userId);
+    }
+
+}

+ 2 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java

@@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
-
 import jakarta.validation.Valid;
+
 import java.util.Collection;
 import java.util.List;
 
@@ -60,7 +60,7 @@ public interface CrmProductService {
      * @param pageReqVO 分页查询
      * @return 产品分页
      */
-    PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO);
+    PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO, Long userId);
 
     /**
      * 获得产品

+ 3 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java

@@ -93,7 +93,7 @@ public class CrmProductServiceImpl implements CrmProductService {
         productMapper.updateById(updateObj);
 
         // 3. 记录操作日志上下文
-        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(crmProductDO,CrmProductSaveReqVO.class));
+        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(crmProductDO, CrmProductSaveReqVO.class));
     }
 
     private CrmProductDO validateProductExists(Long id) {
@@ -145,10 +145,9 @@ public class CrmProductServiceImpl implements CrmProductService {
         return productMapper.selectBatchIds(ids);
     }
 
-    // TODO @anhaohao:可以接入数据权限,参考 CrmCustomerService 的 getCustomerPage
     @Override
-    public PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO) {
-        return productMapper.selectPage(pageReqVO);
+    public PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO, Long userId) {
+        return productMapper.selectPage(pageReqVO, userId);
     }
 
     @Override

+ 1 - 4
yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java

@@ -1,17 +1,14 @@
 package cn.iocoder.yudao.module.crm.service.business;
 
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
+import jakarta.annotation.Resource;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
-import jakarta.annotation.Resource;
-
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;

+ 4 - 5
yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java

@@ -2,9 +2,8 @@ package cn.iocoder.yudao.module.crm.service.contract;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractUpdateReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
 import jakarta.annotation.Resource;
@@ -38,7 +37,7 @@ public class ContractServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testCreateContract_success() {
         // 准备参数
-        CrmContractCreateReqVO reqVO = randomPojo(CrmContractCreateReqVO.class);
+        CrmContractSaveReqVO reqVO = randomPojo(CrmContractSaveReqVO.class);
 
         // 调用
         Long contractId = contractService.createContract(reqVO, getLoginUserId());
@@ -55,7 +54,7 @@ public class ContractServiceImplTest extends BaseDbUnitTest {
         CrmContractDO dbContract = randomPojo(CrmContractDO.class);
         contractMapper.insert(dbContract);// @Sql: 先插入出一条存在的数据
         // 准备参数
-        CrmContractUpdateReqVO reqVO = randomPojo(CrmContractUpdateReqVO.class, o -> {
+        CrmContractSaveReqVO reqVO = randomPojo(CrmContractSaveReqVO.class, o -> {
             o.setId(dbContract.getId()); // 设置更新的 ID
         });
 
@@ -69,7 +68,7 @@ public class ContractServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testUpdateContract_notExists() {
         // 准备参数
-        CrmContractUpdateReqVO reqVO = randomPojo(CrmContractUpdateReqVO.class);
+        CrmContractSaveReqVO reqVO = randomPojo(CrmContractSaveReqVO.class);
 
         // 调用, 并断言异常
         assertServiceException(() -> contractService.updateContract(reqVO), CONTRACT_NOT_EXISTS);

+ 2 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java

@@ -168,6 +168,7 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         if (ObjectUtil.notEqual(spuDO.getStatus(), ProductSpuStatusEnum.RECYCLE.getStatus())) {
             throw exception(SPU_NOT_RECYCLE);
         }
+        // TODO 芋艿:【可选】参与活动中的商品,不允许删除???
 
         // 删除 SPU
         productSpuMapper.deleteById(id);
@@ -235,6 +236,7 @@ public class ProductSpuServiceImpl implements ProductSpuService {
     public void updateSpuStatus(ProductSpuUpdateStatusReqVO updateReqVO) {
         // 校验存在
         validateSpuExists(updateReqVO.getId());
+        // TODO 芋艿:【可选】参与活动中的商品,不允许下架???
 
         // 更新状态
         ProductSpuDO productSpuDO = productSpuMapper.selectById(updateReqVO.getId()).setStatus(updateReqVO.getStatus());

+ 0 - 18
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/operatelog/SysParseFunctionNameConstants.java

@@ -1,18 +0,0 @@
-package cn.iocoder.yudao.module.system.enums.operatelog;
-
-/**
- * functionName 常量枚举
- * 方便别的模块调用
- *
- * @author HUIHUI
- */
-// TODO @puhui999:这个枚举,还是放在对应的 Function 里好。主要考虑,和 Function 实现可以更近一点哈
-public interface SysParseFunctionNameConstants {
-
-    String GET_ADMIN_USER_BY_ID = "getAdminUserById"; // 获取用户信息
-    String GET_DEPT_BY_ID = "getDeptById"; // 获取部门信息
-    String GET_AREA = "getArea"; // 获取区域信息
-    String GET_SEX = "getSex"; // 获取性别
-    String GET_BOOLEAN = "getBoolean"; // 获取是否
-
-}

+ 3 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/AdminUserParseFunction.java

@@ -8,8 +8,6 @@ import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.GET_ADMIN_USER_BY_ID;
-
 /**
  * 管理员名字的 {@link IParseFunction} 实现类
  *
@@ -19,12 +17,14 @@ import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNa
 @Component
 public class AdminUserParseFunction implements IParseFunction {
 
+    public static final String NAME = "getAdminUserById";
+
     @Resource
     private AdminUserApi adminUserApi;
 
     @Override
     public String functionName() {
-        return GET_ADMIN_USER_BY_ID;
+        return NAME;
     }
 
     @Override

+ 3 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/AreaParseFunction.java

@@ -6,8 +6,6 @@ import com.mzt.logapi.service.IParseFunction;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.GET_AREA;
-
 /**
  * 地名的 {@link IParseFunction} 实现类
  *
@@ -17,6 +15,8 @@ import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNa
 @Component
 public class AreaParseFunction implements IParseFunction {
 
+    public static final String NAME = "getArea";
+
     @Override
     public boolean executeBefore() {
         return true; // 先转换值后对比
@@ -24,7 +24,7 @@ public class AreaParseFunction implements IParseFunction {
 
     @Override
     public String functionName() {
-        return GET_AREA;
+        return NAME;
     }
 
     @Override

+ 3 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/BooleanParseFunction.java

@@ -7,8 +7,6 @@ import com.mzt.logapi.service.IParseFunction;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.GET_BOOLEAN;
-
 /**
  * 是否类型的 {@link IParseFunction} 实现类
  *
@@ -18,6 +16,8 @@ import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNa
 @Slf4j
 public class BooleanParseFunction implements IParseFunction {
 
+    public static final String NAME = "getBoolean";
+
     @Override
     public boolean executeBefore() {
         return true; // 先转换值后对比
@@ -25,7 +25,7 @@ public class BooleanParseFunction implements IParseFunction {
 
     @Override
     public String functionName() {
-        return GET_BOOLEAN;
+        return NAME;
     }
 
     @Override

+ 3 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/DeptParseFunction.java

@@ -8,8 +8,6 @@ import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.GET_DEPT_BY_ID;
-
 /**
  * 管理员名字的 {@link IParseFunction} 实现类
  *
@@ -19,12 +17,14 @@ import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNa
 @Component
 public class DeptParseFunction implements IParseFunction {
 
+    public static final String NAME = "getDeptById";
+
     @Resource
     private DeptApi deptApi;
 
     @Override
     public String functionName() {
-        return GET_DEPT_BY_ID;
+        return NAME;
     }
 
     @Override

+ 3 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/operatelog/core/SexParseFunction.java

@@ -7,8 +7,6 @@ import com.mzt.logapi.service.IParseFunction;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNameConstants.GET_SEX;
-
 /**
  * 行业的 {@link IParseFunction} 实现类
  *
@@ -18,6 +16,8 @@ import static cn.iocoder.yudao.module.system.enums.operatelog.SysParseFunctionNa
 @Slf4j
 public class SexParseFunction implements IParseFunction {
 
+    public static final String NAME = "getSex";
+
     @Override
     public boolean executeBefore() {
         return true; // 先转换值后对比
@@ -25,7 +25,7 @@ public class SexParseFunction implements IParseFunction {
 
     @Override
     public String functionName() {
-        return GET_SEX;
+        return NAME;
     }
 
     @Override