📖 CRM:code review 跟进记录的实现

This commit is contained in:
YunaiV 2024-01-13 09:56:53 +08:00
parent d80406acbb
commit e79c750b46
17 changed files with 68 additions and 140 deletions

View File

@ -123,7 +123,7 @@ public class LocalDateTimeUtils {
}
/**
* 获取指定日期到现在过了几天如果指定日期在当前日期之后获取结果为负
* 获取指定日期到现在过了几天如果指定日期在当前日期之后获取结果为负
*
* @param dateTime 日期
* @return 相差天数

View File

@ -28,6 +28,7 @@
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
</dependency>
<!-- TODO @puhui999api 之间不直接引入哈然后logrecord function 在微服务下,这么跑会高不起来,所以每个服务自己写 function -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system-api</artifactId>

View File

@ -6,6 +6,7 @@ package cn.iocoder.yudao.module.crm.enums.operatelog;
*
* @author HUIHUI
*/
// TODO @puhui999这个枚举还是放在对应的 Function 里好主要考虑 Function 实现可以更近一点哈
public interface CrmParseFunctionNameConstants {
String GET_CONTACT_BY_ID = "getContactById"; // 获取联系人信息

View File

@ -115,10 +115,8 @@ public class CrmCustomerController {
}
// 2. 拼接数据
Map<Long, Long> poolDayMap = null;
if (ObjUtil.notEqual(pageVO.getPool(), Boolean.TRUE)) {
poolDayMap = getPoolDayMap(pageResult.getList()); // 距离进入公海的时间
}
Map<Long, Long> poolDayMap = Boolean.TRUE.equals(pageVO.getPool()) ? null :
getPoolDayMap(pageResult.getList()); // 客户界面需要查看距离进入公海的时间
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId())));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
@ -132,21 +130,23 @@ public class CrmCustomerController {
* @return Map<key 客户编号, value 距离进入公海的时间>
*/
private Map<Long, Long> getPoolDayMap(List<CrmCustomerDO> customerList) {
CrmCustomerPoolConfigDO customerPoolConfig = customerPoolConfigService.getCustomerPoolConfig();
if (customerPoolConfig == null || !customerPoolConfig.getEnabled()) {
CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
if (poolConfig == null || !poolConfig.getEnabled()) {
return MapUtil.empty();
}
// TODO @puhui999需要考虑 lock 的情况么 回复锁定正常显示距离进入公海的时间有个提示
return convertMap(customerList, CrmCustomerDO::getId, customer -> {
// 1.1 未成交放入公海天数
long dealExpireDay = 0;
if (!customer.getDealStatus()) { // 检查是否成交
dealExpireDay = customerPoolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime());
if (!customer.getDealStatus()) {
dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime());
}
LocalDateTime lastTime = customer.getContactLastTime() != null ? customer.getContactLastTime() : customer.getCreateTime();
long contactExpireDay = customerPoolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime);
// 1.2 未跟进放入公海天数
LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime());
long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime);
if (contactExpireDay < 0) {
contactExpireDay = 0; // 如果为负的话重置为零
contactExpireDay = 0;
}
// 2. 返回最小的天数
return Math.min(dealExpireDay, contactExpireDay);
});
}

View File

