فهرست منبع

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

# Conflicts:
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
puhui999 1 سال پیش
والد
کامیت
1ad5602a19
47فایلهای تغییر یافته به همراه925 افزوده شده و 1134 حذف شده
  1. 6 10
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
  2. 0 32
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http
  3. 74 27
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
  4. 126 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusController.java
  5. 0 141
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java
  6. 0 75
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java
  7. 104 29
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
  8. 27 34
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
  9. 0 15
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java
  10. 0 19
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java
  11. 35 17
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java
  12. 34 16
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java
  13. 0 15
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java
  14. 0 19
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java
  15. 0 44
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java
  16. 0 29
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java
  17. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
  18. 36 17
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java
  19. 2 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java
  20. 4 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java
  21. 0 23
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java
  22. 0 25
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java
  23. 0 44
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java
  24. 0 46
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java
  25. 41 38
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java
  26. 15 11
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
  27. 2 9
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java
  28. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java
  29. 4 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java
  30. 4 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java
  31. 4 16
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java
  32. 5 22
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java
  33. 0 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java
  34. 0 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java
  35. 13 11
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java
  36. 17 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
  37. 102 61
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
  38. 58 16
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java
  39. 126 31
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java
  40. 0 75
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java
  41. 0 132
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java
  42. 7 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java
  43. 5 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
  44. 2 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
  45. 14 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java
  46. 27 6
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java
  47. 29 12
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java

+ 6 - 10
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java

@@ -22,8 +22,6 @@ public interface ErrorCodeConstants {
     ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在");
     ErrorCode BUSINESS_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除");
 
-    // TODO @lilleo:商机状态、商机类型,都单独错误码段
-
     // ========== 联系人管理 1-020-003-000 ==========
     ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
     ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode(1_020_003_001, "联系人商机关联不存在");
@@ -68,6 +66,7 @@ public interface ErrorCodeConstants {
     // ========== 产品 1_020_008_000 ==========
     ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
     ErrorCode PRODUCT_NO_EXISTS = new ErrorCode(1_020_008_001, "产品编号已存在");
+    ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_020_008_002, "产品【{}】已禁用");
 
     // ========== 产品分类 1_020_009_000 ==========
     ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_009_000, "产品分类不存在");
@@ -77,12 +76,11 @@ public interface ErrorCodeConstants {
     ErrorCode PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_020_009_004, "父分类不能是二级分类");
     ErrorCode product_CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_020_009_005, "存在子分类,无法删除");
 
-    // ========== 商机状态类型 1_020_010_000 ==========
-    ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_010_000, "商机状态类型不存在");
-    ErrorCode BUSINESS_STATUS_TYPE_NAME_EXISTS = new ErrorCode(1_020_010_001, "商机状态类型名称已存在");
-
-    // ========== 商机状态 1_020_011_000 ==========
-    ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_011_000, "商机状态不存在");
+    // ========== 商机状态 1_020_010_000 ==========
+    ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_010_000, "商机状态组不存在");
+    ErrorCode BUSINESS_STATUS_TYPE_NAME_EXISTS = new ErrorCode(1_020_010_001, "商机状态组的名称已存在");
+    ErrorCode BUSINESS_STATUS_UPDATE_FAIL_USED = new ErrorCode(1_020_010_002, "已经被使用的商机状态组,无法进行更新");
+    ErrorCode BUSINESS_STATUS_DELETE_FAIL_USED = new ErrorCode(1_020_010_002, "已经被使用的商机状态组,无法进行删除");
 
     // ========== 客户公海规则设置 1_020_012_000 ==========
     ErrorCode CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_020_012_000, "客户公海配置不存在或未启用");
@@ -92,6 +90,4 @@ public interface ErrorCodeConstants {
     ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
     ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限");
 
-    // ========== 待办消息 1_020_014_000 ==========
-
 }

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

@@ -1,32 +0,0 @@
-### 请求 /transfer
-PUT {{baseUrl}}/crm/business/transfer
-Content-Type: application/json
-Authorization: Bearer {{token}}
-tenant-id: {{adminTenentId}}
-
-{
-  "id": 1,
-  "ownerUserId": 2,
-  "transferType": 2,
-  "permissionType": 2
-}
-
-### 请求 /update
-PUT {{baseUrl}}/crm/business/update
-Content-Type: application/json
-Authorization: Bearer {{token}}
-tenant-id: {{adminTenentId}}
-
-{
-  "id": 1,
-  "name": "2",
-  "statusTypeId": 2,
-  "statusId": 2,
-  "customerId": 1
-}
-
-### 请求 /get
-GET {{baseUrl}}/crm/business/get?id=1024
-Content-Type: application/json
-Authorization: Bearer {{token}}
-tenant-id: {{adminTenentId}}

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

@@ -3,6 +3,8 @@ 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.collection.MapUtils;
+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;
@@ -10,15 +12,20 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
 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.CrmBusinessProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
-import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
+import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -30,13 +37,15 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
 
 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.common.util.collection.CollectionUtils.*;
 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.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
@@ -52,9 +61,16 @@ public class CrmBusinessController {
     @Resource
     private CrmCustomerService customerService;
     @Resource
-    private CrmBusinessStatusTypeService businessStatusTypeService;
+    private CrmBusinessStatusService businessStatusTypeService;
     @Resource
     private CrmBusinessStatusService businessStatusService;
+    @Resource
+    private CrmProductService productService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
 
     @PostMapping("/create")
     @Operation(summary = "创建商机")
@@ -86,9 +102,25 @@ public class CrmBusinessController {
     @PreAuthorize("@ss.hasPermission('crm:business:query')")
     public CommonResult<CrmBusinessRespVO> getBusiness(@RequestParam("id") Long id) {
         CrmBusinessDO business = businessService.getBusiness(id);
-        return success(BeanUtils.toBean(business, CrmBusinessRespVO.class));
+        return success(buildBusinessDetail(business));
+    }
+
+    private CrmBusinessRespVO buildBusinessDetail(CrmBusinessDO business) {
+        if (business == null) {
+            return null;
+        }
+        CrmBusinessRespVO businessVO = buildBusinessDetailList(Collections.singletonList(business)).get(0);
+        // 拼接产品项
+        List<CrmBusinessProductDO> businessProducts = businessService.getBusinessProductListByBusinessId(businessVO.getId());
+        Map<Long, CrmProductDO> productMap = productService.getProductMap(
+                convertSet(businessProducts, CrmBusinessProductDO::getProductId));
+        businessVO.setProducts(BeanUtils.toBean(businessProducts, CrmBusinessRespVO.Product.class, businessProductVO ->
+                MapUtils.findAndThen(productMap, businessProductVO.getProductId(),
+                        product -> businessProductVO.setProductNo(product.getNo()).setProductUnit(product.getUnit()))));
+        return businessVO;
     }
 
+    // TODO 芋艿:处理下
     @GetMapping("/list-by-ids")
     @Operation(summary = "获得商机列表")
     @Parameter(name = "ids", description = "编号", required = true, example = "[1024]")
@@ -97,6 +129,7 @@ public class CrmBusinessController {
         return success(BeanUtils.toBean(businessService.getBusinessList(ids, getLoginUserId()), CrmBusinessRespVO.class));
     }
 
+    // TODO 芋艿:处理下
     @GetMapping("/simple-all-list")
     @Operation(summary = "获得联系人的精简列表")
     @PreAuthorize("@ss.hasPermission('crm:contact:query')")
@@ -113,7 +146,7 @@ public class CrmBusinessController {
     @PreAuthorize("@ss.hasPermission('crm:business:query')")
     public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) {
         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId());
-        return success(buildBusinessDetailPageResult(pageResult));
+        return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
     }
 
     @GetMapping("/page-by-customer")
@@ -123,7 +156,7 @@ public class CrmBusinessController {
             throw exception(CUSTOMER_NOT_EXISTS);
         }
         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO);
-        return success(buildBusinessDetailPageResult(pageResult));
+        return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
     }
 
     @GetMapping("/page-by-contact")
@@ -131,7 +164,7 @@ public class CrmBusinessController {
     @PreAuthorize("@ss.hasPermission('crm:business:query')")
     public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) {
         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByContact(pageReqVO);
-        return success(buildBusinessDetailPageResult(pageResult));
+        return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
     }
 
     @GetMapping("/export-excel")
@@ -141,29 +174,43 @@ public class CrmBusinessController {
     public void exportBusinessExcel(@Valid CrmBusinessPageReqVO exportReqVO,
                                     HttpServletResponse response) throws IOException {
         exportReqVO.setPageSize(PAGE_SIZE_NONE);
-        PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId());
+        List<CrmBusinessDO> list = businessService.getBusinessPage(exportReqVO, getLoginUserId()).getList();
         // 导出 Excel
         ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class,
-                buildBusinessDetailPageResult(pageResult).getList());
+                buildBusinessDetailList(list));
     }
 
-    /**
-     * 构建详细的商机分页结果
-     *
-     * @param pageResult 简单的商机分页结果
-     * @return 详细的商机分页结果
-     */
-    private PageResult<CrmBusinessRespVO> buildBusinessDetailPageResult(PageResult<CrmBusinessDO> pageResult) {
-        if (CollUtil.isEmpty(pageResult.getList())) {
-            return PageResult.empty(pageResult.getTotal());
+    private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return Collections.emptyList();
         }
-        List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.getBusinessStatusTypeList(
-                convertSet(pageResult.getList(), CrmBusinessDO::getStatusTypeId));
-        List<CrmBusinessStatusDO> statusList = businessStatusService.getBusinessStatusList(
-                convertSet(pageResult.getList(), CrmBusinessDO::getStatusId));
-        List<CrmCustomerDO> customerList = customerService.getCustomerList(
-                convertSet(pageResult.getList(), CrmBusinessDO::getCustomerId));
-        return CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList);
+        // 1.1 获取客户列表
+        Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
+                convertSet(list, CrmBusinessDO::getCustomerId));
+        // 1.2 获取创建人、负责人列表
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
+                contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+        // 1.3 获得商机状态组
+        Map<Long, CrmBusinessStatusTypeDO> statusTypeMap = businessStatusTypeService.getBusinessStatusTypeMap(
+                convertSet(list, CrmBusinessDO::getStatusTypeId));
+        Map<Long, CrmBusinessStatusDO> statusMap = businessStatusService.getBusinessStatusMap(
+                convertSet(list, CrmBusinessDO::getStatusId));
+        // 2. 拼接数据
+        return BeanUtils.toBean(list, CrmBusinessRespVO.class, businessVO -> {
+            // 2.1 设置客户名称
+            MapUtils.findAndThen(customerMap, businessVO.getCustomerId(), customer -> businessVO.setCustomerName(customer.getName()));
+            // 2.2 设置创建人、负责人名称
+            MapUtils.findAndThen(userMap, NumberUtils.parseLong(businessVO.getCreator()),
+                    user -> businessVO.setCreatorName(user.getNickname()));
+            MapUtils.findAndThen(userMap, businessVO.getOwnerUserId(), user -> {
+                businessVO.setOwnerUserName(user.getNickname());
+                MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> businessVO.setOwnerUserDeptName(dept.getName()));
+            });
+            // 2.3 设置商机状态
+            MapUtils.findAndThen(statusTypeMap, businessVO.getStatusTypeId(), statusType -> businessVO.setStatusTypeName(statusType.getName()));
+            MapUtils.findAndThen(statusMap, businessVO.getStatusId(), status -> businessVO.setStatusName(status.getName()));
+        });
     }
 
     @PutMapping("/transfer")

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

