6.1 KiB
| status |
|---|
| accepted |
线索模块批量操作与视图统计接口补全(G1–G5)
线索原型(A2-1-1 / A2-1-4 / A7-3-1 + 分配/激活弹窗)要求批量操作与顶部统计卡片,但已实现代码只有单条流转、无批量、无统计。本 ADR 把接口完整性复核(grill-with-docs)定稿的 G1–G5 固化为架构决策。范围只含 G1–G5;G6(线索导入/导出)/ G7(线索合并)/ G8(公海池导入/导出)维持既有 Out of scope(见 .scratch/clue-module/map.md)。
决策来源:.scratch/clue-module/线索业务-PRD.md §13(2026 grill-with-docs,决策清单 B1–B9)。
Decisions
D1 批量操作:非原子、逐条 CAS、部分成功
批量领取/分配到池/分配到销售/释放/激活/删除,统一逐条委派给对应单条操作(各自独立事务),任一条失败只记入结果、不拖垮整批。承接 ADR-0021:批量的「失败」= 单条 CAS 行数 0 或单条守卫抛出的业务错误。
- URL:单条动词 +
-batch后缀(/api/lead/claim-batch等),与既有短横线风格一致(B4)。 - 入参:
@RequestParam("ids") List<Long>;批量分配双深度——到池带poolId,到销售带userId(与单条assign-pool/assign-user对称,B5)。 - 逐条独立事务的落地:
LeadServiceImpl用@Lazyself-injection 走 AOP 代理调单条方法(沿用AuthServiceImpl既有模式),批量方法本身不加@Transactional。
D2 批量返回 Result<BatchResult<F>>(B2/B3)
BatchResult<F>(泛型骨架)放 crm-base/domain/result,与Result/PageResult并列,只装total/successCount/failCount/List<F> failures,对失败项内部结构无感知——保持 crm-base 非业务纯净。- 线索域失败项
LeadBatchFailItem(leadId +LeadBatchFailReason+ message)放 crm-lead。 LeadBatchFailReason新造语义枚举(ALREADY_CONVERTED/CONCURRENT_MODIFIED/OVER_HOLD_LIMIT/OVER_DAILY_LIMIT/STATUS_NOT_ALLOWED/NOT_OWNER,各带code回指 ResultCode 65xxx,B6),供前端按失败类型聚合展示「成功 N 条 / 失败 M 条(各类型明细)」。
D3 视图统计接口 /api/lead/stats(B7/B8)
- 入参复用
LeadPageParam,返回Result<LeadStatsDTO>(total/claimed/converted/todayNew/undistributed)。 - 统计口径 = 当前视图数据集口径,非全库:与
/page吃完全相同的 viewType + 筛选 +@DataScope部门天花板,只把「取一页」换成「按 status 分组计数」。接口通用、全量返回 5 个计数,前端按需取。 claimed(「已被领取」,展示文案前端渲染)= status IN (已领取 3, 跟进中 4)。
D4 复合卡片下钻:LeadPageParam.status 单值 → statusIn 多值(B9)
统计卡片可点击下钻,把该卡状态条件塞进 LeadPageParam 再调 /page。「已被领取」是 status IN(3,4) 的并集,单值 Integer 表达不了,故 status 升级为 List<Integer> statusIn,/page 与 /stats 共用。改动落在接口未联调/未发文档阶段,成本低。
D5 公海池批量删除的失败项归属(跨模块依赖方向约束)
pool/delete-batch 属 crm-rule;但 LeadBatchFailItem/LeadBatchFailReason 在 crm-lead,而 crm-lead 依赖 crm-rule,crm-rule 不能反向依赖 crm-lead(否则成环)。故池批量删除不能复用线索的失败项类型。
决策:crm-rule 自建失败项,复用 crm-base 的 BatchResult<F> 泛型骨架(方案 A)。
PoolBatchFailReason(crm-rule 域枚举,各值带code回指RuleConstants64xxx):NOT_EXIST(64004)、HAS_ACTIVE_LEAD(64005,池下有非终态线索)、外加UNKNOWN兜底。PoolBatchFailItem(poolId +PoolBatchFailReason+ message)放 crm-rule。pool/delete-batch返回Result<BatchResult<PoolBatchFailItem>>,与线索侧对称。- 与线索侧完全对称:crm-base 出泛型骨架,各业务域自持失败语义枚举 + 失败项,天然无环。
注:
CODE_POOL_HAS_ACTIVE_LEAD(64005) 当前是预留常量——单条deletePool尚未实现「池下有非终态线索则拒删」的守卫。本期不补该守卫(grill 决策):PoolBatchFailReason.HAS_ACTIVE_LEAD同样预留,本期池批量删除实际只会产出NOT_EXIST/UNKNOWN。理由:该守卫是独立业务规则,且其查询需跨 crm-rule→线索表(又触及 crm-rule 不能依赖 crm-lead 的方向难题),值得单独 ADR/issue 设计查询归属,不在本批量补全范围。枚举值预留不影响契约,后续补守卫时零契约变更。
Considered Options(D5)
- A crm-rule 自建失败枚举/失败项(选中)(
PoolBatchFailReason+PoolBatchFailItem,同样装进 crm-base 的BatchResult<F>)——各域自持失败语义,crm-base 泛型骨架复用,无环;池删除失败原因(池下有非终态线索 vs 不存在)可结构化返回,前端能分类提示。 - B
pool/delete-batch返回BatchResult<Long>(failures 只装失败的 poolId,不带原因枚举)——被否:最省但丢失「为何失败」,前端无法分类展示,而「池下有非终态线索」是需要明确提示用户的业务态。 - C 池批量删除本期不做——被否:原型 A7-3-1 明确有「批量删除公海池」,且 A 成本可控。
Consequences
- 新增
crm-base通用件BatchResult<F>,可被任意业务域批量接口复用。 - crm-lead 新增
stats读接口与 6 个-batch写接口;LeadPageParam契约变更(status→statusIn),需同步读侧 wrapper 与相关测试。 - 批量接口不引入新的上限校验逻辑——
OVER_HOLD_LIMIT/OVER_DAILY_LIMIT枚举值预留给单条操作后续补齐上限校验时自然生效(当前单条claimLead/assignToUser尚未实现上限校验,属既有 gap,不在本 ADR 范围)。 - D5 依赖方向约束:crm-base 出泛型
BatchResult<F>,crm-lead 与 crm-rule 各自持有本域失败项/失败原因枚举,避免跨业务域依赖成环——此模式作为后续任何模块批量接口的范式。