diff --git a/yudao-module-crm/yudao-module-crm-biz/pom.xml b/yudao-module-crm/yudao-module-crm-biz/pom.xml
index 9e1a9e152..d786a90fa 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/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 63e28329d..c38d114c2 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/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 000000000..3d71df4f4
--- /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 000000000..85cccce72
--- /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/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
index d813b2b4d..e4d06d2fc 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
@@ -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 2958dbdf3..2077359ed 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;
@@ -55,6 +59,7 @@ import static java.util.Collections.singletonList;
* @author Wanwan
*/
@Service
+@Slf4j
@Validated
public class CrmCustomerServiceImpl implements CrmCustomerService {
@@ -67,6 +72,9 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
private CrmCustomerLimitConfigService customerLimitConfigService;
@Resource
@Lazy
+ private CrmCustomerPoolConfigService customerPoolConfigService;
+ @Resource
+ @Lazy
private CrmContactService contactService;
@Resource
@Lazy
@@ -245,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());
@@ -313,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) {