@@ -0,0 +1,126 @@
+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.PageParam;
+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.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 cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
+import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - CRM 商机状态")
+@RestController
+@RequestMapping("/crm/business-status")
+@Validated
+public class CrmBusinessStatusController {
+
+    @Resource
+    private CrmBusinessStatusService businessStatusTypeService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建商机状态")
+    @PreAuthorize("@ss.hasPermission('crm:business-status:create')")
+    public CommonResult<Long> createBusinessStatus(@Valid @RequestBody CrmBusinessStatusSaveReqVO createReqVO) {
+        return success(businessStatusTypeService.createBusinessStatus(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新商机状态")
+    @PreAuthorize("@ss.hasPermission('crm:business-status:update')")
+    public CommonResult<Boolean> updateBusinessStatus(@Valid @RequestBody CrmBusinessStatusSaveReqVO updateReqVO) {
+        businessStatusTypeService.updateBusinessStatus(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除商机状态")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:business-status:delete')")
+    public CommonResult<Boolean> deleteBusinessStatusType(@RequestParam("id") Long id) {
+        businessStatusTypeService.deleteBusinessStatusType(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得商机状态")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
+    public CommonResult<CrmBusinessStatusRespVO> getBusinessStatusType(@RequestParam("id") Long id) {
+        CrmBusinessStatusTypeDO statusType = businessStatusTypeService.getBusinessStatusType(id);
+        if (statusType == null) {
+            return success(null);
+        }
+        List<CrmBusinessStatusDO> statuses = businessStatusTypeService.getBusinessStatusListByTypeId(id);
+        return success(BeanUtils.toBean(statusType, CrmBusinessStatusRespVO.class,
+                statusTypeVO -> statusTypeVO.setStatuses(BeanUtils.toBean(statuses, CrmBusinessStatusRespVO.Status.class))));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得商机状态分页")
+    @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
+    public CommonResult<PageResult<CrmBusinessStatusRespVO>> getBusinessStatusPage(@Valid PageParam pageReqVO) {
+        // 1. 查询数据
+        PageResult<CrmBusinessStatusTypeDO> pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        // 2. 拼接数据
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSet(pageResult.getList(), statusType -> Long.parseLong(statusType.getCreator())));
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
+                convertSetByFlatMap(pageResult.getList(), CrmBusinessStatusTypeDO::getDeptIds, Collection::stream));
+        return success(BeanUtils.toBean(pageResult, CrmBusinessStatusRespVO.class, statusTypeVO -> {
+            statusTypeVO.setCreator(userMap.get(NumberUtils.parseLong(statusTypeVO.getCreator())).getNickname());
+            statusTypeVO.setDeptNames(convertList(statusTypeVO.getDeptIds(),
+                    deptId -> deptMap.containsKey(deptId) ? deptMap.get(deptId).getName() : null));
+        }));
+    }
+
+    @GetMapping("/type-simple-list")
+    @Operation(summary = "获得商机状态组列表")
+    public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusTypeSimpleList() {
+        List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypeList();
+        // 过滤掉部门不匹配的
+        Long deptId = adminUserApi.getUser(getLoginUserId()).getDeptId();
+        list.removeIf(statusType -> CollUtil.isNotEmpty(statusType.getDeptIds()) && !statusType.getDeptIds().contains(deptId));
+        return success(BeanUtils.toBean(list, CrmBusinessStatusRespVO.class));
+    }
+
+    @GetMapping("/status-simple-list")
+    @Operation(summary = "获得商机状态列表")
+    @Parameter(name = "typeId", description = "商机状态组", required = true, example = "1024")
+    public CommonResult<List<CrmBusinessStatusRespVO.Status>> getBusinessStatusSimpleList(@RequestParam("typeId") Long typeId) {
+        List<CrmBusinessStatusDO> list = businessStatusTypeService.getBusinessStatusListByTypeId(typeId);
+        return success(BeanUtils.toBean(list, CrmBusinessStatusRespVO.Status.class));
+    }
+
+}

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

@@ -1,141 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business;
-
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-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.CollectionUtils;
-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.status.CrmBusinessStatusQueryVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;
-import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusConvert;
-import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusTypeConvert;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
-import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
-import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService;
-import cn.iocoder.yudao.module.system.api.dept.DeptApi;
-import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
-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.Collection;
-import java.util.List;
-import java.util.Set;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
-
-@Tag(name = "管理后台 - CRM 商机状态类型")
-@RestController
-@RequestMapping("/crm/business-status-type")
-@Validated
-public class CrmBusinessStatusTypeController {
-
-    @Resource
-    private CrmBusinessStatusTypeService businessStatusTypeService;
-
-    @Resource
-    private CrmBusinessStatusService businessStatusService;
-
-    @Resource
-    private DeptApi deptApi;
-
-    @PostMapping("/create")
-    @Operation(summary = "创建商机状态类型")
-    @PreAuthorize("@ss.hasPermission('crm:business-status-type:create')")
-    public CommonResult<Long> createBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeSaveReqVO createReqVO) {
-        return success(businessStatusTypeService.createBusinessStatusType(createReqVO));
-    }
-
-    @PutMapping("/update")
-    @Operation(summary = "更新商机状态类型")
-    @PreAuthorize("@ss.hasPermission('crm:business-status-type:update')")
-    public CommonResult<Boolean> updateBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeSaveReqVO updateReqVO) {
-        businessStatusTypeService.updateBusinessStatusType(updateReqVO);
-        return success(true);
-    }
-
-    @DeleteMapping("/delete")
-    @Operation(summary = "删除商机状态类型")
-    @Parameter(name = "id", description = "编号", required = true)
-    @PreAuthorize("@ss.hasPermission('crm:business-status-type:delete')")
-    public CommonResult<Boolean> deleteBusinessStatusType(@RequestParam("id") Long id) {
-        businessStatusTypeService.deleteBusinessStatusType(id);
-        return success(true);
-    }
-
-    @GetMapping("/get")
-    @Operation(summary = "获得商机状态类型")
-    @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
-    public CommonResult<CrmBusinessStatusTypeRespVO> getBusinessStatusType(@RequestParam("id") Long id) {
-        CrmBusinessStatusTypeDO statusType = businessStatusTypeService.getBusinessStatusType(id);
-        // 处理状态回显
-        // TODO @lzxhqs:可以在 businessStatusService 加个 getBusinessStatusListByTypeId 方法,直接返回 List<CrmBusinessStatusDO> 哈,常用的,尽量封装个简单易懂的方法,不用追求绝对通用哈;
-        CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
-        queryVO.setTypeId(id);
-        List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);
-        return success(CrmBusinessStatusTypeConvert.INSTANCE.convert(statusType, statusList));
-    }
-
-    @GetMapping("/page")
-    @Operation(summary = "获得商机状态类型分页")
-    @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
-    public CommonResult<PageResult<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypePage(@Valid CrmBusinessStatusTypePageReqVO pageReqVO) {
-        PageResult<CrmBusinessStatusTypeDO> pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO);
-        // 处理部门回显
-        Set<Long> deptIds = CollectionUtils.convertSetByFlatMap(pageResult.getList(), CrmBusinessStatusTypeDO::getDeptIds,Collection::stream);
-        List<DeptRespDTO> deptList = deptApi.getDeptList(deptIds);
-        return success(CrmBusinessStatusTypeConvert.INSTANCE.convertPage(pageResult, deptList));
-    }
-
-    @GetMapping("/export-excel")
-    @Operation(summary = "导出商机状态类型 Excel")
-    @PreAuthorize("@ss.hasPermission('crm:business-status-type:export')")
-    @OperateLog(type = EXPORT)
-    public void exportBusinessStatusTypeExcel(@Valid CrmBusinessStatusTypePageReqVO pageReqVO,
-              HttpServletResponse response) throws IOException {
-        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO).getList();
-        // 导出 Excel
-        ExcelUtils.write(response, "商机状态类型.xls", "数据", CrmBusinessStatusTypeRespVO.class,
-                        BeanUtils.toBean(list, CrmBusinessStatusTypeRespVO.class));
-    }
-
-    @GetMapping("/get-simple-list")
-    @Operation(summary = "获得商机状态类型列表")
-    @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")
-    public CommonResult<List<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypeList() {
-        CrmBusinessStatusTypeQueryVO queryVO = new CrmBusinessStatusTypeQueryVO();
-        queryVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-        List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.selectList(queryVO);
-        return success(BeanUtils.toBean(list, CrmBusinessStatusTypeRespVO.class));
-    }
-
-    // TODO @ljlleo 这个接口,是不是可以和 getBusinessStatusTypeList 合并成一个?
-    @GetMapping("/get-status-list")
-    @Operation(summary = "获得商机状态列表")
-    @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
-    public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusListByTypeId(@RequestParam("typeId") Long typeId) {
-        CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();
-        queryVO.setTypeId(typeId);
-        List<CrmBusinessStatusDO> list = businessStatusService.selectList(queryVO);
-        return success(CrmBusinessStatusConvert.INSTANCE.convertList(list));
-    }
-
-}

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

@@ -1,75 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
-
-import com.alibaba.excel.annotation.ExcelProperty;
-import lombok.Data;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.Set;
-
-/**
- * 商机 Excel VO
- *
- * @author ljlleo
- */
-@Data
-public class CrmBusinessExcelVO {
-
-    @ExcelProperty("主键")
-    private Long id;
-
-    @ExcelProperty("商机名称")
-    private String name;
-
-    @ExcelProperty("商机状态类型编号")
-    private Long statusTypeId;
-
-    @ExcelProperty("商机状态编号")
-    private Long statusId;
-
-    @ExcelProperty("下次联系时间")
-    private LocalDateTime contactNextTime;
-
-    @ExcelProperty("客户编号")
-    private Long customerId;
-
-    @ExcelProperty("预计成交日期")
-    private LocalDateTime dealTime;
-
-    @ExcelProperty("商机金额")
-    private BigDecimal price;
-
-    @ExcelProperty("整单折扣")
-    private BigDecimal discountPercent;
-
-    @ExcelProperty("产品总金额")
-    private BigDecimal productPrice;
-
-    @ExcelProperty("备注")
-    private String remark;
-
-    @ExcelProperty("负责人的用户编号")
-    private Long ownerUserId;
-
-    @ExcelProperty("创建时间")
-    private LocalDateTime createTime;
-
-    @ExcelProperty("只读权限的用户编号数组")
-    private Set<Long> roUserIds;
-
-    @ExcelProperty("读写权限的用户编号数组")
-    private Set<Long> rwUserIds;
-
-    @ExcelProperty("1赢单2输单3无效")
-    private Integer endStatus;
-
-    @ExcelProperty("结束时的备注")
-    private String endRemark;
-
-    @ExcelProperty("最后跟进时间")
-    private LocalDateTime contactLastTime;
-
-    @ExcelProperty("跟进状态")
-    private Integer followUpStatus;
-
-}

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

@@ -1,69 +1,144 @@
 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
 
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
 import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
+import lombok.NoArgsConstructor;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-@Schema(description = "管理后台 - 商机 Response VO")
+@Schema(description = "管理后台 - CRM 商机 Response VO")
 @Data
