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.
 
 
 
 
 
 

4.9 KiB

status
accepted

线索创建规划深模块:LeadCreationPlanner(收拢四分支规则树与归池/领取仪式)

Context

A2-A7-3-1 缺陷修复(22 项裁决、6 张修复票)把 createLead 从单分支养成了 107 行的四分支规则树,修复方式是给每份副本逐份打补丁:

  1. 重复机制:「领取」规则被摊成 5 份资格链(ADMIN_ONLY 判定 ×3 + ClaimLimitChecker 调用 ×5 处)、3 份归池仪式(池快照 / deptId / teamDeptId / 失效计时,4 setter)、3 份领取仪式(状态 / 领取人快照 / claimTime / 回收计时,7 setter),另有状态机里 2 份 CAS 版仪式(applyClaim / applyAssignToUser——那是并发防线,不在本次收拢范围)。
  2. 缺陷实证:P0-4(claimOnCreate 绕过全部校验)与 P0-7(无角色分支)两个 P0 诞生于这份复制结构;补丁修掉了症状,产生缺陷的结构仍在。
  3. 测试摩擦:四分支 × 仪式的组合矩阵只能借道 createLead 全链路测试(mock 9 依赖 + 22 处 mockStatic SecurityUtils)。
  4. ADR-0022 抽取后 LeadServiceImpl 曾瘦到 437 行,本轮回到 633 行——增量全部落在创建路径。

Considered Options

(grilling 2026-08-20,六问六钉)

  • seam 范围:只收创建路径(选中)/连资格链一起收(「领取资格」module 三处共用)/整个领取编排上收。后两者触碰 claim 路径与状态机调用时序,而缺陷证据(两个 P0)全部集中在创建路径;将来 claim 路径出缺陷可从选中方案增量升级。
  • interface 形状:单方法 plan(dto, ctx)(选中)/sealed CreateIntent(分支推导逻辑溢出到 service 或迫使 planner 加宽 interface)/方法重载族(散装参数换皮)。
  • 静态与校验边界:ctx 携带角色与当前用户、planner 零静态依赖、validateLeadFields 留 service(选中)/校验进 planner(editLead 被牵连或被迫反向依赖)/planner 内读 SecurityUtils(mockStatic 回潮 + 异步线程无 SecurityContext 埋雷)。
  • 契约面:wire / ILeadService / 错误码零变更 + 删 impl 重复 3 参重载(选中)/顺手 SaveParam 化(存量豁免决策,将来自单开票)。
  • 测试面:replace 不 layer(选中,ADR-0022 先例)/双层保留(双份漂移)/不建专属测试(interface 不是 test surface)。
  • 落位com.crm.lead.creation 包 + 接口 + impl + ctx record(选中,与 state / query / history / owner 四机制包同构)/service 包无接口 @Component/Factory 命名(表达不出规则树语义)。

Decision

新建 com.crm.lead.creationLeadCreationPlanner 接口(单方法 Lead plan(LeadDTO, LeadCreateContext))、LeadCreateContext record(operatorUserId / admin / assignToUserId / claimOnCreate 纯值入参)、LeadCreationPlannerImpl@Component)。实现独占四分支规则树(销售自建强制主部门池 / 选人即分配 / 管理员建未分发 / 管理员指定池)、资格链(ADMIN_ONLY + ClaimLimitChecker)、归池仪式与领取仪式;依赖仅 leadPoolService / ownerSnapshotResolver / claimLimitChecker 三个 bean,零静态依赖。

createLead 瘦成 validateLeadFields → plan → save → followOnCreate → historyRecorder.record(CREATE);followOnCreate(创建后副作用)与 validateLeadFields(create / edit 共用)留在 service。claimLead / assignToUser 前置与状态机 CAS 仪式不动。wire 契约、错误码、事务边界(createLead 整体一个 @Transactional)全部不变。

Consequences

  • createLead 107 行 → 约 15 行编排;全库 ADMIN_ONLY 判定 3→2 处(planner 1 + claimLead 1)、归池仪式 3→1 处、领取仪式实体版 3→1 处(状态机 CAS 版 2 处保留——那是锁不是规则)。
  • 测试 replace 不 layer:create 分支真测迁 LeadCreationPlannerTest(纯 ctx 表驱动、零 mockStatic),LeadServiceImplTest 留 2-3 个委派验证;claim / assign / edit / delete 等既有用例不动。
  • 删除 impl 中与 interface default 重复的 3 参 createLead 重载(事务性由内层 4 参方法保证,测试经 default 方法调用不受影响)。
  • 术语沉淀:crm-lead/CONTEXT.md 新增「创建规划」(Avoid: 创建工厂、Creator、Builder)。
  • G6「线索导入」(PRD fog,未排期)将来落地时复用 planner,不再复制规则树。
  • 不违背 ADR-0022「停在三个深模块,不再拆」:本次收拢的是缺陷修复期新长出来的重复(ADR-0022 定稿时 createLead 还是单分支),抽取证据是 P0 缺陷成本;LeadTransition 零依赖契约不受影响(快照与计时照旧预算后经 TransitionCmd 传入)。
  • 决策来源:/improve-codebase-architecture 审查(2026-08-20,候选 1)+ grilling 六问;实现票据见 .scratch/lead-creation-planner/issues/