Files
Neo-ZQYY/docs/_overview/04c-conflicts-P2-detail.md
Neo 509cf43284 chore(docs): Wave 0 调研产出 + P0/P1/P2 反馈调研
建立项目级标杆文档 docs/_overview/ 作为产品全景索引,
解决"PRD 零碎、文档膨胀、跨子系统调研无入口"的问题。

主要内容:
- 00-index 总索引 + 维护协议 + 与 CLAUDE.md 关系
- 01-product-overview 产品全景脑图(6 角色 / 6 子系统 / 数据流 /
  7 业务概念 / 8+1 AI 矩阵 / 22 术语)
- 02a-miniprogram-page-matrix 小程序 21 页业务指纹
- 02b-adminweb-page-matrix admin-web 19 路由业务指纹
- 03-test-spec 测试规范 (L1-L5 分层 + 走查模板 + 75-95 case 估算)
- 04-doc-conflicts 39 条冲突索引(P0×8 / P1×13 / P2×13 + 5 子项)
- 04a/b/c-conflicts-*-detail 业务故事卡(7 字段:关联/逻辑/影响/选项/判定)
- 05-orphan-pages-cleanup admin-web 6 孤儿页面处置(1 归档 + 4 保留)
- WAVES-MASTER-PLAN.md 全 Wave 主计划(0-5,共 22-32 工作日)
- WAVE-1-KICKOFF.md Wave 1 实施 kickoff
- GLOBAL-DECISION-DASHBOARD.md 全局决策仪表板

反馈调研产物:
- 04a-feedback/ P0 两轮反馈(8+8 项决策 + D-1/2/3 + F-1/2 子代理产出)
- 04b-feedback/ P1 两轮反馈(13+1+5 项 + E-1/2/3/4 + G-1/2 子代理产出)
- 04c-feedback/ P2 反馈(13 项 + 5 子项 + H-1/2/3 子代理产出)
- NEO-DECISIONS-LOG 累积决策记录

关键追加发现 8 处 D Bug(原蓝本 0):
- P0-3 看板沙箱接入(Wave 1 W1-T1)
- P0-5 致命 1 (4 处 fdw_etl 残留, 已修 commit 17f045a)
- P0-5 致命 2 (JWT aud 缺失, 已修 commit 17f045a)
- P0-6 clearAllTasks 守卫 (Wave 3)
- P0-8 DBViewer 黑名单漏 (已修 commit 17f045a)
- P1-3 task-detail 跳转传 task_id 而非 customer_id
- P2-7 board-finance 隐式 null
- 2 个独立 Bug (page_context.created_at + ClueCategory 字典)

参考: docs/_overview/00-index.md
2026-05-04 07:38:28 +08:00