+@ExcelIgnoreUnannotated
 public class CrmBusinessRespVO {
 
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
+    @ExcelProperty("编号")
     private Long id;
 
     @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
-    @NotNull(message = "商机名称不能为空")
+    @ExcelProperty("商机名称")
     private String name;
 
-    @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
-    @NotNull(message = "商机状态类型不能为空")
+    @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
+    private Long customerId;
+    @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @ExcelProperty("客户名称")
+    private String customerName;
+
+    @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example ="true")
+    @ExcelProperty("跟进状态")
+    private Boolean followUpStatus;
+
+    @Schema(description = "最后跟进时间")
+    @ExcelProperty("最后跟进时间")
+    private LocalDateTime contactLastTime;
+
+    @Schema(description = "下次联系时间")
+    @ExcelProperty("下次联系时间")
+    private LocalDateTime contactNextTime;
+
+    @Schema(description = "负责人的用户编号", example = "25682")
+    @ExcelProperty("负责人的用户编号")
+    private Long ownerUserId;
+    @Schema(description = "负责人名字", example = "25682")
+    @ExcelProperty("负责人名字")
+    private String ownerUserName;
+    @Schema(description = "负责人部门")
+    @ExcelProperty("负责人部门")
+    private String ownerUserDeptName;
+
+    @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
     private Long statusTypeId;
+    @Schema(description = "商机状组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中")
+    @ExcelProperty("商机状态组")
+    private String statusTypeName;
 
     @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotNull(message = "商机状态不能为空")
     private Long statusId;
+    @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中")
+    @ExcelProperty("商机状态")
+    private String statusName;
 
