BIORI BIOTECH
BIORI BIOTECH宝锐生物
营销中心守护生命健康,缔造美好生活
BIORI · 销售工作台

活动 × 待办打通构想

把「活动里写下的下一步」直接变成「待办里能打勾的事」,并让拜访这件事第一次有可视化的账。 六项口径已定稿,本文给出可施工模型 + P0 落点清单。

v0.2 · 口径已定稿 · 2026-10-07

1一页结论

你要的三件事,其实是一个数据模型的三层。

  1. 待办分层:给现有 todos 加 scope(对内/对外)+ type(8 类)+ owner(负责人), 保留已有的 due(截止)与 done(完成),不引入进度百分比。
  2. 活动 = 对外待办的来源:活动已经关联订单(发货/测试),只需补一个 channel(线下/线上)字段, 拜访视图就有了口径基础。
  3. 打通靠「指纹 upsert」:活动的「下一步计划」每行生成/更新一条待办, 用活动ID+块序号+行序号做指纹,同一活动反复保存也不会重复生成。这是本次最容易做错的地方。

现状的关键机遇:云端 activities 集合目前 0 条——活动模块已建成但还没有历史数据, 所以字段可以一次改对,不需要做历史迁移。

六项口径已定稿(v0.2 按此收敛)

#你的决定对模型的影响
1线上线下用下拉选择,所有活动必填 channel 定为必填枚举,不留空分支——采购跟进(催款 / 跟进下单)也要选
2会议邀约 + 出差计划 → 对外新增「邀约及计划」 对外类型 4 → 5 类;旧 meeting / travel 都映射到它
3填了「问题现象」自动生成待办,但提交前人工修改与确认 新增「提交前确认清单」交互层:生成 ≠ 落库,确认才落库
4技术支持只留入口,先不做动作 保留 tech_support 标记 + UI 入口;不做自动派单与跨人镜像
5对外待办手工可建,也可由活动生成,提交前修改确认 source.kind = manual | activity;活动同步只碰 activity 类,绝不覆盖手工条
6拜访视图给次数:线下拜访次数、线上跟进次数 主指标收敛为两个计数;客户清单仅作下钻入口,不做去重客户数

2现状盘点(都是真实读出来的,不是设想)

模块存储现有字段规模缺口
待办
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), 其余为标准销售订单即发货单。不用新造订单源,只要在活动里把关联从「只挂测试单」扩成「发货单 + 测试单」两栏即可。

3目标模型

3.1 待办:两级分类,两层都必填

你给的 8 类天然是 2 × 4:先分对内/对外,再选类型。建议把这两个维度做成两个字段 而不是 8 个平铺的值——因为「筛选对外待办」和「筛出所有客诉跟进」都会用到,平铺会两边都别扭。

scopetype含义典型来源
对内
我自己/内部
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,改成必填(现在是选填)
完成按钮
沿用现有 checkbox → 写 done / closed / closedBy
不做进度
按你的要求:不引入百分比。只有「未完成/已完成」两态

3.2 活动:对外待办的载体

活动已经是结构化录入,本次只做 两处加法、一处回写:

改动字段为什么
① 加channel: 'offline' | 'online'
拜访形式,必填
拜访视图要靠它分「线下拜访 / 线上跟进」。现有数据里没有任何字段能推出这件事(活动类型只有项目/采购跟进),必须显式加字段。
② 加order_refs: {ship:[], test:[]} 你要的「关联发货订单及测试订单」。发货单与测试单用 is_test 分流,互不混淆。
③ 回写「下一步计划」每行加 todo_id / tech_support 让活动和它生成的待办互相找得到;勾了技术支持的行在活动里也能一眼看出来。

3.3 拜访视图:把「拜访」第一次算成账

口径(建议):以活动为原子,按 channel 切线下/线上;客户按名称归一后去重。

指标算法示例
线下拜访次数当月 channel=offline 的活动条数52 次
线上跟进次数当月 channel=online 的活动条数74 次

按 #6 的决定只给次数。客户清单保留为下钻入口(点客户 → 该客户活动清单), 但不再计一个「客户数」指标 —— 客户名归一与去重口径容易起争议,不进主指标。

下钻:点客户名 → 打开该客户的活动清单(时间倒序),再点一条 → 进活动详情。 权限沿用 P4(admin/gm 全量、manager 本部门、sales 只看自己),且必须在服务端过滤—— 前端过滤不是授权。

4数据模型

4.1 待办(在现有结构上加 6 个字段)

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:[...]}), 所以「这是谁的待办」是靠文档分片表达的。一旦要支持「技术支持同事也能看到协同给他的待办」, 就必须有字段级的负责人 + 服务端跨人查询,否则只能靠复制两份数据,必然不同步。

4.2 旧 7 类 → 新 8 类的映射(老数据不用重填)

旧 cat映射到备注
prep 访前准备对外 · 拜访计划直接映射
post 访后跟进对外 · 项目进度直接映射
complaint 客诉对内 · 客诉跟进直接映射
custom 定制需求对内 · 其他(客户需求)直接映射
meeting 会议邀约对外 · 邀约及计划按 #2 定稿;这一次算线上还是线下,由该活动自己的 channel 决定
travel 出差计划对外 · 邀约及计划按 #2 定稿;出差本身不是一次拜访,故不并入「拜访计划」
other 其他对内 · 其他(客户需求)直接映射

映射写进迁移脚本,一次性跑完,前端保留旧值兼容显示(CATS/CAT_COLORS 要同步扩表)。

5打通规则(本次最容易做错的一段)

核心风险:活动可以反复编辑。如果每次保存都「生成一遍待办」,同一件事会变成 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 次 → 待办总条数不变; 在待办侧打勾后再回存活动 → 勾依然是勾。

