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.
 
 
 
 
 
 

11 KiB

确认清单 — P0(缺陷报告 P0-1 ~ P0-7 三方判定)

方法:原型条款(research/原型依据基准.md,蓝湖 MCP 实时版 2026-08-15)× PRD v1.2(.scratch/clue-module/线索业务-PRD.md)× 代码现状(2026-08-19 逐行复核)。 语义优先级:PRD > 原型 > 缺陷报告(用户 2026-08-19 拍板)。 裁决四类:真实缺陷(代码有错)/ 实现遗漏(需求定稿但从未实现)/ 需求变更(PRD 推翻原型,按 PRD)/ 误报(代码已正确实现)。

一、汇总

编号 裁决 一句话结论 修复票
P0-1 实现遗漏 deletePool 无"池下无非终态线索"守卫,ADR-0023 刻意预留的决策债 05
P0-2 实现遗漏 claimLead 无 hold_limit / daily_claim_limit 校验 06
P0-3 实现遗漏 assignToUser 无被分配人上限校验 06
P0-4 实现遗漏 createLead claimOnCreate 分支绕过全部领取校验(含 ADMIN_ONLY) 06
P0-5 误报 claimLead L217-219 已拦截 ADMIN_ONLY 并抛 65005,且有单测覆盖 —(无需修复)
P0-6 实现遗漏 PUBLIC_POOL 视图硬编码 status=PENDING,违反 PRD §7(E) 可配置状态集 07
P0-7 实现遗漏 createLead 无角色分支:销售可建"未分发"、可任意指定池 07

裁决分布:实现遗漏 6 / 误报 1 / 真实缺陷 0 / 需求变更 0。 缺陷报告 P0 段可信度:7 项指控 6 项属实、1 项误报(P0-5),无误引原型条款。

二、分项证据链

P0-1 删除公海池未校验"存在活跃线索" — 实现遗漏

  • 原型:A7-3-1"已存在线索绑定的公海池禁止删除"(单条)、"若任意勾选公海池内存在有效线索,弹出拦截提示禁止批量删除"(批量)→ 基准 §P0-1
  • PRD:§4.3"删除软删且必须池下无非终态线索"——非终态 = status ∉ {5 已转商机, 6 过期失效, 7 作废},即 status IN (1,2,3,4)
  • 代码LeadPoolServiceImpl.deletePool() 直接软删(deleted=1 + delete_key=id)+ 级联硬删关联表,无占用检查deletePoolBatch() L150-168 逐条 self.deletePool 同样无检查。PoolBatchFailReason HAS_ACTIVE_LEAD 枚举已定义,注释自认"本期保留,单条删除与批量删除均不产出,待独立 ADR/issue 落地";RuleConstants.CODE_POOL_HAS_ACTIVE_LEAD=64005 从未被业务逻辑抛出。
  • 报告微瑕:称 64005"从未被引用"——实际在枚举定义处被引用,但运行时从未产出,指控实质成立。
  • 修复方向(票 05):软删前 SELECT COUNT(*) FROM lead WHERE pool_id=? AND status IN (1,2,3,4),>0 抛 64005。跨模块 seam:crm-rule 不得依赖 crm-lead(铁律),需出 ADR——参照 crm-lead OpportunityCreationPort 的 outbound port 模式,crm-rule 定义占用查询 port、crm-lead 实现。批量口径:单条守卫落地后逐条独立事务自然部分成功(与 ADR-0023 一致);原型"整批拦截"由前端预检实现,后端不破坏 ADR-0023。