-    @Schema(description = "下次联系时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime contactNextTime;
+    @Schema
+    @ExcelProperty("1赢单2输单3无效")
+    private Integer endStatus;
 
-    @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
-    @NotNull(message = "客户不能为空")
-    private Long customerId;
+    @ExcelProperty("结束时的备注")
+    private String endRemark;
 
     @Schema(description = "预计成交日期")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ExcelProperty("预计成交日期")
     private LocalDateTime dealTime;
 
-    @Schema(description = "商机金额", example = "12371")
-    private Integer price;
+    @Schema(description = "产品总金额", example = "12025")
+    @ExcelProperty("产品总金额")
+    private BigDecimal totalProductPrice;
 
-    // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题
     @Schema(description = "整单折扣")
-    private Integer discountPercent;
+    @ExcelProperty("整单折扣")
+    private BigDecimal discountPercent;
 
-    @Schema(description = "产品总金额", example = "12025")
-    private BigDecimal productPrice;
+    @Schema(description = "商机总金额", example = "12371")
+    @ExcelProperty("商机总金额")
+    private BigDecimal totalPrice;
 
     @Schema(description = "备注", example = "随便")
+    @ExcelProperty("备注")
     private String remark;
 
+    @Schema(description = "创建人", example = "1024")
+    @ExcelProperty("创建人")
+    private String creator;
+    @Schema(description = "创建人名字", example = "芋道源码")
+    @ExcelProperty("创建人名字")
+    private String creatorName;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
     private LocalDateTime createTime;
 
-    @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
-    private String customerName;
+    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("更新时间")
+    private LocalDateTime updateTime;
 
-    @Schema(description = "状态类型名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中")
-    private String statusTypeName;
+    @Schema(description = "产品列表")
+    private List<Product> products;
 
-    @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中")
-    private String statusName;
+    @Schema(description = "产品列表")
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Product {
+
+        @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
+        private Long id;
+
+        @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
+        private Long productId;
+        @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+        private String productName;
+        @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
+        private String productNo;
+        @Schema(description = "产品单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+        private Integer productUnit;
+
+        @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
+        private BigDecimal productPrice;
+
+        @Schema(description = "合同价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
+        private BigDecimal businessPrice;
+
+        @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
+        private Integer count;
+
+        @Schema(description = "总计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
+        private BigDecimal totalPrice;
+
+    }
 
 }

+ 27 - 34
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,7 @@
 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.enums.business.CrmBizEndStatus;
 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;
@@ -29,74 +28,68 @@ public class CrmBusinessSaveReqVO {
     @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 = "客户编号", 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 contactNextTime;
 
-    @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
-    @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
-    @NotNull(message = "客户不能为空")
-    private Long customerId;
+    @Schema(description = "负责人用户编号", example = "14334")
+    @NotNull(message = "负责人不能为空")
+    @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
+    private Long ownerUserId;
+
+    @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
+    @DiffLogField(name = "商机状态组")
+    @NotNull(message = "商机状态组不能为空")
+    private Long statusTypeId;
 
     @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;
-
     @Schema(description = "整单折扣")
     @DiffLogField(name = "整单折扣")
-    private Integer discountPercent;
-
-    @Schema(description = "产品总金额", example = "12025")
-    @DiffLogField(name = "产品总金额")
-    private BigDecimal productPrice;
+    @NotNull(message = "整单折扣不能为空")
+    private BigDecimal discountPercent;
 
     @Schema(description = "备注", example = "随便")
     @DiffLogField(name = "备注")
     private String remark;
 
-    @Schema(description = "结束状态", example = "1")
-    @InEnum(CrmBizEndStatus.class)
-    private Integer endStatus;
-
     @Schema(description = "联系人编号", example = "110")
     private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段
 
     @Schema(description = "产品列表")
-    private List<Item> items;
+    private List<Product> products;
 
     @Schema(description = "产品列表")
     @Data
     @NoArgsConstructor
     @AllArgsConstructor
-    public static class Item {
+    public static class Product {
 
         @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
         @NotNull(message = "产品编号不能为空")
-        private Long id;
+        private Long productId;
+
+        @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
+        @NotNull(message = "产品单价不能为空")
+        private BigDecimal productPrice;
+
+        @Schema(description = "合同价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
+        @NotNull(message = "合同价格不能为空")
+        private BigDecimal businessPrice;
 
         @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
         @NotNull(message = "产品数量不能为空")
         private Integer count;
 
-        @Schema(description = "产品折扣")
-        private Integer discountPercent;
-
     }
 
 }

+ 0 - 15
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java

@@ -1,15 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - 商机状态分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmBusinessStatusPageReqVO extends PageParam {
-
-}

+ 0 - 19
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java

@@ -1,19 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.ToString;
-
-import java.util.Collection;
-
-@Schema(description = "管理后台 - 商机状态 Query VO")
-@Data
-@ToString(callSuper = true)
-public class CrmBusinessStatusQueryVO {
-
-    @Schema(description = "主键集合")
-    private Collection<Long> idList;
-
-    @Schema(description = "状态类型编号")
-    private Long typeId;
-}

+ 35 - 17
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java

@@ -1,33 +1,51 @@
 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status;
 
-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.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
 @Schema(description = "管理后台 - 商机状态 Response VO")
 @Data
-@ExcelIgnoreUnannotated
 public class CrmBusinessStatusRespVO {
 
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "23899")
-    @ExcelProperty("主键")
+    @Schema(description = "状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934")
     private Long id;
 
-    @Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7139")
-    @ExcelProperty("状态类型编号")
-    private Long typeId;
-
-    @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
-    @ExcelProperty("状态名")
+    @Schema(description = "状态组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
     private String name;
 
-    @Schema(description = "赢单率")
-    @ExcelProperty("赢单率")
-    private String percent;
+    @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<Long> deptIds;
+    @Schema(description = "使用的部门名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> deptNames;
+
+    @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String creator;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "状态集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<Status> statuses;
+
+    @Data
+    public static class Status {
+
+        @Schema(description = "状态编号", example = "23899")
+        private Long id;
+
+        @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+        private String name;
+
+        @Schema(description = "赢单率", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
+        private BigDecimal percent;
+
+        @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Integer sort;
 
-    @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @ExcelProperty("排序")
-    private Integer sort;
+    }
 
 }

+ 34 - 16
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java

@@ -1,32 +1,50 @@
 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
+import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
+import lombok.Data;
 
-@Schema(description = "管理后台 - 商机状态新增/修改 Request VO")
+import java.math.BigDecimal;
+import java.util.List;
+
+@Schema(description = "管理后台 - 商机状态组新增/修改 Request VO")
 @Data
 public class CrmBusinessStatusSaveReqVO {
 
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "23899")
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934")
     private Long id;
 
-    @Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7139")
-    @NotNull(message = "状态类型编号不能为空")
-    private Long typeId;
-
-    @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
-    @NotEmpty(message = "状态名不能为空")
+    @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @NotEmpty(message = "状态类型名不能为空")
     private String name;
 
-    // TODO @lzxhqs::percent 应该是 Integer;
-    @Schema(description = "赢单率")
-    private String percent;
+    @Schema(description = "使用的部门编号")
+    private List<Long> deptIds;
+
+    @Schema(description = "商机状态集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "商机状态集合不能为空")
+    @Valid
+    private List<Status> statuses;
+
+    @Data
+    public static class Status {
+
+        @Schema(description = "状态编号", example = "23899")
+        private Long id;
+
+        @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+        @NotEmpty(message = "状态名不能为空")
+        private String name;
+
+        @Schema(description = "赢单率", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
+        @NotNull(message = "赢单率不能为空")
+        private BigDecimal percent;
+
+        @Schema(description = "排序", hidden = true, example = "1")
+        private Integer sort;
 
-    // TODO @lzxhqs:这个是不是不用前端新增和修改的时候传递,交给顺序计算出来,存储起来就好了;
-    @Schema(description = "排序")
-    private Integer sort;
+    }
 
 }

+ 0 - 15
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java

@@ -1,15 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-@Schema(description = "管理后台 - 商机状态类型分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmBusinessStatusTypePageReqVO extends PageParam {
-
-}

+ 0 - 19
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java

@@ -1,19 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.ToString;
-
-import java.util.Collection;
-
-@Schema(description = "管理后台 - 商机状态类型 Query VO")
-@Data
-@ToString(callSuper = true)
-public class CrmBusinessStatusTypeQueryVO {
-
-    @Schema(description = "主键集合")
-    private Collection<Long> idList;
-
-    @Schema(description = "状态")
-    private Integer status;
-}

+ 0 - 44
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java

@@ -1,44 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type;
-
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
-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;
-
-@Schema(description = "管理后台 - 商机状态类型 Response VO")
-@Data
-@ExcelIgnoreUnannotated
-public class CrmBusinessStatusTypeRespVO {
-
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934")
-    @ExcelProperty("主键")
-    private Long id;
-
-    @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
-    @ExcelProperty("状态类型名")
-    private String name;
-
-    @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("使用的部门编号")
-    private List<Long> deptIds;
-    @Schema(description = "使用的部门名称", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("使用的部门名称")
-    private List<String> deptNames;
-
-    @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("创建人")
-    private String creator;
-
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("创建时间")
-    private LocalDateTime createTime;
-
-    // TODO @ljlleo 字段后缀改成 statuses,保持和 deptIds 风格一致;CrmBusinessStatusDO 改成 VO 哈;一般不使用 do 直接返回
-    @Schema(description = "状态集合", requiredMode = Schema.RequiredMode.REQUIRED)
-    private List<CrmBusinessStatusDO> statusList;
-
-}

+ 0 - 29
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type;
-
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO;
-import com.google.common.collect.Lists;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import jakarta.validation.constraints.NotEmpty;
-import java.util.List;
-
-@Schema(description = "管理后台 - 商机状态类型新增/修改 Request VO")
-@Data
-public class CrmBusinessStatusTypeSaveReqVO {
-
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934")
-    private Long id;
-
-    @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
-    @NotEmpty(message = "状态类型名不能为空")
-    private String name;
-
-    // TODO @lzxhqs: VO 里面,我们不使用默认值哈。这里 Lists.newArrayList() 看看怎么去掉。上面 deptIds 也是类似噢
-    @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED)
-    private List<Long> deptIds = Lists.newArrayList();
-
-    @Schema(description = "商机状态集合", requiredMode = Schema.RequiredMode.REQUIRED)
-    private List<CrmBusinessStatusSaveReqVO> statusList;
-
-}

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

@@ -179,7 +179,7 @@ public class CrmContractController {
         if (contractList.size() == 1) {
             List<CrmContractProductDO> contractProductList = contractService.getContractProductListByContractId(contractList.get(0).getId());
             contractProductMap = convertMap(contractProductList, CrmContractProductDO::getProductId);
-            productList = productService.getProductListByIds(convertSet(contractProductList, CrmContractProductDO::getProductId));
+            productList = productService.getProductList(convertSet(contractProductList, CrmContractProductDO::getProductId));
         }
         return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap, contractProductMap, productList);
     }

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

@@ -1,16 +1,17 @@
 package cn.iocoder.yudao.module.crm.controller.admin.product;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 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.SetUtils;
+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.product.vo.product.CrmProductPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO;
 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
-import cn.iocoder.yudao.module.crm.convert.product.CrmProductConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
 import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService;
@@ -34,10 +35,9 @@ import java.util.Map;
 import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
-import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static java.util.Collections.singletonList;
 
 @Tag(name = "管理后台 - CRM 产品")
 @RestController
@@ -49,6 +49,7 @@ public class CrmProductController {
     private CrmProductService productService;
     @Resource
     private CrmProductCategoryService productCategoryService;
+
     @Resource
     private AdminUserApi adminUserApi;
 
@@ -82,21 +83,30 @@ public class CrmProductController {
     @PreAuthorize("@ss.hasPermission('crm:product:query')")
     public CommonResult<CrmProductRespVO> getProduct(@RequestParam("id") Long id) {
         CrmProductDO product = productService.getProduct(id);
+        return success(buildProductDetail(product));
+    }
+
+    private CrmProductRespVO buildProductDetail(CrmProductDO product) {
         if (product == null) {
-            return success(null);
+            return null;
         }
-        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
-                SetUtils.asSet(Long.valueOf(product.getCreator()), product.getOwnerUserId()));
-        CrmProductCategoryDO category = productCategoryService.getProductCategory(product.getCategoryId());
-        return success(CrmProductConvert.INSTANCE.convert(product, userMap, category));
+        return buildProductDetailList(singletonList(product)).get(0);
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "获得产品精简列表", description = "只包含被开启的产品,主要用于前端的下拉选项")
+    public CommonResult<List<CrmProductRespVO>> getProductSimpleList() {
+        List<CrmProductDO> list = productService.getProductListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        return success(convertList(list, product -> new CrmProductRespVO().setId(product.getId()).setName(product.getName())
+                .setUnit(product.getUnit()).setNo(product.getNo()).setPrice(product.getPrice())));
     }
 
     @GetMapping("/page")
     @Operation(summary = "获得产品分页")
     @PreAuthorize("@ss.hasPermission('crm:product:query')")
     public CommonResult<PageResult<CrmProductRespVO>> getProductPage(@Valid CrmProductPageReqVO pageVO) {
-        PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO, getLoginUserId());
-        return success(new PageResult<>(getProductDetailList(pageResult.getList()), pageResult.getTotal()));
+        PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO);
+        return success(new PageResult<>(buildProductDetailList(pageResult.getList()), pageResult.getTotal()));
     }
 
     @GetMapping("/export-excel")
@@ -106,21 +116,30 @@ 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, getLoginUserId()).getList();
+        List<CrmProductDO> list = productService.getProductPage(exportReqVO).getList();
         // 导出 Excel
         ExcelUtils.write(response, "产品.xls", "数据", CrmProductRespVO.class,
-                getProductDetailList(list));
+                buildProductDetailList(list));
     }
 
-    private List<CrmProductRespVO> getProductDetailList(List<CrmProductDO> list) {
+    private List<CrmProductRespVO> buildProductDetailList(List<CrmProductDO> list) {
         if (CollUtil.isEmpty(list)) {
             return Collections.emptyList();
         }
+        // 1.1 获得用户信息
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                 convertSetByFlatMap(list, user -> Stream.of(Long.valueOf(user.getCreator()), user.getOwnerUserId())));
-        List<CrmProductCategoryDO> productCategoryList = productCategoryService.getProductCategoryList(
+        // 1.2 获得分类信息
+        Map<Long, CrmProductCategoryDO> categoryMap = productCategoryService.getProductCategoryMap(
                 convertSet(list, CrmProductDO::getCategoryId));
-        return CrmProductConvert.INSTANCE.convertList(list, userMap, productCategoryList);
+        // 2. 拼接数据
+        return BeanUtils.toBean(list, CrmProductRespVO.class, productVO -> {
+            // 2.1 设置用户信息
+            MapUtils.findAndThen(userMap, productVO.getOwnerUserId(), user -> productVO.setOwnerUserName(user.getNickname()));
+            MapUtils.findAndThen(userMap, Long.valueOf(productVO.getCreator()), user -> productVO.setCreatorName(user.getNickname()));
+            // 2.2 设置分类名称
+            MapUtils.findAndThen(categoryMap, productVO.getCategoryId(), category -> productVO.setCategoryName(category.getName()));
+        });
     }
 
 }

+ 2 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java

@@ -8,6 +8,7 @@ import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 @Schema(description = "管理后台 - CRM 产品 Response VO")
@@ -34,7 +35,7 @@ public class CrmProductRespVO {
 
     @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
     @ExcelProperty("价格,单位:分")
-    private Long price;
+    private BigDecimal price;
 
     @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架")
     @ExcelProperty(value = "单位", converter = DictConvert.class)

+ 4 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java

@@ -7,6 +7,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
+import java.math.BigDecimal;
+
 @Schema(description = "管理后台 - CRM 产品创建/修改 Request VO")
 @Data
 public class CrmProductSaveReqVO {
@@ -28,10 +30,10 @@ public class CrmProductSaveReqVO {
     @DiffLogField(name = "单位", function = CrmProductUnitParseFunction.NAME)
     private Integer unit;
 
-    @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
+    @Schema(description = "价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
     @NotNull(message = "价格不能为空")
     @DiffLogField(name = "价格")
-    private Long price;
+    private BigDecimal price;
 
     @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架")
     @NotNull(message = "状态不能为空")

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

@@ -1,14 +1,8 @@
 package cn.iocoder.yudao.module.crm.convert.business;
 
-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.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;
-import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
 import org.mapstruct.Mapper;
@@ -16,9 +10,6 @@ import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
 
 import java.util.List;
-import java.util.Map;
-
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * 商机 Convert
@@ -33,20 +24,6 @@ public interface CrmBusinessConvert {
     @Mapping(target = "bizId", source = "reqVO.id")
     CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId);
 
-    default PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> pageResult, List<CrmCustomerDO> customerList,
-                                                      List<CrmBusinessStatusTypeDO> statusTypeList, List<CrmBusinessStatusDO> statusList) {
-        PageResult<CrmBusinessRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmBusinessRespVO.class);
-        // 拼接关联字段
-        Map<Long, String> customerMap = convertMap(customerList, CrmCustomerDO::getId, CrmCustomerDO::getName);
-        Map<Long, String> statusTypeMap = convertMap(statusTypeList, CrmBusinessStatusTypeDO::getId, CrmBusinessStatusTypeDO::getName);
-        Map<Long, String> statusMap = convertMap(statusList, CrmBusinessStatusDO::getId, CrmBusinessStatusDO::getName);
-        voPageResult.getList().forEach(type -> type
-                .setCustomerName(customerMap.get(type.getCustomerId()))
-                .setStatusTypeName(statusTypeMap.get(type.getStatusTypeId()))
-                .setStatusName(statusMap.get(type.getStatusId())));
-        return voPageResult;
-    }
-
     @Mapping(target = "id", source = "reqBO.bizId")
     CrmBusinessDO convert(CrmUpdateFollowUpReqBO reqBO);
 

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

@@ -1,25 +0,0 @@
-package cn.iocoder.yudao.module.crm.convert.business;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-
-/**
- * 商机状态 Convert
- *
- * @author ljlleo
- */
-@Mapper
-public interface CrmBusinessStatusConvert {
-
-    CrmBusinessStatusConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusConvert.class);
-
-    List<CrmBusinessStatusRespVO> convertList(List<CrmBusinessStatusDO> list);
-
-    PageResult<CrmBusinessStatusRespVO> convertPage(PageResult<CrmBusinessStatusDO> page);
-
-}

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

@@ -1,44 +0,0 @@
-package cn.iocoder.yudao.module.crm.convert.business;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;
-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;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-import java.util.Map;
-
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
-
-// TODO @lzxhqs:看看是不是用 BeanUtils 替代了
-/**
- * 商机状态类型 Convert
- *
- * @author ljlleo
- */
-@Mapper
-public interface CrmBusinessStatusTypeConvert {
-
-    CrmBusinessStatusTypeConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusTypeConvert.class);
-
-    CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean);
-
-    PageResult<CrmBusinessStatusTypeRespVO> convertPage(PageResult<CrmBusinessStatusTypeDO> page);
-
-    default PageResult<CrmBusinessStatusTypeRespVO> convertPage(PageResult<CrmBusinessStatusTypeDO> page, List<DeptRespDTO> deptList) {
-        PageResult<CrmBusinessStatusTypeRespVO> pageResult = convertPage(page);
-        // 拼接关联字段
-        Map<Long, String> deptMap = convertMap(deptList, DeptRespDTO::getId, DeptRespDTO::getName);
-        pageResult.getList().forEach(type -> type.setDeptNames(convertList(type.getDeptIds(), deptMap::get)));
-        return pageResult;
-    }
-
-    default CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean, List<CrmBusinessStatusDO> statusList) {
-        return convert(bean).setStatusList(statusList);
-    }
-
-}

+ 0 - 46
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java

@@ -1,46 +0,0 @@
-package cn.iocoder.yudao.module.crm.convert.product;
-
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
-import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
-
-/**
- * 产品 Convert
- *
- * @author ZanGe丶
- */
-@Mapper
-public interface CrmProductConvert {
-
-    CrmProductConvert INSTANCE = Mappers.getMapper(CrmProductConvert.class);
-
-    default List<CrmProductRespVO> convertList(List<CrmProductDO> list,
-                                               Map<Long, AdminUserRespDTO> userMap,
-                                               List<CrmProductCategoryDO> categoryList) {
-        Map<Long, CrmProductCategoryDO> categoryMap = convertMap(categoryList, CrmProductCategoryDO::getId);
-        return CollectionUtils.convertList(list,
-                product -> convert(product, userMap, categoryMap.get(product.getCategoryId())));
-    }
-
-    default CrmProductRespVO convert(CrmProductDO product,
-                                     Map<Long, AdminUserRespDTO> userMap, CrmProductCategoryDO category) {
-        CrmProductRespVO productVO = BeanUtils.toBean(product, CrmProductRespVO.class);
-        Optional.ofNullable(category).ifPresent(c -> productVO.setCategoryName(c.getName()));
-        MapUtils.findAndThen(userMap, productVO.getOwnerUserId(), user -> productVO.setOwnerUserName(user.getNickname()));
-        MapUtils.findAndThen(userMap, Long.valueOf(productVO.getCreator()), user -> productVO.setCreatorName(user.getNickname()));
-        return productVO;
-    }
-
-}

+ 41 - 38
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java

@@ -8,10 +8,11 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 /**
- * 商机 DO
+ * CRM 商机 DO
  *
  * @author ljlleo
  */
@@ -26,7 +27,7 @@ import java.time.LocalDateTime;
 public class CrmBusinessDO extends BaseDO {
 
     /**
-     * 主键
+     * 编号
      */
     @TableId
     private Long id;
@@ -35,50 +36,44 @@ public class CrmBusinessDO extends BaseDO {
      */
     private String name;
     /**
-     * 商机状态类型编号
+     * 客户编号
      *
-     *  关联 {@link CrmBusinessStatusTypeDO#getId()}
+     * 关联 {@link CrmCustomerDO#getId()}
      */
-    private Long statusTypeId;
+    private Long customerId;
+
     /**
-     * 商机状态编号
-     *
-     * 关联 {@link CrmBusinessStatusDO#getId()}
+     * 跟进状态
      */
-    private Long statusId;
+    private Boolean followUpStatus;
+    /**
+     * 最后跟进时间
+     */
+    private LocalDateTime contactLastTime;
     /**
      * 下次联系时间
      */
     private LocalDateTime contactNextTime;
+
     /**
-     * 户编号
+     * 负责人的用户编号
      *
-     * TODO @ljileo:这个字段,后续要写下关联的实体哈
-     * 关联 {@link CrmCustomerDO#getId()}
-     */
-    private Long customerId;
-    /**
-     * 预计成交日期
+     * 关联 AdminUserDO 的 id 字段
      */
-    private LocalDateTime dealTime;
+    private Long ownerUserId;
+
     /**
-     * 商机金额
+     * 商机状态组编号
      *
+     *  关联 {@link CrmBusinessStatusTypeDO#getId()}
      */
-    private Integer price;
+    private Long statusTypeId;
     /**
-     * 整单折扣
+     * 商机状态编号
      *
+     * 关联 {@link CrmBusinessStatusDO#getId()}
      */
-    private Integer discountPercent;
-    /**
-     * 产品总金额,单位:分
-     */
-    private Integer productPrice;
-    /**
-     * 备注
-     */
-    private String remark;
+    private Long statusId;
     /**
      * 结束状态
      *
@@ -89,20 +84,28 @@ public class CrmBusinessDO extends BaseDO {
      * 结束时的备注
      */
     private String endRemark;
+
     /**
-     * 最后跟进时间
+     * 预计成交日期
      */
-    private LocalDateTime contactLastTime;
+    private LocalDateTime dealTime;
     /**
-     * 跟进状态
+     * 产品总金额,单位:元
+     *
+     * productPrice = ∑({@link CrmBusinessProductDO#getTotalPrice()})
      */
-    private Boolean followUpStatus;
-
+    private BigDecimal totalProductPrice;
     /**
-     * 负责人的用户编号
-     *
-     * 关联 AdminUserDO 的 id 字段
+     * 整单折扣,百分比
      */
-    private Long ownerUserId;
+    private BigDecimal discountPercent;
+    /**
+     * 商机总金额,单位:元
+     */
+    private BigDecimal totalPrice;
+    /**
+     * 备注
+     */
+    private String remark;
 
 }

+ 15 - 11
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java

@@ -7,9 +7,13 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
+import java.math.BigDecimal;
+
 /**
  * 商机产品关联表 DO
  *
+ * CrmBusinessDO : CrmBusinessProductDO = 1 : N
+ *
  * @author lzxhqs
  */
 @TableName("crm_business_product")
@@ -40,24 +44,24 @@ public class CrmBusinessProductDO extends BaseDO {
      */
     private Long productId;
     /**
-     * 产品单价
+     * 产品单价,单位:元
+     *
+     * 冗余 {@link CrmProductDO#getPrice()}
      */
-    private Integer price;
+    private BigDecimal productPrice;
     /**
-     * 销售价格, 单位:分
+     * 合同价格, 单位:元
      */
-    private Integer salesPrice;
+    private BigDecimal businessPrice;
     /**
      * 数量
      */
-    private Integer count;
+    private BigDecimal count;
     /**
-     * 折扣
-     */
-    private Integer discountPercent;
-    /**
-     * 总计价格(折扣后价格)
+     * 总计价格,单位:元
+     *
+     * totalPrice = businessPrice * count
      */
-    private Integer totalPrice;
+    private BigDecimal totalPrice;
 
 }

+ 2 - 9
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.crm.dal.dataobject.business;
 
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -12,7 +11,7 @@ import lombok.*;
 import java.util.List;
 
 /**
- * 商机状态类型 DO
+ * 商机状态 DO
  *
  * @author ljlleo
  */
@@ -35,17 +34,11 @@ public class CrmBusinessStatusTypeDO extends BaseDO {
      * 状态类型名
      */
     private String name;
+
     /**
      * 使用的部门编号
      */
     @TableField(typeHandler = LongListTypeHandler.class)
     private List<Long> deptIds;
-    /**
-     * 开启状态
-     *
-     * TODO 改成 Integer,关联 CommonStatus
-     * 枚举 {@link CommonStatusEnum}
-     */
-    private Boolean status;
 
 }

+ 1 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java

@@ -11,7 +11,7 @@ import lombok.*;
 import java.time.LocalDateTime;
 
 /**
- * 线索 DO
+ * CRM 线索 DO
  *
  * @author Wanwan
  */

+ 4 - 2
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java

@@ -8,6 +8,8 @@ import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
+import java.math.BigDecimal;
+
 /**
  * CRM 产品 DO
  *
@@ -43,9 +45,9 @@ public class CrmProductDO extends BaseDO {
      */
     private Integer unit;
     /**
-     * 价格,单位:
+     * 价格,单位:
      */
-    private Integer price;
+    private BigDecimal price;
     /**
      * 状态
      *

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

@@ -63,4 +63,8 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
         return selectJoinList(CrmBusinessDO.class, query);
     }
 
+    default Long selectCountByStatusTypeId(Long statusTypeId) {
+        return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId);
+    }
+
 }

+ 4 - 16
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java

@@ -1,10 +1,6 @@
 package cn.iocoder.yudao.module.crm.dal.mysql.business;
 
-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.module.crm.controller.admin.business.vo.status.CrmBusinessStatusPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
 import org.apache.ibatis.annotations.Mapper;
 
@@ -18,20 +14,12 @@ import java.util.List;
 @Mapper
 public interface CrmBusinessStatusMapper extends BaseMapperX<CrmBusinessStatusDO> {
 
-    default PageResult<CrmBusinessStatusDO> selectPage(CrmBusinessStatusPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<CrmBusinessStatusDO>()
-                .orderByDesc(CrmBusinessStatusDO::getId));
-    }
-
-    default List<CrmBusinessStatusDO> selectList(CrmBusinessStatusQueryVO queryVO) {
-        return selectList(new LambdaQueryWrapperX<CrmBusinessStatusDO>()
-                .eqIfPresent(CrmBusinessStatusDO::getTypeId, queryVO.getTypeId())
-                .inIfPresent(CrmBusinessStatusDO::getId, queryVO.getIdList())
-                .orderByDesc(CrmBusinessStatusDO::getId));
+    default int deleteByTypeId(Long typeId) {
+        return delete(CrmBusinessStatusDO::getTypeId, typeId);
     }
 
-    default int delete(Long typeId) {
-        return delete(CrmBusinessStatusDO::getTypeId, typeId);
+    default List<CrmBusinessStatusDO> selectListByTypeId(Long typeId) {
+        return selectList(CrmBusinessStatusDO::getTypeId, typeId);
     }
 
 }

+ 5 - 22
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java

@@ -1,44 +1,27 @@
 package cn.iocoder.yudao.module.crm.dal.mysql.business;
 
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
 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.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
 import org.apache.ibatis.annotations.Mapper;
 
-import java.util.List;
-
 /**
- * 商机状态类型 Mapper
+ * 商机状态组 Mapper
  *
  * @author ljlleo
  */
 @Mapper
 public interface CrmBusinessStatusTypeMapper extends BaseMapperX<CrmBusinessStatusTypeDO> {
 
-    default PageResult<CrmBusinessStatusTypeDO> selectPage(CrmBusinessStatusTypePageReqVO reqVO) {
+    default PageResult<CrmBusinessStatusTypeDO> selectPage(PageParam reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<CrmBusinessStatusTypeDO>()
                 .orderByDesc(CrmBusinessStatusTypeDO::getId));
     }
 
-    default List<CrmBusinessStatusTypeDO> selectList(CrmBusinessStatusTypeQueryVO queryVO) {
-        return selectList(new LambdaQueryWrapperX<CrmBusinessStatusTypeDO>()
-                .eqIfPresent(CrmBusinessStatusTypeDO::getStatus, queryVO.getStatus())
-                .inIfPresent(CrmBusinessStatusTypeDO::getId, queryVO.getIdList()));
-    }
-
-    // TODO @lzxhqs:这个可以改成 selectByName。业务上基于在判断 id 匹配;这样更通用一些;mapper 尽量通用,不关注或者特别关联业务;
-    /**
-     * 根据ID和name查询
-     *
-     * @param id 商机状态类型id
-     * @param name 状态类型名
-     * @return result
-     */
-    default CrmBusinessStatusTypeDO selectByIdAndName(Long id, String name) {
-        return selectOne(CrmBusinessStatusTypeDO::getId, id, CrmBusinessStatusTypeDO::getName, name);
+    default CrmBusinessStatusTypeDO selectByName(String name) {
+        return selectOne(CrmBusinessStatusTypeDO::getName, name);
     }
 
 }

+ 0 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java

@@ -1,4 +0,0 @@
-/**
- * 商机(销售机会)
- */
-package cn.iocoder.yudao.module.crm.dal.mysql.business;

+ 0 - 4
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java

@@ -1,4 +0,0 @@
-/**
- * 联系人
- */
-package cn.iocoder.yudao.module.crm.dal.mysql.contact;

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

@@ -5,10 +5,10 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 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;
 
+import java.util.List;
+
 /**
  * CRM 产品 Mapper
  *
@@ -17,21 +17,23 @@ import org.apache.ibatis.annotations.Mapper;
 @Mapper
 public interface CrmProductMapper extends BaseMapperX<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)
+    default PageResult<CrmProductDO> selectPage(CrmProductPageReqVO reqVO) {
+        return selectPage(reqVO, new MPJLambdaWrapperX<CrmProductDO>()
                 .likeIfPresent(CrmProductDO::getName, reqVO.getName())
                 .eqIfPresent(CrmProductDO::getStatus, reqVO.getStatus())
-                .orderByDesc(CrmProductDO::getId);
-        return selectJoinPage(reqVO, CrmProductDO.class, query);
+                .orderByDesc(CrmProductDO::getId));
     }
 
     default CrmProductDO selectByNo(String no) {
         return selectOne(CrmProductDO::getNo, no);
     }
 
+    default Long selectCountByCategoryId(Long categoryId) {
+        return selectCount(CrmProductDO::getCategoryId, categoryId);
+    }
+
+    default List<CrmProductDO> selectListByStatus(Integer status) {
+        return selectList(CrmProductDO::getStatus, status);
+    }
+
 }

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

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
 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.business.CrmBusinessProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO;
@@ -90,6 +91,14 @@ public interface CrmBusinessService {
      */
     List<CrmBusinessDO> getBusinessList(Collection<Long> ids);
 
+    /**
+     * 获得指定商机编号的产品列表
+     *
+     * @param businessId 商机编号
+     * @return 商机产品列表
+     */
+    List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId);
+
     /**
      * 获得商机分页
      *
@@ -129,4 +138,12 @@ public interface CrmBusinessService {
      */
     Long getBusinessCountByCustomerId(Long customerId);
 
+    /**
+     * 获得使用指定商机状态组的商机数量
+     *
+     * @param statusTypeId 商机状态组编号
+     * @return 数量
+     */
+    Long getBusinessCountByStatusTypeId(Long statusTypeId);
+
 }

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

@@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.crm.service.business;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
-import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
@@ -14,7 +12,6 @@ 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.CrmBusinessProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
@@ -22,11 +19,14 @@ import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
 import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO;
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
+import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
+import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import com.mzt.logapi.context.LogRecordContext;
 import com.mzt.logapi.service.impl.DiffParseFunction;
 import com.mzt.logapi.starter.annotation.LogRecord;
@@ -36,14 +36,14 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_CONTRACT_EXISTS;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
 
 /**
@@ -60,41 +60,54 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     @Resource
     private CrmBusinessProductMapper businessProductMapper;
 
+    @Resource
+    private CrmBusinessStatusService businessStatusService;
     @Resource
     @Lazy // 延迟加载,避免循环依赖
     private CrmContractService contractService;
     @Resource
+    private CrmCustomerService customerService;
+    @Resource
+    @Lazy // 延迟加载,避免循环依赖
+    private CrmContactService contactService;
+    @Resource
     private CrmPermissionService permissionService;
     @Resource
     private CrmContactBusinessService contactBusinessService;
     @Resource
     private CrmProductService productService;
 
+    @Resource
+    private AdminUserApi adminUserApi;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     @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. 插入商机
+        // 1.1 校验产品项的有效性
+        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getProducts());
+        // 1.2 校验关联字段
+        validateBusinessForCreate(createReqVO);
+
+        // 2.1 插入商机
         CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class).setOwnerUserId(userId);
+        business.setStatusId(businessStatusService.getBusinessStatusListByTypeId(createReqVO.getStatusTypeId()).get(0).getId()); // 默认状态
+        calculateTotalPrice(business, businessProducts);
         businessMapper.insert(business);
-        // 1.2 插入商机关联商品
-        if (CollUtil.isNotEmpty(createReqVO.getItems())) { // 如果有的话
-            List<CrmBusinessProductDO> productList = buildBusinessProductList(createReqVO.getItems(), business.getId());
-            businessProductMapper.insertBatch(productList);
-            // 更新合同商品总金额
-            businessMapper.updateById(new CrmBusinessDO().setId(business.getId()).setProductPrice(
-                    getSumValue(productList, CrmBusinessProductDO::getTotalPrice, Integer::sum)));
+        // 2.2 插入商机关联商品
+        if (CollUtil.isNotEmpty(businessProducts)) {
+            businessProducts.forEach(item -> item.setBusinessId(business.getId()));
+            businessProductMapper.insertBatch(businessProducts);
         }
         // 在联系人的详情页,如果直接【新建商机】,则需要关联下。
         contactBusinessService.createContactBusiness(createReqVO.getContactId(), business.getId());
-        // 2. 创建数据权限
+        // 3. 创建数据权限
         // 设置当前操作的人为负责人
         permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
                 .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
 
-        // 3. 记录操作日志上下文
+        // 4. 记录操作日志上下文
         LogRecordContext.putVariable("business", business);
         return business.getId();
     }
@@ -105,15 +118,20 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
             success = CRM_BUSINESS_UPDATE_SUCCESS)
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
     public void updateBusiness(CrmBusinessSaveReqVO updateReqVO) {
-        // 1. 校验存在
+        updateReqVO.setOwnerUserId(null).setStatusTypeId(null); // 不允许更新的字段
+        // 1.1 校验存在
         CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
+        // 1.2 校验产品项的有效性
+        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts());
+        // 1.3 校验关联字段
+        validateBusinessForCreate(updateReqVO);
 
         // 2.1 更新商机
         CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class);
+        calculateTotalPrice(updateObj, businessProducts);
         businessMapper.updateById(updateObj);
         // 2.2 更新商机关联商品
-        List<CrmBusinessProductDO> productList = buildBusinessProductList(updateReqVO.getItems(), updateObj.getId());
-        updateBusinessProduct(productList, updateObj.getId());
+        updateBusinessProduct(updateObj.getId(), businessProducts);
 
         // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表
         // 3. 记录操作日志上下文
@@ -121,6 +139,56 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         LogRecordContext.putVariable("businessName", oldBusiness.getName());
     }
 
+    private void updateBusinessProduct(Long id, List<CrmBusinessProductDO> newList) {
+        List<CrmBusinessProductDO> oldList = businessProductMapper.selectListByBusinessId(id);
+        List<List<CrmBusinessProductDO>> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录
+                (oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
+        if (CollUtil.isNotEmpty(diffList.get(0))) {
+            diffList.get(0).forEach(o -> o.setBusinessId(id));
+            businessProductMapper.insertBatch(diffList.get(0));
+        }
+        if (CollUtil.isNotEmpty(diffList.get(1))) {
+            businessProductMapper.updateBatch(diffList.get(1));
+        }
+        if (CollUtil.isNotEmpty(diffList.get(2))) {
+            businessProductMapper.deleteBatchIds(convertSet(diffList.get(2), CrmBusinessProductDO::getId));
+        }
+    }
+
+    private void validateBusinessForCreate(CrmBusinessSaveReqVO saveReqVO) {
+        // 校验商机状态
+        if (saveReqVO.getStatusTypeId() != null) {
+            businessStatusService.validateBusinessStatusType(saveReqVO.getStatusTypeId());
+        }
+        // 校验客户
+        if (saveReqVO.getCustomerId() != null) {
+            customerService.validateCustomer(saveReqVO.getCustomerId());
+        }
+        // 校验联系人
+        if (saveReqVO.getContactId() != null) {
+            contactService.validateContact(saveReqVO.getContactId());
+        }
+        // 校验负责人
+        if (saveReqVO.getOwnerUserId() != null) {
+            adminUserApi.validateUser(saveReqVO.getOwnerUserId());
+        }
+    }
+
+    private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.Product> list) {
+        // 1. 校验产品存在
+         productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.Product::getProductId));
+        // 2. 转化为 CrmBusinessProductDO 列表
+        return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class, item -> {
+            item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount()));
+        }));
+    }
+
+    private void calculateTotalPrice(CrmBusinessDO business, List<CrmBusinessProductDO> businessProducts) {
+        business.setTotalProductPrice(getSumValue(businessProducts, CrmBusinessProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
+        BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(business.getTotalProductPrice(), business.getDiscountPercent());
+        business.setTotalPrice(business.getTotalProductPrice().subtract(discountPrice));
+    }
+
     @Override
     public void updateBusinessFollowUpBatch(List<CrmUpdateFollowUpReqBO> updateFollowUpReqBOList) {
         businessMapper.updateBatch(CrmBusinessConvert.INSTANCE.convertList(updateFollowUpReqBOList));
@@ -146,43 +214,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         LogRecordContext.putVariable("businessName", business.getName());
     }
 
-    private void updateBusinessProduct(List<CrmBusinessProductDO> newProductList, Long businessId) {
-        List<CrmBusinessProductDO> oldProducts = businessProductMapper.selectListByBusinessId(businessId);
-        List<List<CrmBusinessProductDO>> diffList = CollectionUtils.diffList(oldProducts, newProductList, (oldValue, newValue) -> {
-            boolean condition = ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId());
-            if (condition) {
-                newValue.setId(oldValue.getId()); // 更新需要原始编号
-            }
-            return condition;
-        });
-        if (CollUtil.isNotEmpty(diffList.get(0))) {
-            businessProductMapper.insertBatch(diffList.get(0));
-        }
-        if (CollUtil.isNotEmpty(diffList.get(1))) {
-            businessProductMapper.updateBatch(diffList.get(1));
-        }
-        if (CollUtil.isNotEmpty(diffList.get(2))) {
-            businessProductMapper.deleteBatchIds(convertSet(diffList.get(2), CrmBusinessProductDO::getId));
-        }
-    }
-
-    private List<CrmBusinessProductDO> buildBusinessProductList(List<CrmBusinessSaveReqVO.Item> productItems, Long businessId) {
-        // 校验商品存在
-        Set<Long> productIds = convertSet(productItems, CrmBusinessSaveReqVO.Item::getId);
-        List<CrmProductDO> productList = productService.getProductList(productIds);
-        if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) {
-            throw exception(PRODUCT_NOT_EXISTS);
-        }
-        Map<Long, CrmProductDO> productMap = convertMap(productList, CrmProductDO::getId);
-        return convertList(productItems, productItem -> {
-            CrmProductDO product = productMap.get(productItem.getId());
-            return BeanUtils.toBean(product, CrmBusinessProductDO.class)
-                    .setId(null).setProductId(productItem.getId()).setBusinessId(businessId)
-                    .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent())
-                    .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent()));
-        });
-    }
-
     /**
      * 删除校验合同是关联合同
      *
@@ -225,10 +256,10 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
 
     @Override
     public void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO) {
-        // 更新商机关联商品
-        List<CrmBusinessProductDO> productList = buildBusinessProductList(
-                BeanUtils.toBean(updateProductReqBO.getItems(), CrmBusinessSaveReqVO.Item.class), updateProductReqBO.getId());
-        updateBusinessProduct(productList, updateProductReqBO.getId());
+        // 更新商机关联商品 TODO yunai
+//        List<CrmBusinessProductDO> productList = buildBusinessProductList(
+//                BeanUtils.toBean(updateProductReqBO.getProductItems(), CrmBusinessSaveReqVO.Product.class), updateProductReqBO.getId());
+//        updateBusinessProduct(productList, updateProductReqBO.getId());
     }
 
     //======================= 查询相关 =======================
@@ -255,6 +286,11 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         return businessMapper.selectBatchIds(ids);
     }
 
+    @Override
+    public List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId) {
+        return businessProductMapper.selectListByBusinessId(businessId);
+    }
+
     @Override
     public PageResult<CrmBusinessDO> getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId) {
         return businessMapper.selectPage(pageReqVO, userId);
@@ -285,4 +321,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         return businessMapper.selectCount(CrmBusinessDO::getCustomerId, customerId);
     }
 
+    @Override
+    public Long getBusinessCountByStatusTypeId(Long statusTypeId) {
+        return businessMapper.selectCountByStatusTypeId(statusTypeId);
+    }
+
 }

+ 58 - 16
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java

@@ -1,15 +1,17 @@
 package cn.iocoder.yudao.module.crm.service.business;
 
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
-
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
 import jakarta.validation.Valid;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * 商机状态 Service 接口
@@ -38,33 +40,63 @@ public interface CrmBusinessStatusService {
      *
      * @param id 编号
      */
-    void deleteBusinessStatus(Long id);
+    void deleteBusinessStatusType(Long id);
+
+    /**
+     * 获得商机状态组
+     *
+     * @param id 编号
+     * @return 商机状态组
+     */
+    CrmBusinessStatusTypeDO getBusinessStatusType(Long id);
 
     /**
-     * 获得商机状态
+     * 校验商机状态组
      *
      * @param id 编号
-     * @return 商机状态
      */
-    CrmBusinessStatusDO getBusinessStatus(Long id);
+    void validateBusinessStatusType(Long id);
 
     /**
-     * 获得商机状态分页
+     * 获得商机状态组列表
+     *
+     * @return 商机状态组列表
+     */
+    List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList();
+
+    /**
+     * 获得商机状态组分页
      *
      * @param pageReqVO 分页查询
-     * @return 商机状态分页
+     * @return 商机状态分页
      */
-    PageResult<CrmBusinessStatusDO> getBusinessStatusPage(CrmBusinessStatusPageReqVO pageReqVO);
+    PageResult<CrmBusinessStatusTypeDO> getBusinessStatusTypePage(PageParam pageReqVO);
 
-    // TODO @ljlleo 常用的 ids 之类的查询,可以封装单独的方法,不用走类似 QueryVO,用起来更方便。
-    // TODO @ljlleo 方法名用 getBusinessStatusList
     /**
-     * 获得商机状态分页
+     * 获得商机状态组列表
      *
-     * @param queryVO 查询参数
-     * @return 商机状态分页
+     * @param ids 编号数组
+     * @return 商机状态组列表
      */
-    List<CrmBusinessStatusDO> selectList(CrmBusinessStatusQueryVO queryVO);
+    List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids);
+
+    /**
+     * 获得商机状态组 Map
+     *
+     * @param ids 编号数组
+     * @return 商机状态组 Map
+     */
+    default Map<Long, CrmBusinessStatusTypeDO> getBusinessStatusTypeMap(Collection<Long> ids) {
+        return convertMap(getBusinessStatusTypeList(ids), CrmBusinessStatusTypeDO::getId);
+    }
+
+    /**
+     * 获得指定类型的商机状态列表
+     *
+     * @param typeId 商机状态组编号
+     * @return 商机状态列表
+     */
+    List<CrmBusinessStatusDO> getBusinessStatusListByTypeId(Long typeId);
 
     /**
      * 获得商机状态列表
@@ -74,4 +106,14 @@ public interface CrmBusinessStatusService {
      */
     List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids);
 