370 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# P2 文档冲突业务化详情(13 条)
> 生成日期:2026-05-04 / 来源:Wave 0 三个子代理产出 / 目的:把 P2 索引行展开为业务故事卡,供 Neo 拍板
> 配套索引见 `docs/_overview/04-doc-conflicts.md` § 一 P2 表格;P2-13 含 5 个子项,合并到一节呈现
---
## P2-1. tabBar 是 app.json 写死 3 项还是动态过滤
**关联**: 全部小程序页面;`apps/miniprogram/miniprogram/app.json` + `custom-tab-bar/` + `utils/auth-guard.ts`
**业务背景**: 不同角色看到的底栏不一样 — 助教/教练 3 项(任务/看板/我的),员工/管理人员只 2 项(看板/我的)。`app.json` 习惯写死 3 项,但实际渲染走 `custom-tab-bar``visibleTabs` 动态过滤。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| `app.json` | 写死 3 项("任务/看板/我的") |
| `auth-guard.ts` + storyboard | 按角色动态过滤,首登选第一个可见 tab |
**影响**: 文档一致性(轻)。代码已按动态实现,旧文档若仍写"3 项 tabBar"会误导新开发者。
**选项**:
- **A**: 旧文档同步为"动态过滤" — 优:与代码一致 劣:需扫一遍引用旧表述的文档
- **B**: 在 `app.json` 配置层加注释"实际由 custom-tab-bar 接管" — 优:零成本 劣:不解决根本
**建议判定**: A,旧文档过期一律改文档
*反馈选项A*
---
## P2-2. 维客线索 tag 字段格式
**关联**: `task-detail` / `customer-detail` 维客线索区;`biz.member_retention_clue.tag`(应用 6/8 输出)
**业务背景**: 维客线索的 tag 作为客户分类标签展示(如"客户基础/消费习惯")。前端 mock 出现 `"客户\n基础"`(含换行符),类型定义却是单字符串,与 SPEC 中 6 大类枚举(客户基础/消费习惯/玩法偏好/促销接受/社交关系/重要反馈)又不一致。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| task-detail 内联 mock | `"客户\n基础"` 含换行 |
| `mock-data.ts` 类型定义 | 单字符串 |
| 契约 TASK-2 / SPEC | 6 大类枚举 key |
**影响**: 联调与展示(中)。如果 AI 真返回 `\n`,WXML 是否解析换行决定渲染样式;枚举/字符串不统一导致前端无法做颜色映射。
**选项**:
- **A**: 后端返回枚举 key + 中文 label 两字段,前端按 key 做色板映射 — 优:可扩展 劣:需改后端响应
- **B**: 后端返回中文长字符串(允许 `\n`),前端 white-space:pre-line 渲染 — 优:简单 劣:无法做样式区分
**建议判定**: A,与应用 6/8 的 JSON 输出对齐(分类 + Emoji + 摘要)
*反馈选项A*
---
## P2-3. 维客线索 source 字段格式
**关联**: 同 P2-2;`biz.member_retention_clue.source`
**业务背景**: 客户线索来源用于在卡片底部展示"By:小燕"或区分人工/AI。当前前端 mock 用展示文案 `By:小燕`,类型定义用枚举 `manual | ai_consumption | ai_note`,两者语义层不同。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| 前端 mock | `'By:小燕' / 'By:系统'`(显示文案) |
| 类型定义 / SPEC | `manual / ai_consumption / ai_note`(枚举) |
**影响**: 前后端契约(中)。混用会导致后端返回枚举时前端无法直接展示,或反之。
**选项**:
- **A**: 后端返回 `source`(枚举) + `recorded_by_name`(可空),前端拼"By:xxx"或"AI" — 优:语义清晰 劣:前端要改拼接逻辑
- **B**: 后端直接返回成品文案 — 优:前端零改造 劣:失去枚举信息,后续做筛选/统计困难
**建议判定**: A,与 P10 SPEC 的 `member_retention_clue` 字段定义一致
*反馈选项A*
---
## P2-4. 课程类型 class 前缀(tag- 前缀 vs 业务名)
**关联**: `performance-records` / `coach-service-records` 服务记录卡片
**业务背景**: 同样是课程类型徽章(基础课/包厢课/激励课),两个页面用了不同的 CSS class 命名约定,WXSS 选择器与 COURSE_TAG_MAP 也是双轨。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| performance-records | `tag-basic / tag-vip / tag-tip` |
| coach-service-records | `basic / room / incentive` |
**影响**: 体验(轻)。两个页面同色块来源不同样式表,改色板需要改两处。
**选项**:
- **A**: 统一为 `tag-basic / tag-vip / tag-tip`(显式语义前缀) — 优:与 design-system 一致 劣:改 coach-service-records 的 WXSS
- **B**: 统一为业务名 `basic / room / incentive`(更贴近后端 course_type) — 优:与后端一致 劣:与已有 design-system 命名冲突
**建议判定**: A,与 `design-system/DISPLAY-STANDARDS.md` 标注一致
*反馈这个你再调研下从数据库真实数据到后端处理再到前端我记得是两套课程体系或者有2级课程体系要确认好所有信息再做最终决定。*
---
## P2-5. ChatMessage timestamp vs 契约 created_at
**关联**: `pages/chat/chat`;`biz.ai_messages.created_at`
**业务背景**: AI 对话页消息列表展示发送时间。前端类型用 `timestamp`,后端契约表是 `created_at`。这是命名风格(camel/snake)与含义层级(时间戳/创建时间)的双重不一致。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| 前端 ChatMessage type | `timestamp` |
| 契约 / 数据库 | `created_at` |
**影响**: 联调(中)。后端按 CamelModel 转 camelCase 时会变 `createdAt`,前端访问 `msg.timestamp` 会拿到 undefined。
**选项**:
- **A**: 前端类型改为 `createdAt` — 优:与后端 CamelModel 自动序列化对齐 劣:WXML/TS 多处改名
- **B**: 后端 alias 输出 `timestamp`(对前端) — 优:前端零改造 劣:与全站 camelCase 规范背离
**建议判定**: A,沿用 CamelModel 默认行为
*反馈选项A*
---
## P2-6. ChatHistoryItem title 是后端返回还是前端截断
**关联**: `pages/chat-history`;`biz.ai_conversations`
**业务背景**: 对话记录列表每条要展示一个"标题"(如"关于客户王昕的咨询")。当前契约 CHAT-1 没有 title 字段,前端 mock 是写死的。可选方案:让 AI 摘要、取首条用户消息截断、用客户名拼接。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| 契约 CHAT-1 | 无 title 字段 |
| 前端展示 | 必须有标题 |
**影响**: 体验(中)。无标题时列表只有"最后消息摘要 + 时间",辨识度低。
**选项**:
- **A**: 后端在 `ai_conversations``title`(由应用 1 首轮自动摘要生成 ≤16 字) — 优:语义最佳 劣:增加一次 AI 调用成本
- **B**: 后端取首条 user 消息前 20 字截断 — 优:零额外成本 劣:可读性差
- **C**: 前端按客户名/时间拼接(如"王昕的对话 03-10") — 优:零后端改造 劣:无业务语义
**建议判定**: A,与应用 1 SSE 流式输出兼容,首轮结束时落库
*反馈选项A是不是要修改百炼APP1的系统Prompt?*
---
## P2-7. board-coach time=last_6m + sort=sv_desc 是否后端拒绝
**关联**: `pages/board-coach`;契约 BOARD-1
**业务背景**: 助教看板支持时间筛选(month/quarter/last_6m)与排序(perf/salary/sv/task)。前端 TIME_OPTIONS 注释"last_6m + sv_desc 不兼容",但未说明是后端会 400 拒绝、还是返回空数据、还是允许但语义无效。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| 前端注释 | 该组合不兼容 |
| 后端契约 | 未明示约束 |
**影响**: 体验(轻)。用户先选 sort=sv_desc 再切 time=last_6m 时不报错但结果可能为空。
**选项**:
- **A**: 后端校验该组合,返回 422 + 错误文案"近 6 月不支持储值排序" — 优:体验明确 劣:前端要捕获 422
- **B**: 前端 disable 该组合(切 last_6m 时把 sv_desc 选项变灰) — 优:UI 提前拦截 劣:前端规则要硬编码
- **C**: 后端允许返回空,前端显示空态 — 优:零约束 劣:用户不知道为何为空
**建议判定**: B,前端拦截最不易引发歧义
*反馈选项B,但要双向禁止/变灰,即切 last_6m 时把 sv_desc 选项变灰,切 sv_desc 时把 last_6m 选项变灰。再看下还有没有类型的切换限制的问题,一同修复?*
---
## P2-8. dev-tools 角色列表缺 site_admin / tenant_admin
**关联**: `pages/dev-tools`;`auth-guard.ts` ROLE_LIST
**业务背景**: dev-tools 是 manager 调试用的"切角色"页。当前 ROLE_LIST 只列了 `[coach, staff, head_coach, manager]` 4 个,小程序的实际角色枚举还有 `site_admin / tenant_admin`(虽然这两个角色主战场在 tenant-admin 后台,小程序只查阅看板)。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| dev-tools ROLE_LIST | 4 角色 |
| auth-guard 全角色枚举 | 6 角色(含 site_admin / tenant_admin) |
**影响**: 调试覆盖(轻)。开发期无法在小程序模拟门店管理员视角。
**选项**:
- **A**: ROLE_LIST 补齐 6 个 — 优:全角色调试 劣:dev-switch-role 后端要支持这两个 aud 在小程序 token 内
- **B**: 文档明确 dev-tools 只调试 4 个 C 端角色,site_admin/tenant_admin 在 tenant-admin Web 调试 — 优:边界清晰 劣:小程序看板的角色显示无法覆盖测试
**建议判定**: A,与小程序 tabBar 角色映射表(全 6 角色)对齐
*反馈选项B,因为site_admin/tenant_admin在小程序端没有相应的配置信息可以读取。两套角色相互平行不能混用。*
---
## P2-9. no-permission 管理员姓名"厉超"硬编码
**关联**: `pages/no-permission`
**业务背景**: 申请被拒/账号禁用页底部显示"如有疑问请联系管理员 厉超"。当前是 WXML 硬编码,`_hardcode-summary.md` 第 6 项决策"保持硬编码(只有一个管理员)"。但多门店上线后每个门店可能有不同的 site_admin。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| `_hardcode-summary.md` | 决策硬编码"厉超" |
| 多门店现实 | 不同店应有不同管理员 |
**影响**: 体验(中)。新门店上线后用户被拒看到的还是"厉超",找不到本店管理员。
**选项**:
- **A**: 后端 `/me` 返回时附带 `siteAdminContact:{ name, phone? }`,无值回落"管理员" — 优:多门店可用 劣:后端要 JOIN auth.tenant_admins
- **B**: 改为不出现具体姓名("请联系门店管理员") — 优:零改造 劣:用户不知道找谁
- **C**: 在 `apply` 表单时记录 `site_id`,被拒时按该 site 查管理员 — 优:精准 劣:实现成本与 A 接近
**建议判定**: A,与 P10 tenant-admin 账号体系联动
*反馈选项A,但不能直接显示管理员的用户名应该在tenant-admin中加入一个管理入口专门编辑no-permission的显示信息这个功能你帮我设计下并进行实现且按照我们的约定方式进行测试。*
---
## P2-10. customer-records 字段 consumption60D 大写 D Pydantic alias 规范
**关联**: `pages/customer-records`;后端 `/customer/{id}/profile`
**业务背景**: 该字段后端命名 `consumption_60d`(snake),前端类型却是 `consumption60D`(大写 D),代码注释里标"踩坑"。这是 Pydantic alias 转 camelCase 时数字+字母的处理差异(`60d``60D` vs `60d` 不同库不同行为)。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| 后端 snake | `consumption_60d` |
| Pydantic CamelModel 输出 | `consumption60D`(踩坑) |
| 业界惯例 | `consumption60d` 全小写 |
**影响**: 全站类型一致性(中)。同类字段(如 `visits_30d`)如果在另一个接口被序列化为 `visits30D` 会产生连锁不一致。
**选项**:
- **A**: 后端字段名改 `consumption60`(去掉单位 d),保留单位在文档说明 — 优:绕开 alias 边界 劣:语义不清
- **B**: 给该类字段显式写 alias `Field(..., alias='consumption60d')` — 优:可控 劣:每个字段都要手写
- **C**: 接受 `60D` 大写,在全站规范里写明"数字+字母时字母大写" — 优:规则清晰 劣:不直观
**建议判定**: B,与 CamelModel 默认行为脱钩,每个 60d/30d/90d 字段显式 alias
*反馈选项B。*
---
## P2-11. AI 需求 2 表头标 6 个实际 8 个(笔误)
**关联**: `docs/prd/AI需求2.md`(产品脑图 §六 引用)
**业务背景**: AI 需求文档第 26 行表头写"6 个 AI 应用的详细需求",实际表格列出应用 1-8 共 8 个。SPEC 总览 P5 也是 8 个。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| AI需求2.md 表头 | 6 个 |
| AI需求2.md 表格内容 | 8 个(应用 1-8) |
| SPEC 总览 P5 | 8 个 |
**影响**: 文档一致性(轻)。仅文字笔误,不影响实现。
**选项**:
- **A**: 改表头为"8 个" — 优:1 行修复 劣:无
- **B**: 保留并注释"含 7/8 整合应用" — 优:解释来源 劣:啰嗦
**建议判定**: A,直接改表头
*反馈选项A。*
---
## P2-12. admin-web /admin/tenant-admins 与 tenant-admin 职责边界
**关联**: `apps/admin-web` `/tenant-admins` 路由 vs `apps/tenant-admin` 整个后台
**业务背景**: 两个 Web 应用看似都涉及"租户管理":
- admin-web `/admin/tenant-admins` 操作 `auth.tenant_admins` 表(账号 CRUD)
- apps/tenant-admin 整个应用是这些账号登录后用的工作台
PRD NS4/NS4.1 主要描述 tenant-admin,未明文边界。
**冲突**:
| 来源 | 说法 |
| --- | --- |
| admin-web | 创建/编辑/重置密码租户管理员账号 + 简写 ID 管理 |
| tenant-admin | 用户审核 / Excel 上传 / 维客线索管理 |
**影响**: 文档边界(中)。两个团队/角色读不同 PRD 容易认为对方负责创建账号。
**选项**:
- **A**: 文档加边界声明 — admin-web 操作"账号本身"(创建/启停/重置),tenant-admin 操作"门店运营"(用户/Excel/线索) — 优:零代码改 劣:仅文档
- **B**: tenant-admin 内部加自助"重置密码"入口 — 优:用户体验好 劣:超出 NS4 范围
- **C**: 把 `/admin/tenant-admins` 从 admin-web 移到独立超管页 — 优:权责清 劣:大重构
**建议判定**: A,在 NS4 加一节"职责边界",admin-web 侧也加 README 说明
*反馈:同意你的建议。*
---
## P2-13. AIPrewarm + 事件枚举 + 侧边栏 ?tab + Login 路径 + ai-group 展开(5 子项)
**关联**: `apps/admin-web` 多个页面;NS3 PRD
**业务背景**: 这 5 个小冲突共同指向"NS3 PRD 与 admin-web 现状对照不足"。统一处理。
### P2-13.1 AIPrewarm 72 组合二分逻辑未在 NS3 写明
**冲突**: 注释"72 组合"实际是 `area=all 走 app2_finance 8 组合 + 8 区域走 app2a_finance_area 64 组合`,NS3 / API-REFERENCE 是否同步该二分?
**选项**:
- **A**: NS3 加一节"72 组合二分逻辑" — 优:文档清晰 劣:维护新章节
- **B**: 仅在 AIPrewarm.tsx 顶部注释扩写 — 优:就近 劣:非前端开发者读不到
**建议**: A
*反馈A。*
### P2-13.2 event_type 枚举完整表缺失
**冲突**: AIOperations.EVENT_TYPE_OPTIONS 列 4 项(consumption / dws_completed / note_created / task_assigned),AIPrewarm 用 dws_completed,AITriggerJobs 不限制。NS3 是否有完整枚举表?
**选项**:
- **A**: NS3 加 `event_type` 枚举完整表(含触发条件 + 后置应用链) — 优:全局一致 劣:工作量
- **B**: 在 `biz/cfg_event_types.py` 维护源,文档自动生成 — 优:不漂移 劣:需要工具
**建议**: A 起步,长期 B
*反馈B。*
### P2-13.3 侧边栏 ?tab= 双入口 UX
**冲突**: "AI 管理 → 触发器设置"与"系统设置 → 触发器配置"都跳到 `/triggers?tab=ai|biz`,两个菜单 entry 共用一个页面。
**选项**:
- **A**: 接受现状(两入口不同语境) — 优:零改造 劣:用户疑惑"为何两处都有"
- **B**: 合并为一个入口,在 `/triggers` 顶部加面包屑显示来源 — 优:UX 清晰 劣:菜单收窄
**建议**: A,在 PRD 解释保留双入口的理由(操作语境不同)
*反馈A。*
### P2-13.4 Login 路径前缀
**冲突**: `useAuthStore.login``/api/auth/login`,401 拦截器引用 `/auth/refresh`。是否含 `/admin` 前缀?本调研未读 `apps/backend/app/routers/auth.py`
**选项**:
- **A**: 校对 auth.py 实际路径并同步 NS1 文档 — 优:无猜测 劣:需要读代码
- **B**: 在 admin-web README 写明前缀来源 — 优:就近 劣:其他端读不到
**建议**: A,补 NS1 后端 API 章节
*反馈:同意你的建议。*
### P2-13.5 ai-group 默认展开
**冲突**: 从 ai 子菜单跳到 `/triggers?tab=ai` 时,侧边栏 `ai-group` 不会自动展开(`getDefaultOpenKeys` 注释"无法判断 tab")。
**选项**:
- **A**: `getSelectedKeys` 改造,根据 `pathname + search` 同时展开多个 group — 优:UX 一致 劣:需要测多种入口组合
- **B**: 接受现状 — 优:零改造 劣:用户跳转后菜单状态丢失
**建议**: A
*反馈A。*
**P2-13 整体建议判定**: 全部走 A,集中在 NS3 同步 + admin-web 侧边栏小重构
---
> 完成日期:2026-05-04 / 用途:为 Neo 提供 P2 13 条的紧凑业务化判定材料,确认后批量进入 Wave 5 文档收尾或对应模块小工单