feat: batch update - gift card breakdown spec, backend APIs, miniprogram pages, ETL finance recharge, docs & migrations

This commit is contained in:
Neo
2026-03-20 01:43:48 +08:00
parent 075caf067f
commit 79f9a0e1da
437 changed files with 118603 additions and 976 deletions

View File

@@ -0,0 +1,997 @@
# NS1小程序后端 API 补全 — xcx-backend-api
> 优先级:最高(上线关键路径)
> 预估工作量:大
> 前置条件P1-P5 已完成数据库、认证、任务、备注、AI 骨架均就绪)
> 契约基准:`docs/miniprogram-dev/API-contract.md`
---
## 一、背景与目标
小程序前端 13 个业务页面已全部完成P5.2 交付),`services/api.ts` 已统一封装 mock → real 切换机制(`USE_REAL_API` 开关)。但后端 23 个接口中仅 16 个已实现,剩余 12 个业务接口缺失,是前后端联调的最大阻塞。
本 SPEC 目标:补全全部缺失接口 + FDW 端到端验证 + 前后端联调修复,使小程序可以连接真实后端运行。
### 已实现的接口16 个)
| 模块 | 接口 | 路由文件 |
|------|------|---------|
| Auth5 | AUTH-1~5登录/dev-login/me/refresh/apply | `xcx_auth.py` |
| Tasks5 | GET tasks + pin/unpin/abandon/cancel-abandon | `xcx_tasks.py` |
| Notes3 | POST/GET/DELETE notes | `xcx_notes.py` |
| AI Chat3 | SSE stream + conversations + messages | `xcx_ai_chat.py` |
### 缺失的接口12 个)
| 接口 ID | 端点 | 页面依赖 | 数据源 |
|---------|------|---------|--------|
| TASK-1 扩展 | GET /tasks增加 performance 附带字段) | task-list | `dws_assistant_salary_calc` via FDW |
| TASK-2 | GET /tasks/{id}(完整详情) | task-detail | biz.coach_tasks + FDW 多表 + ai_cache |
| PERF-1 | GET /performance | performance | `dws_assistant_salary_calc` + `dws_assistant_daily_detail` via FDW |
| PERF-2 | GET /performance/records | performance-records | `dwd_assistant_service_log` via FDW |
| CUST-1 | GET /customers/{id} | customer-detail | `dim_member` + `dws_member_consumption_summary` via FDW |
| CUST-2 | GET /customers/{id}/records | customer-service-records | `dwd_assistant_service_log` + `dwd_table_fee_log` via FDW |
| COACH-1 | GET /coaches/{id} | coach-detail | `dim_assistant` + `dws_member_assistant_relation_index` via FDW |
| BOARD-1 | GET /board/coaches | board-coach | `dws_assistant_salary_calc` + `dws_assistant_monthly_summary` via FDW |
| BOARD-2 | GET /board/customers | board-customer | 多个 DWS 指数表 via FDW |
| BOARD-3 | GET /board/finance | board-finance | 6 个 `dws_finance_*` 表 via FDW |
| CONFIG-1 | GET /config/skill-types | board-coach | 静态配置或 cfg 表 |
| CHAT 路径对齐 | 对齐 CHAT-1/2/3 路径与契约 | chat/chat-history | 现有 xcx_ai_chat.py 路径调整 |
---
## 二、技术架构
### 2.1 后端代码模式(沿用现有模式)
```
apps/backend/app/
├── routers/ # FastAPI 路由xcx_*.py
│ ├── xcx_auth.py ✅ 已有
│ ├── xcx_tasks.py ✅ 已有(需扩展 TASK-1/TASK-2
│ ├── xcx_notes.py ✅ 已有
│ ├── xcx_ai_chat.py ✅ 已有(需路径对齐)
│ ├── xcx_performance.py 🆕 新建
│ ├── xcx_customers.py 🆕 新建
│ ├── xcx_coaches.py 🆕 新建
│ ├── xcx_board.py 🆕 新建
│ └── xcx_config.py 🆕 新建
├── services/ # 业务逻辑层
│ ├── task_manager.py ✅ 已有(需扩展)
│ ├── note_service.py ✅ 已有
│ ├── perf_service.py 🆕 新建
│ ├── customer_service.py 🆕 新建
│ ├── coach_service.py 🆕 新建
│ └── board_service.py 🆕 新建
├── schemas/ # Pydantic 响应模型
└── auth/ # 认证依赖注入 ✅ 已有
```
### 2.2 数据库连接模式
- 业务库zqyy_app`get_connection()` → psycopg2 直连,读写
- ETL 库(通过 FDW`fdw_etl.*` schema 查询,需先 `SET LOCAL app.current_site_id`
- ETL 库(直连只读):`get_etl_readonly_connection(site_id)` → 用于复杂聚合查询
### 2.3 权限控制
- 所有接口需 JWT`require_approved()`
- 看板接口需额外权限检查(`require_permission("view_board_finance")` 等)
- 权限种子数据已就绪auth.role_permissions14 条映射)
---
## 三、接口详细设计
### 3.1 TASK-1 扩展:任务列表增加绩效概览
现有 `GET /api/xcx/tasks` 返回 `list[TaskListItem]`,需扩展为:
- 增加 `performance` 字段(当月工时/收入/客户数/月份标签)
- 增加分页支持(`page`/`pageSize` 参数)
- 增加 `status` 筛选参数
数据源:
- 任务列表:`biz.coach_tasks` + `fdw_etl.v_dim_member`(客户信息)+ `fdw_etl.v_dws_member_winback_index`/`v_dws_member_newconv_index`(指数)
- 绩效概览:`fdw_etl.v_dws_assistant_salary_calc`(当月薪资计算快照)
### 3.2 TASK-2任务详情完整版
现有 `xcx_tasks.py``GET /tasks/{id}` 端点,需新增。
数据源(多表聚合):
- 基础信息:`biz.coach_tasks`
- 客户信息:`fdw_etl.v_dim_member` + `fdw_etl.v_dws_member_consumption_summary`
- 亲密度:`fdw_etl.v_dws_member_assistant_intimacy`
- 近期服务记录:`fdw_etl.v_dwd_assistant_service_log`(最近 N 条)
- 备注:`biz.notes`(最近 N 条,含 ai_score
- 维客线索:`public.member_retention_clue`
- AI 缓存:`biz.ai_cache`app4/app5/app6/app7 缓存)
- 喜好标签:`fdw_etl.v_dwd_table_fee_log`(按房间类型统计)
> ⚠️ 这是最复杂的接口,涉及 8+ 张表的聚合。建议拆分为多个 service 函数组合。
### 3.3 PERF-1/PERF-2绩效模块
PERF-1绩效概览数据源
- `fdw_etl.v_dws_assistant_salary_calc`(收入/档位/工资)
- `fdw_etl.v_dwd_assistant_service_log`(当月服务记录明细)
- `fdw_etl.v_dws_assistant_customer_stats`(新客/常客统计)
PERF-2绩效明细数据源
- `fdw_etl.v_dwd_assistant_service_log`(按月筛选,按日分组)
- `fdw_etl.v_dws_assistant_daily_detail`(定档折算惩罚字段)
### 3.4 CUST-1/CUST-2客户模块
CUST-1客户详情数据源
- `fdw_etl.v_dim_member` + `fdw_etl.v_dim_member_card_account`(基本信息 + 会员卡)
- `fdw_etl.v_dws_member_consumption_summary`(消费汇总)
- `fdw_etl.v_dws_member_assistant_relation_index`(关系指数)
- `public.member_retention_clue`(维客线索)
- 消费记录:`fdw_etl.v_dwd_settlement_head` + `fdw_etl.v_dwd_table_fee_log` + `fdw_etl.v_dwd_store_goods_sale` + `fdw_etl.v_dwd_recharge_order`
> ⚠️ 会员字段断档规则DQ-6/DQ-7`member_phone`/`member_name`/`member_card_type_name` 不可靠,必须通过 `member_id` JOIN `dim_member`/`dim_member_card_account`
CUST-2客户服务记录数据源
- `fdw_etl.v_dwd_assistant_service_log`(按客户+助教筛选)
- `fdw_etl.v_dwd_table_fee_log`(台桌信息)
### 3.5 COACH-1助教详情
数据源:
- `fdw_etl.v_dim_assistant`(基本信息)
- `fdw_etl.v_dws_member_assistant_relation_index`客户数统计RS>2
- `biz.coach_tasks`(任务统计:可见/隐藏/已放弃)
- `biz.notes`(备注列表)
### 3.6 BOARD-1/2/3看板模块
BOARD-1助教看板数据源
- `fdw_etl.v_dws_assistant_salary_calc`(定档业绩/工资)
- `fdw_etl.v_dws_assistant_monthly_summary`(月度汇总)
- `biz.coach_tasks`(任务完成数统计)
BOARD-2客户看板数据源8 个维度排序):
- 最应召回:`fdw_etl.v_dws_member_winback_index`WBI 降序)
- 最大消费潜力:`fdw_etl.v_dws_member_spending_power_index`SPI 降序)
- 最高余额:`fdw_etl.v_dws_member_consumption_summary`
- 最近充值:`fdw_etl.v_dwd_recharge_order`
- 最高消费 60 天:`fdw_etl.v_dws_member_consumption_summary`(基于 `items_sum`
- 最频繁 60 天:`fdw_etl.v_dws_member_consumption_summary`
- 最近到店:`fdw_etl.v_dws_member_visit_detail`
- 最专一:`fdw_etl.v_dws_member_assistant_relation_index`RS 最大值)
BOARD-3财务看板数据源
- `fdw_etl.v_dws_finance_daily_summary`
- `fdw_etl.v_dws_finance_income_structure`
- `fdw_etl.v_dws_finance_recharge_summary`
- `fdw_etl.v_dws_finance_discount_detail`
- `fdw_etl.v_dws_finance_expense_summary`
- `fdw_etl.v_dws_platform_settlement`
- `biz.ai_cache`app2_finance 缓存AI 洞察)
### 3.7 CONFIG-1技能类型列表
静态配置接口,返回助教技能类型列表。数据来源待定(硬编码 or cfg 表)。
### 3.8 CHAT 路径对齐
现有 `xcx_ai_chat.py` 路径:
- `POST /api/ai/chat/stream`
- `GET /api/ai/conversations`
- `GET /api/ai/conversations/{id}/messages`
契约定义路径:
- `GET /api/xcx/chat/history`
- `GET /api/xcx/chat/{chatId}/messages`
- `POST /api/xcx/chat/{chatId}/messages`
需要对齐路径,或在前端 service 层适配。
---
## 四、数据库审查与新增表建议
### 4.1 现有表结构满足度
| 接口 | 现有表是否满足 | 缺口 |
|------|--------------|------|
| TASK-1/2 | ✅ 基本满足 | 需 FDW 多表 JOIN |
| PERF-1/2 | ✅ 满足 | 无 |
| CUST-1/2 | ✅ 满足 | 消费记录需 3 种类型 UNION |
| COACH-1 | ✅ 满足 | 无 |
| BOARD-1 | ⚠️ 部分 | 任务完成数需实时统计 |
| BOARD-2 | ⚠️ 部分 | 8 维度排序需多表查询,性能风险 |
| BOARD-3 | ✅ 满足 | 环比需后端计算 |
### 4.2 建议新增的中间表/物化视图
#### 表 1`biz.board_coach_snapshot`(助教看板快照)
用途:预聚合助教看板数据,避免每次请求实时 JOIN 多张 FDW 表。
```sql
CREATE TABLE biz.board_coach_snapshot (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
assistant_id BIGINT NOT NULL,
snapshot_date DATE NOT NULL,
-- 定档业绩
performance_tier VARCHAR(20),
tier_revenue NUMERIC(12,2),
-- 工资
salary_total NUMERIC(12,2),
-- 客户数
customer_count INTEGER,
-- 高客源储值额
high_value_balance NUMERIC(12,2),
-- 任务完成数
task_completed_count INTEGER,
-- 服务时长
total_hours NUMERIC(8,2),
total_income NUMERIC(12,2),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (site_id, assistant_id, snapshot_date)
);
```
刷新策略ETL 数据更新后触发(`trigger_jobs` 事件驱动),或每日凌晨批量刷新。
#### 表 2`biz.board_customer_snapshot`(客户看板快照)
用途:预聚合客户看板 8 个维度的排序数据,避免实时查询 8 张 FDW 表。
```sql
CREATE TABLE biz.board_customer_snapshot (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
snapshot_date DATE NOT NULL,
-- 8 个维度排序值
wbi_score NUMERIC(8,4), -- 最应召回
spi_score NUMERIC(8,4), -- 最大消费潜力
balance_amount NUMERIC(12,2), -- 最高余额
last_recharge_date DATE, -- 最近充值
spend_60d NUMERIC(12,2), -- 最高消费 60 天items_sum 口径)
visit_count_60d INTEGER, -- 最频繁 60 天
last_visit_date DATE, -- 最近到店
max_rs_score NUMERIC(8,4), -- 最专一
-- 展示字段
member_name VARCHAR(100),
member_avatar VARCHAR(500),
member_tags TEXT[], -- 喜好标签
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (site_id, member_id, snapshot_date)
);
```
#### 表 3`biz.board_finance_snapshot`(财务看板快照)
用途:预聚合财务看板数据 + 环比计算结果。
```sql
CREATE TABLE biz.board_finance_snapshot (
id BIGSERIAL PRIMARY KEY,
site_id BIGINT NOT NULL,
biz_date DATE NOT NULL,
-- 核心指标JSON 存储,灵活扩展)
metrics JSONB NOT NULL,
-- 环比数据
prev_day_metrics JSONB,
prev_week_metrics JSONB,
prev_month_metrics JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (site_id, biz_date)
);
```
#### 表 4`biz.task_detail_cache`(任务详情缓存)
用途TASK-2 接口涉及 8+ 张表聚合,首次查询后缓存结果,后续读缓存。
```sql
CREATE TABLE biz.task_detail_cache (
id BIGSERIAL PRIMARY KEY,
task_id BIGINT NOT NULL REFERENCES biz.coach_tasks(id),
site_id BIGINT NOT NULL,
detail_json JSONB NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (task_id)
);
```
失效策略:备注新增/AI 缓存更新/任务状态变更时DELETE 对应缓存行。
---
## 五、FDW 端到端验证
### 5.1 验证范围
需验证 `fdw_etl` schema 下所有外部表可正常 SELECT重点关注
| 外部表 | 用途 | 验证 SQL |
|--------|------|---------|
| `v_dim_member` | 客户信息 | `SELECT count(*) FROM fdw_etl.v_dim_member` |
| `v_dim_assistant` | 助教信息 | `SELECT count(*) FROM fdw_etl.v_dim_assistant` |
| `v_dws_assistant_salary_calc` | 薪资计算 | `SELECT * FROM fdw_etl.v_dws_assistant_salary_calc LIMIT 5` |
| `v_dws_finance_daily_summary` | 财务日报 | `SELECT * FROM fdw_etl.v_dws_finance_daily_summary LIMIT 5` |
| `v_dws_member_winback_index` | WBI 指数 | `SELECT * FROM fdw_etl.v_dws_member_winback_index LIMIT 5` |
| `v_dws_member_spending_power_index` | SPI 指数 | `SELECT * FROM fdw_etl.v_dws_member_spending_power_index LIMIT 5` |
### 5.2 RLS 验证
```sql
-- 设置门店隔离
SET LOCAL app.current_site_id = '目标 site_id';
-- 验证只返回该门店数据
SELECT site_id, count(*) FROM fdw_etl.v_dim_member GROUP BY site_id;
-- 预期:只有一个 site_id
```
---
## 六、前后端联调
### 6.1 联调步骤
1. 后端接口开发完成后,逐个验证响应格式与 `API-contract.md` 一致
2. 小程序 `services/api.ts``USE_REAL_API = true`
3. 逐页面联调login → task-list → task-detail → performance → board → customer-detail → chat
4. 修复 snake_case → camelCase 映射差异
5. 处理 inline-mock 页面的数据替换7 个页面有 TODO 标记)
### 6.2 已知需要对齐的差异
- TASK-1 响应格式:现有返回 `list[TaskListItem]`,契约要求 `{ items, total, page, pageSize, performance }`
- CHAT 路径:现有 `/api/ai/*`,契约要求 `/api/xcx/chat/*`
- 响应包装:现有直接返回数据,契约要求 `{ code: 0, data: ... }` 包装
---
## 七、参考文档
| 文档 | 路径 | 用途 |
|------|------|------|
| API 契约 | `docs/miniprogram-dev/API-contract.md` | 接口定义基准 |
| 前端 Service 层 | `apps/miniprogram/miniprogram/services/api.ts` | 前端期望的响应格式 |
| BD 手册-认证表 | `docs/database/BD_Manual_auth_tables.md` | auth schema 表结构 |
| BD 手册-业务表 | `docs/database/BD_Manual_biz_tables.md` | biz schema 表结构 |
| DWD-DOC 标杆 | `docs/reports/DWD-DOC/` | 金额口径、字段语义权威参考 |
| 数据依赖矩阵 | `docs/prd/specs/00-数据依赖矩阵.md` | 页面→数据表映射 |
| 现有 task_manager | `apps/backend/app/services/task_manager.py` | 任务服务层模式参考 |
| 现有 note_service | `apps/backend/app/services/note_service.py` | 备注服务层模式参考 |
| 权限中间件 | `apps/backend/app/middleware/permission.py` | 权限检查模式参考 |
| 数据库连接 | `apps/backend/app/database.py` | 连接方式参考 |
| FDW 配置 | `db/fdw/setup_fdw_test.sql` | FDW 外部表映射 |
---
## 八、预审查清单SPEC 启动前确认)
以下问题需要在启动 SPEC 前与用户确认,确保需求无歧义:
### 8.1 接口设计
1. **响应包装格式**:现有后端直接返回数据对象,契约要求 `{ code: 0, data: ... }` 包装。是全局统一改造(中间件),还是仅新接口使用包装格式?
2. **CHAT 路径对齐**:是修改后端路径从 `/api/ai/*` 改为 `/api/xcx/chat/*`,还是在前端 service 层适配现有路径?
3. **分页策略**TASK-1 契约要求分页,但现有实现返回全量列表。是否所有列表接口都需要分页?看板接口(前 100 名)是否需要分页?
4. **CONFIG-1 技能类型**:数据来源是硬编码还是从 `cfg_*` 表读取?如果硬编码,具体的技能类型列表是什么?
### 8.2 数据库
5. **快照表刷新策略**3 个看板快照表coach/customer/finance的刷新频率是 ETL 数据更新后触发,还是每日定时刷新,还是请求时按需刷新?
6. **任务详情缓存**`task_detail_cache` 的过期时间设多久缓存失效触发条件是否完整备注新增、AI 缓存更新、任务状态变更)?
7. **FDW 外部表命名**:现有代码中使用 `fdw_etl.v_dim_member`(带 `v_` 前缀),需确认所有 FDW 外部表的实际命名是否一致。
### 8.3 业务逻辑
8. **TASK-2 服务记录范围**:近期服务记录取最近多少条?最近 30 天还是最近 10 条?
9. **BOARD-2 客户看板**8 个维度排序是否都取前 100 名?是否需要支持类型筛选(如按会员等级筛选)?
10. **BOARD-3 财务看板**:环比计算的基准是什么?日环比、周环比、月环比都需要吗?
11. **CUST-1 消费记录**3 种消费类型(台桌/商城/充值)是混合排序还是分类展示?分页策略?
12. **COACH-1 任务统计**:可见/隐藏/已放弃的分类逻辑是什么?"隐藏"指什么状态?
### 8.4 性能与安全
13. **FDW 查询性能**:看板接口涉及多张 FDW 表的聚合查询,是否需要设置查询超时?超时后返回什么?
14. **维客线索脱敏**TASK-2 返回维客线索时,后端脱敏规则是否与 P6 spec 中定义的一致(移除 recorded_by_assistant_id/name
15. **金额口径确认**:所有涉及消费金额的接口,确认使用 `items_sum` 口径(= table_charge_money + goods_money + assistant_pd_money + assistant_cx_money + electricity_money不使用 `consume_money`
---
## 八½、前置审查发现2026-03-18 Kiro 审查)
> 审查方法:对照现有代码(`xcx_auth.py`、`xcx_tasks.py`、`xcx_ai_chat.py`、`task_manager.py`、`permission.py`、`database.py`、API 契约(`API-contract.md`)、前端 Service 层(`api.ts`、FDW 配置(`setup_fdw_test.sql`、BD 手册、数据依赖矩阵、DWD-DOC 标杆文档,逐项交叉验证。
### R1接口清单准确性
| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R1-1 | 接口总数算术错误 | 🟡 低 | spec 称"23 个接口中仅 16 个已实现,剩余 12 个缺失"16+12=28≠23。需修正总数或重新盘点 |
| R1-2 | Auth 已实现接口数低估 | 🟡 低 | spec 列 Auth 5 个,实际 `xcx_auth.py` 生产端点 7 个login/apply/me/me-sites/switch-site/refresh/dev-login含 dev 端点共 11 个 |
| R1-3 | TASK-4 路径不一致 | 🔴 高 | API 契约定义 `POST /tasks/{taskId}/restore`,前端 `api.ts` 调用 `/tasks/${taskId}/restore`,但后端实现的是 `/tasks/{taskId}/cancel-abandon`。联调必然失败 |
**R1-3 ✅ 已确认**:统一为 `/restore`(改后端路由路径,契约和前端不动)。后端 `xcx_tasks.py``/cancel-abandon` 端点已实现,需改路径为 `/restore`
### R2响应格式全局架构决策阻塞所有新接口开发
| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R2-1 | 成功响应无包装 | 🔴 高 | 契约要求 `{ code: 0, data: ... }`,现有后端直接返回 Pydantic model。全局改造影响所有已实现接口 |
| R2-2 | 错误响应格式不一致 | 🔴 高 | 契约要求 `{ code: number, message: string }`,现有 FastAPI 默认 `{ detail: "..." }`。需自定义异常处理器 |
| R2-3 | snake_case vs camelCase | 🔴 高 | 后端返回 snake_case前端期望 camelCase。api.ts 有 TODO 标记但未实现转换。需决定转换层位置 |
**R2 ✅ 已确认:采用方案 A**(后端全局包装 `{ code: 0, data: ... }` + Pydantic `alias_generator=to_camel`
实施路径:
1. 新增全局响应包装中间件(或 `response_model` 基类),成功响应统一 `{ code: 0, data: ... }`
2. 新增全局异常处理器,`HTTPException``{ code: status_code, message: detail }`
3. 所有 Pydantic schema 加 `model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)`
4. 前端 `request()` 工具函数加 `.data` 解包(一处改动)
5. 已有接口Auth/Tasks/Notes/AI Chat逐个验证兼容性
### R3CHAT 模块路径与功能差异
| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R3-1 | 路径前缀不同 | 🟠 中 | 现有 `/api/ai/*`,契约 `/api/xcx/chat/*` |
| R3-2 | 功能模式不同 | 🔴 高 | 现有是 SSE 流式(`POST /chat/stream`),契约是同步 JSON`POST /chat/{chatId}/messages` 返回 `{ userMessage, aiReply }`)。两者不可互替 |
| R3-3 | 历史列表字段不同 | 🟠 中 | 现有返回 `{ id, app_id, source_page, first_message_preview }`,契约要求 `{ id, customer_name, last_message, last_time, unread_count }` |
**R3 ✅ 已确认:采用选项 A**(保留 SSE 流式 + 新增同步端点)
实施方案:
- 保留 `POST /api/xcx/chat/stream`SSE 流式对话,路径从 `/api/ai/chat/stream` 迁移)
- 新增 `GET /api/xcx/chat/history`(历史对话列表,替代 `/api/ai/conversations`
- 新增 `GET /api/xcx/chat/{chatId}/messages`(消息查看,替代 `/api/ai/conversations/{id}/messages`
- 契约补充 SSE 端点定义
- `ai_conversations` 表无 `unread_count` 字段CHAT-1 响应中此字段暂返回 0或后续按需新增
### R4数据库新增表设计
| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R4-1 | 快照表刷新时机未定义 | 🟠 中 | 3 个 board_*_snapshot 表的刷新策略ETL 后触发 / 每日定时 / 按需)直接影响数据新鲜度和实现复杂度 |
| R4-2 | task_detail_cache 命中率存疑 | 🟡 低 | TASK-2 涉及 8+ 张表,备注/AI 缓存/服务记录变更频繁,缓存频繁失效。建议评估:优化 SQL + 并行查询是否足够,不一定需要缓存表 |
| R4-3 | board_finance_snapshot.metrics 缺 schema | 🟡 低 | JSONB 灵活但无约束,建议在 spec 中定义 metrics 的 key 清单(如 `total_revenue``total_expense``net_income` 等) |
| R4-4 | board_customer_snapshot.spend_60d 窗口定义 | 🟡 低 | "60 天"是 `snapshot_date - 60``snapshot_date`,还是自然月?需明确 |
| R4-5 | 快照表是否真的需要 | 🟠 中 | 看板数据量不大(单店助教 < 50 人、活跃客户 < 5000 人FDW 直查 + 合理索引可能已够用。快照表增加了维护复杂度(刷新逻辑、数据一致性)。建议先不建快照表,性能不足时再加 |
**R4 ✅ 已确认:采用选项 A**(先直查 FDW做好索引和查询优化
- 不建 `board_coach_snapshot``board_customer_snapshot``board_finance_snapshot` 三张快照表
- 不建 `task_detail_cache` 缓存表
- 看板接口直接查 FDW 表,通过合理索引 + SQL 优化保证性能
- 如后续出现性能瓶颈,再按需引入快照/缓存机制
- spec 第四章中的 4 张新增表建议全部移除
### R5权限模型
| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R5-1 | 看板接口权限映射未明确 | 🟠 中 | BOARD-1 → `view_board_coach`BOARD-2 → `view_board_customer`BOARD-3 → `view_board_finance`?需确认 |
| R5-2 | PERF/CUST/COACH 权限未定义 | 🟠 中 | 绩效/客户/助教模块是否只需 `require_approved()`?助教只能看自己的绩效,管理员能看所有人的? |
| R5-3 | 数据隔离粒度 | 🟠 中 | 助教只能看自己的任务/绩效/客户(通过 `assistant_id` 过滤),管理员能看全店。这个逻辑在 service 层实现还是中间件层? |
**R5 ✅ 已确认**:现有 5 个权限 code 够用,从上游入口控制即可。
- `view_tasks`任务模块TASK-1/2、PERF-1/2、CUST-1/2、COACH-1 均通过此权限 + `require_approved()` 控制)
- `view_board` + `view_board_finance` / `view_board_customer` / `view_board_coach`:看板模块
- 数据隔离在 service 层实现:助教通过 `user_assistant_binding` 获取 `assistant_id`,只查自己的数据;管理员角色可查全店
- 权限选项固定,角色-权限映射通过租户管理后台灵活配置
- 不新增权限 code
### R6数据源与口径
| # | 问题 | 严重度 | 说明 |
|---|------|--------|------|
| R6-1 | TASK-1 performance.total_customers 来源不明 | 🟡 低 | `dws_assistant_salary_calc` 是否有客户数字段?可能需要从 `dws_assistant_customer_stats` 单独查 |
| R6-2 | CUST-1 消费记录混合 vs 分类 | 🟡 低 | 3 种消费类型(台桌/商城/充值)在 API 契约中是单一 `consumption_records` 数组,暗示混合排序。确认? |
| R6-3 | BOARD-2 维度切换方式 | 🟡 低 | API 契约通过 `dimension` 参数切换 8 个维度,一次返回一个维度的排序结果。确认? |
### R7spec 中引用但未定义的表
| # | 表名 | 引用位置 | 说明 |
|---|------|---------|------|
| R7-1 | `biz.ai_cache` | TASK-2app4/5/6/7、BOARD-3app2 | 多处引用但 BD 手册无定义。需确认表结构或补充 BD 手册 |
| R7-2 | `biz.ai_conversations` + `biz.ai_messages` | CHAT 模块 | 现有 `xcx_ai_chat.py` 通过 `ConversationService` 操作,但 spec 未引用其结构 |
| R7-3 | `public.member_retention_clue` | TASK-2、CUST-1 | 维客线索表spec 未描述字段结构和脱敏规则 |
**R7 ✅ 已确认(查库验证)**4 张表全部已建,无需新增 DDL。
| 表名 | Schema | 字段数 | 关键字段 |
|------|--------|--------|---------|
| `ai_cache` | biz | 9 | `cache_type`app2_finance/app3_clue/app4_analysis/app5_script/app6_note/app7_task`target_id``result_json`(JSONB)、`expires_at` |
| `ai_conversations` | biz | 8 | `user_id`(varchar)、`app_id``site_id``source_page``source_context`(JSONB) |
| `ai_messages` | biz | 6 | `conversation_id`(FK)、`role``content``tokens_used` |
| `member_retention_clue` | public | 10 | `member_id``category``summary``detail``recorded_by_assistant_id``recorded_by_name``source`(默认 'manual') |
脱敏规则TASK-2/CUST-1 返回维客线索时,后端应排除 `recorded_by_assistant_id``recorded_by_name` 字段(仅返回 `category``summary``detail``source``recorded_at`)。
### R8其他待确认项
| # | 问题 | 来源 | 状态 |
|---|------|------|------|
| R8-1 | CONFIG-1 技能类型 | spec §3.7 + api.ts | ✅ 使用 cfg 表 |
| R8-2 | TASK-2 近期服务记录分页 | spec §3.2 | ✅ 一批 20 条,懒加载 |
| R8-3 | TASK-2 备注分页 | spec §3.2 | ✅ 一批 20 条,懒加载 |
| R8-4 | BOARD-2 每维度取前多少名 | spec §3.6 | ✅ 一批 20 条,懒加载,无上限 |
| R8-5 | BOARD-3 环比 | spec §3.6 | ✅ 全部月环比(详见下方) |
| R8-6 | COACH-1 "隐藏任务" | spec §3.5 + API 契约 | ✅ `inactive` 状态(详见下方) |
| R8-7 | FDW 查询超时 | 性能风险 | 待定(实施时按需设置) |
| R8-8 | 金额口径 | DWD-DOC 标杆 | ✅ `items_sum`,禁用 `consume_money` |
**R8-1 ✅ 已确认**:使用 cfg 表。后端从 ETL 库的 cfg 表读取技能类型列表,前端 api.ts 中的硬编码仅作为 mock 回退。
**R8-2/3 ✅ 已确认**:一批 20 条,懒加载。
- 前端修改需求TASK-2 页面的服务记录区域和备注区域需从一次性渲染改为懒加载模式(`page` + `pageSize=20` 参数 + 滚动到底部触发"加载更多"
**R8-4 ✅ 已确认**:一批 20 条,懒加载,无上限。
- 前端修改需求BOARD-2 客户看板需从一次性渲染改为懒加载模式
**R8-5 ✅ 已确认(环比调研完成)**
- 所有环比均为**月环比**(与上月同期对比)
- 共 60+ 个环比数据点,覆盖 6 个板块经营一览8 项、预收资产6 项 + 赠送卡矩阵 12 项)、应计收入确认(收入结构 9 项 + 正价 4 项 + 优惠 4 项 + 渠道 3 项、现金流入5 项、现金流出15 项)、助教分析(基础课 + 激励课各 6 项)
- 前端已实现环比开关(`compareEnabled`),环比值格式为百分比字符串(如 `"+12.5%"`
- API 契约缺陷BOARD-3 响应中 `trend` 字段仅支持 `up/down/flat`**未定义环比百分比值**,需补充 `compareValue: string`(如 `"12.5%"`)字段
- 后端需计算所有指标的月环比值(当前期 vs 上期),返回百分比和方向
- 页面筛选支持本月、上月、本周、上周、前3个月、本季度、上季度、最近6个月
**R8-6 ✅ 已确认**"隐藏任务"= 回访任务被顶替后,仍有一段生效时间但前端不显示。
- 映射关系:`hidden_tasks` 对应 `coach_tasks.status = 'inactive'`(被新任务顶替但未过期)
- 后端在 COACH-1 接口中按 status 分组返回:`active``visible_tasks``inactive``hidden_tasks``abandoned``abandoned_tasks`
- 前端不展示 `hidden_tasks`,但后端仍需返回(管理端可能需要查看)
---
## 八¾、看板筛选项交叉矩阵2026-03-18 Kiro 调研)
> 调研方法:逐文件阅读 `board-coach.ts`、`board-customer.ts`、`board-finance.ts` 的完整源码,提取所有筛选项定义、可选值、默认值、交叉约束、前端传参方式,并与 API 契约和 `api.ts` service 层交叉验证。
### B1BOARD-1 助教看板board-coach
#### 筛选项清单
| 参数名 | 类型 | 可选值 | 默认值 | 说明 |
|--------|------|--------|--------|------|
| `sort` | 排序维度 | `perf_desc``perf_asc``salary_desc``salary_asc``sv_desc``task_desc` | `perf_desc` | 6 种排序,决定卡片模板 |
| `skill` | 技能筛选 | `all``chinese``snooker``mahjong``karaoke` | `all` | 不限、中式/追分、斯诺克、麻将/棋牌、团建/K歌 |
| `time` | 时间范围 | `month``quarter``last_month``last_3m``last_quarter``last_6m` | `month` | 本月、本季度、上月、前3个月、上季度、最近6个月 |
#### 排序→卡片模板映射(`SORT_TO_DIM`
| sort 值 | dimType | 卡片显示内容 |
|---------|---------|------------|
| `perf_desc` / `perf_asc` | `perf` | 定档业绩工时、上期工时、距升档差距、是否达标 |
| `salary_desc` / `salary_asc` | `salary` | 工资总额、定档工时、上期工时 |
| `sv_desc` | `sv` | 客源储值额、储值客户数、储值消耗 |
| `task_desc` | `task` | 召回任务完成数、回访任务完成数 |
#### 各维度卡片所需字段(后端响应必须包含)
所有维度共享的基础字段(每个 item 都返回):
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 助教 ID |
| `name` | string | 助教姓名 |
| `initial` | string | 姓名首字(头像占位) |
| `avatarGradient` | string | 头像渐变色blue/green/pink/amber/violet/cyan |
| `level` | string | 等级 key`star`/`senior`/`middle`/`junior` |
| `levelClass` | string | 等级样式类(前端可自行映射,后端可不返回) |
| `skills` | array | 技能列表 `[{ text: '🎱', cls: 'skill--chinese' }]` |
| `topCustomers` | string[] | Top 客户列表(如 `['💖 王先生', '💛 李女士']` |
各维度专属字段:
| dimType | 维度名 | 专属字段 | 类型 | 说明 |
|---------|--------|---------|------|------|
| `perf` | 定档业绩 | `perfHours` | number | 当期定档工时 |
| | | `perfHoursBefore` | number? | 上期定档工时(可选,无上期数据时不返回) |
| | | `perfGap` | string? | 距升档差距描述(如 `"距升档 13.8h"`,已达标时不返回) |
| | | `perfReached` | boolean | 是否已达标 |
| `salary` | 工资 | `salary` | number | 工资总额(元) |
| | | `salaryPerfHours` | number | 定档工时 |
| | | `salaryPerfBefore` | number? | 上期定档工时 |
| `sv` | 客源储值 | `svAmount` | number | 客源储值总额(元) |
| | | `svCustomerCount` | number | 储值客户数 |
| | | `svConsume` | number | 储值消耗额(元) |
| `task` | 任务完成 | `taskRecall` | number | 召回任务完成数 |
| | | `taskCallback` | number | 回访任务完成数 |
> 后端设计建议:所有维度的字段统一返回在同一个 item 对象中(扁平结构),前端根据当前 `dimType` 选择性渲染。这样切换维度时无需重新请求(数据已在本地),仅切换卡片模板即可。如果数据量大或查询开销高,也可按 `sort` 参数只返回当前维度所需字段。
#### 交叉约束
| 约束 | 说明 |
|------|------|
| `time=last_6m` + `sort=sv_desc` | ⚠️ 不兼容。前端 TIME_OPTIONS 注释标注"不支持客源储值最高"。后端需返回 400 或忽略 |
| 其他组合 | 无限制3 参数自由组合 |
#### 交叉组合总数
- sort(6) × skill(5) × time(6) = **180 种**(减去 1 个不兼容 = **175 种有效组合**
#### 前端传参(`api.ts`
```typescript
fetchBoardCoaches({ skill, sort, time })
```
#### ⚠️ 前端 Bug筛选变更不触发重新请求
`onSortChange`/`onSkillChange`/`onTimeChange` 仅更新 `data` 状态(`selectedSort`/`selectedSkill`/`selectedTime`**未调用 `loadData()`**。联调时前端需修复:筛选变更后重新调用 `loadData()`
---
### B2BOARD-2 客户看板board-customer
#### 筛选项清单
| 参数名 | 类型 | 可选值 | 默认值 | 说明 |
|--------|------|--------|--------|------|
| `dimension` | 维度切换 | `recall``potential``balance``recharge``recent``spend60``freq60``loyal` | `recall` | 8 个维度,决定卡片模板和排序逻辑 |
| `project` | 项目筛选 | `all``chinese``snooker``mahjong``karaoke` | `all` | 全部、中式/追分、斯诺克、麻将/棋牌、团建/K歌 |
| `page` | 分页页码 | 正整数 | `1` | 前端待补充R8-4 决策20 条懒加载) |
| `pageSize` | 每页条数 | 正整数 | `20` | 前端待补充 |
#### 8 维度→卡片模板映射(`DIMENSION_TO_DIM`
| dimension | 中文名 | 排序依据(后端) | 数据源FDW 表) | 卡片关键字段 |
|-----------|--------|----------------|-----------------|------------|
| `recall` | 最应召回 | WBI 降序 | `v_dws_member_winback_index` | idealDays、elapsedDays、overdueDays、visits30d、balance、recallIndex |
| `potential` | 最大消费潜力 | SPI 降序 | `v_dws_member_spending_power_index` | potentialTags、spend30d、avgVisits、avgSpend |
| `balance` | 最高余额 | balance_amount 降序 | `v_dws_member_consumption_summary` | lastVisit、monthlyConsume、availableMonths |
| `recharge` | 最近充值 | last_recharge_date 降序 | `v_dwd_recharge_order` | lastRecharge、rechargeAmount、recharges60d、currentBalance |
| `recent` | 最近到店 | last_visit_date 降序 | `v_dws_member_visit_detail` | daysAgo、visitFreq、idealDays |
| `spend60` | 最高消费 近60天 | items_sum_60d 降序 | `v_dws_member_consumption_summary` | spend60d、visits60d、highSpendTag |
| `freq60` | 最频繁 近60天 | visit_count_60d 降序 | `v_dws_member_consumption_summary` | avgInterval、weeklyVisits8 周柱状图) |
| `loyal` | 最专一 近60天 | max_rs 降序 | `v_dws_member_assistant_relation_index` | intimacy、topCoachName、coachDetails助教明细表 |
#### 各维度卡片所需字段(后端响应必须包含)
所有维度共享的基础字段(每个 item 都返回):
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 客户 member_id |
| `name` | string | 客户姓名 |
| `initial` | string | 姓名首字(头像占位) |
| `avatarCls` | string | 头像样式类avatar--amber/pink/blue 等) |
| `assistants` | array | 关联助教列表 `[{ name, cls, heartScore, badge?, badgeCls? }]` |
各维度专属字段:
| dimType | 维度名 | 专属字段 | 类型 | 说明 |
|---------|--------|---------|------|------|
| `recall` | 最应召回 | `idealDays` | number | 理想到店间隔(天) |
| | | `elapsedDays` | number | 已过天数 |
| | | `overdueDays` | number | 超期天数(= elapsedDays - idealDays |
| | | `visits30d` | number | 近 30 天到店次数 |
| | | `balance` | string | 余额(格式化,如 `"¥2,680"` |
| | | `recallIndex` | string | 召回指数(如 `"9.2"` |
| `potential` | 最大消费潜力 | `potentialTags` | array | 潜力标签 `[{ text: '高频', theme: 'primary' }]` |
| | | `spend30d` | string | 近 30 天消费 |
| | | `avgVisits` | string | 月均到店(如 `"6.2次"` |
| | | `avgSpend` | string | 次均消费 |
| `balance` | 最高余额 | `balance` | string | 当前余额 |
| | | `lastVisit` | string | 最近到店(如 `"3天前"` |
| | | `monthlyConsume` | string | 月均消耗 |
| | | `availableMonths` | string | 可用月数(如 `"约0.8个月"` |
| `recharge` | 最近充值 | `lastRecharge` | string | 最后充值日期(如 `"2月15日"` |
| | | `rechargeAmount` | string | 充值金额 |
| | | `recharges60d` | string | 近 60 天充值次数 |
| | | `currentBalance` | string | 当前余额 |
| `recent` | 最近到店 | `daysAgo` | number | 距今天数(右上角大字) |
| | | `visitFreq` | string | 到店频率(如 `"6.2次/月"` |
| | | `idealDays` | number | 理想间隔 |
| | | `visits30d` | number | 近 30 天到店 |
| | | `avgSpend` | string | 次均消费 |
| `spend60` | 最高消费 近60天 | `spend60d` | string | 近 60 天消费总额 |
| | | `visits60d` | string | 近 60 天到店次数 |
| | | `highSpendTag` | boolean | 是否高消费标签 |
| | | `avgSpend` | string | 次均消费 |
| `freq60` | 最频繁 近60天 | `visits60d` | string | 近 60 天到店次数(右上角大字) |
| | | `avgInterval` | string | 平均到店间隔(如 `"5.0天"` |
| | | `weeklyVisits` | array | 8 周到店柱状图 `[{ val: number, pct: number }]` |
| | | `spend60d` | string | 近 60 天消费 |
| `loyal` | 最专一 近60天 | `intimacy` | string | 亲密度指数 |
| | | `topCoachName` | string | 最亲密助教姓名 |
| | | `topCoachHeart` | number | 最亲密助教爱心分 |
| | | `topCoachScore` | string | 最亲密助教关系指数 |
| | | `coachName` | string | 主助教姓名 |
| | | `coachRatio` | string | 主助教占比(如 `"78%"` |
| | | `coachDetails` | array | 助教服务明细表 `[{ name, cls, heartScore, badge?, avgDuration, serviceCount, coachSpend, relationIdx }]` |
> 后端设计建议:与助教看板不同,客户看板 8 个维度的字段差异很大且数据来源不同8 张不同的 FDW 表)。建议后端按 `dimension` 参数只查询和返回当前维度所需字段,切换维度时前端重新请求。这样避免一次查询 8 张表的性能开销。
#### 交叉约束
- **无交叉限制**dimension 和 project 完全独立,可自由组合
#### 交叉组合总数
- dimension(8) × project(5) = **40 种有效组合**
#### 前端传参(`api.ts`
```typescript
fetchBoardCustomers({ dimension, project, sort })
// 注意sort 参数在 api.ts 签名中存在但前端未使用(无排序筛选 UI
// page/pageSize 参数缺失需前端补充R8-4 决策)
```
#### ⚠️ 前端 Bug筛选变更不触发重新请求
`onDimensionChange`/`onProjectChange` 仅更新 `data` 状态,**未调用 `loadData()`**。联调时前端需修复。
#### ⚠️ 前端缺失:分页参数
R8-4 已确认 20 条懒加载,但 `fetchBoardCustomers` 签名和 `board-customer.ts` 均无 `page`/`pageSize` 参数和"加载更多"逻辑。联调时前端需补充。
---
### B3BOARD-3 财务看板board-finance
#### 筛选项清单
| 参数名 | 类型 | 可选值 | 默认值 | 说明 |
|--------|------|--------|--------|------|
| `time` | 时间范围 | `month``lastMonth``week``lastWeek``quarter3``quarter``lastQuarter``half6` | `month` | 8 种时间范围 |
| `area` | 区域筛选 | `all``hall``hallA``hallB``hallC``mahjong``teamBuilding` | `all` | 7 种区域 |
| `compareEnabled` | 环比开关 | `true` / `false` | `false` | 控制环比数据显示/隐藏 |
#### 6 个板块及筛选影响
| 板块 | sectionId | 受 time 影响 | 受 area 影响 | 受 compare 影响 | 特殊规则 |
|------|-----------|:-----------:|:-----------:|:--------------:|---------|
| 经营一览 | section-overview | ✅ | ✅ | ✅ | 无 |
| 预收资产 | section-recharge | ✅ | ⚠️ | ✅ | **仅 `area=all` 时显示**,选中具体区域时整个板块隐藏 |
| 应计收入确认 | section-revenue | ✅ | ✅ | ✅ | 含收入结构表(按区域分行)、正价明细、优惠明细、渠道明细 |
| 现金流入 | section-cashflow | ✅ | ✅ | ✅ | 无 |
| 现金流出 | section-expense | ✅ | ✅ | ✅ | 含经营支出、固定支出、助教分成、平台服务费 4 个子分组 |
| 助教分析 | section-coach | ✅ | ✅ | ✅ | 含基础课、激励课两个子表(各按等级分行) |
#### 环比开关行为
| 状态 | 行为 |
|------|------|
| `compareEnabled=false`(默认) | 仅显示当期数值,隐藏所有环比箭头和百分比 |
| `compareEnabled=true` | 每个数据单元格下方显示环比方向+百分比 |
环比样式规则:
- 上升:绿色 ↑ + 百分比(如 `↑12.5%`
- 下降:红色 ↓ + 百分比(如 `↓3.2%`
- 持平:灰色文字 `持平`
环比计算基准:与上一个相同时间周期对比(本月 vs 上月、本周 vs 上周、本季度 vs 上季度等)
#### 交叉约束
| 约束 | 说明 |
|------|------|
| `area ≠ all` → 预收资产板块隐藏 | 储值卡数据不按区域拆分,选中具体区域时无意义 |
| `compareEnabled` 独立 | 与 time/area 无交叉限制 |
| time + area 自由组合 | 无其他限制 |
#### 交叉组合总数
- time(8) × area(7) × compare(2) = **112 种组合**
- 其中 area≠all 时预收资产板块隐藏(不影响请求参数,仅影响前端渲染)
#### ⚠️ 重大设计缺陷:筛选参数不传后端
当前 `fetchBoardFinance` 签名:
```typescript
fetchBoardFinance({ date?: string }) // 仅 date 参数
```
前端 `onTimeChange`/`onAreaChange`/`toggleCompare` 仅更新本地 data 状态,**不重新调用 API**。mock 模式下不需要重新请求(数据内联),但联调后必须:
1. `fetchBoardFinance` 签名扩展为 `{ time, area, compareEnabled }` 三个参数
2. 筛选变更后重新调用 `fetchBoardFinance`
3. 后端根据 `time` 计算日期范围、根据 `area` 过滤区域、根据 `compareEnabled` 决定是否返回环比数据
#### 后端 API 参数设计建议
```
GET /api/xcx/board/finance?time={time}&area={area}&compare={0|1}
```
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|:----:|--------|------|
| `time` | string | 否 | `month` | 时间范围枚举 |
| `area` | string | 否 | `all` | 区域枚举 |
| `compare` | 0/1 | 否 | `0` | 是否返回环比数据(减少不需要时的计算开销) |
---
### 前端修改需求汇总(联调前必须完成)
#### 看板页修复F1-F6
| # | 页面 | 问题 | 修改内容 |
|---|------|------|---------|
| F1 | board-coach | 筛选变更不触发重新请求 | `onSortChange`/`onSkillChange`/`onTimeChange` 末尾加 `this.loadData()` |
| F2 | board-customer | 筛选变更不触发重新请求 | `onDimensionChange`/`onProjectChange` 末尾加 `this.loadData()` |
| F3 | board-customer | 缺少分页参数 | `fetchBoardCustomers` 签名加 `page`/`pageSize`,页面加"加载更多"逻辑 |
| F4 | board-finance | 筛选参数不传后端 | `fetchBoardFinance` 签名扩展为 `{ time, area, compare }`,筛选变更后重新调用 |
| F5 | board-finance | 筛选变更不触发重新请求 | `onTimeChange`/`onAreaChange` 末尾加 `this.loadData()` |
| F6 | board-coach | `time=last_6m` + `sort=sv_desc` 不兼容 | 前端在选择 `last_6m` 时禁用 `sv_desc` 选项,或选择 `sv_desc` 时禁用 `last_6m` |
#### 列表页修复F7-F11
| # | 页面 | 问题 | 修改内容 |
|---|------|------|---------|
| F7 | task-list | status 筛选 UI 未实现 | API 支持 `status` 参数但页面无筛选控件,需添加 Tab 或筛选栏 |
| F8 | performance | 无月份切换功能 | API 支持 `year`/`month` 参数但页面固定当前月,需添加月份切换按钮 |
| F9 | performance-records | 月份切换时未重置分页 | `switchMonth()` 中需将 `page` 重置为 1 |
| F10 | customer-service-records | 月份切换采用本地筛选 | `updateMonthView()` 仅本地过滤,联调后需改为重新调用 API |
| F11 | notes | 无触底加载逻辑 | API 支持分页但页面未实现 `onReachBottom()` |
---
### 非看板列表页筛选矩阵
#### L1task-list任务列表
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `status` | string | `pending``completed``abandoned` | 无(全部) | ❌ 缺失 | API 支持但页面无筛选控件 |
| `page` | number | 1+ | `1` | ✅ 自动 | 触底加载 |
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |
交互:下拉刷新重置 page=1触底 page++。前端将任务分为 pinnedTasks / normalTasks / abandonedTasks 三组渲染。
#### L2performance绩效概览
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `year` | number | 当前年 ± N | 当前年 | ❌ 缺失 | 固定当前年 |
| `month` | number | 1-12 | 当前月 | ❌ 缺失 | 固定当前月 |
交互:无筛选 UI页面加载时固定请求当前年月。无分页。
#### L3performance-records绩效明细
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `year` | number | 当前年 ± N | 当前年 | ✅ 月份切换按钮 | 上月/下月箭头 |
| `month` | number | 1-12 | 当前月 | ✅ 月份切换按钮 | 不可超过当前月 |
| `page` | number | 1+ | `1` | ✅ 自动 | 触底加载 |
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |
月份切换详细交互:
1. 页面顶部显示 `{year}年{month}月` 标签,左右各有箭头按钮
2. 点击左箭头 → `currentMonth--`(跨年自动 `currentYear--``currentMonth=12`
3. 点击右箭头 → `currentMonth++`(跨年自动 `currentYear++``currentMonth=1`
4. 边界:`canGoNext = false``currentYear == nowYear && currentMonth == nowMonth`(不可超过当前月);`canGoPrev = true`(无下限)
5. 月份变更后 → 调用 `loadData()` → 重新请求 API `fetchPerformanceRecords({ year, month, page, pageSize })`
6. ⚠️ Bug`switchMonth()` 中未将 `page` 重置为 1月份切换后可能请求第 N 页数据
后端响应结构(按月返回):
- `summary`:当月汇总(总笔数、总工时、折算前总工时、总收入)
- `date_groups`:按日期降序分组,每组含日期标签、当日总工时/总收入、记录列表
- 每条记录字段:`id``customerName``timeRange`(如 `"20:00-22:00"`)、`hours`(折算后)、`hoursRaw`(折算前,可选)、`courseType``courseTypeClass``tag-basic`/`tag-vip`/`tag-tip`)、`location`(台号)、`income`
后端时间范围处理:
- 接收 `year` + `month` → 转换为 `biz_date BETWEEN '{year}-{month}-01' AND '{year}-{month}-{lastDay}'`
- 数据源:`fdw_etl.v_dwd_assistant_service_log`(按 `assistant_id` + `biz_date` 范围查询)
#### L4customer-service-records客户服务记录
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `customerId` | string | 客户 ID | 从 options 读取 | ✅ 自动 | 页面参数 |
| `year` | number | 当前年 ± N | 当前年 | ✅ 月份切换按钮 | 上月/下月箭头 |
| `month` | number | 1-12 | 当前月 | ✅ 月份切换按钮 | 不可超过当前月 |
| `table` | string | 台号 | 无 | ❌ 缺失 | API 支持但页面未使用 |
月份切换详细交互:
1. 页面顶部显示 `{year}年{month}月` 标签,左右各有箭头按钮
2. 点击左箭头 → `currentMonth--`(跨年处理同 L3
3. 点击右箭头 → `currentMonth++`(跨年处理同 L3
4. 边界:`canPrev` = `yearMonth > minYearMonth`(最早 12 个月前);`canNext` = `yearMonth < maxYearMonth`(不超过当前月)
5. 月份变更后 → 调用 `updateMonthView()`**仅本地过滤**(从 `allRecords` 中按 `date.startsWith('{year}-{month}')` 筛选)
6. ⚠️ 设计问题:首次 `loadData()` 一次性拉取全部记录,月份切换仅本地过滤。联调后如果数据量大(单客户数百条记录),需改为按月请求 API
本地筛选逻辑:
- `allRecords`(首次加载时获取全部)→ 按 `date` 字段前缀 `{year}-{month}` 过滤 → 转换为 `ServiceRecord` 展示格式
- 月度统计:`monthCount`(当月记录数)、`monthHours`(当月总时长)
- 记录字段:`table`(台号)、`type`(课程类型标签)、`typeClass``basic`/`vip`/`tip`/`recharge`)、`recordType``course`/`recharge`)、`duration`(折算后小时)、`income`(到手金额)、`drinks`(饮品描述)、`date`(显示日期+时间段)
后端时间范围处理(联调后建议改为按月请求):
- 当前方案:一次返回全部记录,前端本地按月过滤
- 建议方案:`fetchCustomerRecords({ customerId, year, month })` → 后端按月查询 `fdw_etl.v_dwd_assistant_service_log`(按 `member_id` + `assistant_id` + `biz_date` 范围)
#### L5notes备注列表
| 参数名 | 类型 | 可选值 | 默认值 | UI 实现 | 说明 |
|--------|------|--------|--------|:-------:|------|
| `page` | number | 1+ | `1` | ✅ 自动 | 下拉刷新重置 |
| `pageSize` | number | 1+ | `20` | ✅ 硬编码 | — |
交互:下拉刷新重置 page=1。⚠ 无触底加载(`onReachBottom()` 未实现)。
#### L6customer-detail / coach-detail详情页
无筛选项,单次加载全部数据。
---
## 八⅞、Storyboard 走查 Gap 汇总2026-03-18
> 走查方法:两个子代理分别以助教视角和管理层视角,逐页面对照前端内联 mock 数据 vs API 契约 vs 八¾节字段定义,提取所有未记录的接口需求 Gap。
> 完整报告:`docs/reports/storyboard-walkthrough-assistant-view.md`助教视角51 Gap+ `docs/reports/miniprogram-storyboard-walkthrough-gaps.md`管理层视角31 Gap
### 统计
| 严重度 | 助教视角 | 管理层视角 | 去重后合计 |
|--------|:--------:|:----------:|:----------:|
| 🔴 高(阻塞联调) | 17 | 14 | ~20 |
| 🟠 中(影响完整性) | 19 | 12 | ~18 |
| 🟡 低(可后续修复) | 15 | 5 | ~12 |
### 核心发现
1. **契约与前端严重脱节**BOARD-1/2/3、CUST-1、COACH-1、PERF-1、TASK-1 performance 的契约响应定义与前端实际需求严重不匹配,需完全重写
2. **BOARD-3 财务看板**:契约定义扁平 `metrics` 数组,前端需要 6 个独立板块overview/recharge/revenue/cashflow/expense/coachAnalysis的深度嵌套结构含 200+ 字段
3. **CUST-1 客户详情**:缺少 5 大模块aiInsight/coachTasks/favoriteCoaches/消费记录嵌套结构/备注列表)
4. **COACH-1 助教详情**:缺少 6 大模块performance/income/tierNodes/historyMonths/topCustomers 扩展/备注列表)
5. **跨页面参数传递**:多处 taskId/customerId 混淆、未传参数、用 name 代替 id 跳转(用户决策:统一用唯一 ID
6. **CHAT 模块**referenceCard 未定义、customerId→chatId 映射缺失、SSE 端点未在契约中定义
### 处置方案
全部 Gap 已纳入 **[RNS1 拆分计划](./RNS1-split-plan.md)**,分 5 个子 Spec 逐步实施。Gap 追溯矩阵见拆分计划末尾。
---
## 九、任务清单
> ⚠️ **已拆分**原始任务清单T0-1 ~ T18已被 Storyboard 走查发现的 80+ 个 Gap 大幅扩展,体量超出单个 SPEC 可控范围。
>
> 完整的拆分计划见 **[RNS1-split-plan.md](./RNS1-split-plan.md)**,将 NS1 拆为 5 个子 Spec
>
> | 子 Spec | 名称 | 任务数 | 状态 |
> |---------|------|:------:|:----:|
> | RNS1.0 | 基础设施与契约重写 | 6 | 待启动 |
> | RNS1.1 | 任务与绩效接口 | 6 | 待启动 |
> | RNS1.2 | 客户与助教接口 | 6 | 待启动 |
> | RNS1.3 | 三看板接口 | 7 | 待启动 |
> | RNS1.4 | CHAT 对齐与联调收尾 | 5 | 待启动 |
>
> 执行顺序RNS1.0 → (RNS1.1 ∥ RNS1.2 ∥ RNS1.3) → RNS1.4
>
> 需求追溯:两份走查报告(`docs/reports/storyboard-walkthrough-assistant-view.md` + `docs/reports/miniprogram-storyboard-walkthrough-gaps.md`)中的全部 Gap 已在拆分计划的追溯矩阵中映射到具体任务。
### 原始任务清单(归档参考)
<details>
<summary>点击展开原始 Batch 0~C 任务清单(已被 RNS1 拆分计划替代)</summary>
#### Batch 0基础设施改造阻塞所有新接口
- [ ] T0-1全局响应包装中间件`{ code: 0, data: ... }` + 异常处理器 `{ code, message }`
- [ ] T0-2Pydantic schema 统一 camelCase`alias_generator=to_camel`,所有现有 + 新增 schema
- [ ] T0-3后端 TASK-4 路径从 `/cancel-abandon` 改为 `/restore`(对齐契约)
- [ ] T0-4前端 `request()` 工具函数加 `.data` 解包适配
#### Batch A核心业务接口
- [ ] T1扩展 TASK-1任务列表 + 绩效概览 + 分页 + 懒加载)
- [ ] T2实现 TASK-2任务详情完整版服务记录/备注各 20 条懒加载)
- [ ] T3实现 PERF-1绩效概览
- [ ] T4实现 PERF-2绩效明细20 条懒加载)
- [ ] T5实现 CUST-1客户详情消费记录 3 类混合排序 + 懒加载)
- [ ] T6实现 CUST-2客户服务记录20 条懒加载)
- [ ] T7实现 COACH-1助教详情任务按 active/inactive/abandoned 分组)
#### Batch B看板 + 配置接口
- [ ] T8实现 BOARD-1助教看板FDW 直查)
- [ ] T9实现 BOARD-2客户看板8 维度切换20 条懒加载FDW 直查)
- [ ] T10实现 BOARD-3财务看板60+ 指标月环比计算FDW 直查)
- [ ] T11实现 CONFIG-1技能类型列表从 cfg 表读取)
#### Batch CCHAT 对齐、前端修复与联调
- [ ] T12CHAT 路径迁移(`/api/ai/*``/api/xcx/chat/*`,保留 SSE 流式端点)
- [ ] T13新增 CHAT-1/2 同步端点(历史列表 + 消息查看)
- [ ] T14API 契约补充SSE 端点定义 + BOARD-3 `compareValue` 字段 + BOARD-3 `time`/`area`/`compare` 参数)
- [ ] T15FDW 端到端验证
- [ ] T16前端懒加载改造TASK-2 服务记录/备注、BOARD-2 客户列表)
- [ ] T17前端看板筛选修复F1-F6详见八¾节
- [ ] T18前后端联调 + 修复
</details>