You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5.3 KiB
5.3 KiB
| status |
|---|
| accepted |
线索双计时器深模块:LeadDeadlines(收拢 N/M 规则的唯一家)
Context
双计时器(N=回收天数 / M=失效天数)的规则知识散布在 4 个生产文件、10 个场景:
- 公式重复:
plusDays(pool.getRecycleDays())重复 5 处(creation ×2、LeadServiceImpl×4 方法体,其中 3 处公式恒等now + N)、plusDays(pool.getExpireDays())3 处。 - 锚点等价性无处记载:创建归池写
now + M、assignToPool 写createTime + M——两处写法不同但语义相同(M 从创建起跑,创建时 now ≈ createTime),等价性只活在读代码的人脑子里。 - 条件规则只活在调用方:激活的「失效前态是否私海决定 N、无池保留原 M」十几行条件计算躺在
activateLead。 - 批量与单行两套投影:池参数变更重算公式内联在
PoolChangedEventListener的 SQL 字符串拼接里(DATE_ADD(claim_time, INTERVAL N DAY)×3 段),与单行 Java 版是同一份锚点知识的两个投影,互不引用。 - 缺陷模式:改一条计时规则要人肉排查 4 个文件——P1-6 类缺陷(规则两处实现漂移)的诞生结构。
Considered Options
(grilling 2026-08-20,七问七钉)
- 痛点定位:主治「规则无家」(locality,选中)/公式重复(轻量值对象工厂)/调用方心智(计算彻底下沉状态机)。用删除测试衡量:删掉公式工厂复杂度几乎不回流;删掉规则模块,10 场景的锚点/条件规则立刻散回 4 文件。
- 模块边界:收写入侧单行预算 + 池参数变更批量刷新含执行(选中);停止规则留
LeadTransition(清 recycleDeadline 与清 owner/claimTime 是同一 CAS 语句的同一批字段侧效);到期扫描不动(ADR-0019 失效先/回收后 + ADR-0021 属地,搬家零收益)。 - interface 形状:按锚点语义 4 方法(选中)/按业务事件 6 方法(其中 3 个实现恒等
now + N——shallow 信号,为「未来事件分化」预付接口税;将来真分化时再从锚点方法裂开)/单方法 + ctx(各事件输入天然异构,ctx 成 nullable 堆,且与状态机 cmd 分发重复)。 - 值对象穿越:
TransitionCmd保持LocalDateTime散字段(选中——DeadlineBudget生命周期止于调用方,解构后填 cmd,状态机零改动)/cmd 改携带DeadlineBudget(动 7 个 record + 6 处 applyXxx + 全部状态机测试,纯美学代价)。 - 测试分工:模块测试锁规则真值矩阵 + 调用方 mock 接线(选中,interface 即 test surface)/调用方注入真实实现(构造器带 mapper,纯计算场景传 null 味道差)。
Decision
新建 com.crm.lead.deadline:LeadDeadlines 接口(4 方法)+ DeadlineBudget record(recycle / expire 两字段)在包根,impl/LeadDeadlinesImpl 下沉:
recycleOnPrivateEntry(pool, anchor)——进入/维持私海的 N 预算(领取、分配、作废恢复、有效反馈共用;pool null 无池防御)。expireFromCreation(createTime, pool)——M 锚创建时刻(创建时 now 与 assignToPool 的 createTime 是同一规则的方法名显式化)。budgetForActivation(lead, pool, now)——激活条件全收(失效前态是否私海决定 N、无池保留原 M、statusBeforeExpire null 默认待领取)。refreshForPoolChange(poolId, pool)——§5.3 三段DATE_ADD批量重算(CLAIMED 锚 claim_time、FOLLOWING 锚 feedback_time、非已转商机锚 create_time),返回合计行数。
边界判据:调用方本来就要做的判断不收(assignToUser 按起始态走分支——状态机语言);只有计时器才要做的判断收(锚点选择、激活条件、无池防御)。
ADR-0021 的 cmd 预计算契约不变:错的只是预计算无家,不是契约本身——DeadlineBudget 解构后填 TransitionCmd 既有散字段,TransitionCmd / LeadTransitionImpl / LeadMaintenanceJob 零改动。
Consequences
- 5 处
plusDays调用点全部改走新模块(replace 不 layer);activateLead的十几行条件计算缩成一行;PoolChangedEventListener瘦成纯编排(快照 + dept 两段留下,deadline 三段委派)。 - 锚点规则表落在
LeadDeadlinesjavadoc(N:锚最近持有活动时刻、存在性=私海态、停止=释放/回收/作废清 null 转商机冻结;M:锚创建时刻唯一例外激活重锚 now、存在性=非终态、作废态继续跑)——改一条计时规则只动一处。 - 测试 replace 不 layer:P1-6 三用例(DATE_ADD 锚点断言)自
PoolChangedEventListenerTest原样平移至LeadDeadlinesImplTest(12 用例:8 预算矩阵 + 4 批量重算);调用方测试 mock 接线,激活用例从isNotNull升级为 stub 等值断言。crm-lead 106 → 116 用例。 - interface 混合「纯预算 + 写路径」两种笔法(
recycle/expire/budgetvsrefresh)——方法命名泾渭分明,语义同属「计时器的一生」。 - 商机模块计时器(
last_valid_follow_time实时算、无recycle_deadline字段)与线索的口径统一不在本 ADR 范围(另案)。 - 决策来源:
/improve-codebase-architecture第三轮审查(2026-08-20,候选 ② Strong)+ grilling 七问;实现票据见.scratch/lead-deadline-module/spec.md。