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.

486 lines
28 KiB

4 weeks ago
# 线索业务 PRD
**版本**:v0.1(chart 阶段收敛稿)
**范围**:crm-lead(线索业务)· crm-rule/线索规则(公海池配置)· crm-preference(列偏好平台能力)
**目的**:把线索域端到端业务流程写成产品可验证的规格;不确定点用 **⚠ 待确认** 标出。
---
## 0. 阅读约定
- **⚠ 待确认(编号)**:产品/UX 尚未拍板的点,见文末《待确认事项索引》。
- **术语**沿用 crm-auth 既有:主部门、兼职部门、部门集合、数据范围档位。
- **实体名**用于对齐:线索(Lead)、公海池(LeadPool)、反馈(Feedback)、关注(Follow)、附件(Attachment)、行历史(History)、区划(Region)。
---
## 1. 全景:线索一生
一条线索从**进入系统**到**转化为商机(或作废/失效)**的完整生命周期:
```
┌──────────── 管理员导入/新建 ────────────┐
▼ ▼
[未分发]──分配到池──▶[待领取] [销售新增]
│ │
▼ 领取/分配 ▼
[已领取]────首次反馈────▶[跟进中]
│ │ ▲
│ 释放 / 超时回收 │ │ 再次反馈(重置N)
▼ │ │
[待领取]◀────释放/回收 N ────┘ │
转商机(已领取/跟进中) ──────┘
[已转商机] ← 终态
失效M计时(创建时起持续跑) ──到期──▶[过期失效]──管理员激活──▶恢复原态
```
**四把标尺**贯穿始终:
1. **状态**(7 态单字段,见 §3)
2. **归属**(哪个池 / 哪个部门 / 哪个销售,见 §4)
3. **两把计时器**(回收 N 天 / 失效 M 天,见 §5)
4. **可见性**(谁能看到、谁能操作,见 §9 ⚠ 待权限 PRD)
---
## 2. 模块架构(后端)
| 模块 | 职责 | 关键实体 |
|---|---|---|
| **crm-lead** | 线索业务:CRUD、领取/反馈/释放/转商机、四视图列表、统计、附件 | `lead`, `lead_history`, `lead_follow`, `lead_attachment` |
| **crm-rule**(线索规则子域) | 公海池配置与规则参数 | `lead_pool`, `lead_pool_province`, `lead_pool_region`, `lead_pool_member` |
| **crm-preference**(新平台模块) | 用户级 UI 列偏好(显隐 + 顺序) | `user_column_preference` |
| **crm-auth**(既有,消费) | 用户/部门/角色/数据范围/**行级授权(新增)** | `sys_user`, `sys_dept`, `sys_role_data_scope`, `sys_row_grant` ⚠ 待 PRD |
| **crm-dict**(既有,消费) | 字典:渠道/品牌/需求产品/需求场景 | `dict_group`, `dict_item` |
| **crm-region**(新建,02 定稿) | 行政区划:省/市/区,国标 code | `sys_region` |
| **crm-file**(既有,消费) | 附件文件 | `file` |
| **商机模块** | **不存在**,crm-lead 通过 outbound port 契约声明依赖 | — |
**依赖关系**:`crm-lead → crm-rule / crm-preference / crm-auth / crm-dict / crm-region / crm-file`;单向,无回边。
---
## 3. 线索状态机(7 态单字段 `leadStatus`)
### 3.1 状态定义
| # | 状态 | 含义 | 有领取人 | 回收计时 N | 失效计时 M |
|---|---|---|:---:|:---:|:---:|
| 1 | **未分发** | 管理员新建/导入且未指定归属池;悬于池外 | 无 | 停 | 跑 |
| 2 | **待领取** | 已归池;池内成员可自领 | 无 | 停 | 跑 |
| 3 | **已领取** | 某销售持有,尚未反馈 | 有 | 跑 | 跑 |
| 4 | **跟进中** | 持有销售已产生至少一次反馈 | 有 | 跑(每次反馈重置) | 跑 |
| 5 | **已转商机** | 已通过转商机操作创建对应商机;**终态** | 保留(历史) | 冻结 | 冻结 |
| 6 | **过期失效** | 失效计时 M 到期;可发生在任何非终态 | 保留(若失效前被持有) | 冻结 | 冻结(除激活重置) |
| 7 | **线索作废** | ⚠ 待确认(A) | ⚠ | ⚠ | ⚠ |
**反馈情况**(有效 / 无效 / 未反馈)是**独立字段** `feedbackStatus`,不进状态机;仅供筛选/展示。
### 3.2 合法迁移边
| # | from | 触发 | to | 副作用 |
|---|---|---|---|---|
| 1 | (无) | 管理员新建/导入且未指定池 | 未分发 | 启动 M(起点=创建时刻) |
| 2 | (无) | 销售在"我的线索"新增(自动带出本人池)或管理员指定池 | 待领取 | 启动 M |
| 3 | 待领取 | 销售新增时勾"是否领取" | 已领取 | 领取人=本人;启动 N |
| 4 | 未分发 | 管理员分配到池 | 待领取 | 归池,M 继续 |
| 5 | 未分发 / 待领取 | 管理员分配并指定销售 | 已领取 | 领取人=被指派销售;启动 N |
| 6 | 待领取 | 池内成员点【领取】 | 已领取 | 领取人=本人;启动 N |
| 7 | 已领取 | 首次反馈 | 跟进中 | 写反馈;N 重置(起点=反馈时刻) |
| 8 | 跟进中 | 再次反馈 | 跟进中(自环) | 写反馈;N 重置 |
| 9 | 已领取 / 跟进中 | 转商机 | 已转商机 | 调 OpportunityCreationPort;写 CONVERT 历史;冻结 N、M |
| 10 | 已领取 / 跟进中 | 手动【释放】 | 待领取 | 清领取人;N 停 |
| 11 | 已领取 / 跟进中 | 超时回收(N 到期无跟进) | 待领取 | 清领取人;N 停 |
| 12 | 未分发 / 待领取 / 已领取 / 跟进中 | 失效计时 M 到期 | 过期失效 | 若被持有则保留领取人;只读 |
4 weeks ago
| 13 | 过期失效 | 管理员【激活】(单条/批量) | 恢复失效前状态与原领取人 | **N 和 M 均重置**(起点=激活时刻,`recycle_deadline = now + pool.N`,`expire_deadline = now + pool.M`) |
4 weeks ago
| C | ⚠ | ⚠ | 线索作废 | ⚠ 待确认(A) |
### 3.3 状态 × 操作 权威矩阵
| 操作 | 未分发 | 待领取 | 已领取 | 跟进中 | 已转商机 | 过期失效 | 线索作废 |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 详情 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| 编辑 | ✓(管理) | ✓(管理) | ✓(持有人) | ✓(持有人) | — | — | ⚠ |
| 领取 | — | ✓(池成员) | — | — | — | — | — |
| 分配 | ✓(管理) | ✓(管理) | — | — | — | — | — |
| 反馈 | — | — | ✓(持有人) | ✓(持有人) | — | — | ⚠ |
| 转商机 | — | — | ✓(持有人) | ✓(持有人) | — | — | — |
| 释放 | — | — | ✓(持有人) | ✓(持有人) | — | — | — |
| 关注/取关 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
4 weeks ago
| 删除 | ✓(管理) | ✓(管理) | ✓(管理·强确认) | ✓(管理·强确认) | ✓(管理·强确认) | ✓(管理/持有人·强确认) | ⚠ |
4 weeks ago
| 激活 | — | — | — | — | — | ✓(管理) | — |
「管理」= 具有该线索所属数据范围的管理权限;具体谁是"管理"随 §9 权限 PRD 定。
---
## 4. 归属模型
### 4.1 三层归属
一条线索的归属由**三层**表达,各有用途:
| 层 | 字段 | 由什么决定 | 变化时机 |
|---|---|---|---|
| **池归属** | `pool_id` | 建线索时指定(或后续分配) | 分配变更 |
| **部门归属** | `dept_id`(冗余 = 池的 dept_id) | 池所属部门;**一部门一池** → 线索的 dept_id 恒等于池的 dept_id | 池不变则永不变 |
| **销售归属** | `owner_user_id` | 领取/分配到销售 | 领取/释放/回收/分配 |
### 4.2 快照策略
以下字段在 lead 主表以**快照**形式冗余,避免关联查询:
| 字段 | 快照源 | 刷新时机 |
|---|---|---|
| `pool_name_snapshot` | `lead_pool.pool_name` | 池改名时批量刷 |
| `dept_id` | 池的 dept_id | 一部门一池 → 永不变 |
| `team_dept_id` | 池的 team_dept_id | 同上 |
| `owner_name_snapshot` | 领取人姓名 | 领取/分配时快照,释放时清空 |
| `owner_dept_id_snapshot` | 领取时销售所属部门 | 领取/分配时快照(用于审计"这条线索历史属于哪个部门") |
| `feedback_*`(5 列) | 最新一条反馈 | 每次反馈时刷 |
**⚠ 待确认(B)**:销售换部门后,`owner_dept_id_snapshot` 是否需要刷新?当前定为**不刷**(快照即历史)。
4 weeks ago
### 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 weeks ago
### 4.3 公海池(`lead_pool`,crm-rule 定义)
一个公海池 = 一个**部门**的线索容器 + 一组**规则参数**:
| 字段组 | 内容 |
|---|---|
| **归属** | `dept_id`(UNIQUE,一部门一池)、`team_dept_id`(冗余,团队 = 部门上溯节点) |
| **负责市场** | 多省(`lead_pool_province`)+ 多市/区(`lead_pool_region`);用国标 `region_code` 前缀 LIKE 支持祖先查询 |
| **人员** | `lead_pool_member(pool_id, user_id, role_type)` role=OWNER(1)/COLLABORATOR(多);部门成员 = 部门全体动态推导,不落库 |
| **规则参数** | `recycle_days`(N,默认 7)、`expire_days`(M,默认 180)、`hold_limit`(持有上限,默认 200)、`daily_claim_limit`(每日领取上限,默认 10,0=无限)、`claim_rule`(1=成员可领+管理员分配 / 2=仅管理员分配) |
**池名**全局 UNIQUE;**删除**软删且必须"池下无非终态线索"。
4 weeks ago
**换部门**:管理员可修改 `dept_id`,前置校验目标部门当前无池(`UNIQUE(dept_id)`)。换部门后,待领取/未分发线索 `dept_id` 批量跟池走,已领取/跟进中线索 `dept_id` 不动(见 §4.1.1)。
4 weeks ago
---
## 5. 双计时器
**两把独立计时器,各管各的**(对齐产品最新口径):
### 5.1 回收计时 N(`recycle_deadline`)
- **计什么**:销售领取到线索后,多久没跟进就退回公海。
- **起点**:领取时刻。
- **重置**:每次新增反馈时重置为 `now + N`
- **停止**:释放 / 回收 / 转商机 / 失效。
- **到期动作**:定时任务扫 `WHERE recycle_deadline < now AND status IN ('已领取','跟进中')` → 状态回到"待领取"、清领取人、写 RECYCLE 历史。
- **存储**:`lead.recycle_deadline datetime`,加索引。
### 5.2 失效计时 M(`expire_deadline`)
- **计什么**:一条线索从入库起多久后自动失效。
- **起点**:**创建时刻**,不随领取/释放变化。
- **重置**:**仅**管理员激活时(`now + M`)。
- **停止**:转商机 / 已失效。
- **到期动作**:定时任务扫 `WHERE expire_deadline < now AND status IN ('未分发','待领取','已领取','跟进中')` → 状态置"过期失效"(保留领取人若有)、写 EXPIRE 历史。
- **存储**:`lead.expire_deadline datetime`,加索引。
### 5.3 池规则参数变更时的批量刷新
管理员改了池的 N 或 M:
4 weeks ago
- 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='跟进中'`
4 weeks ago
- M 改动 → 批量 `UPDATE lead SET expire_deadline = create_time + new_M WHERE pool_id=? AND status != '已转商机'`
**当日不批量清算已超时的**——只改截止时刻,实际状态转移交给次日定时任务自然执行(对齐原型 A7-3-1 口径)。
---
## 6. 关键业务流程
### 6.1 新增线索(销售侧 · a2-1-2-2)
**入口**:【我的线索】列表 → 【新增线索】按钮。
**表单字段**(必填标 `*`):
| 分组 | 字段 | 类型 | 校验 |
|---|---|---|---|
| 基础 | 线索名称 | 文本 | 无 |
| | 联系电话 | 文本 | 手机/座机通用正则(单字段) |
| | 微信 | 文本 | 微信号正则;无效提示"无效微信号" |
| | 电子邮箱 | 文本 | 通用邮箱正则;无效提示"无效邮箱号" |
| | 省份 * | 下拉 | 联动城市 |
| | 城市 * | 下拉 | 依赖省份;未选省份不可选城市 |
| | 详细地址 | 文本 | 无 |
| 业务 | 渠道 * | 字典 | dict group=`lead.channel` |
| | 品牌 * | 字典 | dict group=`lead.brand` |
| | 需求产品 * | 字典 | dict group=`lead.product` |
| | 需求场景 | 字典 | dict group=`lead.scene` |
| 核心 | 咨询内容 * | 多行文本 | 无 |
| 只读回填 | 线索状态 | 展示"待领取" | 不可编辑 |
| | 线索公海池 | 自动带出本人所属池 | 不可编辑 |
| | 销售团队 / 销售部门 / 销售人员 / 新增时间 | 自动回填 | 不可编辑 |
| 标记 | 是否加急 | 单选是/否,默认否 | — |
| | 是否领取 | 复选,勾则新增即领 | — |
| | 是否关注 | 复选,勾则加我的关注 | — |
**底部**:连续新增(复选)· 新增线索(主按钮)· 关闭(二次确认放弃)。
**副作用**:
-`lead` 行 → 状态 = 待领取(勾"是否领取"则一步转已领取,走边 #3
-`lead_history` CREATE 记录
- 勾"是否关注" → 落 `lead_follow`
- 触发失效计时 M(起点=`create_time`);若同步领取,触发回收计时 N
### 6.2 领取线索(池成员 · a2-1-1 系列)
**入口**:【线索公海】列表 → 单条【领取】 / 批量勾选后【批量领取】。
**前置**(应用层校验):
1. 当前用户在池的可领取成员集合内(`claim_rule=1` 时=部门全员;`claim_rule=2` 时**销售不可自领**,按钮隐藏)
4 weeks ago
2. 未超"每日领取上限"`daily_claim_limit`(0=无限);**"每日" = 自然日**(00:00-23:59,`DATE(op_time) = CURRENT_DATE`)
3. 未超"个人持有上限"`hold_limit`;**计数口径 = 仅已领取 + 跟进中**(已转商机、过期失效、线索作废不占名额)
4 weeks ago
4. 目标线索状态 = 待领取
**副作用**:设 `owner_user_id`;快照 owner 姓名/部门;状态转 已领取(边 #6);启动 N;写 CLAIM 历史。
### 6.3 分配线索(管理侧 · A7 相关,部分在 A2 管理视图)
**入口**:管理员在【线索管理】/【线索公海】的管理视图选中线索 → 【分配】。
**两种分配深度**:
- **仅分配到池**(未分发 → 待领取,边 #4
- **分配到销售**(未分发/待领取 → 已领取,边 #5
**权限**:仅管理员 + 池 OWNER + 池 COLLABORATOR 可执行(对齐 04-3 语义)。
### 6.4 反馈(持有人 · a2-1-2 系列)
**入口**:详情页 / 列表操作【反馈】按钮。
4 weeks ago
**表单**:反馈情况(有效/无效/未反馈)· 反馈内容 · 需求产品(可切换)· 附件(走 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)
```
4 weeks ago
### 6.5 转商机(持有人 · a2-1-2 转商机弹窗)
**入口**:详情页 / 列表【转商机】按钮。
**前置**:`status IN (已领取, 跟进中)`;当前用户=持有人;反馈情况**不参与判定**(对齐产品最新口径,⚠ 已推翻原型"仅有效/跟进中/已联系"文案,UX 修订清单见 §11)。
**表单**:商机名称* · 行业*(字典 `opportunity.industry`,⚠ 待商机模块创建)· 甲方 · 意向客户* · 地区*(region_code)· 备注。
**副作用(同一本地事务)**:
1.`OpportunityCreationPort#createOpportunity(cmd)` → 拿商机 id
2. `UPDATE lead SET status='已转商机' WHERE id=? AND status IN ('已领取','跟进中')`;行数=0 抛并发异常
3.`lead_history` CONVERT 记录,detail 含 `opportunityId + opportunityName`
4. 冻结 N/M(靠定时任务 `WHERE status != '已转商机'` 天然实现,无需 UPDATE 截止字段)
**幂等**:状态终态保护(crm-lead 侧) + `opportunity``UNIQUE(source_lead_id)`(商机侧契约要求)。
**失败**:本地事务性回滚,线索状态不变。
### 6.6 释放(持有人 · a2-1-2)
**前置**:`status IN (已领取, 跟进中)`;当前用户=持有人。
4 weeks ago
**副作用**:清 `owner_user_id`、`owner_name_snapshot`、`owner_dept_id_snapshot`、`claim_time`、`recycle_deadline`;状态转"待领取"(边 #10);N 停;写 RELEASE 历史。**反馈快照 5 列不清空**(保留上一位持有人的反馈,供下一位领取人参考)。线索回到池内待领取,原持有人失去所有非查看权限。
4 weeks ago
### 6.7 定时任务:超时回收 & 失效
**回收 Job**(每日凌晨 / 或按配置节奏):
```sql
SELECT id, pool_id, owner_user_id FROM lead
WHERE recycle_deadline < now() AND status IN ('已领取','跟进中');
```
逐行:清领取人,状态→待领取,写 RECYCLE 历史。
**失效 Job**(同上):
```sql
SELECT id, status FROM lead
WHERE expire_deadline < now() AND status IN ('未分发','待领取','已领取','跟进中');
```
逐行:状态→过期失效(若在私海则保留 owner_user_id 作历史记录),写 EXPIRE 历史。
**⚠ 待确认(D)**:两个 Job 的执行节奏(每日凌晨?每小时?按公海池独立?)。当前定为**统一每日 02:00 全库扫**。
4 weeks ago
### 6.8 激活失效线索(管理员/持有人)
**入口**:过期失效线索详情 → 【激活】(单条);线索管理列表 → 勾选多条 → 【批量激活】。
**副作用**:状态恢复为失效前状态、owner 保留;`expire_deadline = now + pool.expire_days`(M 重置);**`recycle_deadline = now + pool.recycle_days`(N 同步重置)**;写 ACTIVATE 历史。
**批量激活**:逐条执行单条激活逻辑,**部分成功**——成功 N 条、失败 M 条(已被他人操作),弹结果提示。非原子。
### 6.9 编辑线索(管理/持有人)
4 weeks ago
4 weeks ago
**入口**:列表操作【编辑】按钮,打开线索详情编辑页(含【线索详情】【历史记录】双 Tab)。
**可编辑字段**(= 新增线索表单的业务字段):
| 分组 | 可编辑字段 |
|---|---|
| 基础 | 线索名称、联系电话、微信、电子邮箱、省份、城市、详细地址 |
| 业务来源 | 渠道、品牌、需求产品、需求场景 |
| 核心 | 咨询内容、是否加急 |
**不可编辑字段**:状态、所属公海池、领取人、领取时刻、所有时效字段(`recycle_deadline`/`expire_deadline`)、所有快照字段(`pool_name_snapshot`/`owner_*`/`feedback_*`)——由状态机和业务流程管,不走编辑入口。
**权限**:
- 未分发 / 待领取 → **管理**可编辑
- 已领取 / 跟进中 → **持有人**可编辑
- 已转商机 / 过期失效 → **不可编辑**(终态锁 / 只读)
- 线索作废 → ⚠ 待确认(A)
**副作用**:
-`lead_history` EDIT 记录,detail 含变更字段清单(旧值→新值)
- **不改状态**,不影响 N/M 计时器
4 weeks ago
---
## 7. 四视图(前端列表页)
| 视图 | 路径 | 数据集 | 关键操作 |
|---|---|---|---|
| **线索公海**(a2-1-1) | /lead/pool | 池内**非私海**(待领取 + 已被别人领取的观察态?⚠ 待确认(E)) | 领取、关注 |
| **我的线索**(a2-1-2) | /lead/mine | `owner_user_id = 当前用户`(含已转商机、过期失效) | 反馈、转商机、释放、编辑、删除 |
| **我的关注**(a2-1-3) | /lead/follow | 我在 `lead_follow` 里关注的线索 | 取消关注、跳转详情 |
| **线索管理**(a2-1-4) | /lead/manage | 数据可见范围内**全部**线索 | 分配、导入/导出、批量释放、删除、激活 |
**列偏好**:每视图**独立一套**字段显隐 + 顺序(scope_key 各不同:`lead.public_pool` / `lead.my_lead` / `lead.my_follow` / `lead.manage`),互不同步(对齐 07 定稿,⚠ 已推翻原型"共用一套"文案,UX 修订清单见 §11)。
**⚠ 待确认(E)**:线索公海视图是否只显示 `status='待领取'` 的线索,还是把"已被别人领取的"(已领取/跟进中)也展示为只读观察?原型混合展示,产品需明确。
---
## 8. crm-preference 契约(列偏好,07 定稿)
**表**:`user_column_preference(id, user_id, scope_key varchar(64), visible_keys json, column_order json, updated_at)`,`UNIQUE(user_id, scope_key)`。
**接口**(仅 2 方法):
```java
Optional<ColumnPreference> get(Long userId, String scopeKey);
void save(Long userId, String scopeKey, ColumnPreference preference);
record ColumnPreference(List<String> visibleKeys, List<String> columnOrder) {}
```
**scope_key**:由业务方(crm-lead)约定的**稳定字符串**(不是路由、不是主键)。线索域 4 个:
- `lead.public_pool` · `lead.my_lead` · `lead.my_follow` · `lead.manage`
**字段池归属**:业务方(crm-lead)拥有字段元数据、校验、未知 key 兜底;preference 只存"勾了哪些 key + 什么顺序",无感知语义。
**保存时机**:**仅**点【保存】按钮才落库;拖拽临时顺序仅前端本地保留,关页面/刷新即丢。
**明确不做**:跨用户默认模板、管理员强制列、多租户 scope 隔离、偏好版本历史、字段池注册表、scopeKey 注册表校验。
---
## 9. 数据可见性与权限
**⚠ 整个 §9 待产品拍板权限 PRD(`权限与数据可见性-PRD.md`)**:
已升级为**三机制模型**(升级动因:团队成员/线索池协作人这类"行级授权"必须能越过部门天花板,纯 AND 不够表达):
1. **功能权限**(既有):权限点管理运行期配置按钮/API 开关
2. **数据可见范围**(既有,四档天花板):SELF / DEPT / DEPT_AND_CHILDREN / ALL,多角色取最宽
3. **行级授权(通行证,新增)**:`sys_row_grant(resource_type, resource_id, user_id, grant_source, grant_level)` 通用表;拦截器统一 OR/EXISTS 注入
**组合公式**:
```
能否看到某数据 = 页面业务条件 AND (落在数据可见范围 OR 已被行级授权)
```
**5 项待产品拍板(PRD-1/2/3/D/E,详见待确认索引)**:见 §11《待确认事项索引》。
---
## 10. 依赖与集成
| 目标 | 依赖模块 | 依赖形态 |
|---|---|---|
| 用户/部门/角色 | crm-auth | 消费既有 API |
| 数据范围拦截 | crm-auth | `@DataScope` 注解 + 拦截器 |
| **行级授权** | crm-auth(**待加强**) | `sys_row_grant` 通用表 + 拦截器 OR/EXISTS 注入(⚠ 待权限 PRD) |
| 字典 | crm-dict | 4 个 group:`lead.channel/brand/product/scene` |
| 行政区划 | crm-region(新建,02 定稿) | `sys_region.code` 国标前缀 LIKE 查询 |
| 附件 | crm-file | 关联表 `lead_attachment(lead_id, file_id)` |
| 列偏好 | crm-preference(新建,07 定稿) | 2-方法接口 |
| **商机模块** | **不存在** | crm-lead 定 outbound port `OpportunityCreationPort`,实现待商机模块落地 |
---
## 11. 待确认事项索引
### 11.1 产品拍板类(阻塞 spec 收尾)
| 编号 | 事项 | 阻塞对象 | 优先级 |
|---|---|:---:|:---:|
| **⚠ (A)** | 线索作废:触发方(自动/手动)、入口状态、可逆性、归属处理 | 03 收尾 | P1 |
| **⚠ (B)** | 销售换部门后 `owner_dept_id_snapshot` 是否刷新?(当前定"不刷") | 04 完善 | P2 |
4 weeks ago
| ~~⚠ (C)~~ | ~~反馈是否可上传附件?~~ **✅ 已解决**:反馈可上传附件,存 `lead_feedback.attachment_ids`(本轮 grill #3) | — | — |
4 weeks ago
| **⚠ (D)** | 回收/失效 Job 执行节奏(每日 02:00?每小时?按池独立?) | §6.7 | P2 |
| **⚠ (E)** | 线索公海视图是否显示"已被别人领取"的线索(只读观察态) | §7 | P2 |
| **⚠ PRD-1** | 行级授权**触发动作清单**是否完整 | 05、§9 | **P0** |
| **⚠ PRD-2** | 被授权者**权限边界**:只读/可操作,是否分级 | 05、§9 | **P0** |
| **⚠ PRD-3** | 移除授权后**即时失效**行为确认 | 05、§9 | **P0** |
| **⚠ PRD-D** | 线索是否存在**跨部门逐条指派**(决定是否需 `resource_type=lead` 直接授权通道) | 05、§9 | **P0** |
| **⚠ PRD-E** | 池负责人/协作人的**授权范围**(仅池内线索/兼池配置管理) | 05、§9 | **P0** |
### 11.2 UX 修订类(不阻塞后端,但上线前必改,详见 `待确认事项清单.md`)
| 编号 | 事项 | 处数 |
|---|---|:---:|
| **⚠ UX-1** | 列表字段"共用一套/同步生效" → "每菜单独立" | 7 处 |
| **⚠ UX-2** | "拖拽实时保存" → "点保存才落库" | 2 处 |
| **⚠ UX-3** | 公海池"江苏 1/2/3" → 合并为 1 池 | 数据行 |
| **⚠ UX-4** | 转商机触发条件"跟进中/已联系/有效" → "已领取/跟进中" | 3 处 4 行 |
---
## 12. 附:字段清单速查
见各 ticket Answer:
- `lead` 主表 → `issues/06-线索实体与字段模型.md`
4 weeks ago
- `lead_feedback`(独立反馈实体,DRAFT/SUBMITTED) → 本 PRD §6.4(本轮 grill #3 改写 [Q2=c])
4 weeks ago
- `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`
4 weeks ago
### 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 |