!851 CRM: 完善线索

Merge pull request !851 from puhui999/develop
This commit is contained in:
芋道源码 2024-01-23 00:42:57 +00:00 committed by Gitee
commit ff7e492887
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
22 changed files with 558 additions and 186 deletions

View File

@ -14,9 +14,13 @@ public interface LogRecordConstants {
String CRM_LEADS_CREATE_SUB_TYPE = "创建线索";
String CRM_LEADS_CREATE_SUCCESS = "创建了线索{{#clue.name}}";
String CRM_LEADS_UPDATE_SUB_TYPE = "更新线索";
String CRM_LEADS_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReqVO}}";
String CRM_LEADS_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}";
String CRM_LEADS_DELETE_SUB_TYPE = "删除线索";
String CRM_LEADS_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";
String CRM_LEADS_TRANSFER_SUB_TYPE = "转移线索";
String CRM_LEADS_TRANSFER_SUCCESS = "将线索【{{#clue.name}}】的负责人从【{getAdminUserById{#clue.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
String CRM_LEADS_TRANSLATE_SUB_TYPE = "线索转化为客户";
String CRM_LEADS_TRANSLATE_SUCCESS = "将线索【{{#clue.name}}】转化为客户";
// ======================= CRM_CUSTOMER 客户 =======================

View File

