Browse Source

commit

master
luoweijian 4 weeks ago
parent
commit
54aa290c89
  1. 33
      .scratch/clue-module/issues/03-线索状态机权威定义.md
  2. 46
      .scratch/clue-module/issues/06-线索实体与字段模型.md
  3. 17
      .scratch/clue-module/map.md
  4. 11
      .scratch/clue-module/prototype/README.md
  5. BIN
      .scratch/clue-module/prototype/__pycache__/lead_engine.cpython-313.pyc
  6. 40
      .scratch/clue-module/prototype/lead_engine.py
  7. 23
      .scratch/clue-module/待确认事项清单.md
  8. 108
      .scratch/clue-module/线索业务-PRD.md
  9. 13
      tmp_filelist.txt

33
.scratch/clue-module/issues/03-线索状态机权威定义.md

@ -104,7 +104,7 @@ Status: claimed
| 10 | 已领取/跟进中 | 手动释放 | 待领取 | 清领取人;回收计时N清零 |
| 11 | 已领取/跟进中 | 超时回收(N天无跟进) | 待领取 | 清领取人;回收计时N清零 |
| 12 | 任何非终态(未分发/待领取/已领取/跟进中) | 失效计时M到期 | 过期失效 | 若被持有则保留领取人;只读 |
| 13 | 过期失效 | 管理员激活(F2) | 恢复失效前状态+原领取人 | 重置失效计时M(从激活时间) |
| 13 | 过期失效 | 管理员激活(F2) | 恢复失效前状态+原领取人 | **重置 N 和 M**(`recycle_deadline=now+pool.N`,`expire_deadline=now+pool.M`) |
| C | ⚠ | ⚠ | 线索作废 | ⚠ 待产品确认 |
**计时器口径**:
@ -125,7 +125,7 @@ Status: claimed
| 转商机 | ✓— | ✓— | ✓(持有人) | ✓(持有人) | ✓— | ✓— | ✓— |
| 释放 | ✓— | ✓— | ✓(持有人) | ✓(持有人) | ✓— | ✓— | ✓— |
| 关注/取关 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| 删除 | ✓(管理) | ✓(管理) | ✓(管理·强确认) | ✓(管理·强确认) | ✓(管理·强确认) | ✓— (**不可删**) | ⚠C |
| 删除 | ✓(管理) | ✓(管理) | ✓(管理·强确认) | ✓(管理·强确认) | ✓(管理·强确认) | ✓(管理/持有人·强确认) | ⚠C |
| 激活 | ✓— | ✓— | ✓— | ✓— | ✓— | ✓(管理) | ✓— |
> 注:编辑/反馈的"(持有人)"表示仅当前领取人可操作;"管理"表示需部门负责人/超管权限(详见 ticket 05)。已转商机为终态,编辑/反馈按产品拍板一律锁死(覆盖原型旧说法)。
@ -133,3 +133,32 @@ Status: claimed
### 五、待产品确认后收尾
- **[C] 线索作废**:触发方/入口状态/作废后归属与可逆性 → 补边 C、矩阵 ⚠C 格、状态定义表末行。C 明确即可将本 ticket 置 resolved。
---
## Amendment 1(2026-08-12 grill)
### A1.1 边 #13 激活时 N 也重置(grill #6
**改前**:激活只重置 M(`expire_deadline = now + pool.M`),N 保持失效前的值(若已过期则被下一次 tick 回收)。
**改后**:激活时 **N 和 M 均重置**
- `recycle_deadline = now + pool.recycle_days`(若失效前是已领取/跟进中)
- `expire_deadline = now + pool.expire_days`
**理由**:产品确认 M 的起始时间 = 线索创建时间或重新激活时间;N 同理也应从激活时刻重新起算,给足完整的跟进窗口,否则激活后立刻被 tick 回收。
**影响**:边 #13 副作用列已更新;PRD §6.8 已更新;原型引擎 `activate()` 已修改。
### A1.2 过期失效线索可删(grill #4 + #11
**改前**:矩阵“过期失效 × 删除” = `✗ 不可删`
**改后**:矩阵“过期失效 × 删除” = `✓(管理/持有人·强确认)`
- **管理**可删(强确认)——grill #4
- **持有人**可删自己的过期失效线索(强确认)——grill #11,原型“我的线索”页面有删除按钮
**理由**:过期失效是只读死数据,如果管理员不激活,永久占在列表里;让持有人自己清掉合理。
**影响**:矩阵已更新;PRD §3.3 已更新。

46
.scratch/clue-module/issues/06-线索实体与字段模型.md

