From 54aa290c8950c87ceca910c6a954097946eec3f4 Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Wed, 12 Aug 2026 21:55:32 +0800 Subject: [PATCH] commit --- .../issues/03-线索状态机权威定义.md | 33 +++++- .../issues/06-线索实体与字段模型.md | 46 +++++++- .scratch/clue-module/map.md | 17 ++- .scratch/clue-module/prototype/README.md | 11 +- .../__pycache__/lead_engine.cpython-313.pyc | Bin 16036 -> 16497 bytes .scratch/clue-module/prototype/lead_engine.py | 40 ++++--- .scratch/clue-module/待确认事项清单.md | 23 ++++ .scratch/clue-module/线索业务-PRD.md | 108 +++++++++++++++--- tmp_filelist.txt | 13 +++ 9 files changed, 251 insertions(+), 40 deletions(-) create mode 100644 tmp_filelist.txt diff --git a/.scratch/clue-module/issues/03-线索状态机权威定义.md b/.scratch/clue-module/issues/03-线索状态机权威定义.md index fa33805..23958a5 100644 --- a/.scratch/clue-module/issues/03-线索状态机权威定义.md +++ b/.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 已更新。 diff --git a/.scratch/clue-module/issues/06-线索实体与字段模型.md b/.scratch/clue-module/issues/06-线索实体与字段模型.md index 8851d19..d664735 100644 --- a/.scratch/clue-module/issues/06-线索实体与字段模型.md +++ b/.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` diff --git a/.scratch/clue-module/map.md b/.scratch/clue-module/map.md index b1e9845..47fbf99 100644 --- a/.scratch/clue-module/map.md +++ b/.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 -- **crm-lead 领取/流转行为规格** — 领取(单/批量)、关注/取关、反馈(含暂存/文件上传)、转商机、释放、分配、合并、批量删除各自的前置状态、校验、状态迁移、历史记录写入。等"线索状态机权威定义"ticket 定稿后逐条毕业。 +- **crm-lead 领取/流转行为规格** — ~~领取(单/批量)、关注/取关、反馈(含暂存/文件上传)、转商机、释放、分配、合并、批量删除各自的前置状态、校验、状态迁移、历史记录写入~~ **大部分已在 PRD §6 定稿**(领取/反馈/释放/分配/转商机/激活/编辑);合并已 Out of scope;导入留 fog。 - **四视图列表规格** — 公海(分屏/列表)、我的线索、我的关注、线索管理各自的字段集、统计卡片、筛选项、按角色/状态动态渲染的操作按钮。等"数据隔离在线索域的落地"与"列偏好契约"定稿后毕业。 - **导入/导出规格** — 三种导入模式、模板字段、校验/异常、进度状态、数据同步、按角色的数据范围限制;导出等待状态。等线索字段模型定稿后毕业。 - **定时任务规格** — 超时回收(N 天未跟进回流公海、状态重置待领取、清领取人)、线索失效(入库满 M 天标记过期失效);触发时机、幂等、存量 vs 增量生效规则。等公海池配置契约定稿后毕业。 @@ -55,3 +69,4 @@ - 商机模块内部实现——转商机只定接口契约。 - 组织架构(部门树/成员/钉钉同步)、数据字典、数据范围机制本身——均消费既存能力。 - crm-preference 的"全平台通用化"打磨——本次只做到线索够用。 +- **线索合并**——不做(grill #2 决策)。 diff --git a/.scratch/clue-module/prototype/README.md b/.scratch/clue-module/prototype/README.md index a4f74f3..0febf43 100644 --- a/.scratch/clue-module/prototype/README.md +++ b/.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 对齐 diff --git a/.scratch/clue-module/prototype/__pycache__/lead_engine.cpython-313.pyc b/.scratch/clue-module/prototype/__pycache__/lead_engine.cpython-313.pyc index c520721195c13a2919a3fcb9697a41da26a0d06b..17bf3a16778f8ec6b018f7dafaa66480f2e1ce27 100644 GIT binary patch delta 1601 zcmZ`(TTl~M7|z*T2#H*TKr|{G1cWsrgqwiGnL0+sR?r$_>0o6X%mba6&{d08gn&iD zO*;G{T0pJEqT&UO6;y;ib;hSI~OzWvYlU-sYs zd}jwwymX?-`G^RWgsiaIx({n}?nG`=BCESx`As~!=3btdqd=cPY3deK58bJMs>4a3 zR3bt1ze`|5cL-{-mcTI9Kt|e@a9N?kF11K2mO_78BKjW2(@YI2RS8#Zv*F~tA}n-u z&VA()CWpPvLpTo$u3pbrr#RN^z4q|Mc!y_lz}s`)GuGxAb9x?kiXDw&f2-%=L+|IG z1`|CKeV(!QpjYT>@;OgFb2h1juIr=(cGTLfT;`w!Tv?SyMm0ZKO&bBD>7%!chUZ8yDTHXTVLJF&H0XuCSs(k7mn5c)0%%{Roa zM_-J)7G59d@5Oj48xD!9Q?yAWgWE=cm2n?X`+W-aFq>YWJVs2X@ASJBKNI(%K48~Aa= z<@efn{lTYkc3$J)qa3`_;r?M=G$WlwEIePYN5|dTOc`R1qV5DUIrtKVnX$_(QrMS} z06Vc7PA!Xt=oJdt{-qL0caj!<$_m%mWff!>T;)?Zjo2S zg#s*$Sz!*dNO#2pGO!GbV4b0;p$rS%9k>(=11)nco!-Z{$dULa9^hm{4mpiQf#nxr zandQ?dnESW$7R^_;NoKRzeRwJ*zTysdR*gh*w&YkDr9!5QoNX8Jz7)EEr_OpsOFyu zr6hss$k%>F7`!9)xP;bD@!V};^zPi{Q$p9-OyB55-y{+1O;T2DyetfM3ZI=O#l^ED z-bd$s_uay+4zcGRQ8sB*7`zsY_g?Q6y1t}^kE|dQp~CKO^-Y4ykQ*c8jD+G!C~T!* zf+vR5_~5bkmFeG@4!Z51gd3##3W=gZ>hH5&!cq+Y^k2OA3 z8)tN@rmR3CM}zaFPB^cMd%u+W>#g6$VOFnR9EZxjWC+G z!HsiL7!>I?p&WL{V-7r69q7AEe* z!T)s=2de=?lm@mJ8B!W(Tr-7M3a2Rmh4U0HQMls%qR4^F5%TxYyi}ry@iWx)V`9!T zYI#kFrKVpOV3qQHA(oo{_yD_9z9Ym^)1MAi#XL%bTw|*C27MFN9Cwof9RwT8U4od5s; delta 1229 zcmaiyUrbYX6vyxH-rnnlwiKb4cEwOS3V1CAN}FJgf!b?f`&c8gAYH#gNDopA3%LDlpwz8_B$;XOw8i1$>)CW z_nhDPo&L^UJWp=-6)ah;`5ar{wD*1+4U84+l1VI`lqX7vn*PKcl*o6`S{WmKFjD!J zSm1cYD9~~Tw3Zf^XoB%e6=DfoZAIV-6q^Oj6wd1w5<*IWo@zH~hU?x9(Cu3flhsbR z>+t|=^zQvt*0-@e`gTMV(SnF0Vu&4xCWhH{ZSmdlCM|wSVQ=$87w3lWz8n~yzw&VI z+D~&A?#=)4mud$0r(TZ-w^Sok#4ZNaq#s1xj_8A0b+bIk*{FBI=h|VG!$lq$eGyGZ9`UBV`)gEVsaq z^$}~zZ2Xy|puxE^XNAcxa-5&Jniw{9n-Zp!%}7b`nxw5u2r%Zf!;OH0Z{^_6z!s&o z{N=!<=MP2~#s^%u8@QY zjR>5a-ov1Zx$z~@z!}b(j_Y5a){ioIlC{XeY_L}DXCgb%!cYWyL(aNIf!JCIo1<*Z zF`w4%n6OW}{&r1RroNyx$r+`c+B>MNgUTIntFbiwD&*yflrCz#Psnw6wD|(Dr`y5~ zBHdxtRT-uu3XE>aH@n!Yp7ks%UPUgYz)10)!IbP+dr>tY`Ve)9Tmf)JT&^|q`_;si z@SccESqrZnDuFwqNGnN?SdAO2g|pxI`$u5-=l>0lZ2jDO7Nb8Ph7gw#fVhgdiMS18 z7H7JD>sLfGi*d3fa8gl*N155c tuple[Lead, list[Event]]: - """已领取/跟进中 → 待领取(边 #10),清 owner + 停 N""" + """已领取/跟进中 → 待领取(边 #10),清 owner + 停 N + + Grill #5: 反馈快照 5 列不清空,保留上一位持有人的反馈供下一位参考。 + 只清 owner_*、claim_time、recycle_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 diff --git a/.scratch/clue-module/待确认事项清单.md b/.scratch/clue-module/待确认事项清单.md index 8726be6..46c0d55 100644 --- a/.scratch/clue-module/待确认事项清单.md +++ b/.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`。 diff --git a/.scratch/clue-module/线索业务-PRD.md b/.scratch/clue-module/线索业务-PRD.md index 09bbe4c..9593349 100644 --- a/.scratch/clue-module/线索业务-PRD.md +++ b/.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 visibleKeys, List 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 visibleKeys, List 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 | diff --git a/tmp_filelist.txt b/tmp_filelist.txt new file mode 100644 index 0000000..69ec28f --- /dev/null +++ b/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