@ -39,7 +39,7 @@ public class CrmClueController {
@Operation(summary = "创建线索")
@PreAuthorize("@ss.hasPermission('crm:clue:create')")
public CommonResult<Long> createClue(@Valid @RequestBody CrmClueSaveReqVO createReqVO) {
return success(clueService.createClue(createReqVO));
return success(clueService.createClue(createReqVO, getLoginUserId()));
}
@PutMapping("/update")

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.framework.common.validation.Telephone;
import com.mzt.logapi.starter.annotation.DiffLogField;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
@ -19,32 +20,39 @@ public class CrmClueSaveReqVO {
private Long id;
@Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx")
@DiffLogField(name = "线索名称")
@NotEmpty(message = "线索名称不能为空")
private String name;
@Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
@DiffLogField(name = "下次联系时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime contactNextTime;
@Schema(description = "电话", example = "18000000000")
@DiffLogField(name = "电话")
@Telephone
private String telephone;
@Schema(description = "手机号", example = "18000000000")
@DiffLogField(name = "手机号")
@Mobile
private String mobile;
@Schema(description = "地址", example = "北京市海淀区")
@DiffLogField(name = "地址")
private String address;
@Schema(description = "最后跟进时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@DiffLogField(name = "最后跟进时间")
private LocalDateTime contactLastTime;
@Schema(description = "负责人编号", example = "2048")
private Long ownerUserId;
@Schema(description = "备注", example = "随便")
@DiffLogField(name = "备注")
private String remark;
}

View File

@ -17,9 +17,6 @@ import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.collect.Lists;
@ -46,7 +43,6 @@ import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CONTACT_TYPE;
@Tag(name = "管理后台 - CRM 联系人")
@RestController
@ -64,8 +60,6 @@ public class CrmContactController {
@Resource
private AdminUserApi adminUserApi;
@Resource
private OperateLogApi operateLogApi;
@PostMapping("/create")
@Operation(summary = "创建联系人")
@ -158,17 +152,6 @@ public class CrmContactController {
buildContactDetailPage(pageResult).getList());
}
@GetMapping("/operate-log-page")
@Operation(summary = "获得客户操作日志")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(@RequestParam("bizId") Long bizId) {
OperateLogV2PageReqDTO reqVO = new OperateLogV2PageReqDTO();
reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
reqVO.setBizType(CRM_CONTACT_TYPE);
reqVO.setBizId(bizId);
return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class)));
}
/**
* 构建详细的联系人分页结果
*

View File

@ -17,9 +17,6 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
@ -44,7 +41,6 @@ import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_CUSTOMER_TYPE;
@Tag(name = "管理后台 - CRM 客户")
@RestController
@ -60,8 +56,7 @@ public class CrmCustomerController {
private DeptApi deptApi;
@Resource
private AdminUserApi adminUserApi;
@Resource
private OperateLogApi operateLogApi;
@PostMapping("/create")
@Operation(summary = "创建客户")
@ -182,19 +177,6 @@ public class CrmCustomerController {
return success(true);
}
// TODO @puhui999要不搞个通用的 CrmOperateLogController之后所有业务都调用它
@GetMapping("/operate-log-page")
@Operation(summary = "获得客户操作日志")
@Parameter(name = "id", description = "客户编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(@RequestParam("id") Long id) {
OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO();
reqDTO.setPageSize(PAGE_SIZE_NONE); // 不分页
reqDTO.setBizType(CRM_CUSTOMER_TYPE);
reqDTO.setBizId(id);
return success(operateLogApi.getOperateLogPage(reqDTO));
}
@PutMapping("/lock")
@Operation(summary = "锁定/解锁客户")
@PreAuthorize("@ss.hasPermission('crm:customer:update')")

View File

@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.crm.controller.admin.operatelog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogPageReqVO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
@Tag(name = "管理后台 - 操作日志")
@RestController
@RequestMapping("/crm/operate-log")
@Validated
public class CrmOperateLogController {
@Resource
private OperateLogApi operateLogApi;
private static final Map<Integer, String> BIZ_TYPE_MAP = new HashMap<>();
static {
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_LEADS.getType(), CRM_LEADS_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CUSTOMER.getType(), CRM_CUSTOMER_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTACT.getType(), CRM_CONTACT_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_BUSINESS.getType(), CRM_BUSINESS_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTRACT.getType(), CRM_CONTRACT_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_PRODUCT.getType(), CRM_PRODUCT_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), CRM_RECEIVABLE_TYPE);
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), CRM_RECEIVABLE_PLAN_TYPE);
}
@GetMapping("/page")
@Operation(summary = "获得操作日志")
@Parameter(name = "id", description = "客户编号", required = true)
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<PageResult<OperateLogV2RespDTO>> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO();
reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页需要分页需注释
reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType()));
reqDTO.setBizId(pageReqVO.getBizId());
return success(operateLogApi.getOperateLogPage(reqDTO));
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 跟进记录分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CrmOperateLogPageReqVO extends PageParam {
@Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(CrmBizTypeEnum.class)
@NotNull(message = "数据类型不能为空")
private Integer bizType;
@Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "数据编号不能为空")
private Long bizId;
}

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.permission;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
@ -11,6 +12,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@ -55,7 +57,7 @@ public class CrmPermissionController {
@PreAuthorize("@ss.hasPermission('crm:permission:create')")
@CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER)
public CommonResult<Boolean> addPermission(@Valid @RequestBody CrmPermissionCreateReqVO reqVO) {
permissionService.createPermission(CrmPermissionConvert.INSTANCE.convert(reqVO));
permissionService.createPermission(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class));
return success(true);
}

View File

@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
@ -16,9 +15,6 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService;
import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO;
import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
@ -38,12 +34,10 @@ import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.CRM_PRODUCT_TYPE;
@Tag(name = "管理后台 - CRM 产品")
@RestController
@ -56,8 +50,6 @@ public class CrmProductController {
@Resource
private CrmProductCategoryService productCategoryService;
@Resource
private OperateLogApi operateLogApi;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@ -131,14 +123,4 @@ public class CrmProductController {
return CrmProductConvert.INSTANCE.convertList(list, userMap, productCategoryList);
}
@GetMapping("/operate-log-page")
@Operation(summary = "获得产品操作日志")
@PreAuthorize("@ss.hasPermission('crm:product:query')")
public CommonResult<PageResult<OperateLogV2RespDTO>> getProductOperateLog(@RequestParam("bizId") Long bizId) {
OperateLogV2PageReqDTO reqVO = new OperateLogV2PageReqDTO();
reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
reqVO.setBizType(CRM_PRODUCT_TYPE).setBizId(bizId);
return success(operateLogApi.getOperateLogPage(BeanUtils.toBean(reqVO, OperateLogV2PageReqDTO.class)));
}
}

View File

@ -3,12 +3,9 @@ package cn.iocoder.yudao.module.crm.convert.permission;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionUpdateReqBO;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@ -32,14 +29,6 @@ public interface CrmPermissionConvert {
CrmPermissionConvert INSTANCE = Mappers.getMapper(CrmPermissionConvert.class);
CrmPermissionDO convert(CrmPermissionCreateReqBO createBO);
CrmPermissionDO convert(CrmPermissionUpdateReqBO updateBO);
CrmPermissionCreateReqBO convert(CrmPermissionCreateReqVO reqVO);
CrmPermissionUpdateReqBO convert(CrmPermissionUpdateReqVO updateReqVO);
List<CrmPermissionRespVO> convert(List<CrmPermissionDO> permission);
default List<CrmPermissionRespVO> convert(List<CrmPermissionDO> permission, List<AdminUserRespDTO> userList,
@ -65,7 +54,4 @@ public interface CrmPermissionConvert {
id -> new CrmPermissionDO().setId(id).setLevel(updateReqVO.getLevel()));
}
// TODO @puhui999搞成 BeanUtils
List<CrmPermissionDO> convertList(List<CrmPermissionCreateReqBO> list);
}

View File

@ -7,6 +7,9 @@ import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecor
import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* 跟进记录 Mapper
*
@ -22,4 +25,14 @@ public interface CrmFollowUpRecordMapper extends BaseMapperX<CrmFollowUpRecordDO
.orderByDesc(CrmFollowUpRecordDO::getId));
}
default void deleteByBiz(Integer bizType, Long bizId) {
delete(new LambdaQueryWrapperX<CrmFollowUpRecordDO>().eq(CrmFollowUpRecordDO::getBizType, bizType)
.eq(CrmFollowUpRecordDO::getBizId, bizId));
}
default List<CrmFollowUpRecordDO> selectListByBiz(Integer bizType, Collection<Long> bizIds) {
return selectList(new LambdaQueryWrapperX<CrmFollowUpRecordDO>().eq(CrmFollowUpRecordDO::getBizType, bizType)
.in(CrmFollowUpRecordDO::getBizId, bizIds));
}
}

View File

@ -23,9 +23,10 @@ public interface CrmClueService {
* 创建线索
*
* @param createReqVO 创建信息
* @param userId 用户编号
* @return 编号
*/
Long createClue(@Valid CrmClueSaveReqVO createReqVO);
Long createClue(@Valid CrmClueSaveReqVO createReqVO, Long userId);
/**
* 更新线索

View File

@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.crm.service.clue;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
@ -15,11 +15,16 @@ import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransformReqV
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO;
import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper;
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;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
@ -32,17 +37,14 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.*;
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.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS;
import static java.util.Collections.singletonList;
/**
* 线索 Service 实现类
@ -61,7 +63,8 @@ public class CrmClueServiceImpl implements CrmClueService {
@Resource
private CrmPermissionService crmPermissionService;
@Resource
private CrmFollowUpRecordService followUpRecordService;
@Resource
private AdminUserApi adminUserApi;
@ -69,21 +72,23 @@ public class CrmClueServiceImpl implements CrmClueService {
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_CREATE_SUB_TYPE, bizNo = "{{#clue.id}}",
success = CRM_LEADS_CREATE_SUCCESS)
public Long createClue(CrmClueSaveReqVO createReqVO) {
public Long createClue(CrmClueSaveReqVO createReqVO, Long userId) {
// 1. 校验关联数据
validateRelationDataExists(createReqVO);
// 2. 插入
CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class);
CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class).setId(null);
if (ObjUtil.isNull(createReqVO.getOwnerUserId())) {
clue.setOwnerUserId(userId); // 如果没有设置负责人那么默认操作人为负责人
} else {
// 校验负责人是否存在
adminUserApi.validateUserList(singletonList(createReqVO.getOwnerUserId()));
}
clueMapper.insert(clue);
// 3. 创建数据权限
CrmPermissionCreateReqBO createReqBO = new CrmPermissionCreateReqBO()
.setBizType(CrmBizTypeEnum.CRM_LEADS.getType())
.setBizId(clue.getId())
// 设置当前操作的人为负责人
.setUserId(getLoginUserId())
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel());
CrmPermissionCreateReqBO createReqBO = new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_LEADS.getType())
.setBizId(clue.getId()).setUserId(clue.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel());
crmPermissionService.createPermission(createReqBO);
// 4. 记录操作日志上下文
@ -95,16 +100,16 @@ public class CrmClueServiceImpl implements CrmClueService {
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = CRM_LEADS_UPDATE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateClue(CrmClueSaveReqVO updateReqVO) {
Assert.notNull(updateReqVO.getId(), "线索编号不能为空");
@CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER)
public void updateClue(CrmClueSaveReqVO updateReq) {
Assert.notNull(updateReq.getId(), "线索编号不能为空");
// 1. 校验线索是否存在
CrmClueDO oldClue = validateClueExists(updateReqVO.getId());
CrmClueDO oldClue = validateClueExists(updateReq.getId());
// 2. 校验关联数据
validateRelationDataExists(updateReqVO);
validateRelationDataExists(updateReq);
// 3. 更新
CrmClueDO updateObj = BeanUtils.toBean(updateReqVO, CrmClueDO.class);
CrmClueDO updateObj = BeanUtils.toBean(updateReq, CrmClueDO.class);
clueMapper.updateById(updateObj);
// 3. 记录操作日志上下文
@ -113,8 +118,19 @@ public class CrmClueServiceImpl implements CrmClueService {
}
@Override
public void updateClueFollowUp(CrmUpdateFollowUpReqBO clueUpdateFollowUpReqBO) {
clueMapper.updateById(BeanUtils.toBean(clueUpdateFollowUpReqBO, CrmClueDO.class).setId(clueUpdateFollowUpReqBO.getBizId()));
@LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReq.bizId}",
success = CRM_LEADS_UPDATE_SUCCESS)
public void updateClueFollowUp(CrmUpdateFollowUpReqBO updateReq) {
// 校验线索是否存在
CrmClueDO oldClue = validateClueExists(updateReq.getBizId());
// 更新
clueMapper.updateById(BeanUtils.toBean(updateReq, CrmClueDO.class).setId(updateReq.getBizId()));
// 3. 记录操作日志上下文
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmUpdateFollowUpReqBO.class));
LogRecordContext.putVariable("clueName", oldClue.getName());
}
@Override
@ -132,10 +148,99 @@ public class CrmClueServiceImpl implements CrmClueService {
// 3. 删除数据权限
crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_LEADS.getType(), id);
// 4. 记录操作日志上下文
// 4. 删除跟进
followUpRecordService.deleteFollowUpRecordByBiz(CrmBizTypeEnum.CRM_LEADS.getType(), id);
// 记录操作日志上下文
LogRecordContext.putVariable("clueName", clue.getName());
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}",
success = CRM_LEADS_TRANSFER_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void transferClue(CrmClueTransferReqVO reqVO, Long userId) {
// 1 校验线索是否存在
CrmClueDO clue = validateClueExists(reqVO.getId());
// 2.1 数据权限转移
crmPermissionService.transferPermission(CrmClueConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_LEADS.getType()));
// 2.2 设置新的负责人
clueMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
// 3. 记录转移日志
LogRecordContext.putVariable("clue", clue);
}
@Override
@Transactional(rollbackFor = Exception.class)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void translateCustomer(CrmClueTransformReqVO reqVO, Long userId) {
// 1.1 校验线索都存在
Set<Long> clueIds = reqVO.getIds();
List<CrmClueDO> clues = getClueList(clueIds, userId);
if (CollUtil.isEmpty(clues) || ObjectUtil.notEqual(clues.size(), clueIds.size())) {
clueIds.removeAll(convertSet(clues, CrmClueDO::getId));
throw exception(CLUE_ANY_CLUE_NOT_EXISTS, clueIds);
}
// 1.2 存在已经转化的直接提示哈避免操作的用户以为都转化成功了
List<CrmClueDO> translatedClues = filterList(clues,
clue -> ObjectUtil.equal(Boolean.TRUE, clue.getTransformStatus()));
if (CollUtil.isNotEmpty(translatedClues)) {
throw exception(CLUE_ANY_CLUE_ALREADY_TRANSLATED, convertSet(translatedClues, CrmClueDO::getId));
}
// 2. 遍历线索(未转化的线索)创建对应的客户
List<CrmClueDO> translateClues = filterList(clues, clue -> ObjUtil.equal(Boolean.FALSE, clue.getTransformStatus()));
List<CrmCustomerDO> customers = customerService.createCustomerBatch(convertList(translateClues, clue ->
BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class)), userId);
Map<Long, Long> clueCustomerIdMap = new HashMap<>(translateClues.size());
// 2.1 更新线索
clueMapper.updateBatch(convertList(customers, customer -> {
CrmClueDO firstClue = findFirst(translateClues, clue -> ObjUtil.equal(clue.getName(), customer.getName()));
clueCustomerIdMap.put(firstClue.getId(), customer.getId());
return new CrmClueDO().setId(firstClue.getId()).setTransformStatus(Boolean.TRUE).setCustomerId(customer.getId());
}));
// 2.3 复制跟进
updateFollowUpRecords(clueCustomerIdMap);
// 3. 记录操作日志
for (CrmClueDO clue : translateClues) {
getSelf().receiveClueLog(clue);
}
}
private void updateFollowUpRecords(Map<Long, Long> clueCustomerIdMap) {
List<CrmFollowUpRecordDO> followUpRecords = followUpRecordService.getFollowUpRecordByBiz(
CrmBizTypeEnum.CRM_LEADS.getType(), clueCustomerIdMap.keySet());
if (CollUtil.isEmpty(followUpRecords)) {
return;
}
// 创建跟进
followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, followUpRecord ->
BeanUtils.toBean(followUpRecord, CrmFollowUpCreateReqBO.class).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(clueCustomerIdMap.get(followUpRecord.getBizId()))));
}
@LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSLATE_SUB_TYPE, bizNo = "{{#clue.id}}",
success = CRM_LEADS_TRANSLATE_SUCCESS)
public void receiveClueLog(CrmClueDO clue) {
// 记录操作日志上下文
LogRecordContext.putVariable("clue", clue);
}
private void validateRelationDataExists(CrmClueSaveReqVO reqVO) {
// 校验负责人
if (Objects.nonNull(reqVO.getOwnerUserId()) &&
Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) {
throw exception(USER_NOT_EXISTS);
}
}
private CrmClueDO validateClueExists(Long id) {
CrmClueDO crmClueDO = clueMapper.selectById(id);
if (crmClueDO == null) {
@ -163,55 +268,13 @@ public class CrmClueServiceImpl implements CrmClueService {
return clueMapper.selectPage(pageReqVO, userId);
}
@Override
public void transferClue(CrmClueTransferReqVO reqVO, Long userId) {
// 1 校验线索是否存在
validateClueExists(reqVO.getId());
// 2.1 数据权限转移
crmPermissionService.transferPermission(CrmClueConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_LEADS.getType()));
// 2.2 设置新的负责人
clueMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
// 3. TODO 记录转移日志
}
@Override
@Transactional(rollbackFor = Exception.class)
public void translateCustomer(CrmClueTransformReqVO reqVO, Long userId) {
// 校验线索都存在
Set<Long> clueIds = reqVO.getIds();
List<CrmClueDO> clues = getClueList(clueIds, userId);
if (CollUtil.isEmpty(clues) || ObjectUtil.notEqual(clues.size(), clueIds.size())) {
clueIds.removeAll(convertSet(clues, CrmClueDO::getId));
throw exception(CLUE_ANY_CLUE_NOT_EXISTS, StrUtil.join(",", clueIds));
}
// 存在已经转化的直接提示哈避免操作的用户以为都转化成功了
List<CrmClueDO> translatedClues = CollectionUtils.filterList(clues,
clue -> ObjectUtil.equal(Boolean.TRUE, clue.getTransformStatus()));
if (CollUtil.isNotEmpty(translatedClues)) {
throw exception(CLUE_ANY_CLUE_ALREADY_TRANSLATED, StrUtil.join(",", convertSet(translatedClues, CrmClueDO::getId)));
}
// 遍历线索(未转化的线索)创建对应的客户
reqVO.getIds().forEach(id -> {
// 1. 创建客户
CrmCustomerSaveReqVO customerSaveReqVO = BeanUtils.toBean(id, CrmCustomerSaveReqVO.class).setId(null);
Long customerId = customerService.createCustomer(customerSaveReqVO, userId);
// TODO @puhui999如果有跟进记录需要一起转过去提问艿艿这里是复制线索所有的跟进吗还是直接把线索相关的跟进 bizTypebizId 全改为关联客户
// 2. 更新线索
clueMapper.updateById(new CrmClueDO().setId(id)
.setTransformStatus(Boolean.TRUE).setCustomerId(customerId));
});
}
private void validateRelationDataExists(CrmClueSaveReqVO reqVO) {
// 校验负责人
if (Objects.nonNull(reqVO.getOwnerUserId()) &&
Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) {
throw exception(USER_NOT_EXISTS);
}
/**
* 获得自身的代理对象解决 AOP 生效问题
*
* @return 自己
*/
private CrmClueServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
}

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageR
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
import jakarta.validation.Valid;
@ -98,6 +99,15 @@ public interface CrmCustomerService {
*/
void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO);
/**
* 批量创建客户
*
* @param customerCreateReqBOs 请求
* @param userId 用户编号
* @return 客户列表
*/
List<CrmCustomerDO> createCustomerBatch(List<CrmCustomerCreateReqBO> customerCreateReqBOs, Long userId);
// ==================== 公海相关操作 ====================
/**

View File

@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionU
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
@ -42,10 +43,12 @@ import java.util.Collections;
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.convertList;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT;
import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_OWNER_LIMIT;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
/**
@ -209,6 +212,25 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
customerMapper.updateById(BeanUtils.toBean(customerUpdateFollowUpReqBO, CrmCustomerDO.class).setId(customerUpdateFollowUpReqBO.getBizId()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<CrmCustomerDO> createCustomerBatch(List<CrmCustomerCreateReqBO> customerCreateReqBOs, Long userId) {
if (CollUtil.isEmpty(customerCreateReqBOs)) {
return emptyList();
}
// 创建客户
List<CrmCustomerDO> customers = convertList(customerCreateReqBOs, customerBO ->
BeanUtils.toBean(customerBO, CrmCustomerDO.class).setOwnerUserId(userId));
customerMapper.insertBatch(customers);
// 创建负责人数据权限
permissionService.createPermissionBatch(convertList(customers, customer -> new CrmPermissionCreateReqBO()
.setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()).setBizId(customer.getId()).setUserId(userId)
.setLevel(CrmPermissionLevelEnum.OWNER.getLevel())));
return customers;
}
// ==================== 公海相关操作 ====================
@Override

View File

@ -0,0 +1,125 @@
package cn.iocoder.yudao.module.crm.service.customer.bo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.framework.common.validation.Telephone;
import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 客户创建 Create Req BO
*
* @author HUIHUI
*/
@Data
public class CrmCustomerCreateReqBO {
/**
* 客户名称
*/
@NotEmpty(message = "客户名称不能为空")
private String name;
/**
* 跟进状态
*/
private Boolean followUpStatus;
/**
* 锁定状态
*/
private Boolean lockStatus;
/**
* 成交状态
*/
private Boolean dealStatus;
/**
* 所属行业
*
* 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_INDUSTRY}
*/
private Integer industryId;
/**
* 客户等级
*
* 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_LEVEL}
*/
private Integer level;
/**
* 客户来源
*
* 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_SOURCE}
*/
private Integer source;
/**
* 手机
*/
@Mobile
private String mobile;
/**
* 电话
*/
@Telephone
private String telephone;
/**
* 网址
*/
private String website;
/**
* QQ
*/
private String qq;
/**
* wechat
*/
private String wechat;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
private String email;
/**
* 客户描述
*/
@Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")
private String description;
/**
* 备注
*/
private String remark;
/**
* 负责人的用户编号
*
* 关联 AdminUserDO id 字段
*/
private Long ownerUserId;
/**
* 所在地
*
* 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段
*/
private Integer areaId;
/**
* 详细地址
*/
private String detailAddress;
/**
* 最后跟进时间
*/
private LocalDateTime contactLastTime;
/**
* 最后跟进内容
*/
private String contactLastContent;
/**
* 下次联系时间
*/
private LocalDateTime contactNextTime;
}