+    /**
+     * 获得商机状态 Map
+     *
+     * @param ids 编号数组
+     * @return 商机状态 Map
+     */
+    default Map<Long, CrmBusinessStatusDO> getBusinessStatusMap(Collection<Long> ids) {
+        return convertMap(getBusinessStatusList(ids), CrmBusinessStatusDO::getId);
+    }
+
 }

+ 126 - 31
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java

@@ -1,22 +1,29 @@
 package cn.iocoder.yudao.module.crm.service.business;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
 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.status.CrmBusinessStatusPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusTypeMapper;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
-
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
 
 /**
  * 商机状态 Service 实现类
@@ -27,59 +34,147 @@ import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STAT
 @Validated
 public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService {
 
+    @Resource
+    private CrmBusinessStatusTypeMapper businessStatusTypeMapper;
     @Resource
     private CrmBusinessStatusMapper businessStatusMapper;
 
+    @Resource
+    @Lazy // 延迟加载,避免循环依赖
+    private CrmBusinessService businessService;
+
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createBusinessStatus(CrmBusinessStatusSaveReqVO createReqVO) {
-        // 插入
-        CrmBusinessStatusDO businessStatus = BeanUtils.toBean(createReqVO, CrmBusinessStatusDO.class);
-        businessStatusMapper.insert(businessStatus);
-        // 返回
-        return businessStatus.getId();
+        // 1.1 检验名称是否存在
+        validateBusinessStatusTypeNameUnique(createReqVO.getName(), null);
+        // 1.2 设置状态的排序
+        int sort = 0;
+        for (CrmBusinessStatusSaveReqVO.Status status : createReqVO.getStatuses()) {
+            status.setSort(sort++);
+        }
+
+        // 2.1 插入类型
+        CrmBusinessStatusTypeDO statusType = BeanUtils.toBean(createReqVO, CrmBusinessStatusTypeDO.class);
+        businessStatusTypeMapper.insert(statusType);
+        // 2.2 插入状态
+        List<CrmBusinessStatusDO> statuses = BeanUtils.toBean(createReqVO.getStatuses(), CrmBusinessStatusDO.class,
+                status -> status.setTypeId(statusType.getId()));
+        businessStatusMapper.insertBatch(statuses);
+        return statusType.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateBusinessStatus(CrmBusinessStatusSaveReqVO updateReqVO) {
-        // 校验存在
-        validateBusinessStatusExists(updateReqVO.getId());
-        // 更新
-        CrmBusinessStatusDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessStatusDO.class);
-        businessStatusMapper.updateById(updateObj);
+        // 1.1 校验存在
+        validateBusinessStatusTypeExists(updateReqVO.getId());
+        // 1.2 校验名称是否存在
+        validateBusinessStatusTypeNameUnique(updateReqVO.getName(), updateReqVO.getId());
+        // 1.3 设置状态的排序
+        int sort = 0;
+        for (CrmBusinessStatusSaveReqVO.Status status : updateReqVO.getStatuses()) {
+            status.setSort(sort++);
+        }
+        // 1.4 已经使用,无法更新
+        if (businessService.getBusinessCountByStatusTypeId(updateReqVO.getId()) > 0) {
+            throw exception(BUSINESS_STATUS_UPDATE_FAIL_USED);
+        }
+
+        // 2.1 更新类型
+        CrmBusinessStatusTypeDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessStatusTypeDO.class);
+        businessStatusTypeMapper.updateById(updateObj);
+        // 2.2 更新状态
+        updateBusinessStatus(updateReqVO.getId(), BeanUtils.toBean(updateReqVO.getStatuses(), CrmBusinessStatusDO.class));
     }
 
-    @Override
-    public void deleteBusinessStatus(Long id) {
-        // 校验存在
-        validateBusinessStatusExists(id);
-        // TODO @ljlleo 这里可以考虑,如果有商机在使用,不允许删除
-        // 删除
-        businessStatusMapper.deleteById(id);
+    private void updateBusinessStatus(Long id, List<CrmBusinessStatusDO> newList) {
+        List<CrmBusinessStatusDO> oldList = businessStatusMapper.selectListByTypeId(id);
+        List<List<CrmBusinessStatusDO>> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录
+                (oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
+        if (CollUtil.isNotEmpty(diffList.get(0))) {
+            diffList.get(0).forEach(o -> o.setTypeId(id));
+            businessStatusMapper.insertBatch(diffList.get(0));
+        }
+        if (CollUtil.isNotEmpty(diffList.get(1))) {
+            businessStatusMapper.updateBatch(diffList.get(1));
+        }
+        if (CollUtil.isNotEmpty(diffList.get(2))) {
+            businessStatusMapper.deleteBatchIds(convertSet(diffList.get(2), CrmBusinessStatusDO::getId));
+        }
+    }
+
+    private void validateBusinessStatusTypeExists(Long id) {
+        if (businessStatusTypeMapper.selectById(id) == null) {
+            throw exception(BUSINESS_STATUS_TYPE_NOT_EXISTS);
+        }
     }
 
-    private void validateBusinessStatusExists(Long id) {
-        if (businessStatusMapper.selectById(id) == null) {
-            throw exception(BUSINESS_STATUS_NOT_EXISTS);
+    private void validateBusinessStatusTypeNameUnique(String name, Long id) {
+        CrmBusinessStatusTypeDO statusType = businessStatusTypeMapper.selectByName(name);
+        if (statusType == null
+                || statusType.getId().equals(id)) {
+            return;
+        }
+        throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteBusinessStatusType(Long id) {
+        // 1.1 校验存在
+        validateBusinessStatusTypeExists(id);
+        // 1.2 已经使用,无法更新
+        if (businessService.getBusinessCountByStatusTypeId(id) > 0) {
+            throw exception(BUSINESS_STATUS_DELETE_FAIL_USED);
         }
+
+        // 2.1 删除类型
+        businessStatusTypeMapper.deleteById(id);
+        // 2.2 删除状态
+        businessStatusMapper.deleteByTypeId(id);
+    }
+
+    @Override
+    public CrmBusinessStatusTypeDO getBusinessStatusType(Long id) {
+        return businessStatusTypeMapper.selectById(id);
     }
 
     @Override
-    public CrmBusinessStatusDO getBusinessStatus(Long id) {
-        return businessStatusMapper.selectById(id);
+    public void validateBusinessStatusType(Long id) {
+        validateBusinessStatusTypeExists(id);
     }
 
     @Override
-    public PageResult<CrmBusinessStatusDO> getBusinessStatusPage(CrmBusinessStatusPageReqVO pageReqVO) {
-        return businessStatusMapper.selectPage(pageReqVO);
+    public List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList() {
+        return businessStatusTypeMapper.selectList();
     }
 
     @Override
-    public List<CrmBusinessStatusDO> selectList(CrmBusinessStatusQueryVO queryVO) {
-        return businessStatusMapper.selectList(queryVO);
+    public PageResult<CrmBusinessStatusTypeDO> getBusinessStatusTypePage(PageParam pageReqVO) {
+        return businessStatusTypeMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        return businessStatusTypeMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public List<CrmBusinessStatusDO> getBusinessStatusListByTypeId(Long typeId) {
+        List<CrmBusinessStatusDO> list = businessStatusMapper.selectListByTypeId(typeId);
+        list.sort(Comparator.comparingInt(CrmBusinessStatusDO::getSort));
+        return list;
     }
 
     @Override
     public List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
         return businessStatusMapper.selectBatchIds(ids);
     }
 

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

@@ -1,75 +0,0 @@
-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.type.CrmBusinessStatusTypePageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
-import jakarta.validation.Valid;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * 商机状态类型 Service 接口
- *
- * @author ljlleo
- */
-public interface CrmBusinessStatusTypeService {
-
-    /**
-     * 创建商机状态类型
-     *
-     * @param createReqVO 创建信息
-     * @return 编号
-     */
-    Long createBusinessStatusType(@Valid CrmBusinessStatusTypeSaveReqVO createReqVO);
-
-    /**
-     * 更新商机状态类型
-     *
-     * @param updateReqVO 更新信息
-     */
-    void updateBusinessStatusType(@Valid CrmBusinessStatusTypeSaveReqVO updateReqVO);
-
-    /**
-     * 删除商机状态类型
-     *
-     * @param id 编号
-     */
-    void deleteBusinessStatusType(Long id);
-
-    /**
-     * 获得商机状态类型
-     *
-     * @param id 编号
-     * @return 商机状态类型
-     */
-    CrmBusinessStatusTypeDO getBusinessStatusType(Long id);
-
-    /**
-     * 获得商机状态类型分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 商机状态类型分页
-     */
-    PageResult<CrmBusinessStatusTypeDO> getBusinessStatusTypePage(CrmBusinessStatusTypePageReqVO pageReqVO);
-
-    // TODO @ljlleo 常用的 ids 之类的查询,可以封装单独的方法,不用走类似 QueryVO,用起来更方便。
-    /**
-     * 获得商机状态类型列表
-     *
-     * @param queryVO 查询参数
-     * @return 商机状态类型列表
-     */
-    List<CrmBusinessStatusTypeDO> selectList(CrmBusinessStatusTypeQueryVO queryVO);
-
-    /**
-     * 获得商机状态类型列表
-     *
-     * @param ids 编号数组
-     * @return 商机状态类型列表
-     */
-    List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids);
-
-}

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

