CRM:优化客户的过期到公海的逻辑

This commit is contained in:
YunaiV 2024-02-20 19:07:48 +08:00
parent 3e6524932b
commit b444312ea8
9 changed files with 187 additions and 183 deletions

View File

@ -45,6 +45,8 @@ public interface LogRecordConstants {
String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}";
String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE = "更新客户成交状态";
String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS = "更新了客户【{{#customerName}}】的成交状态为【{{#dealStatus ? '已成交' : '未成交'}}】";
String CRM_CUSTOMER_FOLLOW_UP_SUB_TYPE = "客户跟进";
String CRM_CUSTOMER_FOLLOW_UP_SUCCESS = "客户跟进【{{#customerName}}】";
// ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 =======================

View File

@ -43,6 +43,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static java.util.Collections.singletonList;
@Tag(name = "管理后台 - 线索")
@RestController
@ -93,11 +94,11 @@ public class CrmClueController {
return success(buildClueDetail(clue));
}
public CrmClueRespVO buildClueDetail(CrmClueDO clue) {
private CrmClueRespVO buildClueDetail(CrmClueDO clue) {
if (clue == null) {
return null;
}
return buildClueDetailList(Collections.singletonList(clue)).get(0);
return buildClueDetailList(singletonList(clue)).get(0);
}
@GetMapping("/page")

View File

@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
@ -15,7 +15,6 @@ import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
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;
@ -40,13 +39,11 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED;
import static java.util.Collections.singletonList;
@Tag(name = "管理后台 - CRM 客户")
@ -123,6 +120,7 @@ public class CrmCustomerController {
@Operation(summary = "获得客户分页")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
customerService.autoPutCustomerPool();
// 1. 查询客户分页
PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId());
if (CollUtil.isEmpty(pageResult.getList())) {
@ -159,45 +157,21 @@ public class CrmCustomerController {
});
}
// TODO @芋艿需要 review
@GetMapping("/put-in-pool-remind-page")
@GetMapping("/put-pool-remind-page")
@Operation(summary = "获得待进入公海客户分页")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<PageResult<CrmCustomerRespVO>> getPutInPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
// 获取公海配置 TODO @dbh52合并到 getPutInPoolRemindCustomerPage 会更合适哈
CrmCustomerPoolConfigDO poolConfigDO = customerPoolConfigService.getCustomerPoolConfig();
if (ObjUtil.isNull(poolConfigDO)
|| Boolean.FALSE.equals(poolConfigDO.getEnabled())
|| Boolean.FALSE.equals(poolConfigDO.getNotifyEnabled())) {
throw exception(CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED);
}
public CommonResult<PageResult<CrmCustomerRespVO>> getPutPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
// 1. 查询客户分页
PageResult<CrmCustomerDO> pageResult = customerService.getPutInPoolRemindCustomerPage(pageVO, poolConfigDO, getLoginUserId());
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
}
PageResult<CrmCustomerDO> pageResult = customerService.getPutPoolRemindCustomerPage(pageVO, getLoginUserId());
// 2. 拼接数据
return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/put-in-pool-remind-count")
@GetMapping("/put-pool-remind-count")
@Operation(summary = "获得待进入公海客户数量")
@PreAuthorize("@ss.hasPermission('crm:customer:query')")
public CommonResult<Long> getPutInPoolRemindCustomerCount() {
// 获取公海配置
CrmCustomerPoolConfigDO poolConfigDO = customerPoolConfigService.getCustomerPoolConfig();
if (ObjUtil.isNull(poolConfigDO)
|| Boolean.FALSE.equals(poolConfigDO.getEnabled())
|| Boolean.FALSE.equals(poolConfigDO.getNotifyEnabled())) {
throw exception(CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED);
}
CrmCustomerPageReqVO pageVO = new CrmCustomerPageReqVO()
.setPool(null)
.setContactStatus(CrmCustomerPageReqVO.CONTACT_TODAY)
.setSceneType(CrmSceneTypeEnum.OWNER.getType());
return success(customerService.getPutInPoolRemindCustomerCount(pageVO, poolConfigDO, getLoginUserId()));
public CommonResult<Long> getPutPoolRemindCustomerCount() {
return success(customerService.getPutPoolRemindCustomerCount(getLoginUserId()));
}
@GetMapping("/today-customer-count")
@ -225,24 +199,26 @@ public class CrmCustomerController {
if (poolConfig == null || !poolConfig.getEnabled()) {
return MapUtil.empty();
}
list = CollectionUtils.filterList(list, customer -> {
// 特殊如果没负责人则说明已经在公海不用计算
if (customer.getOwnerUserId() == null) {
return false;
}
// 已成交 or 已锁定不进入公海
return !customer.getDealStatus() && !customer.getLockStatus();
});
return convertMap(list, CrmCustomerDO::getId, customer -> {
// TODO 芋艿这样计算貌似有点问题
// 1.1 未成交放入公海天数
long dealExpireDay = 0;
if (!customer.getDealStatus()) {
dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime());
}
if (dealExpireDay < 0) {
dealExpireDay = 0;
}
long dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getOwnerTime());
// 1.2 未跟进放入公海天数
LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime());
long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime);
if (contactExpireDay < 0) {
contactExpireDay = 0;
LocalDateTime lastTime = customer.getOwnerTime();
if (customer.getContactLastTime() != null && customer.getContactLastTime().isAfter(lastTime)) {
lastTime = customer.getContactLastTime();
}
long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime);
// 2. 返回最小的天数
return Math.min(dealExpireDay, contactExpireDay);
long poolDay = Math.min(dealExpireDay, contactExpireDay);
return poolDay > 0 ? poolDay : 0;
});
}