View File

@ -4,8 +4,12 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* 跟进记录 Service 接口
*
@ -21,6 +25,13 @@ public interface CrmFollowUpRecordService {
*/
Long createFollowUpRecord(@Valid CrmFollowUpRecordSaveReqVO createReqVO);
/**
* 创建更进
*
* @param followUpCreateReqBOs 请求
*/
void createFollowUpRecordBatch(List<CrmFollowUpCreateReqBO> followUpCreateReqBOs);
/**
* 删除跟进记录 (数据权限基于 bizType bizId)
*
@ -29,6 +40,14 @@ public interface CrmFollowUpRecordService {
*/
void deleteFollowUpRecord(Long id, Long userId);
/**
* 删除跟进
*
* @param bizType 模块类型
* @param bizId 模块数据编号
*/
void deleteFollowUpRecordByBiz(Integer bizType, Long bizId);
/**
* 获得跟进记录
*
@ -45,4 +64,13 @@ public interface CrmFollowUpRecordService {
*/
PageResult<CrmFollowUpRecordDO> getFollowUpRecordPage(CrmFollowUpRecordPageReqVO pageReqVO);
/**
* 获取跟进记录
*
* @param bizType 模块类型
* @param bizIds 模块数据编号
* @return 跟进列表
*/
List<CrmFollowUpRecordDO> getFollowUpRecordByBiz(Integer bizType, Collection<Long> bizIds);
}