@@ -1,132 +0,0 @@
-package cn.iocoder.yudao.module.crm.service.business;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
-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.crm.dal.mysql.business.CrmBusinessStatusMapper;
-import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusTypeMapper;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.validation.annotation.Validated;
-
-import jakarta.annotation.Resource;
-
-import java.util.Collection;
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NOT_EXISTS;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NAME_EXISTS;
-
-/**
- * 商机状态类型 Service 实现类
- *
- * @author ljlleo
- */
-@Service
-@Validated
-public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeService {
-
-    @Resource
-    private CrmBusinessStatusTypeMapper businessStatusTypeMapper;
-
-    @Resource
-    private CrmBusinessStatusMapper businessStatusMapper;
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Long createBusinessStatusType(CrmBusinessStatusTypeSaveReqVO createReqVO) {
-        //检验名称是否存在
-        validateBusinessStatusTypeExists(createReqVO.getName(), null);
-        // 插入类型
-        CrmBusinessStatusTypeDO businessStatusType = BeanUtils.toBean(createReqVO, CrmBusinessStatusTypeDO.class);
-        businessStatusTypeMapper.insert(businessStatusType);
-        // 插入状态
-        if (CollUtil.isNotEmpty(createReqVO.getStatusList())) {
-            createReqVO.getStatusList().forEach(status -> status.setTypeId(businessStatusType.getId()));
-            businessStatusMapper.insertBatch(BeanUtils.toBean(createReqVO.getStatusList(), CrmBusinessStatusDO.class));
-        }
-        return businessStatusType.getId();
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateBusinessStatusType(CrmBusinessStatusTypeSaveReqVO updateReqVO) {
-        // 校验存在
-        validateBusinessStatusTypeExists(updateReqVO.getId());
-        // 校验名称是否存在
-        validateBusinessStatusTypeExists(updateReqVO.getName(), updateReqVO.getId());
-        // 更新类型
-        CrmBusinessStatusTypeDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessStatusTypeDO.class);
-        businessStatusTypeMapper.updateById(updateObj);
-        // 更新状态(删除 + 更新)
-        // TODO @ljlleo 可以参考 DeliveryExpressTemplateServiceImpl 的 updateExpressTemplateFree 方法;主要没变化的,还是不删除了哈。
-        businessStatusMapper.delete(updateReqVO.getId());
-        updateReqVO.getStatusList().forEach(status -> status.setTypeId(updateReqVO.getId()));
-        businessStatusMapper.insertBatch(BeanUtils.toBean(updateReqVO.getStatusList(), CrmBusinessStatusDO.class));
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void deleteBusinessStatusType(Long id) {
-        // TODO 待添加被引用校验
-        //...
-
-        // 校验存在
-        validateBusinessStatusTypeExists(id);
-        // 删除类型
-        businessStatusTypeMapper.deleteById(id);
-        // 删除状态
-        businessStatusMapper.delete(id);
-    }
-
-    private void validateBusinessStatusTypeExists(Long id) {
-        if (businessStatusTypeMapper.selectById(id) == null) {
-            throw exception(BUSINESS_STATUS_TYPE_NOT_EXISTS);
-        }
-    }
-
-    // TODO @ljlleo 这个方法,这个参考 validateDeptNameUnique 实现。
-    private void validateBusinessStatusTypeExists(String name, Long id) {
-        CrmBusinessStatusTypeDO businessStatusTypeDO = businessStatusTypeMapper.selectByIdAndName(id, name);
-        if (businessStatusTypeDO != null) {
-            throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS);
-        }
-//        LambdaQueryWrapper<CrmBusinessStatusTypeDO> wrapper = new LambdaQueryWrapperX<>();
-//        if(null != id) {
-//            wrapper.ne(CrmBusinessStatusTypeDO::getId, id);
-//        }
-//        long cnt = businessStatusTypeMapper.selectCount(wrapper.eq(CrmBusinessStatusTypeDO::getName, name));
-//        if (cnt > 0) {
-//            throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS);
-//        }
-    }
-
-    @Override
-    public CrmBusinessStatusTypeDO getBusinessStatusType(Long id) {
-        return businessStatusTypeMapper.selectById(id);
-    }
-
-    @Override
-    public PageResult<CrmBusinessStatusTypeDO> getBusinessStatusTypePage(CrmBusinessStatusTypePageReqVO pageReqVO) {
-        return businessStatusTypeMapper.selectPage(pageReqVO);
-    }
-
-    @Override
-    public List<CrmBusinessStatusTypeDO> selectList(CrmBusinessStatusTypeQueryVO queryVO) {
-        return businessStatusTypeMapper.selectList(queryVO);
-    }
-
-    @Override
-    public List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids) {
-        return businessStatusTypeMapper.selectBatchIds(ids);
-    }
-
-}

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

