把「活动里写下的下一步」直接变成「待办里能打勾的事」,并让拜访这件事第一次有可视化的账。 六项口径已定稿,本文给出可施工模型 + P0 落点清单。
你要的三件事,其实是一个数据模型的三层。
todos 加 scope(对内/对外)+ type(8 类)+ owner(负责人),
保留已有的 due(截止)与 done(完成),不引入进度百分比。channel(线下/线上)字段,
拜访视图就有了口径基础。现状的关键机遇:云端 activities 集合目前 0 条——活动模块已建成但还没有历史数据, 所以字段可以一次改对,不需要做历史迁移。
六项口径已定稿(v0.2 按此收敛)
| # | 你的决定 | 对模型的影响 |
|---|---|---|
| 1 | 线上线下用下拉选择,所有活动必填 | channel 定为必填枚举,不留空分支——采购跟进(催款 / 跟进下单)也要选 |
| 2 | 会议邀约 + 出差计划 → 对外新增「邀约及计划」 | 对外类型 4 → 5 类;旧 meeting / travel 都映射到它 |
| 3 | 填了「问题现象」自动生成待办,但提交前人工修改与确认 | 新增「提交前确认清单」交互层:生成 ≠ 落库,确认才落库 |
| 4 | 技术支持只留入口,先不做动作 | 保留 tech_support 标记 + UI 入口;不做自动派单与跨人镜像 |
| 5 | 对外待办手工可建,也可由活动生成,提交前修改确认 | source.kind = manual | activity;活动同步只碰 activity 类,绝不覆盖手工条 |
| 6 | 拜访视图给次数:线下拜访次数、线上跟进次数 | 主指标收敛为两个计数;客户清单仅作下钻入口,不做去重客户数 |
| 模块 | 存储 | 现有字段 | 规模 | 缺口 |
|---|---|---|---|---|
| 待办 Tab: 待办 |
本地 数据/todos.json+ CloudBase todos(一人一文档) |
id / text / done / cat / client / due / note / created另有 conclusion / closed / closedBy 已用于关闭留痕 |
8 个用户文档 刘新元 37 条、何超锦 1 条 |
无 内/外 分层
无 owner 字段 负责人靠写在正文里,如「【赵云浩+财务】…」 |
| 活动 Tab: 活动录入 / 我的活动 |
本地 数据/activities/+ CloudBase activities |
type(项目跟进/采购跟进)、blocks[](按商机分块)每块:客户需求 / 技术参数 / 测试单结果跟进 / 问题现象 / 客户反馈 / 竞对信息 / 下一步计划(事项+负责人+时间节点) |
0 条(模块已上线,尚无数据) | 无 线下/线上 标记 仅关联测试单,未关联发货单 |
| 订单索引 活动关联订单的数据基础 |
数据/orders_2026_index.json由 ERP 导出生成 |
no / customer / sales / date / types[] / is_test / skus[] |
6,251 单 测试销售订单 3,480 / 标准销售订单 2,570 / 预测 200 / 样机测试 1 |
够用is_test 可直接区分测试单与发货单 |
一个立刻能省事的发现:你要求的「活动关联发货订单及测试订单」,数据基础已经有了——
订单索引里 is_test=true 有 3,481 单(测试销售订单 3,480 + 样机测试 1),
其余为标准销售订单即发货单。不用新造订单源,只要在活动里把关联从「只挂测试单」扩成「发货单 + 测试单」两栏即可。
你给的 8 类天然是 2 × 4:先分对内/对外,再选类型。建议把这两个维度做成两个字段 而不是 8 个平铺的值——因为「筛选对外待办」和「筛出所有客诉跟进」都会用到,平铺会两边都别扭。
| scope | type | 含义 | 典型来源 |
|---|---|---|---|
| 对内 我自己/内部 |
internal_project 立项跟进 | 新项目立项、转产、注册报证的推进 | 手工 / 活动派生 |
internal_complaint 客诉跟进 | 客诉闭环(排查、回复、CAPA) | 手工 / 客诉页派生 | |
internal_test_plan 测试方案 | 出测试方案、确认测试项与判据 | 手工 / 活动派生 | |
internal_other 其他(客户需求) | 客户提出的、还没归类的需求 | 手工 / 活动派生 | |
| 对外 客户侧 |
external_visit 拜访计划 | 要去的拜访、要见的人 | 手工 / 活动派生 |
external_test_req 测试需求 | 客户要做/要安排的测试 | 活动派生 | |
external_test_result 测试结果 | 测试做完了、要回传结果 | 活动派生 | |
external_project 项目进度 | 客户项目推进到下一节点 | 活动派生 | |
external_appointment 邀约及计划 新增 |
会议邀约、出差计划、要安排的行程 | 手工 / 活动派生 承接旧 meeting / travel |
due,改成必填(现在是选填)done / closed / closedBy活动已经是结构化录入,本次只做 两处加法、一处回写:
| 改动 | 字段 | 为什么 |
|---|---|---|
| ① 加 | channel: 'offline' | 'online'拜访形式,必填 |
拜访视图要靠它分「线下拜访 / 线上跟进」。现有数据里没有任何字段能推出这件事(活动类型只有项目/采购跟进),必须显式加字段。 |
| ② 加 | order_refs: {ship:[], test:[]} |
你要的「关联发货订单及测试订单」。发货单与测试单用 is_test 分流,互不混淆。 |
| ③ 回写 | 「下一步计划」每行加 todo_id / tech_support |
让活动和它生成的待办互相找得到;勾了技术支持的行在活动里也能一眼看出来。 |
口径(建议):以活动为原子,按 channel 切线下/线上;客户按名称归一后去重。
| 指标 | 算法 | 示例 |
|---|---|---|
| 线下拜访次数 | 当月 channel=offline 的活动条数 | 52 次 |
| 线上跟进次数 | 当月 channel=online 的活动条数 | 74 次 |
按 #6 的决定只给次数。客户清单保留为下钻入口(点客户 → 该客户活动清单), 但不再计一个「客户数」指标 —— 客户名归一与去重口径容易起争议,不进主指标。
下钻:点客户名 → 打开该客户的活动清单(时间倒序),再点一条 → 进活动详情。 权限沿用 P4(admin/gm 全量、manager 本部门、sales 只看自己),且必须在服务端过滤—— 前端过滤不是授权。
todo = {
id, text, client, due, created, // 现有,due 改必填
done, closed, closedBy, // 现有,完成按钮留痕
scope: 'internal' | 'external', // 新增 ① 对内/对外
type: 'internal_project' | ..., // 新增 ② 8 类之一
owner: '赵云浩', // 新增 ③ 负责人(现在是写在正文里的)
tech_support: false, // 新增 ④ 是否需要技术支持协同
tech_owner: '', // 新增 ⑤ 协同人(tech_support=true 时必填)
source: { // 新增 ⑥ 溯源 + 幂等
kind: 'activity' | 'manual',
activity_id: 'ACT...', block_idx: 0, plan_idx: 1
},
fp: 'ACT123#0#1#回传测试报告' // 幂等指纹
}
为什么要 owner:现在待办是「一人一文档」({user:'刘新元', todos:[...]}),
所以「这是谁的待办」是靠文档分片表达的。一旦要支持「技术支持同事也能看到协同给他的待办」,
就必须有字段级的负责人 + 服务端跨人查询,否则只能靠复制两份数据,必然不同步。
旧 cat | 映射到 | 备注 |
|---|---|---|
prep 访前准备 | 对外 · 拜访计划 | 直接映射 |
post 访后跟进 | 对外 · 项目进度 | 直接映射 |
complaint 客诉 | 对内 · 客诉跟进 | 直接映射 |
custom 定制需求 | 对内 · 其他(客户需求) | 直接映射 |
meeting 会议邀约 | 对外 · 邀约及计划 | 按 #2 定稿;这一次算线上还是线下,由该活动自己的 channel 决定 |
travel 出差计划 | 对外 · 邀约及计划 | 按 #2 定稿;出差本身不是一次拜访,故不并入「拜访计划」 |
other 其他 | 对内 · 其他(客户需求) | 直接映射 |
映射写进迁移脚本,一次性跑完,前端保留旧值兼容显示(CATS/CAT_COLORS 要同步扩表)。
核心风险:活动可以反复编辑。如果每次保存都「生成一遍待办」,同一件事会变成 3 条、5 条; 如果改成「先删后建」,销售在待办侧打的勾、写的结论会被静默抹掉。两种都不行。
做法:指纹 upsert。
行在创建时领一个稳定 id:plan_row.uid = 'r-xxxx'(不随文本修改、不随排序变化)
fp = sha1(activity_id + '#' + plan_row.uid)
if 存在 fp: # 活动改了时间/负责人/措辞 → 更新,不新增
只更新 due / owner / text ← 绝不动 done / closed / conclusion
elif 不存在 fp:
新建待办(done=false)
计划行被删掉:
对应待办标 source_removed=true,不物理删除 ← 留痕,由人来决定要不要关
⚠ v0.1 把「事项文本」写进指纹是错的,v0.2 已修正:销售把「回传报告」改成 「回传报告(含原始数据)」会被当成一条新待办,同一件事越改越多。指纹只能锚在稳定行 id 上, 绝不能锚在会变的文本上。
验收硬指标:同一个活动连续保存 3 次 → 待办总条数不变; 在待办侧打勾后再回存活动 → 勾依然是勾。
生成 ≠ 落库。绝对不能让系统「填了问题现象就直接塞一条待办进去」—— 销售得先在现场看一眼、改一改、删掉不需要的,确认后才落库。
活动录入页 ├─ ① 填「下一步计划」/「问题现象」 ├─ ② 点「生成待办预览」→ 页面下方出现【本次将生成的待办】清单(草稿态 · 未落库) │ 每行可改:事项文本 / 负责人 / 截止 / 类型 可删 可勾「需要技术支持」 │ 「问题现象」自动派生 1 条,默认 客诉跟进(可改成其他类型) ├─ ③ 销售逐条修改并勾选确认 └─ ④ 点「确认并提交」→ 活动落库 + 待办按确认后的清单落库(写 activity_id)
没勾确认就提交怎么办:允许提交,但页面明确提示「还有 N 条待办未确认」, 由销售自己决定补确认还是放弃。绝不静默生成,也绝不静默丢弃。
二次编辑同一个活动:按 source.activity_id 把该活动已生成的待办拉回来,
和当前清单一起进同一个「可改可确认」的清单,确认后做 diff upsert——
只更新变动项,不动销售在待办侧已经打的勾和写的结论。
草稿期不落库,所以草稿期不需要指纹;落库后指纹才生效。 活动 ID 是提交那一刻才生成的,这正是「必须先确认再落库」的另一个理由—— 否则待办生成时还没有 ID 可挂,溯源就断了。
本次做:活动和待办上保留「需要技术支持协同」勾选项 + tech_support 字段并落库;
待办行显示 需技术协同 标记,销售能自己筛出来。
本次不做:不给技术同事自动派单、不做跨人镜像可见、不做状态双向同步、不建独立的「技术待办」Tab。
这样安排是对的:入口先立、数据先攒(谁在哪些事上需要技术、频率多高),
等口径成熟再决定走「镜像可见」还是「飞书通知」。跨人可见一旦开,owner 就得配服务端过滤,
那是另一个量级的改动,不该和本次打通混在一起做。
「只留入口」不等于字段先不设计。本次仍然把 tech_support 落库、把标记显示在待办行上——
三个月后要真接技术协同,手上已经有真实使用数据(谁在哪些事上要技术、频率多高),不用回头补历史。
自动派单 / 跨人可见 / 状态双向同步一律留到口径成熟再做。
| 期 | 做什么 | 验收 |
|---|---|---|
| P0 打底 |
待办加 scope/type/owner/due必填;旧 7 类映射迁移;
前端 CATS 扩表、按对内/对外分组渲染 |
老数据一条不丢;内外分组正确;node --check + 三身份权限对测 |
| P1 打通 |
活动加 channel + order_refs;下一步计划 → 待办指纹 upsert;
tech_support 协同镜像 |
同一活动存 3 次 → 待办条数不变;待办打勾回存活动后勾还在; 技术支持同事能看到协同条 |
| P2 可视化 |
拜访视图(当月线上线下客户数/次数 + 点客户进活动); 月份切换与人级下钻 | 数字与活动原始数据手算勾稽一致;权限三身份对测;414px 不横向溢出 |
顺序不能颠倒:P1 依赖 P0 的 owner 字段,P2 依赖 P1 的 channel 字段。
| 处 | 文件 | 改什么 |
|---|---|---|
| 后端 | 脚本/销售工作台服务.py |
加一条一次性迁移:旧 cat → scope + type(幂等,可重复跑)。
已核实:/api/todos 的 save 是「整体覆盖写」
(store['users'][user] = todos),没有字段白名单,所以新字段不用改后端就能落盘。 |
| 数据 | 数据/todos.json(8 个用户)+ CloudBase todos(8 个文档) |
双写(本地 + 云端同一批 id),改前备份;迁移后回读校验条数不变。 现网:刘新元 37 条、何超锦 1 条,其余 6 人 0 条 |
| 前端 ×2 | 工具/index.html工具/app.html(同一份代码两个入口,必须同步) |
① CATS / CAT_COLORS 扩表(对内 4 + 对外 5,保留旧 key 兼容显示);
② renderTodos 改按 对内 / 对外 分组;
③ 新增待办表单补 owner 必填、due 必填 |
| 已具备 不用做 | 完成 / 归档 | /api/todos 已有 close / unclose 动作 + closed 归档区
(带 closedBy)→ 你要的「完成按钮」不用新建,只需在 UI 上把归档区摆出来 |
| 重启 | launchd | launchctl kickstart -k gui/$(id -u)/ai.hermes.workbench
(改了 .py 必须重启,否则不生效且不报错) |
P0 的字段白名单风险,其实在 P1 而不在 P0。我核过源码:待办侧是整体覆盖写、无白名单,
所以加字段是安全的;活动侧才有 _clean_block() 的逐字段白名单
(不在表里的 key 会被静默丢弃,还会被 _block_empty() 判成空块整块丢弃)——
那是 P1 加 channel / order_refs 时必须先改的地方,顺序是
先改后端白名单 → 再改前端 → 最后重启。
你已拍板 6 项,全部收敛进 v0.2:
channel 定为必填枚举source.kind = manual | activityscope/type/owner + 旧 7 类映射迁移 + 双入口前端分组渲染。
会写生产数据(8 个用户的真实待办 + 云端 8 个文档),所以我想先拿你一句确认再动。工具/index.html 与 工具/app.html 是同一份代码两个入口,改一处另一处会静默不同步。_clean_block() 逐字段重建 dict,不在表内的新字段会被静默丢弃;
_block_empty() 若只认旧字段,会让「只填了新字段」的记录整块消失。加字段必须同时改这两处。launchctl kickstart -k gui/$(id -u)/ai.hermes.workbench),
不重启不生效且不报错;只改 JSON 数据不用重启。—,不能写 0,否则「没发生」和「真没做」分不出来。