View File

@ -17,13 +17,16 @@ import cn.iocoder.yudao.module.crm.service.clue.CrmClueService;
import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -46,16 +49,22 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
private CrmFollowUpRecordMapper crmFollowUpRecordMapper;
@Resource
@Lazy
private CrmPermissionService permissionService;
@Resource
@Lazy
private CrmBusinessService businessService;
@Resource
@Lazy
private CrmClueService clueService;
@Resource
@Lazy
private CrmContactService contactService;
@Resource
@Lazy
private CrmContractService contractService;
@Resource
@Lazy
private CrmCustomerService customerService;
@Override
@ -96,6 +105,16 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
return followUpRecord.getId();
}
@Override
public void createFollowUpRecordBatch(List<CrmFollowUpCreateReqBO> followUpCreateReqBOs) {
if (CollUtil.isEmpty(followUpCreateReqBOs)) {
return;
}
List<CrmFollowUpRecordDO> followUpRecords = BeanUtils.toBean(followUpCreateReqBOs, CrmFollowUpRecordDO.class);
crmFollowUpRecordMapper.insertBatch(followUpRecords);
}
@Override
public void deleteFollowUpRecord(Long id, Long userId) {
// 校验存在
@ -113,6 +132,12 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
crmFollowUpRecordMapper.deleteById(id);
}
@Override
public void deleteFollowUpRecordByBiz(Integer bizType, Long bizId) {
// 删除
crmFollowUpRecordMapper.deleteByBiz(bizType, bizId);
}
private CrmFollowUpRecordDO validateFollowUpRecordExists(Long id) {
CrmFollowUpRecordDO followUpRecord = crmFollowUpRecordMapper.selectById(id);
if (followUpRecord == null) {
@ -132,4 +157,9 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
return crmFollowUpRecordMapper.selectPage(pageReqVO);
}
@Override
public List<CrmFollowUpRecordDO> getFollowUpRecordByBiz(Integer bizType, Collection<Long> bizIds) {
return crmFollowUpRecordMapper.selectListByBiz(bizType, bizIds);
}
}