@@ -88,6 +88,13 @@ public interface CrmContactService {
      */
     CrmContactDO getContact(Long id);
 
+    /**
+     * 校验联系人
+     *
+     * @param id 编号
+     */
+    void validateContact(Long id);
+
     /**
      * 获得联系人列表
      *

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

@@ -251,6 +251,11 @@ public class CrmContactServiceImpl implements CrmContactService {
         return contactMapper.selectById(id);
     }
 
+    @Override
+    public void validateContact(Long id) {
+        validateContactExists(id);
+    }
+
     @Override
     public List<CrmContactDO> getContactListByIds(Collection<Long> ids, Long userId) {
         if (CollUtil.isEmpty(ids)) {

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

@@ -184,7 +184,8 @@ public class CrmContractServiceImpl implements CrmContractService {
             return BeanUtils.toBean(product, CrmContractProductDO.class)
                     .setId(null).setProductId(productItem.getId()).setContractId(contractId)
                     .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent())
-                    .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent()));
+                    // TODO 芋艿:这里临时注释掉
+                    .setTotalPrice(MoneyUtils.calculator(null, productItem.getCount(), productItem.getDiscountPercent()));
         });
     }
 

+ 14 - 1
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java

@@ -3,10 +3,13 @@ package cn.iocoder.yudao.module.crm.service.product;
 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryCreateReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
-
 import jakarta.validation.Valid;
+
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * CRM 产品分类 Service 接口
@@ -61,4 +64,14 @@ public interface CrmProductCategoryService {
      */
     List<CrmProductCategoryDO> getProductCategoryList(Collection<Long> ids);
 
+    /**
+     * 获得产品分类 Map
+     *
+     * @param ids 编号数组
+     * @return 产品分类 Map
+     */
+    default Map<Long, CrmProductCategoryDO> getProductCategoryMap(Collection<Long> ids) {
+        return convertMap(getProductCategoryList(ids), CrmProductCategoryDO::getId);
+    }
+
 }

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