View File

@ -57,6 +57,10 @@ public class CrmCustomerDO extends BaseDO {
* 关联 AdminUserDO id 字段
*/
private Long ownerUserId;
/**
* 成为负责人的时间
*/
private LocalDateTime ownerTime;
/**
* 锁定状态

View File

@ -31,40 +31,6 @@ import java.util.List;
@Mapper
public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
private static MPJLambdaWrapperX<CrmCustomerDO> buildPutInPoolRemindCustomerWrapper(CrmCustomerPageReqVO pageReqVO, CrmCustomerPoolConfigDO poolConfigDO, Long userId) {
MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null);
// 锁定状态不需要提醒
query.ne(CrmCustomerDO::getLockStatus, true);
// 情况一未成交提醒日期区间
Integer dealExpireDays = poolConfigDO.getDealExpireDays();
LocalDateTime startDealRemindDate = LocalDateTimeUtil.beginOfDay(LocalDateTime.now())
.minusDays(dealExpireDays);
LocalDateTime endDealRemindDate = LocalDateTimeUtil.endOfDay(LocalDateTime.now())
.minusDays(Math.max(dealExpireDays - poolConfigDO.getNotifyDays(), 0));
// 情况二未跟进提醒日期区间
Integer contactExpireDays = poolConfigDO.getContactExpireDays();
LocalDateTime startContactRemindDate = LocalDateTimeUtil.beginOfDay(LocalDateTime.now())
.minusDays(contactExpireDays);
LocalDateTime endContactRemindDate = LocalDateTimeUtil.endOfDay(LocalDateTime.now())
.minusDays(Math.max(contactExpireDays - poolConfigDO.getNotifyDays(), 0));
query
// 情况一1. 未成交放入公海提醒
.eq(CrmCustomerDO::getDealStatus, false)
.between(CrmCustomerDO::getCreateTime, startDealRemindDate, endDealRemindDate)
// 情况二未跟进放入公海提醒
.or() // 2.1 contactLastTime 为空 TODO 芋艿这个要不要搞个默认值
.isNull(CrmCustomerDO::getContactLastTime)
.between(CrmCustomerDO::getCreateTime, startContactRemindDate, endContactRemindDate)
.or() // 2.2 ContactLastTime 不为空
.between(CrmCustomerDO::getContactLastTime, startContactRemindDate, endContactRemindDate);
return query;
}
default Long selectCountByLockStatusAndOwnerUserId(Boolean lockStatus, Long ownerUserId) {
return selectCount(new LambdaUpdateWrapper<CrmCustomerDO>()
.eq(CrmCustomerDO::getLockStatus, lockStatus)
@ -124,30 +90,80 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
return selectJoinList(CrmCustomerDO.class, query);
}
default List<CrmCustomerDO> selectListByLockAndNotPool(Boolean lockStatus) {
return selectList(new LambdaQueryWrapper<CrmCustomerDO>()
.eq(CrmCustomerDO::getLockStatus, lockStatus)
.gt(CrmCustomerDO::getOwnerUserId, 0));
}
default CrmCustomerDO selectByCustomerName(String name) {
return selectOne(CrmCustomerDO::getName, name);
}
default PageResult<CrmCustomerDO> selectPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO,
CrmCustomerPoolConfigDO poolConfigDO,
Long userId) {
final MPJLambdaWrapperX<CrmCustomerDO> query = buildPutInPoolRemindCustomerWrapper(pageReqVO, poolConfigDO, userId);
default PageResult<CrmCustomerDO> selectPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO,
CrmCustomerPoolConfigDO poolConfig,
Long userId) {
final MPJLambdaWrapperX<CrmCustomerDO> query = buildPutPoolRemindCustomerQuery(pageReqVO, poolConfig, userId);
return selectJoinPage(pageReqVO, CrmCustomerDO.class, query.selectAll(CrmCustomerDO.class));
}
default Long selectPutInPoolRemindCustomerCount(CrmCustomerPageReqVO pageReqVO,
CrmCustomerPoolConfigDO poolConfigDO,
Long userId) {
final MPJLambdaWrapperX<CrmCustomerDO> query = buildPutInPoolRemindCustomerWrapper(pageReqVO, poolConfigDO, userId);
default Long selectPutPoolRemindCustomerCount(CrmCustomerPageReqVO pageReqVO,
CrmCustomerPoolConfigDO poolConfigDO,
Long userId) {
final MPJLambdaWrapperX<CrmCustomerDO> query = buildPutPoolRemindCustomerQuery(pageReqVO, poolConfigDO, userId);
return selectCount(query);
}
private static MPJLambdaWrapperX<CrmCustomerDO> buildPutPoolRemindCustomerQuery(CrmCustomerPageReqVO pageReqVO,
CrmCustomerPoolConfigDO poolConfig,
Long userId) {
MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
// 拼接数据权限的查询条件
CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null);
// 未锁定 + 未成交
query.eq(CrmCustomerDO::getLockStatus, false).eq(CrmCustomerDO::getDealStatus, false);
// 情况一未成交提醒日期区间
Integer dealExpireDays = poolConfig.getDealExpireDays();
LocalDateTime startDealRemindTime = LocalDateTime.now().minusDays(dealExpireDays);
LocalDateTime endDealRemindTime = LocalDateTime.now()
.minusDays(Math.max(dealExpireDays - poolConfig.getNotifyDays(), 0));
// 情况二未跟进提醒日期区间
Integer contactExpireDays = poolConfig.getContactExpireDays();
LocalDateTime startContactRemindTime = LocalDateTime.now().minusDays(contactExpireDays);
LocalDateTime endContactRemindTime = LocalDateTime.now()
.minusDays(Math.max(contactExpireDays - poolConfig.getNotifyDays(), 0));
query.and(q -> {
// 情况一成交超时提醒
q.between(CrmCustomerDO::getOwnerTime, startDealRemindTime, endDealRemindTime)
// 情况二跟进超时提醒
.or(w -> w.between(CrmCustomerDO::getOwnerTime, startContactRemindTime, endContactRemindTime)
.and(p -> p.between(CrmCustomerDO::getContactLastTime, startContactRemindTime, endContactRemindTime)
.or().isNull(CrmCustomerDO::getContactLastTime)));
});
return query;
}
/**
* 获得需要过期到公海的客户列表
*
* @return 客户列表
*/
default List<CrmCustomerDO> selectListByAutoPool(CrmCustomerPoolConfigDO poolConfig) {
LambdaQueryWrapper<CrmCustomerDO> query = new LambdaQueryWrapper<>();
query.gt(CrmCustomerDO::getOwnerUserId, 0);
// 未锁定 + 未成交
query.eq(CrmCustomerDO::getLockStatus, false).eq(CrmCustomerDO::getDealStatus, false);
// 已经超时
LocalDateTime dealExpireTime = LocalDateTime.now().minusDays(poolConfig.getDealExpireDays());
LocalDateTime contactExpireTime = LocalDateTime.now().minusDays(poolConfig.getContactExpireDays());
query.and(q -> {
// 情况一成交超时
q.lt(CrmCustomerDO::getOwnerTime, dealExpireTime)
// 情况二跟进超时
.or(w -> w.lt(CrmCustomerDO::getOwnerTime, contactExpireTime)
.and(p -> p.lt(CrmCustomerDO::getContactLastTime, contactExpireTime)
.or().isNull(CrmCustomerDO::getContactLastTime)));
});
return selectList(query);
}
default Long selectTodayCustomerCount(Long userId) {
MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
// 我负责的 + 非公海

View File

@ -78,8 +78,7 @@ public class CrmClueServiceImpl implements CrmClueService {
adminUserApi.validateUser(createReqVO.getOwnerUserId());
// 2. 插入线索
CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class)
.setContactLastTime(LocalDateTime.now());
CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class);
clueMapper.insert(clue);
// 3. 创建数据权限
@ -129,7 +128,7 @@ public class CrmClueServiceImpl implements CrmClueService {
// 校验线索是否存在
CrmClueDO oldClue = validateClueExists(id);
// 更新
// 更新线索
clueMapper.updateById(new CrmClueDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime)
.setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent));

