Skip to content

14:保护配置、公海、认领、转移与分享

1. 本模块解决的业务问题

客户资源不能被无限期占用。当前 CRM 用“客户-产品跟进”的保护期和公海机制解决资源流转:

text
创建/认领跟进产品
  -> 处于保护期
  -> 持续跟进记录可刷新截止时间
  -> 到期后变为过期或进入公海
  -> 其他人可以认领

这套规则不是客户主表的规则,而是 客户 + 产品 + 跟进人 维度的规则。

2. 相关代码和表

内容Service / 类
普通保护配置CustomerProtectConfigurationServiceImplcustomer_protect_configuration
特殊客户配置CustomerProtectSpecialConfigServiceImplcustomer_protect_special_config
产品跟进CustomerProductFollowServiceImplcustomer_product_follow
公海CustomerProductPublicPoolServiceImplcustomer_product_public_pool
认领历史CustomerProductClaimLogServiceImplcustomer_product_claim_log
转移历史CustomerTransferLogServiceImplcustomer_transfer_log
时间审计CustomerFollowDeadlineLogServiceImplcustomer_follow_deadline_log
定时调度job/PublicPoolJob调用公海 Service

3. 保护配置的作用

CustomerProtectConfigurationServiceImpl.saveOrUpdateConfiguration 维护规则参数。产品跟进新增时通过:

java
Map<String, Integer> protectConfig = getProtectConfig(customerId);

读取规则。源码中会使用的典型参数包括:

text
newFollowDay              新增跟进的保护天数
maxFollowDay              最大保护天数
maxClaim                  最大认领次数
cannotClaimDay            多次掉海后的冻结天数
maxCustomer               单人保护中的客户数量上限
maxCustomerProduct        单人对同一客户可跟进未成交产品数上限
contractApprovalProtect   合同审批中是否保护
dealExpireDowngrade       已成交到期后的降级天数

特殊配置按客户维度覆盖或补充通用配置。因此规则读取必须集中在 Service,不能散落在 Controller。

4. 到期进入公海的真实算法

入口:

text
PublicPoolJob
  -> CustomerProductPublicPoolServiceImpl.expireFollowProductToPublicPool()

任务会按租户循环执行,且每次都清理 TenantContextHolder,避免线程复用导致跨租户污染。

expireFollowProductToPublicPool 的主要逻辑:

text
1. 查询截止时间已到的跟进记录
2. 以 customerId + productId 分组
3. 查询相同客户-产品是否仍有“其他人正在跟进且未到期”的记录
4. 若存在:本条只改为 EXPIRED,不进入公海
5. 若不存在:交给 followProductToPublicPool 批量入公海

这样同一个客户-产品只要还有一个有效跟进人,就不会因为另一人的记录到期而被整体释放到公海。

5. 入公海时的特殊分支

followProductToPublicPool(...) 在批量入公海前还会判断:

text
合同审批保护:相关跟进产品正在合同审批时,按配置可能跳过掉海
VIP 已成交:不直接掉海;源码会降级为 A.待签约,重置首次跟进时间与截止时间
正常记录:创建公海快照、更新跟进状态/公海标记、写认领日志与变更日志

这说明公海不是简单的 followStatus = EXPIRED;它有独立的公海表用于保留待认领资源和历史关系。

6. 认领、转移、分享的区别

6.1 认领 claim

入口:CustomerProductPublicPoolServiceImpl.claim(CustomerProductFollowDto)

业务前置条件来自产品跟进 Service:

text
不能超过当前用户最大保护客户数
不能超过同一客户的产品数上限
不能超过同产品认领次数
超过认领次数后,尚在冻结天数内不能再次认领

认领成功后要同时更新公海记录和跟进记录,并写入认领日志。它是一个事务性状态迁移,不是复制一条客户记录。

6.2 转移 transfer

入口:CustomerProductFollowServiceImpl.transfer(...)

转移代表跟进责任从 A 人/部门变更为 B 人/部门。必须记录:旧跟进人、目标跟进人、转移时间和对应产品跟进 id,写入 customer_transfer_log

6.3 分享 share

入口:CustomerProductFollowServiceImpl.share(...)

分享不是转移。它允许在规则范围内让其他人参与同一客户产品的跟进,原跟进关系的业务含义仍然存在。实现前必须先读源码确认共享后的状态、负责人和保护期如何处理。

7. Demo 对应的最小业务版本

不要直接做多人共享。按下面的状态机实现:

mermaid
stateDiagram-v2
    [*] --> FOLLOWING: 创建或认领
    FOLLOWING --> EXPIRED: deadline 到期
    EXPIRED --> PUBLIC_POOL: 无任何有效跟进人
    PUBLIC_POOL --> FOLLOWING: 用户认领

Demo 表建议:

text
demo_protect_config
demo_customer_product_follow
demo_public_pool
demo_claim_log

最小规则:

text
新建跟进:deadline = now + config.new_follow_days
定时任务:deadline < now 的 FOLLOWING -> PUBLIC_POOL
认领:PUBLIC_POOL -> FOLLOWING,写 claim_log
限制:同一用户最多 20 条 FOLLOWING

当这个闭环正确后,再增加“同客户-产品仍有有效跟进则只过期不掉海”的正式项目规则。

8. 必须验证的边界

  • A 和 B 同时跟进同一客户产品,A 到期,B 未到期:A 仅过期,不入公海;
  • 所有人都到期:客户产品进入公海;
  • 已成交 VIP 到期:按配置降级,而不是直接入公海;
  • 认领次数超过上限:拒绝认领;
  • 冻结期内:拒绝认领;
  • 任务处理多个租户:每个租户结束后上下文必须清理。

9. 源码阅读顺序

text
CustomerProtectConfigurationServiceImpl.saveOrUpdateConfiguration
CustomerProductFollowServiceImpl.getProtectConfig
CustomerProductFollowServiceImpl.checkMaxClaim
CustomerProductFollowServiceImpl.transfer
CustomerProductFollowServiceImpl.share
CustomerProductPublicPoolServiceImpl.claim
CustomerProductPublicPoolServiceImpl.expireFollowProductToPublicPool
CustomerProductPublicPoolServiceImpl.followProductToPublicPool
PublicPoolJob

Lucking