@ -24,7 +24,7 @@ Blocked by: 04
## Decisions(grill 定稿)
- **[Q1=a]** 联系电话单字段 `phone` varchar(32),正则同时兼容手机/座机/带区号。
- **[Q2=c]** 反馈:统一操作日志子表 `lead_history` + 主表冗余最新反馈 5 列(feedback_status/content/time/by/product)。写反馈时事务内插子表 + 更新主表最新值。历史记录 Tab 复用同一张 `lead_history`(含领取/释放/反馈/新增/编辑等所有操作类型)。
- **[Q2=c]** 反馈:统一操作日志子表 `lead_history` + 主表冗余最新反馈 5 列(feedback_status/content/time/by/product)。写反馈时事务内插子表 + 更新主表最新值。历史记录 Tab 复用同一张 `lead_history`(含领取/释放/反馈/新增/编辑等所有操作类型)。**⚠ 见 Amendment 2:拆出 `lead_feedback` 独立表(草稿/提交双态)。**
- **[Q3=a]** 团队/部门/领取人名 快照冗余;`dept_id` 冗余(= 池的 dept_id,因一部门一池永不变);`team_dept_id` 同理。领取/释放时刷新快照。
- **[Q4=b]** `recycle_deadline` / `expire_deadline` 存字段。
- 领取时:`recycle_deadline = claim_time + pool.recycle_days`
@ -105,8 +105,8 @@ lead_attachment(id, lead_id, file_id) -- 客户相关文件, file_id
|---|---|
| 领取 | `claim_time` `owner_user_id` `owner_name_snapshot` `owner_dept_id_snapshot` `recycle_deadline` |
| 反馈 | `feedback_*` 5 列 + `recycle_deadline = now + pool.N` |
| 释放/超时回收 | `owner_*` 清空、`claim_time`/`recycle_deadline` 清空 |
| 激活(F2) | `expire_deadline = now + pool.M`(03 已定) |
| 释放/超时回收 | `owner_*` 清空、`claim_time`/`recycle_deadline` 清空;**反馈 5 列不清空**(grill #5 |
| 激活(F2) | `expire_deadline = now + pool.M` + **`recycle_deadline = now + pool.N`**(grill #6,N 也重置) |
| 池改名 | 批量刷 `pool_name_snapshot` |
| 池 recycle_days 变 | 批量刷该池已领取/跟进中的 `recycle_deadline = claim_time + new_N` |
| 池 expire_days 变 | 批量刷该池全部非终态的 `expire_deadline = create_time + new_M` |
@ -161,3 +161,43 @@ lead_attachment(id, lead_id, file_id) -- 客户相关文件, file_id
**依赖**:sys_region 表 `code UNIQUE` 字段(见 02 Amendment)。
**其余 06 定稿不变**:字段名、必填标记、其他字段、索引/子表全部保留(相关索引 `idx_dept_status` 等不涉及省市字段无需改)。
## Amendment 2(2026-08-12 grill)—— 拆出 `lead_feedback` 独立表 + 快照保留 + 激活重置 N
### A2.1 [Q2=c] 改写:拆出 `lead_feedback` 独立表(grill #3
**改前**:反馈直接写 `lead_history` FEEDBACK 记录(detail 含 content),主表冗余最新值。
**改后**:拆出独立的 `lead_feedback` 表,支持草稿/提交双态:
```
lead_feedback
├─ id, lead_id, user_id
├─ feedback_status, content, product_code
├─ attachment_ids json -- crm-file 文件 id 列表
├─ record_status tinyint -- 1=DRAFT, 2=SUBMITTED
├─ created_at, submitted_at
└─ INDEX(lead_id, user_id, record_status)
```
- **【保存草稿】**:写/更新 `lead_feedback``record_status='DRAFT'`,不校验必填,不改状态机,不写 `lead_history`,不刷主表快照。
- **【保存】(正式提交)**:`lead_feedback` 行 `record_status='SUBMITTED'`,主表更新 5 列快照,状态迁移(已领取→跟进中),N 重置,写 `lead_history` FEEDBACK 记录(detail 引用 `lead_feedback.id`,**不冗余内容**)。
**理由**:原型有“保存草稿”按钮,spec 未覆盖;拆表后草稿不污染操作日志,历史 Tab 从 `lead_history` 取,反馈内容从 `lead_feedback` 取。
**⚠(C) 已解决**:反馈可上传附件,存 `lead_feedback.attachment_ids`
### A2.2 快照刷新表修正(grill #5 + #6
| 触发 | 原规则 | 修正后 |
|---|---|---|
| 释放/超时回收 | `owner_*` 清空、`claim_time`/`recycle_deadline` 清空、**反馈 5 列也清空** | `owner_*`、`claim_time`、`recycle_deadline` 清空;**反馈 5 列不清空**(保留供下一位参考) |
| 激活 | `expire_deadline = now + pool.M` | `expire_deadline = now + pool.M` + `recycle_deadline = now + pool.N`(N 也重置) |
### A2.3 池 N 改动刷新修正
原快照表“池 recycle_days 变”行写的是 `recycle_deadline = claim_time + new_N`,对已领取和跟进中未区分。
**修正**:
- 已领取:`recycle_deadline = claim_time + new_N`
- 跟进中:`recycle_deadline = last_feedback_time + new_N`

17
.scratch/clue-module/map.md

@ -37,11 +37,25 @@
- **crm-preference 列偏好契约定稿**:scopeKey=业务方约定稳定 code(如 `lead.public_pool`);字段池归业务方;存储 `user_column_preference(user_id, scope_key, visible_keys, column_order)`;契约仅 `get/save` 两法;点【保存】才落库,拖拽临时态仅前端本地。原型 9 处文案作废(7 处"共用/同步"+ 2 处"拖拽实时保存")。[07](issues/07-preference列偏好scope模型与契约.md)
- **列表字段配置每菜单独立**(产品确认,推翻原型 7 处"共用一套/同步生效"文案):线索域 4 个 scope(公海/我的线索/我的关注/线索管理),互不同步。UX 侧同步修订原型。[07](issues/07-preference列偏好scope模型与契约.md)
### Grill 决策(2026-08-12,详见 PRD §12.1)
- **池换部门允许**:待领取/未分发 `dept_id` 跟池走,已领取/跟进中不动;目标部门须无池。改写 [I]。
- **线索合并不做**:Out of scope。
- **拆出 `lead_feedback` 独立表**(DRAFT/SUBMITTED),改写 [Q2=c]。`lead_history` FEEDBACK 只记引用。
- **过期失效线索可删**:管理/持有人·强确认。改写 03 矩阵。
- **释放/回收时反馈快照不清空**:保留 5 列供下一位参考。
- **激活时 N 和 M 均重置**:`recycle_deadline = now + pool.N`。改写 03 边 #13
- **hold_limit 只算已领取 + 跟进中**:不含已转商机/过期失效/作废。
- **批量激活**:逐条执行,部分成功。补 PRD §6.8。
- **daily_claim_limit "每日" = 自然日**:`DATE(op_time) = CURRENT_DATE`。
- **导入线索不做**:留 fog。
- **编辑范围**:业务字段可改,状态/池/归属/时效/快照不可改。新增 PRD §6.9。
## Not yet specified
<!-- fog:范围之内、尚不够清晰无法立 ticket 的部分,随前沿推进逐步毕业成 ticket -->
- **crm-lead 领取/流转行为规格** — 领取(单/批量)、关注/取关、反馈(含暂存/文件上传)、转商机、释放、分配、合并、批量删除各自的前置状态、校验、状态迁移、历史记录写入。等"线索状态机权威定义"ticket 定稿后逐条毕业。
- **crm-lead 领取/流转行为规格**~~领取(单/批量)、关注/取关、反馈(含暂存/文件上传)、转商机、释放、分配、合并、批量删除各自的前置状态、校验、状态迁移、历史记录写入~~ **大部分已在 PRD §6 定稿**(领取/反馈/释放/分配/转商机/激活/编辑);合并已 Out of scope;导入留 fog
- **四视图列表规格** — 公海(分屏/列表)、我的线索、我的关注、线索管理各自的字段集、统计卡片、筛选项、按角色/状态动态渲染的操作按钮。等"数据隔离在线索域的落地"与"列偏好契约"定稿后毕业。
- **导入/导出规格** — 三种导入模式、模板字段、校验/异常、进度状态、数据同步、按角色的数据范围限制;导出等待状态。等线索字段模型定稿后毕业。
- **定时任务规格** — 超时回收(N 天未跟进回流公海、状态重置待领取、清领取人)、线索失效(入库满 M 天标记过期失效);触发时机、幂等、存量 vs 增量生效规则。等公海池配置契约定稿后毕业。
@ -55,3 +69,4 @@
- 商机模块内部实现——转商机只定接口契约。
- 组织架构(部门树/成员/钉钉同步)、数据字典、数据范围机制本身——均消费既存能力。
- crm-preference 的"全平台通用化"打磨——本次只做到线索够用。
- **线索合并**——不做(grill #2 决策)。

11
.scratch/clue-module/prototype/README.md

@ -10,8 +10,9 @@
2. 释放/回收:N 是否清零重启?
3. 跟进中 M 到期:过期失效是否保留 owner?
4. 已转商机后:N/M 是否真冻结?
5. 激活失效线索:是否恢复失效前状态+原领取人+重置 M?
5. 激活失效线索:是否恢复失效前状态+原领取人+重置 **N 和 M**
6. 池 N/M 改动:deadline 是否按规则批量重算?
7. 释放/回收后:反馈快照是否保留(供下一位领取人参考)?
## 跑
@ -36,5 +37,11 @@ set PYTHONIOENCODING=utf-8
## 验证结论
`../线索业务-PRD.md` §3;smoke test 5 个 case 全过。
`../线索业务-PRD.md` §3 + §12.1 grill 决策清单;smoke test 7 个 case 全过。
不涵盖:"线索作废"边(03 [C] 待产品拍板)、权限、并发/事务。
## Grill 后变更记录(2026-08-12)
- **release/try_recycle**:不再清 `feedback_status`/`last_feedback_content`/`last_feedback_time`,只清 `owner_*`/`claim_time`/`recycle_deadline`(grill #5
- **activate**:`recycle_deadline` 也重置为 `now + pool.N`(grill #6),不再只重置 M
- 上述变更与 PRD §6.6/§6.8 对齐

BIN
.scratch/clue-module/prototype/__pycache__/lead_engine.cpython-313.pyc

Binary file not shown.

40
.scratch/clue-module/prototype/lead_engine.py

@ -205,17 +205,19 @@ class LeadEngine:
# -- 释放 --
@staticmethod
def release(lead: Lead, now: int, op_user_id: int) -> tuple[Lead, list[Event]]:
"""已领取/跟进中 → 待领取(边 #10),清 owner + 停 N"""
"""已领取/跟进中 → 待领取(边 #10),清 owner + 停 N
Grill #5: 反馈快照 5 列不清空,保留上一位持有人的反馈供下一位参考。
只清 owner_*claim_timerecycle_deadline
"""
_require(lead.status in (LeadStatus.CLAIMED, LeadStatus.FOLLOWING),
"只有已领取/跟进中可释放")
_require(lead.owner_user_id == op_user_id, "只有持有人可释放")
new = replace(lead, status=LeadStatus.PENDING,
owner_user_id=None, owner_name_snapshot=None,
claim_time=None, recycle_deadline=None,
last_feedback_time=None,
feedback_status=FeedbackStatus.NONE,
last_feedback_content=None)
return new, [Event(now, "RELEASE", "手动释放回公海")]
claim_time=None, recycle_deadline=None)
# feedback_status / last_feedback_content / last_feedback_time 保留
return new, [Event(now, "RELEASE", "手动释放回公海(反馈保留)")]
# -- 定时任务 tick --
@staticmethod
@ -228,11 +230,9 @@ class LeadEngine:
prev_owner = lead.owner_name_snapshot
new = replace(lead, status=LeadStatus.PENDING,
owner_user_id=None, owner_name_snapshot=None,
claim_time=None, recycle_deadline=None,
last_feedback_time=None,
feedback_status=FeedbackStatus.NONE,
last_feedback_content=None)
return new, [Event(now, "RECYCLE", f"N 到期超时回收 (原持有人 {prev_owner})")]
claim_time=None, recycle_deadline=None)
# Grill #5: 反馈快照不清空,与 release 一致
return new, [Event(now, "RECYCLE", f"N 到期超时回收 (原持有人 {prev_owner},反馈保留)")]
@staticmethod
def try_expire(lead: Lead, now: int) -> tuple[Lead, list[Event]]:
@ -248,15 +248,23 @@ class LeadEngine:
# -- 激活 --
@staticmethod
def activate(lead: Lead, pool: Pool, now: int) -> tuple[Lead, list[Event]]:
"""过期失效 → 恢复失效前状态 + 原领取人;M 重置(边 #13)"""
"""过期失效 → 恢复失效前状态 + 原领取人;N 和 M 均重置(边 #13)
Grill #6: 激活时 N 也重置(recycle_deadline = now + pool.N),
M 重置expire_deadline = now + pool.M
若失效前是已领取/跟进中恢复后重新给足完整的 N 天跟进窗口
"""
_require(lead.status == LeadStatus.EXPIRED, "只有过期失效可激活")
_require(lead.status_before_expire is not None, "失效前状态丢失")
prev = lead.status_before_expire
new_recycle = None
if prev in (LeadStatus.CLAIMED, LeadStatus.FOLLOWING):
new_recycle = now + pool.recycle_days * 24
new = replace(lead, status=prev, status_before_expire=None,
expire_deadline=now + pool.expire_days * 24)
# 激活不重置 N —— 若失效前是已领取/跟进中,N 已在失效时冻结未变;这里也不重启
# (原型口径:激活只重置 M;N 保持失效前的值,若已过期则会被下一次 tick 回收)
return new, [Event(now, "ACTIVATE", f"激活 → 恢复 {prev.value};M 重置")]
expire_deadline=now + pool.expire_days * 24,
recycle_deadline=new_recycle)
return new, [Event(now, "ACTIVATE",
f"激活 → 恢复 {prev.value}N+M 重置")]
# -- 池规则参数变更 (批量刷新) --
@staticmethod

23
.scratch/clue-module/待确认事项清单.md

@ -128,3 +128,26 @@ grill 过程中,产品口径多处推翻了原型;原型文案与 spec 不
1. **P0 - 产品-B(PRD 5 项)**:阻塞 05 与 04 权限部分;影响整个 crm-auth 是否要加通用 `sys_row_grant` 表。**最先拿**。
2. **P1 - 产品-A(线索作废)**:阻塞 03 完整定稿,但业务规则轻,可快速拍板。**次先拿**。
3. **P2 - UX 修订清单**:不阻塞后端 spec,但影响前端/UX 迭代。**并行推进**即可。
---
## 五、本轮 grill 决策记录(2026-08-12)
grill 过程中发现并解决了 12 项 spec 自身一致性和定义缺口问题(不依赖外部输入)。详见 `线索业务-PRD.md` §12.1 决策清单。摘要:
| # | 决策 | 影响 |
|---|---|---|
| 1 | 池换部门允许;待领取/未分发 dept_id 跟池走,已领取/跟进中不动;目标部门须无池 | 改写 [I] |
| 2 | 线索合并不做,Out of scope | map.md |
| 3 | 拆出 `lead_feedback` 独立表(DRAFT/SUBMITTED),改写 [Q2=c] | §6.4、ticket 06 |
| 4 | 过期失效线索可删(管理/持有人·强确认) | 03 矩阵 |
| 5 | 释放/回收时反馈快照不清空 | §6.6、原型引擎 |
| 6 | 激活时 N 和 M 均重置 | 03 边 #13、§6.8、原型引擎 |
| 7 | hold_limit 只算已领取 + 跟进中 | §6.2 |
| 8 | 批量激活,部分成功 | §6.8 |
| 9 | daily_claim_limit "每日" = 自然日 | §6.2 |
| 10 | 导入线索不做,留 fog | map.md |
| 11 | 销售可删自己过期失效线索(持有人·强确认) | 03 矩阵 |
| 12 | 编辑范围 = 业务字段可改,状态/池/归属/时效/快照不可改 | 新增 §6.9 |
**注**:⚠(C)(反馈是否可上传附件)已通过 grill #3 解决——反馈可上传附件,存 `lead_feedback.attachment_ids`

108
.scratch/clue-module/线索业务-PRD.md

@ -95,7 +95,7 @@
| 10 | 已领取 / 跟进中 | 手动【释放】 | 待领取 | 清领取人;N 停 |
| 11 | 已领取 / 跟进中 | 超时回收(N 到期无跟进) | 待领取 | 清领取人;N 停 |
| 12 | 未分发 / 待领取 / 已领取 / 跟进中 | 失效计时 M 到期 | 过期失效 | 若被持有则保留领取人;只读 |
| 13 | 过期失效 | 管理员【激活】 | 恢复失效前状态与原领取人 | M 重置(起点=激活时刻) |
| 13 | 过期失效 | 管理员【激活】(单条/批量) | 恢复失效前状态与原领取人 | **N 和 M 重置**(起点=激活时刻,`recycle_deadline = now + pool.N`,`expire_deadline = now + pool.M`) |
| C | ⚠ | ⚠ | 线索作废 | ⚠ 待确认(A) |
### 3.3 状态 × 操作 权威矩阵
@ -110,7 +110,7 @@
| 转商机 | — | — | ✓(持有人) | ✓(持有人) | — | — | — |
| 释放 | — | — | ✓(持有人) | ✓(持有人) | — | — | — |
| 关注/取关 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| 删除 | ✓(管理) | ✓(管理) | ✓(管理·强确认) | ✓(管理·强确认) | ✓(管理·强确认) | **✗ 不可删** | ⚠ |
| 删除 | ✓(管理) | ✓(管理) | ✓(管理·强确认) | ✓(管理·强确认) | ✓(管理·强确认) | ✓(管理/持有人·强确认) | ⚠ |
| 激活 | — | — | — | — | — | ✓(管理) | — |
「管理」= 具有该线索所属数据范围的管理权限;具体谁是"管理"随 §9 权限 PRD 定。
@ -144,6 +144,20 @@
**⚠ 待确认(B)**:销售换部门后,`owner_dept_id_snapshot` 是否需要刷新?当前定为**不刷**(快照即历史)。
### 4.1.1 公海池换部门时的 dept_id 联动
管理员可修改公海池的归属部门(如从 A 部门改挂到 B 部门)。此时线索 `dept_id` 的联动规则:
| 线索状态 | dept_id 是否跟池走 | 说明 |
|---|:---:|---|
| 未分发 / 待领取 | ✓ 跟池走 | 批量 `UPDATE lead SET dept_id = new_dept_id WHERE pool_id=? AND status IN ('未分发','待领取')`,新部门成员可查到、可领取 |
| 已领取 / 跟进中 | ✗ 不动 | `dept_id` 保持原值(=旧部门),已归属销售的私海线索不受池换部门影响 |
| 已转商机 / 过期失效 / 线索作废 | ✗ 不动 | 终态/失效线索不参与 |
**前置校验**:目标部门必须当前**无池**(`UNIQUE(dept_id)` 约束);源部门换出后无池不拦截。
**改写 [I]**:`dept_id` 不再是"恒定永不变动",而是"池归属部门变更时,待领取/未分发线索 dept_id 跟池走;已领取/跟进中线索 dept_id 不动"。
### 4.3 公海池(`lead_pool`,crm-rule 定义)
一个公海池 = 一个**部门**的线索容器 + 一组**规则参数**:
@ -157,6 +171,8 @@
**池名**全局 UNIQUE;**删除**软删且必须"池下无非终态线索"。
**换部门**:管理员可修改 `dept_id`,前置校验目标部门当前无池(`UNIQUE(dept_id)`)。换部门后,待领取/未分发线索 `dept_id` 批量跟池走,已领取/跟进中线索 `dept_id` 不动(见 §4.1.1)。
---
## 5. 双计时器
@ -184,7 +200,9 @@
### 5.3 池规则参数变更时的批量刷新
管理员改了池的 N 或 M:
- N 改动 → 批量 `UPDATE lead SET recycle_deadline = claim_time + new_N WHERE pool_id=? AND status IN ('已领取','跟进中')`(对跟进中的按最后反馈时刻算:`last_feedback_time + new_N`)
- N 改动 → 分状态批量刷新:
- 已领取:`UPDATE lead SET recycle_deadline = claim_time + new_N WHERE pool_id=? AND status='已领取'`
- 跟进中:`UPDATE lead SET recycle_deadline = last_feedback_time + new_N WHERE pool_id=? AND status='跟进中'`
- M 改动 → 批量 `UPDATE lead SET expire_deadline = create_time + new_M WHERE pool_id=? AND status != '已转商机'`
**当日不批量清算已超时的**——只改截止时刻,实际状态转移交给次日定时任务自然执行(对齐原型 A7-3-1 口径)。
@ -233,8 +251,8 @@
**入口**:【线索公海】列表 → 单条【领取】 / 批量勾选后【批量领取】。
**前置**(应用层校验):
1. 当前用户在池的可领取成员集合内(`claim_rule=1` 时=部门全员;`claim_rule=2` 时**销售不可自领**,按钮隐藏)
2. 未超"每日领取上限"`daily_claim_limit`(0=无限)
3. 未超"个人持有上限"`hold_limit`
2. 未超"每日领取上限"`daily_claim_limit`(0=无限);**"每日" = 自然日**(00:00-23:59,`DATE(op_time) = CURRENT_DATE`)
3. 未超"个人持有上限"`hold_limit`;**计数口径 = 仅已领取 + 跟进中**(已转商机、过期失效、线索作废不占名额)
4. 目标线索状态 = 待领取
**副作用**:设 `owner_user_id`;快照 owner 姓名/部门;状态转 已领取(边 #6);启动 N;写 CLAIM 历史。
@ -249,12 +267,27 @@
### 6.4 反馈(持有人 · a2-1-2 系列)
**入口**:详情页 / 列表操作【反馈】按钮。
**表单**:反馈情况(有效/无效/未反馈)· 反馈内容 · 需求产品(可切换)· ⚠ 待确认(C):反馈是否可上传附件?
**副作用**:
- 落 `lead_history` FEEDBACK 记录
- 主表更新 5 列反馈快照(`feedback_status/content/time/by/product_code`)
- 状态:已领取 → 跟进中(边 #7);跟进中 → 跟进中(边 #8
- **回收计时 N 重置**(起点=`now`)
**表单**:反馈情况(有效/无效/未反馈)· 反馈内容 · 需求产品(可切换)· 附件(走 crm-file,存 `lead_feedback.attachment_ids`)。
**草稿/提交双态**(改写 [Q2=c],拆出独立 `lead_feedback` 表):
- **【保存草稿】**:写/更新 `lead_feedback``record_status='DRAFT'`,**不校验必填**,不改状态机,不写 `lead_history`,不刷主表快照。草稿按 `(user_id, lead_id)` 唯一,重复保存为覆盖写。
- **【保存】(正式提交)**:`lead_feedback` 行 `record_status='SUBMITTED'`,同时:
- 主表更新 5 列反馈快照(`feedback_status/content/time/by/product_code`)
- 状态:已领取 → 跟进中(边 #7);跟进中 → 跟进中(边 #8
- **回收计时 N 重置**(起点=`now`)
- 写 `lead_history` FEEDBACK 记录(detail 引用 `lead_feedback.id`,不冗余内容)
- 清除同 `(user_id, lead_id)` 的草稿行(转为 SUBMITTED)
**`lead_feedback` 表结构**:
```
lead_feedback
├─ id, lead_id, user_id
├─ feedback_status, content, product_code
├─ attachment_ids json -- crm-file 文件 id 列表
├─ record_status tinyint -- 1=DRAFT, 2=SUBMITTED
├─ created_at, submitted_at
└─ INDEX(lead_id, user_id, record_status)
```
### 6.5 转商机(持有人 · a2-1-2 转商机弹窗)
@ -272,7 +305,7 @@
### 6.6 释放(持有人 · a2-1-2)
**前置**:`status IN (已领取, 跟进中)`;当前用户=持有人。
**副作用**:清 `owner_user_id`;状态转"待领取"(边 #10);N 停;写 RELEASE 历史。线索回到池内待领取,原持有人失去所有非查看权限。
**副作用**:清 `owner_user_id`、`owner_name_snapshot`、`owner_dept_id_snapshot`、`claim_time`、`recycle_deadline`;状态转"待领取"(边 #10);N 停;写 RELEASE 历史。**反馈快照 5 列不清空**(保留上一位持有人的反馈,供下一位领取人参考)。线索回到池内待领取,原持有人失去所有非查看权限。
### 6.7 定时任务:超时回收 & 失效
@ -292,10 +325,35 @@ WHERE expire_deadline < now() AND status IN ('未分发','待领取','已领取'
**⚠ 待确认(D)**:两个 Job 的执行节奏(每日凌晨?每小时?按公海池独立?)。当前定为**统一每日 02:00 全库扫**。
### 6.8 激活失效线索(管理员)
### 6.8 激活失效线索(管理员/持有人)
**入口**:过期失效线索详情 → 【激活】(单条);线索管理列表 → 勾选多条 → 【批量激活】。
**副作用**:状态恢复为失效前状态、owner 保留;`expire_deadline = now + pool.expire_days`(M 重置);**`recycle_deadline = now + pool.recycle_days`(N 同步重置)**;写 ACTIVATE 历史。
**批量激活**:逐条执行单条激活逻辑,**部分成功**——成功 N 条、失败 M 条(已被他人操作),弹结果提示。非原子。
### 6.9 编辑线索(管理/持有人)
**入口**:过期失效线索详情 → 【激活】。
**副作用**:状态恢复为失效前状态、owner 保留;`expire_deadline = now + pool.expire_days`(M 重新计);写 ACTIVATE 历史。
**入口**:列表操作【编辑】按钮,打开线索详情编辑页(含【线索详情】【历史记录】双 Tab)。
**可编辑字段**(= 新增线索表单的业务字段):
| 分组 | 可编辑字段 |
|---|---|
| 基础 | 线索名称、联系电话、微信、电子邮箱、省份、城市、详细地址 |
| 业务来源 | 渠道、品牌、需求产品、需求场景 |
| 核心 | 咨询内容、是否加急 |
**不可编辑字段**:状态、所属公海池、领取人、领取时刻、所有时效字段(`recycle_deadline`/`expire_deadline`)、所有快照字段(`pool_name_snapshot`/`owner_*`/`feedback_*`)——由状态机和业务流程管,不走编辑入口。
**权限**:
- 未分发 / 待领取 → **管理**可编辑
- 已领取 / 跟进中 → **持有人**可编辑
- 已转商机 / 过期失效 → **不可编辑**(终态锁 / 只读)
- 线索作废 → ⚠ 待确认(A)
**副作用**:
- 写 `lead_history` EDIT 记录,detail 含变更字段清单(旧值→新值)
- **不改状态**,不影响 N/M 计时器
---
@ -379,7 +437,7 @@ record ColumnPreference(List<String> visibleKeys, List<String> columnOrder) {}
|---|---|:---:|:---:|
| **⚠ (A)** | 线索作废:触发方(自动/手动)、入口状态、可逆性、归属处理 | 03 收尾 | P1 |
| **⚠ (B)** | 销售换部门后 `owner_dept_id_snapshot` 是否刷新?(当前定"不刷") | 04 完善 | P2 |
| **⚠ (C)** | 反馈是否可上传附件? | 06 完善 | P2 |
| ~~⚠ (C)~~ | ~~反馈是否可上传附件?~~ **✅ 已解决**:反馈可上传附件,存 `lead_feedback.attachment_ids`(本轮 grill #3) | — | — |
| **⚠ (D)** | 回收/失效 Job 执行节奏(每日 02:00?每小时?按池独立?) | §6.7 | P2 |
| **⚠ (E)** | 线索公海视图是否显示"已被别人领取"的线索(只读观察态) | §7 | P2 |
| **⚠ PRD-1** | 行级授权**触发动作清单**是否完整 | 05、§9 | **P0** |
@ -403,7 +461,25 @@ record ColumnPreference(List<String> visibleKeys, List<String> columnOrder) {}
见各 ticket Answer:
- `lead` 主表 → `issues/06-线索实体与字段模型.md`
- `lead_feedback`(独立反馈实体,DRAFT/SUBMITTED) → 本 PRD §6.4(本轮 grill #3 改写 [Q2=c])
- `lead_pool` + 3 关联表 → `issues/04-公海池实体与归属建模.md`
- `sys_region``issues/02-行政区划数据源调研.md`
- `user_column_preference``issues/07-preference列偏好scope模型与契约.md`
- `OpportunityCreationPort``issues/08-转商机outbound-port契约.md`
### 12.1 本轮 grill 决策清单(2026-08-12)
| # | 决策 | 影响章节 |
|---|---|---|
| 1 | 池换部门允许;待领取/未分发 `dept_id` 跟池走,已领取/跟进中不动;目标部门须无池 | §4.1.1、§4.3,改写 [I] |
| 2 | 线索合并不做,Out of scope | map.md |
| 3 | 拆出 `lead_feedback` 独立表(DRAFT/SUBMITTED),改写 [Q2=c] | §6.4 |
| 4 | 过期失效线索可删(管理/持有人·强确认) | §3.3 矩阵 |
| 5 | 释放/回收时反馈快照不清空 | §6.6 |
| 6 | 激活时 N 和 M 均重置 | §3.2 边 #13、§6.8 |
| 7 | `hold_limit` 只算已领取 + 跟进中 | §6.2 |
| 8 | 批量激活,部分成功 | §6.8 |
| 9 | `daily_claim_limit` "每日" = 自然日 | §6.2 |
| 10 | 导入线索不做,留 fog | map.md |
| 11 | 销售可删自己过期失效线索(持有人·强确认) | §3.3 矩阵 |
| 12 | 编辑范围 = 业务字段可改,状态/池/归属/时效/快照不可改 | §6.9 |

13
tmp_filelist.txt

@ -0,0 +1,13 @@
E:\code\crm-backend-matt\.scratch\clue-module\map.md
E:\code\crm-backend-matt\.scratch\clue-module\待确认事项清单.md
E:\code\crm-backend-matt\.scratch\clue-module\权限与数据可见性-PRD.md
E:\code\crm-backend-matt\.scratch\clue-module\线索业务-PRD.md
E:\code\crm-backend-matt\.scratch\clue-module\issues\01-商机模块现状调研.md
E:\code\crm-backend-matt\.scratch\clue-module\issues\02-行政区划数据源调研.md
E:\code\crm-backend-matt\.scratch\clue-module\issues\03-线索状态机权威定义.md
E:\code\crm-backend-matt\.scratch\clue-module\issues\04-公海池实体与归属建模.md
E:\code\crm-backend-matt\.scratch\clue-module\issues\05-线索域数据隔离落地.md
E:\code\crm-backend-matt\.scratch\clue-module\issues\06-线索实体与字段模型.md
E:\code\crm-backend-matt\.scratch\clue-module\issues\07-preference列偏好scope模型与契约.md
E:\code\crm-backend-matt\.scratch\clue-module\issues\08-转商机outbound-port契约.md
E:\code\crm-backend-matt\.scratch\clue-module\prototype\README.md
Loading…
Cancel
Save