View File

@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.crm.service.followup.bo;
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.enums.DictTypeConstants;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 跟进信息 Create Req BO
*
* @author HUIHUI
*/
@Data
public class CrmFollowUpCreateReqBO {
/**
* 数据类型
*
* 枚举 {@link CrmBizTypeEnum}
*/
@NotNull(message = "数据类型不能为空")
private Integer bizType;
/**
* 数据编号
*
* 关联 {@link CrmBizTypeEnum} 对应模块 DO id 字段
*/
@NotNull(message = "数据编号不能为空")
private Long bizId;
/**
* 跟进类型
*
* 关联 {@link DictTypeConstants#CRM_FOLLOW_UP_TYPE} 字典
*/
@NotNull(message = "跟进类型不能为空")
private Integer type;
/**
* 跟进内容
*/
@NotEmpty(message = "跟进内容不能为空")
private String content;
/**
* 下次联系时间
*/
@NotNull(message = "下次联系时间不能为空")
private LocalDateTime nextTime;
/**
* 图片
*/
private List<String> picUrls;
/**
* 附件
*/
private List<String> fileUrls;
/**
* 关联的商机编号数组
*
* 关联 {@link CrmBusinessDO#getId()}
*/
private List<Long> businessIds;
/**
* 关联的联系人编号数组
*
* 关联 {@link CrmContactDO#getId()}
*/
private List<Long> contactIds;
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
@ -50,7 +51,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId()));
// 2. 创建
CrmPermissionDO permission = CrmPermissionConvert.INSTANCE.convert(createReqBO);
CrmPermissionDO permission = BeanUtils.toBean(createReqBO, CrmPermissionDO.class);
permissionMapper.insert(permission);
return permission.getId();
}
@ -62,7 +63,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
adminUserApi.validateUserList(convertSet(createReqBOs, CrmPermissionCreateReqBO::getUserId));
// 2. 创建
List<CrmPermissionDO> permissions = CrmPermissionConvert.INSTANCE.convertList(createReqBOs);
List<CrmPermissionDO> permissions = BeanUtils.toBean(createReqBOs, CrmPermissionDO.class);
permissionMapper.insertBatch(permissions);
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.module.crm.service.permission.bo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
/**
* crm 数据权限 Update Req BO
*
* @author HUIHUI
*/
@Data
public class CrmPermissionUpdateReqBO {
/**
* 数据权限编号
*/
@NotNull(message = "数据权限编号不能为空")
private Long id;
/**
* 当前登录用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
* 权限级别
*/
@NotNull(message = "权限级别不能为空")
@InEnum(CrmPermissionLevelEnum.class)
private Integer level;
}

View File

@ -15,6 +15,7 @@ import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
@ -45,7 +46,7 @@ public class CrmClueServiceImplTest extends BaseDbUnitTest {
CrmClueSaveReqVO reqVO = randomPojo(CrmClueSaveReqVO.class);
// 调用
Long clueId = clueService.createClue(reqVO);
Long clueId = clueService.createClue(reqVO, getLoginUserId());
// 断言
assertNotNull(clueId);
// 校验记录的属性是否正确