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.
85 lines
11 KiB
85 lines
11 KiB
|
3 weeks ago
|
# 确认清单 — P0(缺陷报告 P0-1 ~ P0-7 三方判定)
|
||
|
|
|
||
|
|
> 方法:原型条款([research/原型依据基准.md](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()](../../crm-rule/src/main/java/com/crm/rule/service/impl/LeadPoolServiceImpl.java#L135-L147) 直接软删(deleted=1 + delete_key=id)+ 级联硬删关联表,**无占用检查**;`deletePoolBatch()` L150-168 逐条 `self.deletePool` 同样无检查。[PoolBatchFailReason](../../crm-rule/src/main/java/com/crm/rule/domain/enums/PoolBatchFailReason.java#L17-L18) `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_limit`(**0=无限**,"每日"=自然日);前置 3:`hold_limit`,**计数口径 = 仅已领取+跟进中**(已转商机/过期失效/作废不占名额)。
|
||
|
|
- **代码**:[LeadServiceImpl.claimLead()](../../crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java#L213-L226) 仅 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()](../../crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java#L242-L259) 仅做 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()](../../crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java#L120-L129) 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](../../crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java#L217-L219) **已有** `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](../../crm-lead/src/main/java/com/crm/lead/query/impl/LeadViewQueryImpl.java#L129-L135) `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](../../crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java#L109-L133)——`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 与原型均无定论"的产品分歧;唯一范围级待确认项见 §三。
|