P0-2 领取未校验"个人持有上限 / 每日领取上限" — 实现遗漏

  • 原型:A7-3-1 其他设置(0=无限仅明说于每日上限、达到上限禁止领取)+ A2-1-1"领取超限"弹窗 → 基准 §P0-2
  • PRD:§6.2 前置 2:daily_claim_limit0=无限,"每日"=自然日);前置 3:hold_limit计数口径 = 仅已领取+跟进中(已转商机/过期失效/作废不占名额)。
  • 代码LeadServiceImpl.claimLead() 仅 ADMIN_ONLY 拦截;LeadTransitionImpl.applyClaim() L118-131 仅状态 CAS(PENDING→CLAIMED);crm-lead 全模块 grep holdLimit|dailyClaimLimit 0 匹配;错误码 65006(每日超限)/65007(持有超限)已在 LeadConstants L55-56 定义、从未抛出。
  • 修复方向(票 06):claimLead 按目标池参数补两上限校验。hold 计数 = owner_user_id=? AND pool_id=? AND status IN (3,4);daily 计数数据源(lead_history 当日 CLAIM 日志 vs 冗余计数)为票 06 内部工程决策。hold_limit=0 语义:原型仅授权 daily=0 无限,hold 校验按 hold_limit>=1 强制(PRD §4.3 默认 200、validatePool 已要求 ≥1,代码口径一致)。

P0-3 管理员分配未校验被分配人上限 — 实现遗漏

  • 原型:无直接条款(分配后=已领取语义支持);权威 = PRD §6.3
  • PRD:§6.3 grill #7——分配到销售时两上限都校验(与自领同一套约束),额度归属=被指派销售(非管理员),按目标线索所属池上限值算;批量分配部分成功;仅分配到池(无 owner)不占上限。
  • 代码LeadServiceImpl.assignToUser() 仅做 pool 归属预检 + recycleDeadline 预算,无上限校验applyAssignToUser 仅 CAS。批量 assignToUserBatch 走 runBatch 部分成功框架(与 PRD 口径兼容)。
  • 修复方向(票 06):以被分配人为主体复用 P0-2 同一校验器(同一深模块,避免两处口径漂移)。基准附加发现 #2"分配可不指定销售人员"在现有接口形态(userId 必传)下不存在,无需处理。

P0-4 新增线索勾选"是否领取"绕过校验 — 实现遗漏

  • 原型:A2-1-2-2"勾选则新增完成后自动领取该线索"→ 基准 §P0-4 ;超限处理原型留白。
  • PRD:§6.1 副作用"勾'是否领取'则一步转已领取(边 #3)";§6.2 领取前置语义适用于一切领取行为。用户 2026-08-19 拍板:超限或池规则禁自领时,整个新增失败报错,用户只能改选"否"再提交。
  • 代码LeadServiceImpl.createLead() claimOnCreate 分支直接置 CLAIMED + owner + claimTime + recycleDeadline,未走任何校验链——ADMIN_ONLY 池也能通过"新增即领取"自领(报告"后门"指控属实)。
  • 修复方向(票 06):claimOnCreate=true 时先跑完整领取校验链(ADMIN_ONLY + hold + daily;lead 未落库,计数天然不含本条),不满足则整个新增事务失败(@Transactional 回滚,用户拍板语义)。

P0-5 领取规则"管理员可分配"未拦截销售自领 — 误报

  • 原型:A7-3-1 选项 2"销售仅能查看公海线索,无自主领取按钮"→ 基准 §P0-5
  • PRD:§6.2 前置 1:claim_rule=2 时销售不可自领。
  • 代码LeadServiceImpl.claimLead() L217-219 已有 pool.getClaimRule() == RuleConstants.CLAIM_RULE_ADMIN_ONLY → 抛 CODE_CLAIM_RULE_DENIED(65005)"该公海池仅管理员可分配,不可自助领取";单测覆盖(LeadServiceImplTest L241 断言 code=65005)。
  • 裁决依据:报告指控"claimLead 未读取 claim_rule"与代码事实不符。前端置灰属展示层,后端兜底已存在——原型条款与 PRD 均已满足。
  • 处理:无需修复;票 06 落地上限校验时保持该拦截不变(校验链顺序:claimRule → hold → daily)。