5.1 「提交前确认」——本次最关键的一层(按 #3 / #5)

生成 ≠ 落库。绝对不能让系统「填了问题现象就直接塞一条待办进去」—— 销售得先在现场看一眼、改一改、删掉不需要的,确认后才落库。

活动录入页
 ├─ ① 填「下一步计划」/「问题现象」
 ├─ ② 点「生成待办预览」→ 页面下方出现【本次将生成的待办】清单(草稿态 · 未落库)
 │      每行可改:事项文本 / 负责人 / 截止 / 类型   可删   可勾「需要技术支持」
 │      「问题现象」自动派生 1 条,默认 客诉跟进(可改成其他类型)
 ├─ ③ 销售逐条修改并勾选确认
 └─ ④ 点「确认并提交」→ 活动落库 + 待办按确认后的清单落库(写 activity_id)

没勾确认就提交怎么办:允许提交,但页面明确提示「还有 N 条待办未确认」, 由销售自己决定补确认还是放弃。绝不静默生成,也绝不静默丢弃。

二次编辑同一个活动:按 source.activity_id 把该活动已生成的待办拉回来, 和当前清单一起进同一个「可改可确认」的清单,确认后做 diff upsert—— 只更新变动项,不动销售在待办侧已经打的勾和写的结论。

草稿期不落库,所以草稿期不需要指纹;落库后指纹才生效。 活动 ID 是提交那一刻才生成的,这正是「必须先确认再落库」的另一个理由—— 否则待办生成时还没有 ID 可挂,溯源就断了。

5.2 技术支持协同:只留入口(按 #4)

本次做:活动和待办上保留「需要技术支持协同」勾选项 + tech_support 字段并落库; 待办行显示 需技术协同 标记,销售能自己筛出来。

本次不做:不给技术同事自动派单、不做跨人镜像可见、不做状态双向同步、不建独立的「技术待办」Tab。

这样安排是对的:入口先立、数据先攒(谁在哪些事上需要技术、频率多高), 等口径成熟再决定走「镜像可见」还是「飞书通知」。跨人可见一旦开,owner 就得配服务端过滤, 那是另一个量级的改动,不该和本次打通混在一起做。

活动 · 下一步计划 → 待办 的流向
活动(线下/线上) 下一步计划 ① 事项 / 负责人 / 时间节点 下一步计划 ② ☑ 需要技术支持协同 下一步计划 ③ 事项 / 负责人 / 时间节点 待办(指纹 upsert) ① 对外 · 项目进度 owner=赵云浩 · due=10-15 ② 对外 · 测试需求 ☑ tech_support · 技术协同 ③ 对外 · 拜访计划 owner=宋学伟 · due=10-20 需技术协同(标记) 只落 tech_support 字段 不派单 · 不跨人可见 先攒数据,口径后定

「只留入口」不等于字段先不设计。本次仍然把 tech_support 落库、把标记显示在待办行上—— 三个月后要真接技术协同,手上已经有真实使用数据(谁在哪些事上要技术、频率多高),不用回头补历史。 自动派单 / 跨人可见 / 状态双向同步一律留到口径成熟再做。

6页面构想(三处改动)

① 待办 Tab(改造)
✅ 待办事项 对内+对外 全部 对外 ☐ 回传 XX 测试报告 测试结果·10-15 ☐ 拜访 上农课题组 拜访计划·10-20 对内 ☑ 客诉:批间差排查 客诉跟进·已完成 ☐ 出 FMD 系列测试方案 测试方案·10-18 按 对内/对外 → 类型 → 截止 分组;过期红标
② 活动录入(加两个字段)
✍️ 新建活动 拜访形式 * ● 线下拜访 ○ 线上跟进 关联订单 发货单(is_test=false) 测试单(is_test=true) 下一步计划 事项:回传 XX 测试报告 负责人:赵云浩 节点:2026-10-15 ☑ 需要技术支持协同 保存即生成/更新待办(指纹 upsert)
③ 拜访视图(新增)
📊 拜访视图 · 2026-10 线下拜访 38 家 52 次 线上跟进 61 家 74 次 线下客户(点击进活动) 上海某生物科技 3 次 › 华南某农业大学 2 次 › 武汉某明德 1 次 › 月份可切;权限内按人下钻(服务端过滤)

7落地分期

期做什么验收
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 字段。

7.1 P0 落点清单(改动面 · 可逐条对着核)

处文件改什么
后端脚本/销售工作台服务.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 上把归档区摆出来
重启launchdlaunchctl kickstart -k gui/$(id -u)/ai.hermes.workbench (改了 .py 必须重启,否则不生效且不报错)

P0 的字段白名单风险,其实在 P1 而不在 P0。我核过源码:待办侧是整体覆盖写、无白名单, 所以加字段是安全的;活动侧才有 _clean_block() 的逐字段白名单 (不在表里的 key 会被静默丢弃,还会被 _block_empty() 判成空块整块丢弃)—— 那是 P1 加 channel / order_refs 时必须先改的地方,顺序是 先改后端白名单 → 再改前端 → 最后重启。

8口径回执与下一步

你已拍板 6 项,全部收敛进 v0.2:

现在需要你点头的只有一件事:P0 可以开工吗?
P0 = 待办加 scope/type/owner + 旧 7 类映射迁移 + 双入口前端分组渲染。 会写生产数据(8 个用户的真实待办 + 云端 8 个文档),所以我想先拿你一句确认再动。
A 开工,按 §7.1 清单执行(改前备份 + 双写 + 回读校验条数)| B 先只做迁移脚本的干跑(dry-run),把 37 条映射结果逐条列给你看,确认后再写库| C 先做 P1(活动侧打通)

9已知的坑(做之前先知道)