商机模块

This commit is contained in:
lzxhqs 2024-01-14 09:52:35 +08:00
parent ed34b18d82
commit f361d05940
25 changed files with 527 additions and 36 deletions

View 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;

View File

@ -17,6 +17,7 @@ public interface ErrorCodeConstants {
// ========== 商机管理 1-020-002-000 ========== // ========== 商机管理 1-020-002-000 ==========
ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(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商机状态商机类型都单独错误码段 // TODO @lilleo商机状态商机类型都单独错误码段

View File

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

View File

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

View File

@ -55,14 +55,14 @@ public class CrmBusinessController {
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建商机") @Operation(summary = "创建商机")
@PreAuthorize("@ss.hasPermission('crm:business:create')") @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())); return success(businessService.createBusiness(createReqVO, getLoginUserId()));
} }
@PutMapping("/update") @PutMapping("/update")
@Operation(summary = "更新商机") @Operation(summary = "更新商机")
@PreAuthorize("@ss.hasPermission('crm:business:update')") @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); businessService.updateBusiness(updateReqVO);
return success(true); return success(true);
} }

View File

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

View File

@ -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<>();
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 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;
}

View File

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

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.business; 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.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
@ -43,6 +44,7 @@ public class CrmBusinessStatusTypeDO extends BaseDO {
* 开启状态 * 开启状态
* *
* TODO 改成 Integer关联 CommonStatus * TODO 改成 Integer关联 CommonStatus
* {@link CommonStatusEnum}
*/ */
private Boolean status; private Boolean status;

View File

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

View File

@ -28,4 +28,15 @@ public interface CrmBusinessStatusTypeMapper extends BaseMapperX<CrmBusinessStat
.eqIfPresent(CrmBusinessStatusTypeDO::getStatus, queryVO.getStatus()) .eqIfPresent(CrmBusinessStatusTypeDO::getStatus, queryVO.getStatus())
.inIfPresent(CrmBusinessStatusTypeDO::getId, queryVO.getIdList())); .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);
}
} }

View File

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

View File

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

View File

@ -4,14 +4,18 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.collection.ListUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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.*;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
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.convert.business.CrmBusinessConvert; 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.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.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.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.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
@ -26,11 +30,14 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 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.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.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
@ -46,6 +53,13 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
@Resource @Resource
private CrmBusinessMapper businessMapper; private CrmBusinessMapper businessMapper;
@Resource
private CrmBusinessProductMapper businessProductMapper;
@Resource
private CrmContractMapper contractMapper;
@Resource
private CrmContactBusinessMapper contactBusinessMapper;
@Resource @Resource
private CrmPermissionService permissionService; private CrmPermissionService permissionService;
@Resource @Resource
@ -55,29 +69,79 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE, bizNo = "{{#business.id}}", @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE, bizNo = "{{#business.id}}",
success = CRM_BUSINESS_CREATE_SUCCESS) success = CRM_BUSINESS_CREATE_SUCCESS)
public Long createBusiness(CrmBusinessCreateReqVO createReqVO, Long userId) { public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
// 1. 插入商机 // 1. 插入商机
CrmBusinessDO business = CrmBusinessConvert.INSTANCE.convert(createReqVO); CrmBusinessDO business = CrmBusinessConvert.INSTANCE.convert(createReqVO);
businessMapper.insert(business); businessMapper.insert(business);
// TODO 商机待定插入商机与产品的关联表校验商品存在 // TODO 商机待定插入商机与产品的关联表校验商品存在
verifyCrmBusinessProduct(business.getId());
if (!createReqVO.getProduct().isEmpty()) {
createBusinessProducts(createReqVO.getProduct(), business.getId());
}
// TODO 商机待定在联系人的详情页如果直接新建商机则需要关联下这里要搞个 CrmContactBusinessDO // TODO 商机待定在联系人的详情页如果直接新建商机则需要关联下这里要搞个 CrmContactBusinessDO
createContactBusiness(business.getId(), createReqVO.getContactId());
// 2. 创建数据权限 // 2. 创建数据权限
// 设置当前操作的人为负责人
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()) 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. 记录操作日志上下文 // 4. 记录操作日志上下文
LogRecordContext.putVariable("business", business); LogRecordContext.putVariable("business", business);
return business.getId(); 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 @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = CRM_BUSINESS_UPDATE_SUCCESS) success = CRM_BUSINESS_UPDATE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateBusiness(CrmBusinessUpdateReqVO updateReqVO) { public void updateBusiness(CrmBusinessSaveReqVO updateReqVO) {
// 1. 校验存在 // 1. 校验存在
CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId()); CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
@ -85,6 +149,10 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
CrmBusinessDO updateObj = CrmBusinessConvert.INSTANCE.convert(updateReqVO); CrmBusinessDO updateObj = CrmBusinessConvert.INSTANCE.convert(updateReqVO);
businessMapper.updateById(updateObj); businessMapper.updateById(updateObj);
// TODO 商机待定插入商机与产品的关联表校验商品存在 // TODO 商机待定插入商机与产品的关联表校验商品存在
verifyCrmBusinessProduct(updateReqVO.getId());
if (!updateReqVO.getProduct().isEmpty()) {
createBusinessProducts(updateReqVO.getProduct(), updateReqVO.getId());
}
// TODO @商机待定如果状态发生变化插入商机状态变更记录表 // TODO @商机待定如果状态发生变化插入商机状态变更记录表
// 3. 记录操作日志上下文 // 3. 记录操作日志上下文
@ -101,6 +169,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
// 校验存在 // 校验存在
CrmBusinessDO business = validateBusinessExists(id); CrmBusinessDO business = validateBusinessExists(id);
// TODO @商机待定需要校验有没关联合同CrmContractDO businessId 字段 // TODO @商机待定需要校验有没关联合同CrmContractDO businessId 字段
validateContractExists(id);
// 删除 // 删除
businessMapper.deleteById(id); businessMapper.deleteById(id);
@ -111,6 +180,19 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
LogRecordContext.putVariable("businessName", business.getName()); 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) { private CrmBusinessDO validateBusinessExists(Long id) {
CrmBusinessDO crmBusiness = businessMapper.selectById(id); CrmBusinessDO crmBusiness = businessMapper.selectById(id);
if (crmBusiness == null) { if (crmBusiness == null) {

View File

@ -95,14 +95,18 @@ public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeSe
// TODO @ljlleo 这个方法这个参考 validateDeptNameUnique 实现 // TODO @ljlleo 这个方法这个参考 validateDeptNameUnique 实现
private void validateBusinessStatusTypeExists(String name, Long id) { private void validateBusinessStatusTypeExists(String name, Long id) {
LambdaQueryWrapper<CrmBusinessStatusTypeDO> wrapper = new LambdaQueryWrapperX<>(); CrmBusinessStatusTypeDO businessStatusTypeDO = businessStatusTypeMapper.selectByIdAndName(id, name);
if(null != id) { if (businessStatusTypeDO != null) {
wrapper.ne(CrmBusinessStatusTypeDO::getId, id);
}
long cnt = businessStatusTypeMapper.selectCount(wrapper.eq(CrmBusinessStatusTypeDO::getName, name));
if (cnt > 0) {
throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS); 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 @Override

View File

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