View File

@ -3,11 +3,10 @@ package cn.iocoder.yudao.module.crm.service.customer;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
import jakarta.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -45,6 +44,15 @@ public interface CrmCustomerService {
*/
void updateCustomerDealStatus(Long id, Boolean dealStatus);
/**
* 更新客户相关的跟进信息
*
* @param id 编号
* @param contactNextTime 下次联系时间
* @param contactLastContent 最后联系内容
*/
void updateCustomerFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent);
/**
* 删除客户
*
@ -88,6 +96,23 @@ public interface CrmCustomerService {
*/
PageResult<CrmCustomerDO> getCustomerPage(CrmCustomerPageReqVO pageReqVO, Long userId);
/**
* 获得放入公海提醒的客户分页
*
* @param pageVO 分页查询
* @param userId 用户编号
* @return 客户分页
*/
PageResult<CrmCustomerDO> getPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, Long userId);
/**
* 获得待进入公海的客户数量
*
* @param userId 用户编号
* @return 提醒数量
*/
Long getPutPoolRemindCustomerCount(Long userId);
/**
* 校验客户是否存在
*
@ -111,13 +136,6 @@ public interface CrmCustomerService {
*/
void lockCustomer(@Valid CrmCustomerLockReqVO lockReqVO, Long userId);
/**
* 更新客户相关更进信息
*
* @param customerUpdateFollowUpReqBO 请求
*/
void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO);
/**
* 创建客户
*
@ -161,18 +179,6 @@ public interface CrmCustomerService {
*/
int autoPutCustomerPool();
/**
* 获得放入公海提醒的客户分页数据
*
* @param pageVO 分页查询
* @param poolConfigDO 公海配置
* @param userId 用户编号
* @return 客户分页
*/
PageResult<CrmCustomerDO> getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO,
CrmCustomerPoolConfigDO poolConfigDO,
Long userId);
/**
* 获得今日需联系客户数量
*
@ -181,18 +187,6 @@ public interface CrmCustomerService {
*/
Long getTodayCustomerCount(Long userId);
/**
* 获得待进入公海的客户数量
*
* @param pageVO 分页查询
* @param poolConfigDO 公海配置
* @param userId 用户编号
* @return 提醒数量
*/
Long getPutInPoolRemindCustomerCount(CrmCustomerPageReqVO pageVO,
CrmCustomerPoolConfigDO poolConfigDO,
Long userId);
/**
* 获得分配给我的客户数量
*

View File

@ -8,7 +8,6 @@ import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@ -16,6 +15,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfi
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
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.util.CrmPermissionUtils;
@ -23,7 +23,6 @@ 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;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
@ -43,7 +42,6 @@ import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
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;
@ -114,7 +112,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
*/
private static CrmCustomerDO initCustomer(Object customer, Long ownerUserId) {
return BeanUtils.toBean(customer, CrmCustomerDO.class).setOwnerUserId(ownerUserId)
.setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now());
.setOwnerTime(LocalDateTime.now());
}
@Override
@ -157,6 +155,22 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
LogRecordContext.putVariable("dealStatus", dealStatus);
}
@Override
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}",
success = CRM_CUSTOMER_FOLLOW_UP_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
public void updateCustomerFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) {
// 1.1 校验存在
CrmCustomerDO customer = validateCustomerExists(id);
// 2. 更新客户的跟进信息
customerMapper.updateById(new CrmCustomerDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime)
.setContactLastTime(LocalDateTime.now()));
// 3. 记录操作日志上下文
LogRecordContext.putVariable("customerName", customer.getName());
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
@ -168,7 +182,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
// 1.2 检查引用
validateCustomerReference(id);
// 2. 删除
// 2. 删除客户
customerMapper.deleteById(id);
// 3. 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
@ -209,7 +223,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel()));
// 2.2 转移后重新设置负责人
customerMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId());
customerMapper.updateById(new CrmCustomerDO().setId(reqVO.getId())
.setOwnerUserId(reqVO.getNewOwnerUserId()).setOwnerTime(LocalDateTime.now()));
// 3. 记录转移日志
LogRecordContext.putVariable("customer", customer);
@ -226,7 +241,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
if (customer.getLockStatus().equals(lockReqVO.getLockStatus())) {
throw exception(customer.getLockStatus() ? CUSTOMER_LOCK_FAIL_IS_LOCK : CUSTOMER_UNLOCK_FAIL_IS_UNLOCK);
}
// 1.3 校验锁定上限
// 1.3 校验锁定上限
if (lockReqVO.getLockStatus()) {
validateCustomerExceedLockLimit(userId);
}
@ -239,11 +254,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
LogRecordContext.putVariable("customer", customer);
}
@Override
public void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO) {
customerMapper.updateById(BeanUtils.toBean(customerUpdateFollowUpReqBO, CrmCustomerDO.class).setId(customerUpdateFollowUpReqBO.getBizId()));
}
@Override
@Transactional(rollbackFor = Exception.class)
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}",
@ -263,7 +273,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
}
@Override
public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, CrmCustomerImportReqVO importReqVO) {
public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers,
CrmCustomerImportReqVO importReqVO) {
if (CollUtil.isEmpty(importCustomers)) {
throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY);
}
@ -383,12 +394,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
// 1.4 校验负责人是否到达上限
validateCustomerExceedOwnerLimit(ownerUserId, customers.size());
// 2.1 领取公海数据
// 2. 领取公海数据
List<CrmCustomerDO> updateCustomers = new ArrayList<>();
List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>();
customers.forEach(customer -> {
// 2.1. 设置负责人
updateCustomers.add(new CrmCustomerDO().setId(customer.getId()).setOwnerUserId(ownerUserId));
updateCustomers.add(new CrmCustomerDO().setId(customer.getId())
.setOwnerUserId(ownerUserId).setOwnerTime(LocalDateTime.now()));
// 2.2. 创建负责人数据权限
createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
@ -415,34 +427,23 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
if (poolConfig == null || !poolConfig.getEnabled()) {
return 0;
}
// 1.1 获取没有锁定的不在公海的客户
List<CrmCustomerDO> customerList = customerMapper.selectListByLockAndNotPool(Boolean.FALSE);
// TODO @puhui999下面也搞到 sql 里去哈 or 查询问题不大的 393 402原因是避免无用的太多数据查询到 java 进程里
List<CrmCustomerDO> poolCustomerList = new ArrayList<>();
poolCustomerList.addAll(filterList(customerList, customer ->
!customer.getDealStatus() && (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0));
poolCustomerList.addAll(filterList(customerList, customer -> {
if (!customer.getDealStatus()) { // 这里只处理成交的
return false;
}
LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime());
return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0;
}));
// 1. 获得需要放到的客户列表
List<CrmCustomerDO> customerList = customerMapper.selectListByAutoPool(poolConfig);
// 2. 逐个放入公海
int count = 0;
for (CrmCustomerDO customer : poolCustomerList) {
for (CrmCustomerDO customer : customerList) {
try {
getSelf().putCustomerPool(customer);
count++;
} catch (Throwable e) {
log.error("[autoPutCustomerPool][Customer 客户({}) 放入公海异常]", customer.getId(), e);
log.error("[autoPutCustomerPool][客户({}) 放入公海异常]", customer.getId(), e);
}
}
return count;
}
private void putCustomerPool(CrmCustomerDO customer) {
@Transactional // 需要 protected 修饰因为需要在事务中调用
protected void putCustomerPool(CrmCustomerDO customer) {
// 1. 设置负责人为 NULL
int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null);
if (updateOwnerUserIncr == 0) {
@ -486,17 +487,29 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
}
@Override
public PageResult<CrmCustomerDO> getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO,
CrmCustomerPoolConfigDO poolConfigDO,
Long userId) {
return customerMapper.selectPutInPoolRemindCustomerPage(pageReqVO, poolConfigDO, userId);
public PageResult<CrmCustomerDO> getPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, Long userId) {
CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
if (ObjUtil.isNull(poolConfig)
|| Boolean.FALSE.equals(poolConfig.getEnabled())
|| Boolean.FALSE.equals(poolConfig.getNotifyEnabled())) {
return PageResult.empty();
}
return customerMapper.selectPutPoolRemindCustomerPage(pageVO, poolConfig, userId);
}
@Override
public Long getPutInPoolRemindCustomerCount(CrmCustomerPageReqVO pageReqVO,
CrmCustomerPoolConfigDO poolConfigDO,
Long userId) {
return customerMapper.selectPutInPoolRemindCustomerCount(pageReqVO, poolConfigDO, userId);
public Long getPutPoolRemindCustomerCount(Long userId) {
CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
if (ObjUtil.isNull(poolConfig)
|| Boolean.FALSE.equals(poolConfig.getEnabled())
|| Boolean.FALSE.equals(poolConfig.getNotifyEnabled())) {
return 0L;
}
CrmCustomerPageReqVO pageVO = new CrmCustomerPageReqVO()
.setPool(null)
.setContactStatus(CrmCustomerPageReqVO.CONTACT_TODAY)
.setSceneType(CrmSceneTypeEnum.OWNER.getType());
return customerMapper.selectPutPoolRemindCustomerCount(pageVO, poolConfig, userId);
}
@Override
@ -596,7 +609,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
}
}
/**
* 获得自身的代理对象解决 AOP 生效问题
*

View File

@ -72,24 +72,24 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
CrmFollowUpRecordDO followUpRecord = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class);
crmFollowUpRecordMapper.insert(followUpRecord);
LocalDateTime now = LocalDateTime.now();
CrmUpdateFollowUpReqBO updateFollowUpReqBO = new CrmUpdateFollowUpReqBO().setBizId(followUpRecord.getBizId())
.setContactLastTime(now).setContactNextTime(followUpRecord.getNextTime()).setContactLastContent(followUpRecord.getContent());
// 2. 更新 bizId 对应的记录
if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_BUSINESS.getType(), followUpRecord.getBizType())) { // 更新商机跟进信息
CrmUpdateFollowUpReqBO updateFollowUpReqBO = new CrmUpdateFollowUpReqBO().setBizId(followUpRecord.getBizId())
.setContactLastTime(LocalDateTime.now())
.setContactNextTime(followUpRecord.getNextTime()).setContactLastContent(followUpRecord.getContent());
if (ObjUtil.equal(CrmBizTypeEnum.CRM_BUSINESS.getType(), followUpRecord.getBizType())) { // 更新商机跟进信息
businessService.updateBusinessFollowUpBatch(Collections.singletonList(updateFollowUpReqBO));
}
if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CLUE.getType(), followUpRecord.getBizType())) { // 更新线索跟进信息
if (ObjUtil.equal(CrmBizTypeEnum.CRM_CLUE.getType(), followUpRecord.getBizType())) { // 更新线索跟进信息
clueService.updateClueFollowUp(followUpRecord.getId(), followUpRecord.getNextTime(), followUpRecord.getContent());
}
if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTACT.getType(), followUpRecord.getBizType())) { // 更新联系人跟进信息
if (ObjUtil.equal(CrmBizTypeEnum.CRM_CONTACT.getType(), followUpRecord.getBizType())) { // 更新联系人跟进信息
contactService.updateContactFollowUpBatch(Collections.singletonList(updateFollowUpReqBO));
}
if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTRACT.getType(), followUpRecord.getBizType())) { // 更新合同跟进信息
if (ObjUtil.equal(CrmBizTypeEnum.CRM_CONTRACT.getType(), followUpRecord.getBizType())) { // 更新合同跟进信息
contractService.updateContractFollowUp(updateFollowUpReqBO);
}
if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CUSTOMER.getType(), followUpRecord.getBizType())) { // 更新客户跟进信息
customerService.updateCustomerFollowUp(updateFollowUpReqBO);
if (ObjUtil.equal(CrmBizTypeEnum.CRM_CUSTOMER.getType(), followUpRecord.getBizType())) { // 更新客户跟进信息
customerService.updateCustomerFollowUp(followUpRecord.getBizId(), followUpRecord.getNextTime(), followUpRecord.getContent());
}
// 3.1 更新 contactIds 对应的记录不更新 lastTime lastContent