diff --git a/yudao-module-crm/yudao-module-crm-biz/pom.xml b/yudao-module-crm/yudao-module-crm-biz/pom.xml
index 9e1a9e1521..d786a90fad 100644
--- a/yudao-module-crm/yudao-module-crm-biz/pom.xml
+++ b/yudao-module-crm/yudao-module-crm-biz/pom.xml
@@ -70,5 +70,9 @@
cn.iocoder.boot
yudao-spring-boot-starter-test
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-biz-tenant
+
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java
index 2290d73079..982ad3c0b1 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java
@@ -2,14 +2,14 @@ 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.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogV2RespVO;
import cn.iocoder.yudao.module.crm.enums.LogRecordConstants;
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;
@@ -51,16 +51,14 @@ public class CrmOperateLogController {
BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), CRM_RECEIVABLE_PLAN_TYPE);
}
- // TODO @puhui999:还是搞个 VO 出来哈
@GetMapping("/page")
@Operation(summary = "获得操作日志")
- @Parameter(name = "id", description = "客户编号", required = true)
- @PreAuthorize("@ss.hasPermission('crm:customer:query')")
- public CommonResult> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
+ @PreAuthorize("@ss.hasPermission('crm:operate-log:query')")
+ public CommonResult> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO();
reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页,需要分页需注释
reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId());
- return success(operateLogApi.getOperateLogPage(reqDTO));
+ return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogV2RespVO.class));
}
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java
new file mode 100644
index 0000000000..298b5a51d5
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java
@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - CRM 跟进 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class CrmOperateLogV2RespVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+ private Long id;
+ /**
+ * 链路追踪编号
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+ private String traceId;
+ /**
+ * 用户编号
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long userId;
+ /**
+ * 用户名称
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+ private String userName;
+ /**
+ * 用户类型
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer userType;
+ /**
+ * 操作模块类型
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+ private String type;
+ /**
+ * 操作名
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "修改客户")
+ private String subType;
+ /**
+ * 操作模块业务编号
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+ private Long bizId;
+ /**
+ * 操作内容
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "将什么从什么改为了什么")
+ private String action;
+ /**
+ * 拓展字段
+ */
+ @Schema(description = "编号", example = "{orderId: 1}")
+ private String extra;
+
+ /**
+ * 请求方法名
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+ private String requestMethod;
+ /**
+ * 请求地址
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+ private String requestUrl;
+ /**
+ * 用户 IP
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+ private String userIp;
+ /**
+ * 浏览器 UA
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
+ private String userAgent;
+
+ /**
+ * 创建时间
+ */
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-01-01")
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java
index d6c36f176b..f51544caea 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java
@@ -3,6 +3,7 @@ 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.framework.common.util.object.BeanUtils;
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;
@@ -29,13 +30,10 @@ public interface CrmPermissionConvert {
CrmPermissionConvert INSTANCE = Mappers.getMapper(CrmPermissionConvert.class);
- // TODO @puhui999:这个要不也搞到 copy 里
- List convert(List permission);
-
- default List convert(List permission, List userList,
+ default List convert(List permissions, List userList,
Map deptMap, Map postMap) {
Map userMap = CollectionUtils.convertMap(userList, AdminUserRespDTO::getId);
- return CollectionUtils.convertList(convert(permission), item -> {
+ return CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> {
findAndThen(userMap, item.getUserId(), user -> {
item.setNickname(user.getNickname());
findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName()));
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
index 96e4bf5200..2bd614f571 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
@@ -114,10 +114,15 @@ public class CrmCustomerDO extends BaseDO {
*/
private String detailAddress;
+ /**
+ * 最后接收时间
+ */
+ private LocalDateTime receiveTime;
/**
* 最后跟进时间
*/
private LocalDateTime contactLastTime;
+
/**
* 最后跟进内容
*/
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
index 63e28329dc..c38d114c2b 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageR
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.lang.Nullable;
@@ -99,4 +100,10 @@ public interface CrmCustomerMapper extends BaseMapperX {
return selectJoinPage(pageReqVO, CrmCustomerDO.class, query);
}
+ default List selectListByLockStatusAndOwnerUserIdNotNull(Boolean lockStatus) {
+ return selectList(new LambdaQueryWrapper()
+ .eq(CrmCustomerDO::getLockStatus, lockStatus)
+ .isNotNull(CrmCustomerDO::getOwnerUserId));
+ }
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java
index 43bb729b6c..8f9c194108 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java
@@ -1,9 +1,17 @@
package cn.iocoder.yudao.module.crm.framework.permission.core.util;
+import cn.hutool.core.util.ObjUtil;
import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
+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.CrmPermissionRoleCodeEnum;
+import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
/**
@@ -22,6 +30,21 @@ public class CrmPermissionUtils {
return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode());
}
+ /**
+ * 校验权限
+ *
+ * @param bizType 数据类型,关联 {@link CrmBizTypeEnum}
+ * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
+ * @param userId 用户编号
+ * @param levelEnum 权限级别
+ * @return boolean
+ */
+ public static boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum levelEnum) {
+ List permissionList = SingletonManager.getCrmPermissionService().getPermissionListByBiz(bizType, bizId);
+ return anyMatch(permissionList, permission ->
+ ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), levelEnum.getLevel()));
+ }
+
/**
* 静态内部类实现单例获取
*
@@ -30,11 +53,16 @@ public class CrmPermissionUtils {
private static class SingletonManager {
private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class);
+ private static final CrmPermissionService CRM_PERMISSION_SERVICE = SpringUtil.getBean(CrmPermissionService.class);
public static PermissionApi getPermissionApi() {
return PERMISSION_API;
}
+ public static CrmPermissionService getCrmPermissionService() {
+ return CRM_PERMISSION_SERVICE;
+ }
+
}
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java
new file mode 100644
index 0000000000..3d71df4f49
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.crm.job.customer;
+
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 客户自动掉入公海 Job
+ *
+ * @author 芋道源码
+ */
+@Component
+public class CrmCustomerAutoPutPoolJob implements JobHandler {
+
+ @Resource
+ private CrmCustomerService customerService;
+
+ @Override
+ @TenantJob
+ public String execute(String param) {
+ int count = customerService.customerAutoPutPoolBySystem();
+ return String.format("掉入公海客户 %s 个", count);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java
new file mode 100644
index 0000000000..85cccce729
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.crm.job;
\ No newline at end of file
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
index 304b150fe3..2329c4dce1 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
@@ -3,7 +3,6 @@ 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.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -15,7 +14,6 @@ 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;
@@ -193,46 +191,39 @@ public class CrmClueServiceImpl implements CrmClueService {
}
// 2. 遍历线索(未转化的线索),创建对应的客户
- // TODO @puhui999:这里不用过滤了;
- List translateClues = filterList(clues, clue -> ObjUtil.equal(Boolean.FALSE, clue.getTransformStatus()));
- List customers = customerService.createCustomerBatch(convertList(translateClues, clue ->
- BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class)), userId);
+ clues.forEach(clue -> {
+ Long customerId = customerService.createCustomer(BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class), userId);
+ clue.setCustomerId(customerId);
+ });
- // TODO @puhui999:这里不用搞一个 clueCustomerIdMap 出来;可以考虑逐个创建,然后把 customerId 设置回 CrmClueDO;避免 name 匹配,极端会有问题哈;
- // TODO 是不是就直接 foreach 处理好了;因为本身量不大,for 处理性能 ok,可阅读性好
- Map 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());
- }));
+ clueMapper.updateBatch(convertList(clues, clue -> new CrmClueDO().setId(clue.getId()).setTransformStatus(Boolean.TRUE)
+ .setCustomerId(clue.getCustomerId())));
// 2.3 复制跟进
- updateFollowUpRecords(clueCustomerIdMap);
-
+ updateFollowUpRecords(clues);
// 3. 记录操作日志
- for (CrmClueDO clue : translateClues) {
- // TODO @puhui999:这里优化下,translate 操作日志
- getSelf().receiveClueLog(clue);
+ for (CrmClueDO clue : clues) {
+ getSelf().translateCustomerLog(clue);
}
}
- private void updateFollowUpRecords(Map clueCustomerIdMap) {
+ private void updateFollowUpRecords(List clues) {
List followUpRecords = followUpRecordService.getFollowUpRecordByBiz(
- CrmBizTypeEnum.CRM_LEADS.getType(), clueCustomerIdMap.keySet());
+ CrmBizTypeEnum.CRM_LEADS.getType(), convertSet(clues, CrmClueDO::getId));
if (CollUtil.isEmpty(followUpRecords)) {
return;
}
+ Map clueMap = convertMap(clues, CrmClueDO::getId);
// 创建跟进
followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, followUpRecord ->
BeanUtils.toBean(followUpRecord, CrmFollowUpCreateReqBO.class).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
- .setBizId(clueCustomerIdMap.get(followUpRecord.getBizId()))));
+ .setBizId(clueMap.get(followUpRecord.getBizId()).getCustomerId())));
}
@LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSLATE_SUB_TYPE, bizNo = "{{#clue.id}}",
success = CRM_LEADS_TRANSLATE_SUCCESS)
- public void receiveClueLog(CrmClueDO clue) {
+ public void translateCustomerLog(CrmClueDO clue) {
// 记录操作日志上下文
LogRecordContext.putVariable("clue", clue);
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java
index 10b6a2ec30..7b07113346 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java
@@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper;
+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.business.CrmBusinessService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
@@ -19,7 +22,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS;
-// TODO @puhui999:数据权限的校验;每个操作;
/**
* 联系人与商机的关联 Service 实现类
*
@@ -40,6 +42,7 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService
private CrmContactService contactService;
@Override
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#createReqVO.contactId", level = CrmPermissionLevelEnum.WRITE)
public void createContactBusinessList(CrmContactBusinessReqVO createReqVO) {
CrmContactDO contact = contactService.getContact(createReqVO.getContactId());
if (contact == null) {
@@ -65,6 +68,7 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService
}
@Override
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#deleteReqVO.contactId", level = CrmPermissionLevelEnum.WRITE)
public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) {
CrmContactDO contact = contactService.getContact(deleteReqVO.getContactId());
if (contact == null) {
@@ -76,11 +80,13 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService
}
@Override
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.WRITE)
public void deleteContactBusinessByContactId(Long contactId) {
- contactBusinessMapper.delete(CrmContactBusinessDO::getContactId,contactId);
+ contactBusinessMapper.delete(CrmContactBusinessDO::getContactId, contactId);
}
@Override
+ @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.READ)
public List getContactBusinessListByContactId(Long contactId) {
return contactBusinessMapper.selectListByContactId(contactId);
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
index 38d5f082b3..e4d06d2fc1 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
@@ -100,13 +100,13 @@ public interface CrmCustomerService {
void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO);
/**
- * 批量创建客户
+ * 创建客户
*
- * @param customerCreateReqBOs 请求
- * @param userId 用户编号
+ * @param customerCreateReq 请求信息
+ * @param userId 用户编号
* @return 客户列表
*/
- List createCustomerBatch(List customerCreateReqBOs, Long userId);
+ Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId);
// ==================== 公海相关操作 ====================
@@ -126,4 +126,11 @@ public interface CrmCustomerService {
*/
void receiveCustomer(List ids, Long ownerUserId, Boolean isReceive);
+ /**
+ * 【系统】客户自动掉入公海
+ *
+ * @return 掉入公海数量
+ */
+ int customerAutoPutPoolBySystem();
+
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
index 45893fa873..2077359ed8 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.crm.service.customer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjUtil;
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.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLockReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;
@@ -13,6 +15,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTrans
import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
+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.permission.CrmPermissionLevelEnum;
@@ -31,6 +34,7 @@ import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -43,12 +47,10 @@ 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;
/**
@@ -57,6 +59,7 @@ import static java.util.Collections.singletonList;
* @author Wanwan
*/
@Service
+@Slf4j
@Validated
public class CrmCustomerServiceImpl implements CrmCustomerService {
@@ -69,6 +72,9 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
private CrmCustomerLimitConfigService customerLimitConfigService;
@Resource
@Lazy
+ private CrmCustomerPoolConfigService customerPoolConfigService;
+ @Resource
+ @Lazy
private CrmContactService contactService;
@Resource
@Lazy
@@ -91,9 +97,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
// 2. 插入客户
CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class)
- .setLockStatus(false).setDealStatus(false)
- .setContactLastTime(LocalDateTime.now());
- // TODO @puhui999:可能要加个 receiveTime 字段,记录最后接收时间
+ .setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now());
customerMapper.insert(customer);
// 3. 创建数据权限
@@ -214,24 +218,24 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
@Override
@Transactional(rollbackFor = Exception.class)
- public List createCustomerBatch(List customerCreateReqBOs, Long userId) {
- if (CollUtil.isEmpty(customerCreateReqBOs)) {
- return emptyList();
- }
+ @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}",
+ success = CRM_CUSTOMER_CREATE_SUCCESS)
+ public Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId) {
+ // 1. 插入客户
+ CrmCustomerDO customer = BeanUtils.toBean(customerCreateReq, CrmCustomerDO.class).setOwnerUserId(userId)
+ .setLockStatus(false).setDealStatus(false).setReceiveTime(LocalDateTime.now());
+ customerMapper.insert(customer);
- // 创建客户
- List customers = convertList(customerCreateReqBOs, customerBO ->
- BeanUtils.toBean(customerBO, CrmCustomerDO.class).setOwnerUserId(userId));
- customerMapper.insertBatch(customers);
+ // 2. 创建数据权限
+ permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
+ .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
- // 创建负责人数据权限
- permissionService.createPermissionBatch(convertList(customers, customer -> new CrmPermissionCreateReqBO()
- .setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()).setBizId(customer.getId()).setUserId(userId)
- .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())));
- return customers;
+ // 3. 记录操作日志上下文
+ LogRecordContext.putVariable("customer", customer);
+ return customer.getId();
}
- // ==================== 公海相关操作 ====================
+// ==================== 公海相关操作 ====================
@Override
@Transactional(rollbackFor = Exception.class)
@@ -249,17 +253,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
// 1.3. 校验客户是否锁定
validateCustomerIsLocked(customer, true);
- // 2.1 设置负责人为 NULL
- int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null);
- if (updateOwnerUserIncr == 0) {
- throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL);
- }
- // 2.2 删除负责人数据权限
- permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(),
- CrmPermissionLevelEnum.OWNER.getLevel());
-
- // 3. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的
- contactService.updateOwnerUserIdByCustomerId(customer.getId(), null);
+ // 2. 客户放入公海
+ putCustomerPool(customer);
// 记录操作日志上下文
LogRecordContext.putVariable("customerName", customer.getName());
@@ -317,6 +312,49 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
}
}
+ @Override
+ public int customerAutoPutPoolBySystem() {
+ CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
+ if (poolConfig == null || !poolConfig.getEnabled()) {
+ return 0;
+ }
+ // 获取没有锁定的不在公海的客户
+ List customerList = customerMapper.selectListByLockStatusAndOwnerUserIdNotNull(Boolean.FALSE);
+ List poolCustomerList = CollectionUtils.filterList(customerList, customer -> {
+ // 1.1 未成交放入公海
+ if (!customer.getDealStatus()) {
+ return (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0;
+ }
+ // 1.2 未跟进放入公海
+ LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime());
+ return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0;
+ });
+ int count = 0;
+ for (CrmCustomerDO customer : poolCustomerList) {
+ try {
+ getSelf().putCustomerPool(customer);
+ count++;
+ } catch (Throwable e) {
+ log.error("[customerAutoPutPoolBySystem][Customer 客户({}) 放入公海异常]", customer.getId(), e);
+ }
+ }
+ return count;
+ }
+
+ private void putCustomerPool(CrmCustomerDO customer) {
+ // 1. 设置负责人为 NULL
+ int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null);
+ if (updateOwnerUserIncr == 0) {
+ throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL);
+ }
+ // 2. 删除负责人数据权限
+ permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(),
+ CrmPermissionLevelEnum.OWNER.getLevel());
+
+ // 3. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的
+ contactService.updateOwnerUserIdByCustomerId(customer.getId(), null);
+ }
+
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_RECEIVE_SUB_TYPE, bizNo = "{{#customer.id}}",
success = CRM_CUSTOMER_RECEIVE_SUCCESS)
public void receiveCustomerLog(CrmCustomerDO customer, String ownerUserName) {
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
index dc2001565f..9a04e2d94d 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
@@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
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.dal.dataobject.permission.CrmPermissionDO;
import cn.iocoder.yudao.module.crm.dal.mysql.followup.CrmFollowUpRecordMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
@@ -31,10 +30,10 @@ 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.anyMatch;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_DELETE_DENIED;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS;
+import static cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils.hasPermission;
/**
* 跟进记录 Service 实现类
@@ -94,14 +93,15 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
customerService.updateCustomerFollowUp(updateFollowUpReqBO);
}
- // TODO @puhui999:这两个,不更新 contactLastTime、contactLastContent,只更新 nextTime
// 3.1 更新 contactIds 对应的记录
if (CollUtil.isNotEmpty(createReqVO.getContactIds())) {
- contactService.updateContactFollowUpBatch(convertList(createReqVO.getContactIds(), updateFollowUpReqBO::setBizId));
+ contactService.updateContactFollowUpBatch(convertList(createReqVO.getContactIds(),
+ contactId -> updateFollowUpReqBO.setBizId(contactId).setContactLastTime(null).setContactLastContent(null)));
}
// 3.2 需要更新 businessIds、contactIds 对应的记录
if (CollUtil.isNotEmpty(createReqVO.getBusinessIds())) {
- businessService.updateBusinessFollowUpBatch(convertList(createReqVO.getBusinessIds(), updateFollowUpReqBO::setBizId));
+ businessService.updateBusinessFollowUpBatch(convertList(createReqVO.getBusinessIds(),
+ businessId -> updateFollowUpReqBO.setBizId(businessId).setContactLastTime(null).setContactLastContent(null)));
}
return followUpRecord.getId();
}
@@ -112,8 +112,7 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
return;
}
- List followUpRecords = BeanUtils.toBean(followUpCreateReqBOs, CrmFollowUpRecordDO.class);
- crmFollowUpRecordMapper.insertBatch(followUpRecords);
+ crmFollowUpRecordMapper.insertBatch(BeanUtils.toBean(followUpCreateReqBOs, CrmFollowUpRecordDO.class));
}
@Override
@@ -121,12 +120,7 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
// 校验存在
CrmFollowUpRecordDO followUpRecord = validateFollowUpRecordExists(id);
// 校验权限
- // TODO @puhui999:是不是封装一个 hasPermission,更简介一点;
- List permissionList = permissionService.getPermissionListByBiz(
- followUpRecord.getBizType(), followUpRecord.getBizId());
- boolean hasPermission = anyMatch(permissionList, permission ->
- ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), CrmPermissionLevelEnum.OWNER.getLevel()));
- if (!hasPermission) {
+ if (!hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(), userId, CrmPermissionLevelEnum.OWNER)) {
throw exception(FOLLOW_UP_RECORD_DELETE_DENIED);
}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java
index 74e7894367..57f1849eb8 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java
@@ -21,7 +21,6 @@ public class CrmUpdateFollowUpReqBO {
@Schema(description = "最后跟进时间")
@DiffLogField(name = "最后跟进时间")
- @NotNull(message = "最后跟进时间不能为空")
private LocalDateTime contactLastTime;
@Schema(description = "下次联系时间")
@@ -30,7 +29,6 @@ public class CrmUpdateFollowUpReqBO {
@Schema(description = "最后更进内容")
@DiffLogField(name = "最后更进内容")
- @NotNull(message = "最后更进内容不能为空")
private String contactLastContent;
}