商机模块
This commit is contained in:
parent
ed34b18d82
commit
f361d05940
55
sql/mysql/optinal/crm_20240114.sql
Normal file
55
sql/mysql/optinal/crm_20240114.sql
Normal file
@ -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;
|
@ -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:商机状态、商机类型,都单独错误码段
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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<>();
|
||||
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -39,7 +39,7 @@ public class CrmBusinessStatusDO {
|
||||
*
|
||||
* TODO 这里是不是改成 Integer 存储,百分比 * 100 ;
|
||||
*/
|
||||
private String percent;
|
||||
private Integer percent;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
* 删除商机
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user