@ -29,8 +29,8 @@ public class CrmCustomerPoolConfigController {
@Operation(summary = "获取客户公海规则设置")
@PreAuthorize("@ss.hasPermission('crm:customer-pool-config:query')")
public CommonResult<CrmCustomerPoolConfigRespVO> getCustomerPoolConfig() {
CrmCustomerPoolConfigDO customerPoolConfig = customerPoolConfigService.getCustomerPoolConfig();
return success(BeanUtils.toBean(customerPoolConfig, CrmCustomerPoolConfigRespVO.class));
CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
return success(BeanUtils.toBean(poolConfig, CrmCustomerPoolConfigRespVO.class));
}
@PutMapping("/save")

View File

@ -1,25 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - CRM 回款计划转移 Request VO")
@Data
public class CrmReceivablePlanTransferReqVO {
@Schema(description = "回款计划编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "回款计划编号不能为空")
private Long id;
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(value = CrmPermissionLevelEnum.class)
private Integer oldOwnerPermissionLevel; // 老负责人加入团队后的权限级别如果 null 说明移除
}

View File

@ -1,25 +0,0 @@
package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - CRM 回款转移 Request VO")
@Data
public class CrmReceivableTransferReqVO {
@Schema(description = "回款编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "回款编号不能为空")
private Long id;
@Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430")
@NotNull(message = "新负责人的用户编号不能为空")
private Long newOwnerUserId;
@Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@InEnum(value = CrmPermissionLevelEnum.class)
private Integer oldOwnerPermissionLevel; // 老负责人加入团队后的权限级别如果 null 说明移除
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.convert.customer;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO;
@ -26,7 +27,22 @@ public interface CrmCustomerConvert {
CrmCustomerConvert INSTANCE = Mappers.getMapper(CrmCustomerConvert.class);
CrmCustomerRespVO convert(CrmCustomerDO bean);
default CrmCustomerRespVO convert(CrmCustomerDO customer, Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
CrmCustomerRespVO customerResp = BeanUtils.toBean(customer, CrmCustomerRespVO.class);
setUserInfo(customerResp, userMap, deptMap);
return customerResp;
}
default PageResult<CrmCustomerRespVO> convertPage(PageResult<CrmCustomerDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap, Map<Long, Long> poolDayMap) {
PageResult<CrmCustomerRespVO> result = BeanUtils.toBean(pageResult, CrmCustomerRespVO.class);
result.getList().forEach(item -> {
setUserInfo(item, userMap, deptMap);
findAndThen(poolDayMap, item.getId(), item::setPoolDay);
});
return result;
}
/**
* 设置用户信息
@ -47,23 +63,4 @@ public interface CrmCustomerConvert {
@Mapping(target = "bizId", source = "reqVO.id")
CrmPermissionTransferReqBO convert(CrmCustomerTransferReqVO reqVO, Long userId);
PageResult<CrmCustomerRespVO> convertPage(PageResult<CrmCustomerDO> page);
default CrmCustomerRespVO convert(CrmCustomerDO customer, Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
CrmCustomerRespVO customerResp = convert(customer);
setUserInfo(customerResp, userMap, deptMap);
return customerResp;
}
default PageResult<CrmCustomerRespVO> convertPage(PageResult<CrmCustomerDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap, Map<Long, Long> poolDayMap) {
PageResult<CrmCustomerRespVO> result = convertPage(pageResult);
result.getList().forEach(item -> {
setUserInfo(item, userMap, deptMap);
findAndThen(poolDayMap, item.getId(), item::setPoolDay);
});
return result;
}
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.convert.customer;
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.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@ -9,6 +10,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
/**
@ -21,34 +23,20 @@ public interface CrmCustomerLimitConfigConvert {
CrmCustomerLimitConfigConvert INSTANCE = Mappers.getMapper(CrmCustomerLimitConfigConvert.class);
CrmCustomerLimitConfigRespVO convert(CrmCustomerLimitConfigDO bean);
PageResult<CrmCustomerLimitConfigRespVO> convertPage(PageResult<CrmCustomerLimitConfigDO> page);
default PageResult<CrmCustomerLimitConfigRespVO> convertPage(PageResult<CrmCustomerLimitConfigDO> pageResult,
Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
PageResult<CrmCustomerLimitConfigRespVO> result = convertPage(pageResult);
result.getList().forEach(respVo -> fillNameField(userMap, deptMap, respVo));
return result;
default PageResult<CrmCustomerLimitConfigRespVO> convertPage(
PageResult<CrmCustomerLimitConfigDO> pageResult,
Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
List<CrmCustomerLimitConfigRespVO> list = CollectionUtils.convertList(pageResult.getList(),
limitConfig -> convert(limitConfig, userMap, deptMap));
return new PageResult<>(list, pageResult.getTotal());
}
default CrmCustomerLimitConfigRespVO convert(CrmCustomerLimitConfigDO customerLimitConfig,
default CrmCustomerLimitConfigRespVO convert(CrmCustomerLimitConfigDO limitConfig,
Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
CrmCustomerLimitConfigRespVO respVo = convert(customerLimitConfig);
fillNameField(userMap, deptMap, respVo);
return respVo;
}
/**
* 填充名称字段
*
* @param userMap 用户映射
* @param deptMap 部门映射
* @param respVo 响应实体
*/
static void fillNameField(Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap, CrmCustomerLimitConfigRespVO respVo) {
respVo.setUsers(CollectionUtils.convertList(respVo.getUserIds(), userMap::get));
respVo.setDepts(CollectionUtils.convertList(respVo.getDeptIds(), deptMap::get));
CrmCustomerLimitConfigRespVO limitConfigVO = BeanUtils.toBean(limitConfig, CrmCustomerLimitConfigRespVO.class);
limitConfigVO.setUsers(CollectionUtils.convertList(limitConfigVO.getUserIds(), userMap::get));
limitConfigVO.setDepts(CollectionUtils.convertList(limitConfigVO.getDeptIds(), deptMap::get));
return limitConfigVO;
}
}

View File

@ -4,15 +4,12 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
@ -52,7 +49,4 @@ public interface CrmReceivableConvert {
return voPageResult;
}
@Mapping(target = "bizId", source = "reqVO.id")
CrmPermissionTransferReqBO convert(CrmReceivableTransferReqVO reqVO, Long userId);
}

View File

@ -4,16 +4,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
@ -56,7 +53,4 @@ public interface CrmReceivablePlanConvert {
return voPageResult;
}
@Mapping(target = "bizId", source = "reqVO.id")
CrmPermissionTransferReqBO convert(CrmReceivablePlanTransferReqVO reqVO, Long userId);
}

View File

@ -142,9 +142,11 @@ public class CrmClueServiceImpl implements CrmClueService {
clues.forEach(clue -> {
clue.setId(null);
// 创建客户
// TODO @puhui999上面的 id 置空适合 bean copy 在设置为 null不直接修改 clu
customerService.createCustomer(BeanUtils.toBean(clue, CrmCustomerSaveReqVO.class), userId);
// 更新线索状态
// TODO @min新建一个 CrmClueDO 去更新尽量规避直接用原本的对象去更新因为这样万一并发更新会存在覆盖的问题
// TODO @mincustomerId 没有更新进去
// TODO @puhui999如果有跟进记录需要一起转过去
clue.setTransformStatus(Boolean.TRUE);
clueMapper.updateById(clue);

View File

@ -154,7 +154,6 @@ public class CrmContactServiceImpl implements CrmContactService {
permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
// 4.2 删除商机关联
contactBusinessService.deleteContactBusinessByContactId(id);
// TODO @puhui999删除跟进记录
// 记录操作日志上下文
LogRecordContext.putVariable("contactName", contact.getName());

View File

@ -48,13 +48,13 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig
public Long createCustomerLimitConfig(CrmCustomerLimitConfigSaveReqVO createReqVO) {
validateUserAndDept(createReqVO.getUserIds(), createReqVO.getDeptIds());
// 插入
CrmCustomerLimitConfigDO customerLimitConfig = BeanUtils.toBean(createReqVO, CrmCustomerLimitConfigDO.class);
customerLimitConfigMapper.insert(customerLimitConfig);
CrmCustomerLimitConfigDO limitConfig = BeanUtils.toBean(createReqVO, CrmCustomerLimitConfigDO.class);
customerLimitConfigMapper.insert(limitConfig);
// 记录操作日志上下文
LogRecordContext.putVariable("limitType", CrmCustomerLimitConfigTypeEnum.getNameByType(customerLimitConfig.getType()));
LogRecordContext.putVariable("limitId", customerLimitConfig.getId());
return customerLimitConfig.getId();
LogRecordContext.putVariable("limitType", CrmCustomerLimitConfigTypeEnum.getNameByType(limitConfig.getType()));
LogRecordContext.putVariable("limitId", limitConfig.getId());
return limitConfig.getId();
}
@Override
@ -77,12 +77,12 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig
success = CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUCCESS)
public void deleteCustomerLimitConfig(Long id) {
// 校验存在
CrmCustomerLimitConfigDO limitConfigDO = validateCustomerLimitConfigExists(id);
CrmCustomerLimitConfigDO limitConfig = validateCustomerLimitConfigExists(id);
// 删除
customerLimitConfigMapper.deleteById(id);
// 记录操作日志上下文
LogRecordContext.putVariable("limitType", CrmCustomerLimitConfigTypeEnum.getNameByType(limitConfigDO.getType()));
LogRecordContext.putVariable("limitType", CrmCustomerLimitConfigTypeEnum.getNameByType(limitConfig.getType()));
}
@Override

View File

@ -127,17 +127,17 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
success = CRM_CUSTOMER_DELETE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
public void deleteCustomer(Long id) {
// 校验存在
// 1.1 校验存在
CrmCustomerDO customer = validateCustomerExists(id);
// 检查引用
// 1.2 检查引用
checkCustomerReference(id);
// 删除
customerMapper.deleteById(id);
// 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
// TODO @puhui999删除跟进记录
// 记录操作日志上下文
// 2. 删除
customerMapper.deleteById(id);
// 3. 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id);
// 4. 记录操作日志上下文
LogRecordContext.putVariable("customerName", customer.getName());
}

View File

@ -77,7 +77,8 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType())
.setBizId(receivable.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
// TODO @liuhongfeng需要更新关联的 plan
// 记录操作日志上下文
// 4. 记录操作日志上下文
LogRecordContext.putVariable("receivable", receivable);
return receivable.getId();
}

View File

@ -6,6 +6,7 @@ package cn.iocoder.yudao.module.system.enums.operatelog;
*
* @author HUIHUI
*/
// TODO @puhui999这个枚举还是放在对应的 Function 里好主要考虑 Function 实现可以更近一点哈
public interface SysParseFunctionNameConstants {
String GET_ADMIN_USER_BY_ID = "getAdminUserById"; // 获取用户信息