@@ -8,6 +8,9 @@ import jakarta.validation.Valid;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * CRM 产品 Service 接口
@@ -54,28 +57,46 @@ public interface CrmProductService {
      */
     List<CrmProductDO> getProductList(Collection<Long> ids);
 
+    /**
+     * 获得产品 Map
+     *
+     * @param ids 编号
+     * @return 产品 Map
+     */
+    default Map<Long, CrmProductDO> getProductMap(Collection<Long> ids) {
+        return convertMap(getProductList(ids), CrmProductDO::getId);
+    }
+
     /**
      * 获得产品分页
      *
      * @param pageReqVO 分页查询
      * @return 产品分页
      */
-    PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO, Long userId);
+    PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO);
 
     /**
-     * 获得产品
+     * 获得产品数量
      *
      * @param categoryId 分类编号
      * @return 产品
      */
-    CrmProductDO getProductByCategoryId(Long categoryId);
+    Long getProductByCategoryId(Long categoryId);
 
     /**
-     * 获得产品列表
+     * 获得指定状态的产品列表
+     *
+     * @param status 状态
+     * @return 产品列表
+     */
+    List<CrmProductDO> getProductListByStatus(Integer status);
+
+    /**
+     * 校验产品们的有效性
      *
-     * @param ids 产品编号
+     * @param ids 编号数组
      * @return 产品列表
      */
-    List<CrmProductDO> getProductListByIds(Collection<Long> ids);
+    List<CrmProductDO> validProductList(Collection<Long> ids);
 
 }

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

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.crm.service.product;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.collection.ListUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 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.product.vo.product.CrmProductPageReqVO;
@@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPerm
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.mzt.logapi.context.LogRecordContext;
 import com.mzt.logapi.service.impl.DiffParseFunction;
 import com.mzt.logapi.starter.annotation.LogRecord;
@@ -27,8 +26,10 @@ import org.springframework.validation.annotation.Validated;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
 
@@ -138,25 +139,41 @@ public class CrmProductServiceImpl implements CrmProductService {
     }
 
     @Override
-    public List<CrmProductDO> getProductList(Collection<Long> ids) {
-        if (CollUtil.isEmpty(ids)) {
-            return ListUtil.empty();
-        }
-        return productMapper.selectBatchIds(ids);
+    public PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO) {
+        return productMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public Long getProductByCategoryId(Long categoryId) {
+        return productMapper.selectCountByCategoryId(categoryId);
     }
 
     @Override
-    public PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO, Long userId) {
-        return productMapper.selectPage(pageReqVO, userId);
+    public List<CrmProductDO> getProductListByStatus(Integer status) {
+        return productMapper.selectListByStatus(status);
     }
 
     @Override
-    public CrmProductDO getProductByCategoryId(Long categoryId) {
-        return productMapper.selectOne(new LambdaQueryWrapper<CrmProductDO>().eq(CrmProductDO::getCategoryId, categoryId));
+    public List<CrmProductDO> validProductList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        List<CrmProductDO> list = productMapper.selectBatchIds(ids);
+        Map<Long, CrmProductDO> productMap = convertMap(list, CrmProductDO::getId);
+        for (Long id : ids) {
+            CrmProductDO product = productMap.get(id);
+            if (productMap.get(id) == null) {
+                throw exception(PRODUCT_NOT_EXISTS);
+            }
+            if (CommonStatusEnum.isDisable(product.getStatus())) {
+                throw exception(PRODUCT_NOT_ENABLE, product.getName());
+            }
+        }
+        return list;
     }
 
     @Override
-    public List<CrmProductDO> getProductListByIds(Collection<Long> ids) {
+    public List<CrmProductDO> getProductList(Collection<Long> ids) {
         if (CollUtil.isEmpty(ids)) {
             return Collections.emptyList();
         }