lzxhqs пре 1 година
родитељ
комит
f361d05940
25 измењених фајлова са 527 додато и 36 уклоњено
  1. 55 0
      sql/mysql/optinal/crm_20240114.sql
  2. 1 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
  3. 52 0
      yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java
  4. 5 0
      yudao-module-crm/yudao-module-crm-biz/pom.xml
  5. 2 2
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
  6. 7 5
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java
  7. 75 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
  8. 18 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java
  9. 14 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java
  10. 50 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java
  11. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java
  12. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java
  13. 20 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java
  14. 3 3
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java
  15. 4 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java
  16. 74 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
  17. 1 1
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java
  18. 2 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java
  19. 21 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java
  20. 11 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java
  21. 3 0
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
  22. 3 6
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java
  23. 90 8
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
  24. 10 6
      yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java
  25. 4 3
      yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java

+ 55 - 0
sql/mysql/optinal/crm_20240114.sql

@@ -0,0 +1,55 @@
+
+-- ----------------------------
+-- Table structure for crm_business_product
+-- ----------------------------
+DROP TABLE IF EXISTS `crm_business_product`;
+CREATE TABLE `crm_business_product`  (
+  `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '主键',
+  `business_id` bigint(0) NOT NULL COMMENT '商机ID',
+  `product_id` bigint(0) NOT NULL COMMENT '产品ID',
+  `price` decimal(18, 2) NOT NULL COMMENT '产品单价',
+  `sales_price` decimal(18, 2) NULL DEFAULT NULL COMMENT '销售价格',
+  `num` int(0) NULL DEFAULT NULL COMMENT '数量',
+  `discount` decimal(10, 2) NULL DEFAULT NULL COMMENT '折扣',
+  `subtotal` decimal(18, 2) NULL DEFAULT NULL COMMENT '小计(折扣后价格)',
+  `unit` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '单位',
+  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+  `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+  `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
+  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+  `tenant_id` bigint(0) NOT NULL DEFAULT 1 COMMENT '租户编号',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商机产品关联表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for crm_business_status
+-- ----------------------------
+DROP TABLE IF EXISTS `crm_business_status`;
+CREATE TABLE `crm_business_status`  (
+  `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `type_id` bigint(0) NOT NULL COMMENT '状态类型编号',
+  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '状态名',
+  `percent` bigint(0) NULL DEFAULT NULL COMMENT '赢单率',
+  `sort` int(0) NULL DEFAULT NULL COMMENT '排序',
+  `tenant_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '租户编号',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商机状态' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for crm_business_status_type
+-- ----------------------------
+DROP TABLE IF EXISTS `crm_business_status_type`;
+CREATE TABLE `crm_business_status_type`  (
+  `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '状态类型名',
+  `dept_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '使用的部门编号',
+  `status` int(0) NOT NULL DEFAULT 1 COMMENT '开启状态',
+  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+  `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+  `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
+  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+  `tenant_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '租户编号',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商机状态类型' ROW_FORMAT = Dynamic;

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

@@ -17,6 +17,7 @@ public interface ErrorCodeConstants {
 
     // ========== 商机管理 1-020-002-000 ==========
     ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在");
+    ErrorCode BUSINESS_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除");
 
     // TODO @lilleo:商机状态、商机类型,都单独错误码段
 

+ 52 - 0
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.crm.enums.business;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * @author lzxhqs
+ * @version 1.0
+ * @title CrmBizEndStatus
+ * @description
+ * @create 2024/1/12
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CrmBizEndStatus implements IntArrayValuable {
+    WIN(1, "赢单"),
+    LOSE(2, "输单"),
+    INVALID(3, "无效");
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBizEndStatus::getStatus).toArray();
+
+    public static boolean isWin(Integer status) {
+        return ObjectUtil.equal(WIN.getStatus(), status);
+    }
+
+    public static boolean isLose(Integer status) {
+        return ObjectUtil.equal(LOSE.getStatus(), status);
+    }
+
+    public static boolean isInvalid(Integer status) {
+        return ObjectUtil.equal(INVALID.getStatus(), status);
+    }
+
+    /**
+     * 场景类型
+     */
+    private final Integer status;
+    /**
+     * 场景名称
+     */
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+}

+ 5 - 0
yudao-module-crm/yudao-module-crm-biz/pom.xml

@@ -22,6 +22,11 @@
             <artifactId>yudao-module-system-api</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-system-biz</artifactId>
+            <version>${revision}</version>
+        </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-crm-api</artifactId>

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

@@ -55,14 +55,14 @@ public class CrmBusinessController {
     @PostMapping("/create")
     @Operation(summary = "创建商机")
     @PreAuthorize("@ss.hasPermission('crm:business:create')")
-    public CommonResult<Long> createBusiness(@Valid @RequestBody CrmBusinessCreateReqVO createReqVO) {
+    public CommonResult<Long> createBusiness(@Valid @RequestBody CrmBusinessSaveReqVO createReqVO) {
         return success(businessService.createBusiness(createReqVO, getLoginUserId()));
     }
 
     @PutMapping("/update")
     @Operation(summary = "更新商机")
     @PreAuthorize("@ss.hasPermission('crm:business:update')")
-    public CommonResult<Boolean> updateBusiness(@Valid @RequestBody CrmBusinessUpdateReqVO updateReqVO) {
+    public CommonResult<Boolean> updateBusiness(@Valid @RequestBody CrmBusinessSaveReqVO updateReqVO) {
         businessService.updateBusiness(updateReqVO);
         return success(true);
     }

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

@@ -4,6 +4,7 @@ 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;
@@ -101,11 +102,12 @@ public class CrmBusinessStatusTypeController {
         PageResult<CrmBusinessStatusTypeDO> pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO);
         // 处理部门回显
         // TODO @ljlleo:可以使用 CollectionUtils.convertSet 替代常用的 stream 操作,更简洁一点;下面几个也是哈;
-        Set<Long> deptIds = pageResult.getList().stream()
-                .map(CrmBusinessStatusTypeDO::getDeptIds)
-                .filter(Objects::nonNull)
-                .flatMap(Collection::stream)
-                .collect(Collectors.toSet());
+//        Set<Long> deptIds = pageResult.getList().stream()
+//                .map(CrmBusinessStatusTypeDO::getDeptIds)
+//                .filter(Objects::nonNull)
+//                .flatMap(Collection::stream)
+//                .collect(Collectors.toSet());
+        Set<Long> deptIds = CollectionUtils.convertSetByFlatMap(pageResult.getList(), CrmBusinessStatusTypeDO::getDeptIds,Collection::stream);
         List<DeptRespDTO> deptList = deptApi.getDeptList(deptIds);
         return success(CrmBusinessStatusTypeConvert.INSTANCE.convertPage(pageResult, deptList));
     }

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

@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
+import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * @author lzxhqs
+ */
+@Schema(description = "管理后台 - CRM 商机创建/更新 Request VO")
+@Data
+public class CrmBusinessSaveReqVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
+    private Long id;
+
+    @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @NotNull(message = "商机名称不能为空")
+    private String name;
+
+    @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
+    @NotNull(message = "商机状态类型不能为空")
+    private Long statusTypeId;
+
+    @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "商机状态不能为空")
+    private Long statusId;
+
+    @Schema(description = "下次联系时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime contactNextTime;
+
+    @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
+    @NotNull(message = "客户不能为空")
+    private Long customerId;
+
+    @Schema(description = "预计成交日期")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime dealTime;
+
+    @Schema(description = "商机金额", example = "12371")
+    private Integer price;
+
+    @Schema(description = "整单折扣")
+    private Integer discountPercent;
+
+    @Schema(description = "产品总金额", example = "12025")
+    private BigDecimal productPrice;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+    @Schema(description = "联系人编号", example = "110")
+    @NotNull(message = "联系人编号不能为空")
+    private Long contactId;
+
+    @Schema(description = "1赢单2输单3无效", example = "1")
+    @InEnum(CrmBizEndStatus.class)
+    private Integer endStatus;
+
+    @Schema(description = "产品列表", example = "")
+    private List<CrmProductSaveReqVO> product = new ArrayList<>();
+
+}

+ 18 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java

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

+ 14 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * @author lzxhqs
+ */
+@Schema(description = "管理后台 - 商机产品关联 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class CrmBusinessProductRespVO {
+}

+ 50 - 0
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @author lzxhqs
+ */
+@Schema(description = "管理后台 - CRM 商机产品关联表 创建/更新 Request VO")
+@Data
+public class CrmBusinessProductSaveReqVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
+    private Long id;
+
+    @Schema(description = "商机ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "商机ID不能为空")
+    private Integer businessId;
+
+    @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "产品ID不能为空")
+    private Integer productId;
+
+    @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "产品单价不能为空")
+    private BigDecimal price;
+
+    @Schema(description = "销售价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "销售价格不能为空")
+    private BigDecimal salesPrice;
+
+    @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "数量不能为空")
+    private BigDecimal num;
+
+    @Schema(description = "折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "折扣不能为空")
+    private BigDecimal discount;
+
+    @Schema(description = "小计(折扣后价格)", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "小计(折扣后价格)不能为空")
+    private BigDecimal subtotal;
+
+    @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
+    @NotNull(message = "单位不能为空")
+    private String unit;
+}

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

@@ -24,6 +24,6 @@ public class CrmBusinessStatusTypeSaveReqVO {
 
     // TODO @ljlleo VO 里面,我们不使用默认值哈。这里 Lists.newArrayList() 看看怎么去掉。上面 deptIds 也是类似噢
     @Schema(description = "商机状态集合", requiredMode = Schema.RequiredMode.REQUIRED)
-    private List<CrmBusinessStatusSaveReqVO> statusList = Lists.newArrayList();
+    private List<CrmBusinessStatusSaveReqVO> statusList;
 
 }

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

@@ -27,7 +27,7 @@ public interface CrmBusinessConvert {
 
     CrmBusinessConvert INSTANCE = Mappers.getMapper(CrmBusinessConvert.class);
 
-    CrmBusinessDO convert(CrmBusinessCreateReqVO bean);
+    CrmBusinessDO convert(CrmBusinessSaveReqVO bean);
 
     CrmBusinessDO convert(CrmBusinessUpdateReqVO bean);
 

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

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.crm.convert.businessproduct;
+
+import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author lzxhqs
+ * @version 1.0
+ * @title CrmBusinessProductConvert
+ * @description
+ * @create 2024/1/12
+ */
+@Mapper
+public interface CrmBusinessProductConvert {
+    CrmBusinessProductConvert INSTANCE = Mappers.getMapper(CrmBusinessProductConvert.class);
+
+    CrmBusinessProductDO convert(CrmProductSaveReqVO product);
+}

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

@@ -41,9 +41,9 @@ public interface CrmBusinessStatusTypeConvert {
 
     default CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean, List<CrmBusinessStatusDO> statusList) {
         // TODO @ljlleo 可以链式赋值,简化成一行;
-        CrmBusinessStatusTypeRespVO result = convert(bean);
-        result.setStatusList(statusList);
-        return result;
+//        CrmBusinessStatusTypeRespVO result = convert(bean);
+//        result.setStatusList(statusList);
+        return convert(bean).setStatusList(statusList);
     }
 
 }

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

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.crm.dal.dataobject.business;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -52,6 +54,7 @@ public class CrmBusinessDO extends BaseDO {
      * 客户编号
      *
      * TODO @ljileo:这个字段,后续要写下关联的实体哈
+     * 关联 {@link CrmCustomerDO#getId()}
      */
     private Long customerId;
     /**
@@ -101,6 +104,7 @@ public class CrmBusinessDO extends BaseDO {
      * 负责人的用户编号
      *
      * 关联 AdminUserDO 的 id 字段
+     * {@link AdminUserDO#getId()}
      */
     private Long ownerUserId;
 

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

@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.business;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * 商机产品关联表 DO
+ *
+ * @author lzxhqs
+ */
+@TableName("crm_business_product")
+@KeySequence("crm_business_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmBusinessProductDO extends BaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 商机ID
+     * 关联 {@link CrmBusinessDO#getId()}
+     */
+    private Long businessId;
+
+    /**
+     * 产品ID
+     * 关联{@link CrmProductDO#getId()}
+     */
+    private Long productId;
+
+    /**
+     * 产品单价
+     */
+    private BigDecimal price;
+
+    /**
+     * 销售价格
+     */
+    private BigDecimal salesPrice;
+
+    /**
+     * 数量
+     */
+    private BigDecimal num;
+
+    /**
+     * 折扣
+     */
+    private BigDecimal discount;
+
+    /**
+     * 小计(折扣后价格)
+     */
+    private BigDecimal subtotal;
+
+    /**
+     * 单位
+     */
+    private String unit;
+}

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

@@ -39,7 +39,7 @@ public class CrmBusinessStatusDO {
      *
      * TODO 这里是不是改成 Integer 存储,百分比 * 100 ;
      */
-    private String percent;
+    private Integer percent;
     /**
      * 排序
      */

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

@@ -1,5 +1,6 @@
 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;
@@ -43,6 +44,7 @@ public class CrmBusinessStatusTypeDO extends BaseDO {
      * 开启状态
      *
      * TODO 改成 Integer,关联 CommonStatus
+     * {@link CommonStatusEnum}
      */
     private Boolean status;
 

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

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.business;
+
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 商机产品 Mapper
+ * @author lzxhqs
+ */
+@Mapper
+public interface CrmBusinessProductMapper extends BaseMapperX<CrmBusinessProductDO> {
+    default void deleteByBusinessId(Long id) {
+        delete(CrmBusinessProductDO::getBusinessId, id);
+    }
+
+    default CrmBusinessProductDO selectByBusinessId(Long id) {
+        return selectOne(CrmBusinessProductDO::getBusinessId, id);
+    }
+}

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

@@ -28,4 +28,15 @@ public interface CrmBusinessStatusTypeMapper extends BaseMapperX<CrmBusinessStat
                 .eqIfPresent(CrmBusinessStatusTypeDO::getStatus, queryVO.getStatus())
                 .inIfPresent(CrmBusinessStatusTypeDO::getId, queryVO.getIdList()));
     }
+
+    /**
+     * 根据ID和name查询
+     *
+     * @param id 商机状态类型id
+     * @param name 状态类型名
+     * @return result
+     */
+    default CrmBusinessStatusTypeDO selectByIdAndName(Long id, String name) {
+        return selectOne(CrmBusinessStatusTypeDO::getId, id, CrmBusinessStatusTypeDO::getName, name);
+    }
 }

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

@@ -63,5 +63,8 @@ public interface CrmContractMapper extends BaseMapperX<CrmContractDO> {
     default Long selectCountByContactId(Long contactId) {
         return selectCount(CrmContractDO::getContactId, contactId);
     }
+    default CrmContractDO selectByBizId(Long businessId) {
+        return selectOne(CrmContractDO::getBusinessId, businessId);
+    }
 
 }

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

@@ -1,10 +1,7 @@
 package cn.iocoder.yudao.module.crm.service.business;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@@ -27,14 +24,14 @@ public interface CrmBusinessService {
      * @param userId      用户编号
      * @return 编号
      */
-    Long createBusiness(@Valid CrmBusinessCreateReqVO createReqVO, Long userId);
+    Long createBusiness(@Valid CrmBusinessSaveReqVO createReqVO, Long userId);
 
     /**
      * 更新商机
      *
      * @param updateReqVO 更新信息
      */
-    void updateBusiness(@Valid CrmBusinessUpdateReqVO updateReqVO);
+    void updateBusiness(@Valid CrmBusinessSaveReqVO updateReqVO);
 
     /**
      * 删除商机

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

@@ -4,14 +4,18 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessCreateReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
+import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
+import cn.iocoder.yudao.module.crm.convert.businessproduct.CrmBusinessProductConvert;
 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.contract.CrmContractDO;
 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.dal.mysql.contactbusinesslink.CrmContactBusinessMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
@@ -26,11 +30,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.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_CONTRACT_EXISTS;
 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
 
@@ -46,6 +53,13 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     @Resource
     private CrmBusinessMapper businessMapper;
 
+    @Resource
+    private CrmBusinessProductMapper businessProductMapper;
+    @Resource
+    private CrmContractMapper contractMapper;
+
+    @Resource
+    private CrmContactBusinessMapper contactBusinessMapper;
     @Resource
     private CrmPermissionService permissionService;
     @Resource
@@ -55,29 +69,79 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     @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(CrmBusinessCreateReqVO createReqVO, Long userId) {
+    public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
         // 1. 插入商机
         CrmBusinessDO business = CrmBusinessConvert.INSTANCE.convert(createReqVO);
         businessMapper.insert(business);
         // TODO 商机待定:插入商机与产品的关联表;校验商品存在
-
+        verifyCrmBusinessProduct(business.getId());
+        if (!createReqVO.getProduct().isEmpty()) {
+            createBusinessProducts(createReqVO.getProduct(), business.getId());
+        }
         // TODO 商机待定:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表
+        createContactBusiness(business.getId(), createReqVO.getContactId());
 
         // 2. 创建数据权限
+        // 设置当前操作的人为负责人
         permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())
-                .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
+                .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
 
         // 4. 记录操作日志上下文
         LogRecordContext.putVariable("business", business);
         return business.getId();
     }
+    /**
+     * @param businessId 商机id
+     * @param contactId 联系人id
+     * @throws
+     * @description 联系人与商机的关联
+     * @author lzxhqs
+     */
+    private void createContactBusiness(Long businessId, Long contactId) {
+        CrmContactBusinessDO contactBusiness = new CrmContactBusinessDO();
+        contactBusiness.setBusinessId(businessId);
+        contactBusiness.setContactId(contactId);
+        contactBusinessMapper.insert(contactBusiness);
+
+    }
+
+    /**
+     * @param products 产品集合
+     * @description 插入商机产品关联表
+     * @author lzxhqs
+     */
+    private void createBusinessProducts(List<CrmProductSaveReqVO> products, Long businessId) {
+        List<CrmBusinessProductDO> list = new ArrayList<>();
+        for (CrmProductSaveReqVO product : products) {
+            CrmBusinessProductDO businessProductDO = new CrmBusinessProductDO();
+            businessProductDO.setBusinessId(businessId)
+                    .setProductId(product.getId())
+                    .setPrice(BigDecimal.valueOf(product.getPrice()));
+            list.add(businessProductDO);
+        }
+        businessProductMapper.insertBatch(list);
+    }
+
+    /**
+     * @param id businessId
+     * @description 校验管理的产品存在则删除
+     * @author lzxhqs
+     */
+    private void verifyCrmBusinessProduct(Long id) {
+        CrmBusinessProductDO businessProductDO = businessProductMapper.selectByBusinessId(id);
+        if (businessProductDO != null) {
+            //通过商机Id删除
+            businessProductMapper.deleteByBusinessId(id);
+        }
+
+    }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
             success = CRM_BUSINESS_UPDATE_SUCCESS)
     @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
-    public void updateBusiness(CrmBusinessUpdateReqVO updateReqVO) {
+    public void updateBusiness(CrmBusinessSaveReqVO updateReqVO) {
         // 1. 校验存在
         CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
 
@@ -85,6 +149,10 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         CrmBusinessDO updateObj = CrmBusinessConvert.INSTANCE.convert(updateReqVO);
         businessMapper.updateById(updateObj);
         // TODO 商机待定:插入商机与产品的关联表;校验商品存在
+        verifyCrmBusinessProduct(updateReqVO.getId());
+        if (!updateReqVO.getProduct().isEmpty()) {
+            createBusinessProducts(updateReqVO.getProduct(), updateReqVO.getId());
+        }
 
         // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表
         // 3. 记录操作日志上下文
@@ -101,6 +169,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         // 校验存在
         CrmBusinessDO business = validateBusinessExists(id);
         // TODO @商机待定:需要校验有没关联合同。CrmContractDO 的 businessId 字段
+        validateContractExists(id);
 
         // 删除
         businessMapper.deleteById(id);
@@ -111,6 +180,19 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         LogRecordContext.putVariable("businessName", business.getName());
     }
 
+    /**
+     * @param businessId 商机id
+     * @throws
+     * @description 删除校验合同是关联合同
+     * @author lzxhqs
+     */
+    private void validateContractExists(Long businessId) {
+        CrmContractDO contract = contractMapper.selectByBizId(businessId);
+        if(contract != null) {
+            throw exception(BUSINESS_CONTRACT_EXISTS);
+        }
+    }
+
     private CrmBusinessDO validateBusinessExists(Long id) {
         CrmBusinessDO crmBusiness = businessMapper.selectById(id);
         if (crmBusiness == null) {

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

@@ -95,14 +95,18 @@ public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeSe
 
     // TODO @ljlleo 这个方法,这个参考 validateDeptNameUnique 实现。
     private void validateBusinessStatusTypeExists(String name, Long id) {
-        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) {
+        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

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

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.service.business;
 
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessCreateReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
@@ -39,7 +40,7 @@ public class CrmBusinessServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testCreateBusiness_success() {
         // 准备参数
-        CrmBusinessCreateReqVO reqVO = randomPojo(CrmBusinessCreateReqVO.class);
+        CrmBusinessSaveReqVO reqVO = randomPojo(CrmBusinessSaveReqVO.class);
 
         // 调用
         Long businessId = businessService.createBusiness(reqVO, getLoginUserId());
@@ -56,7 +57,7 @@ public class CrmBusinessServiceImplTest extends BaseDbUnitTest {
         CrmBusinessDO dbBusiness = randomPojo(CrmBusinessDO.class);
         businessMapper.insert(dbBusiness);// @Sql: 先插入出一条存在的数据
         // 准备参数
-        CrmBusinessUpdateReqVO reqVO = randomPojo(CrmBusinessUpdateReqVO.class, o -> {
+        CrmBusinessSaveReqVO reqVO = randomPojo(CrmBusinessSaveReqVO.class, o -> {
             o.setId(dbBusiness.getId()); // 设置更新的 ID
         });
 
@@ -70,7 +71,7 @@ public class CrmBusinessServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testUpdateBusiness_notExists() {
         // 准备参数
-        CrmBusinessUpdateReqVO reqVO = randomPojo(CrmBusinessUpdateReqVO.class);
+        CrmBusinessSaveReqVO reqVO = randomPojo(CrmBusinessSaveReqVO.class);
 
         // 调用, 并断言异常
         assertServiceException(() -> businessService.updateBusiness(reqVO), BUSINESS_NOT_EXISTS);