P0-6 线索公海硬编码只展示"待领取" — 实现遗漏

  • 原型:A2-1-1 列表混合展示(状态四态标识 + 领取人列 + 防撞单文案 + 已领取行示例)→ 基准 §P0-6
  • PRD:§7 已定稿(E)(grill #9)——公海视图混合展示(待领取 + 已领取/跟进中只读观察态,防撞单),展示范围作为可配置项,不硬编码:后端以"公海可见状态集"参数(默认 {待领取, 已领取, 跟进中})驱动过滤;领取按钮仅对 status='待领取' 行可点。
  • 代码LeadViewQueryImpl.buildViewWrapper() L130 case PUBLIC_POOL -> wrapper.eq(Lead::getStatus, STATUS_PENDING) 硬编码;L138 的 statusIn 筛选与之 AND 叠加(交集),即使前端传 {PENDING,CLAIMED,FOLLOWING} 也被压回仅 PENDING。
  • 修复方向(票 07):PUBLIC_POOL 分支改为"公海可见状态集"驱动(默认 {2,3,4};配置载体——系统参数/字典——票 07 内定)。statusIn 与状态集 AND(下钻收窄)语义保留。领取操作安全性天然成立:claimLead 的 CAS 仅 PENDING 起始可成功。领取人列回显走 ownerNameSnapshot 快照字段(已随实体返回,无需新查询)。

P0-7 新增线索缺少角色/归属约束 — 实现遗漏

  • 原型:A2-1-2-2"线索公海池自动回填当前销售所属区域公海池,不可编辑";A2-1-4 / A2-1-4-1(08-15 新页,强化):管理侧新增默认未分发、超管可切换任意池、选人即分配(已领取)、销售无入口 → 基准 §P0-7
  • PRD:§6.1 grill #6 定稿——自动带池 = 本人主部门的池(不给选、不可编辑;兼职部门有池也只落主部门池);主部门无池 → 报错拦截("本部门尚未配置公海池,请联系管理员");"未分发/空池"是管理员导入专属中间态,销售不得创建。
  • 代码LeadServiceImpl.createLead() L110-133——poolId==null 一律置 UNDISTRIBUTED(无角色判定,销售也能建未分发);poolId!=null 时前端传什么用什么(无销售强制回填、无管理员可切换分支)。Controller 层 create 接口同样无角色约束(claimOnCreate/followOnCreate 由前端任意传)。
  • 修复方向(票 07):createLead 增加角色分支——销售:忽略前端 poolId,强制回填主部门池(经 crm-auth 主部门 → getPoolByDept),主部门无池抛错拦截;管理员:可指定任意池,poolId 可空 = 未分发(对齐 A2-1-4-1)。角色判定来源(SecurityUtils 会话角色)在票 07 内核对现有 crm-auth 接口。

三、待用户确认(不阻塞本判定,阻塞修复票 07)

  1. 管理侧新增(A2-1-4-1)的"选人即分配"是否纳入本期修复范围:原型已画(选中人员→创建即已领取),PRD §6.1 未定义管理侧新增流程(属 PRD 留白、原型有页)。若做,语义应为"分配"(走 §6.3 上限校验,额度归被选销售)而非"自领"。票 07 开工前拍板。

四、结论

  1. P0-1~P0-7 判定完毕:6 项实现遗漏、1 项误报(P0-5),无真实缺陷、无需求变更。
  2. 缺陷报告 P0 段可信度良好:唯一失实指控为 P0-5(claim_rule 拦截已存在)。
  3. 修复映射:票 05(P0-1 池删除守卫 + 跨模块 seam ADR)→ 票 06(P0-2/3/4 共用领取额度校验器;P0-5 保持现状)→ 票 07(P0-6 可配置状态集 + P0-7 角色分支)。
  4. 所有"实现遗漏"的 PRD 依据均已定稿(§4.3 / §6.1 / §6.2 / §6.3 / §7(E)),无"PRD 与原型均无定论"的产品分歧;唯一范围级待确认项见 §三。