Jelajahi Sumber

✨ CRM:完善商机的列表

YunaiV 1 tahun lalu
induk
melakukan
e01dda9baf
28 mengubah file dengan 501 tambahan dan 412 penghapusan
  1. 1 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
  2. 73 25
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
  3. 0 75
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java
  4. 103 28
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
  5. 27 35
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
  6. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
  7. 36 17
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java
  8. 2 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java
  9. 4 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java
  10. 0 23
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java
  11. 0 46
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java
  12. 41 38
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java
  13. 15 11
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
  14. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java
  15. 4 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java
  16. 0 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java
  17. 0 4
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java
  18. 13 11
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java
  19. 9 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
  20. 62 62
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
  21. 13 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java
  22. 5 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java
  23. 13 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java
  24. 6 5
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java
  25. 2 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
  26. 14 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java
  27. 27 6
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java
  28. 29 12
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java

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

@@ -67,6 +67,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, "产品分类不存在");

+ 73 - 25
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,21 @@ 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 +38,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;
@@ -55,6 +65,13 @@ public class CrmBusinessController {
     private CrmBusinessStatusTypeService businessStatusTypeService;
     @Resource
     private CrmBusinessStatusService businessStatusService;
+    @Resource
+    private CrmProductService productService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
 
     @PostMapping("/create")
     @Operation(summary = "创建商机")
@@ -86,9 +103,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 +130,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 +147,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 +157,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 +165,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 +175,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")

+ 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;
-
-}

+ 103 - 28
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 = "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")
-    @NotNull(message = "商机状态类型不能为空")
     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 - 35
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,75 +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 字段
 
-    // TODO @puhui999:传递 items 就行啦;
     @Schema(description = "产品列表")
-    private List<CrmBusinessProductItem> productItems;
+    private List<Product> products;
 
     @Schema(description = "产品列表")
     @Data
     @NoArgsConstructor
     @AllArgsConstructor
-    public static class CrmBusinessProductItem {
+    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;
-
     }
 
 }

+ 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 - 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;
 
 }

+ 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;
     /**
      * 状态
      *

+ 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);
+    }
+
 }

+ 9 - 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);
+
     /**
      * 获得商机分页
      *

+ 62 - 62
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;
@@ -36,14 +33,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.*;
 
 /**
@@ -75,27 +72,29 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE, bizNo = "{{#business.id}}",
             success = CRM_BUSINESS_CREATE_SUCCESS)
     public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
-        createReqVO.setId(null);
-        // 1. 插入商机
+        // 1.1 校验产品项的有效性
+        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getProducts());
+        // 1.2 TODO 芋艿:校验关联字
+
+        // 2.1 插入商机
         CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class).setOwnerUserId(userId);
+        calculateTotalPrice(business, businessProducts);
         businessMapper.insert(business);
-        // 1.2 插入商机关联商品
-        if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话
-            List<CrmBusinessProductDO> productList = buildBusinessProductList(createReqVO.getProductItems(), 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);
         }
+
         // TODO @puhui999:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表
         createContactBusiness(business.getId(), createReqVO.getContactId());
 
-        // 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();
     }
@@ -114,15 +113,18 @@ 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. 校验存在
+        // 1.1 校验存在
         CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
+        // 1.2 校验产品项的有效性
+        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts());
+        // 1.3 TODO 芋艿:校验关联字
 
         // 2.1 更新商机
         CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class);
+        calculateTotalPrice(updateObj, businessProducts);
         businessMapper.updateById(updateObj);
         // 2.2 更新商机关联商品
-        List<CrmBusinessProductDO> productList = buildBusinessProductList(updateReqVO.getProductItems(), updateObj.getId());
-        updateBusinessProduct(productList, updateObj.getId());
+        updateBusinessProduct(updateObj.getId(), businessProducts);
 
         // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表
         // 3. 记录操作日志上下文
@@ -130,6 +132,37 @@ 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 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));
@@ -155,44 +188,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.CrmBusinessProductItem> productItems,
-                                                                Long businessId) {
-        // 校验商品存在
-        Set<Long> productIds = convertSet(productItems, CrmBusinessSaveReqVO.CrmBusinessProductItem::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()));
-        });
-    }
-
     /**
      * 删除校验合同是关联合同
      *
@@ -235,10 +230,10 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
 
     @Override
     public void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO) {
-        // 更新商机关联商品
-        List<CrmBusinessProductDO> productList = buildBusinessProductList(
-                BeanUtils.toBean(updateProductReqBO.getProductItems(), CrmBusinessSaveReqVO.CrmBusinessProductItem.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());
     }
 
     //======================= 查询相关 =======================
@@ -265,6 +260,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);

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

@@ -5,11 +5,13 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusine
 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 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 接口
@@ -74,4 +76,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);
+    }
+
 }

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

@@ -1,5 +1,6 @@
 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.module.crm.controller.admin.business.vo.status.CrmBusinessStatusPageReqVO;
@@ -13,6 +14,7 @@ import org.springframework.validation.annotation.Validated;
 import jakarta.annotation.Resource;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -80,6 +82,9 @@ public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService {
 
     @Override
     public List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
         return businessStatusMapper.selectBatchIds(ids);
     }
 

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

@@ -9,6 +9,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;
 
 /**
  * 商机状态类型 Service 接口
@@ -72,4 +75,14 @@ public interface CrmBusinessStatusTypeService {
      */
     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);
+    }
+
 }

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

@@ -3,7 +3,6 @@ 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;
@@ -11,19 +10,18 @@ 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 jakarta.annotation.Resource;
 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.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;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NOT_EXISTS;
 
 /**
  * 商机状态类型 Service 实现类
@@ -126,6 +124,9 @@ public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeSe
 
     @Override
     public List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
         return businessStatusTypeMapper.selectBatchIds(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();
         }