feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本

包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-06 00:03:48 +08:00
parent 70324d8542
commit 6f8f12314f
515 changed files with 76604 additions and 7456 deletions

View File

@@ -0,0 +1,238 @@
# 小程序多页面联调改造任务书
> 创建日期2026-03-27
> 来源:本轮对话排查 customer-service-records 页面加载问题后,用户提出的 4 页面改造需求
> 状态:第 1 步后端改造已完成,第 2-4 步待执行
---
## 一、背景与前提
### 1.1 已完成的工作(本轮对话)
1. **权限守卫修复**`auth-guard.ts``PAGE_ROLES``customer-detail``customer-service-records``coach-detail` 三个页面加入了 `coach` 角色,解决了 coach 进入后被 `reLaunch` 踢回首页的问题。
2. **ETL 连接复用**`fdw_queries.py``_fdw_context` 新增 `etl_conn` 可选参数,`customer_service.py``get_customer_records` 中只创建一个 ETL 连接传给所有子查询,连接开销从 ~10s 降到 ~2.6s。
3. **后端 assistant_id 过滤**
- `customer_service.py` 新增 `_get_assistant_id()``auth.user_assistant_binding` 获取 assistant_id
- `get_customer_records` 签名新增 `user_id` 参数,内部获取 assistant_id 后传给所有 fdw_queries 调用
- `fdw_queries.get_customer_service_records``get_total_service_count` 加入可选 `assistant_id` 过滤(`site_assistant_id = %s`
- `_get_month_aggregation` 同步加入 `assistant_id` 过滤
- 路由层 `xcx_customers.py` 传入 `user.user_id`
4. **后端到手收入计算**
- `get_customer_records` 中查询 `v_dws_assistant_salary_calc` 获取当月费率
- 基础课到手 = hours × (base_course_price - base_deduction)
- 激励课到手 = hours × bonus_course_price × (1 - bonus_deduction_ratio)
- 充值提成到手 = ledger_amount直接取
- Schema 新增 `month_income: float` 字段
5. **BACKLOG 更新**`docs/roadmap/BACKLOG.md` 新增 AI BudgetTracker 和 admin_db_health UnicodeDecodeError 两条待办。
6. **联调规范更新**`docs/guides/FRONTEND-BACKEND-INTEGRATION.md``.kiro/steering/frontend-backend-integration.md` 新增 ETL 连接复用规则。
### 1.2 已识别但未修复的数据问题
通过开发者工具读取页面 data 发现以下问题:
| # | 问题 | 原因 | 影响 |
|---|------|------|------|
| 1 | `customerName` 为空 | `loadCustomerInfo` 调的 `get_customer_detail` 还没做 ETL 连接复用优化,可能超时 | 顶部不显示客户名 |
| 2 | `totalServiceCount: 0` | 后端 `get_total_service_count` 返回 0需排查 assistant_id 是否正确传入 | 顶部"服务X次"显示 0 |
| 3 | `monthHours: "0.0h"` | 后端返回 `duration` 是小时数(如 1.1),前端 `loadMonthRecords``rawRecords.reduce` 累加的是 `r.duration`,但后端字段名是 `duration`CamelModel 输出 camelCase需确认字段映射 | 月度时长显示 0 |
| 4 | `income: 0` | 后端到手收入计算中 `salary_calc` 可能没查到数据assistant_id 为 None 或费率为 0 | 列表收入全部显示 0 |
| 5 | `displayDate` 格式错误 | 前端 `generateTimeRange``r.duration`(小时数)当分钟数处理,导致 `19:1.116...` | 时间显示乱码 |
| 6 | `table` 显示 ID | 后端返回 `table_id`(数字 ID前端直接显示应该显示台桌名称 | 台桌号显示为长数字 |
| 7 | `durationHours: 0` | 前端 `(r.duration \|\| 0) / 60` 把小时数再除以 60 变成了接近 0 的值 | 列表时长显示 0 |
### 1.3 开发环境
- Windows + PowerShell
- 后端:`start-admin.bat` 启动uvicorn --reload 端口 8000
- 前端:微信开发者工具(自动化端口 9420
- 数据库ETL 库PG_DSN+ 业务库APP_DB_DSNETL 连接耗时 ~2.6s
- 可用调试工具weixin-devtools MCP power连接 `ws://127.0.0.1:9420`
### 1.4 关键文件位置
| 文件 | 说明 |
|------|------|
| `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts` | 前端页面逻辑 |
| `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.wxml` | 前端页面模板 |
| `apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.wxss` | 前端页面样式 |
| `apps/miniprogram/miniprogram/services/api.ts` | 前端 API 调用层 |
| `apps/miniprogram/miniprogram/utils/request.ts` | 前端请求封装(含 ResponseWrapper 解包) |
| `apps/miniprogram/miniprogram/utils/auth-guard.ts` | 权限守卫 |
| `apps/miniprogram/miniprogram/pages/task-detail/task-detail.ts` | 任务详情页参考60天服务记录列表格式 |
| `apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml` | 任务详情页模板参考service-record-card |
| `apps/miniprogram/miniprogram/pages/performance/performance.ts` | 绩效页 |
| `apps/miniprogram/miniprogram/pages/performance/performance.wxml` | 绩效页模板 |
| `apps/miniprogram/miniprogram/pages/task-list/task-list.ts` | 任务列表页 |
| `apps/miniprogram/miniprogram/pages/task-list/task-list.wxml` | 任务列表页模板 |
| `apps/backend/app/services/customer_service.py` | 后端客户服务 |
| `apps/backend/app/services/fdw_queries.py` | 后端 FDW 查询 |
| `apps/backend/app/routers/xcx_customers.py` | 后端客户路由 |
| `apps/backend/app/schemas/xcx_customers.py` | 后端响应 Schema |
| `apps/backend/app/routers/xcx_auth.py` | 后端认证路由(/api/xcx/me |
| `apps/backend/app/auth/dependencies.py` | JWT 解析CurrentUser |
### 1.5 关键数据口径
- **到手收入**:基础课 = hours × (base_course_price - base_deduction);激励课 = hours × bonus_course_price × (1 - bonus_deduction_ratio);充值提成 = ledger_amount
- **费率来源**`v_dws_assistant_salary_calc`,按 `assistant_id + salary_month` 查询
- **DWD 层助教字段**`site_assistant_id`(与 `auth.user_assistant_binding.assistant_id` 相同)
- **废单排除**`is_delete = 0`
- **会员信息**:通过 `member_id` JOIN `v_dim_member (scd2_is_current=1)`
- **CamelModel**:后端 Pydantic schema 自动将 snake_case 转 camelCase 输出
- **ResponseWrapper**:后端中间件将 2xx JSON 包装为 `{ code: 0, data: <原始body> }`,前端 `request.ts` 自动解包
---
## 二、待执行任务
### 任务 Acustomer-service-records 前端改造(优先级最高)
#### A1. 修复数据转换逻辑
前端 `loadMonthRecords` 中的字段映射需要对齐后端 CamelModel 输出:
```
后端返回camelCase 前端当前读取 问题
─────────────────────────────────────────────────
duration (float, 小时) r.duration / 60 多除了一次 60
durationRaw (float, 小时) 未使用 应直接用
income (float, 到手元) r.amount 字段名不匹配
timeRange (string|null) 未使用 后端已返回,前端用 generateTimeRange 自己算
courseType (string) r.courseType ✓ 正确
table (string|null) r.table 后端返回 table_id 数字,需要改为台桌名
recordType (string) 前端自己判断 后端已返回,应直接用
isEstimate (bool) r.isEstimate ✓ 正确
drinks (string|null) r.drinks 后端当前返回 null后续需补齐
```
修改要点:
- `durationHours` 直接用 `r.duration`(已是小时数),不再 `/60`
- `durationRaw` 直接用 `r.durationRaw`
- `income``r.income`(后端已计算到手)
- `displayDate` 优先用后端返回的 `r.timeRange`,无值时才用 `generateTimeRange`
- `generateTimeRange` 的参数是小时数不是分钟数,需修正或移除
- `table` 字段:后端当前返回 `table_id`(数字),需要后端改为返回台桌名称,或前端做映射
#### A2. 修复月度统计
- `monthHours` 累加逻辑:`rawRecords.reduce((sum, r) => sum + (r.duration || 0), 0)` — 后端返回的 `duration` 已是小时数,不需要再 `/60`
- 新增 `monthIncome`:从后端返回的 `monthIncome` 字段读取并展示
#### A3. 修复顶部信息
- `customerName``loadCustomerInfo` 调的 `fetchCustomerDetail` 超时问题 — 方案 1`get_customer_detail` 也做 ETL 连接复用;方案 2`get_customer_records` 的返回值中直接取 `customerName`(后端已返回)
- `totalServiceCount`:从 `get_customer_records` 返回值中取(后端已返回 `totalServiceCount`
- 手机号查看/复制交互:对齐 task-detail 页面的实现
- 首字母头像:使用通用颜色规则(`nameToAvatarColor`
#### A4. 列表记录格式对齐 task-detail
- 对齐 task-detail 的"60天内服务记录"列表的 `service-record-card` 组件格式
- 确认 `service-record-card` 组件是否已存在,还是内联在 task-detail 的 WXML 中
- 到手收入显示:`¥XXX`
- 课程类型标签:基础课/激励课/充值
- 折前/折后时长:`durationRaw !== durationHours` 时才显示折前
#### A5. 加速加载
- `loadCustomerInfo` 可以去掉(改为从 `loadMonthRecords` 的返回值中取 `customerName``customerPhone`
- 减少一个 API 请求 = 减少一次 ETL 连接开销
---
### 任务 Btask-detail 折前时长条件显示
文件:`apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml`
- 找到服务记录列表中显示"折前X.Xh"的位置
- 加条件判断:`wx:if="{{item.durationRaw && item.durationRaw !== item.durationHours}}"`
- 折前和折后相等时隐藏折前标签
---
### 任务 Cperformance 页面改造
文件:`apps/miniprogram/miniprogram/pages/performance/performance.ts` + `.wxml`
#### C1. 未知客户点击拦截
- 找到客户卡片点击跳转逻辑
- 判断 `memberId <= 0`(散客/未知客户)时,`wx.showToast({ title: '未知客户不提供查看客户任务详情', icon: 'none' })` 并 return
- 非散客正常跳转
#### C2. 角色标签对齐 task-list
- 查看 task-list 页面的角色标签展示方式WXML + WXSS
- 对齐 performance 页面顶部的角色标签样式
---
### 任务 D头像修复performance + task-list
文件:`performance.ts``task-list.ts`、对应 `.wxml`
- 后端 `/api/xcx/me` 已返回 `avatarUrl` 字段apply 时上传的头像)
- 前端 `fetchMe()` 返回的 `data.avatarUrl` 需要绑定到页面 data
- WXML 中头像 `<image>``src` 应优先用 `avatarUrl`,无值时 fallback 到默认占位图或首字母头像
- 确认 task-list 和 performance 两个页面的 banner 区域头像都使用此逻辑
---
### 任务 E后端补充优化可选
#### E1. get_customer_detail ETL 连接复用
-`get_customer_records` 相同的模式:创建一个 ETL 连接传给所有 fdw_queries 调用
- 涉及的子查询:`get_member_info``get_member_balance``get_consumption_60d``get_last_visit_days``_build_consumption_records``_build_coach_tasks``_build_favorite_coaches`
#### E2. 台桌名称返回
- 当前 `get_customer_service_records` 返回 `site_table_id`(数字 ID
- 需要 JOIN `v_dim_table`(或类似维度表)获取台桌名称
- 或者前端做映射(不推荐,增加前端复杂度)
#### E3. 到手收入排查
- 确认 `_get_assistant_id` 返回值是否正确(可能 `user_assistant_binding` 中没有对应记录)
- 确认 `get_salary_calc` 是否能查到当月数据(可能该月还没有 salary_calc 记录)
- 费率为 0 时的兜底处理
---
## 三、执行顺序建议
1. **E3**(排查到手收入为 0 的原因)→ 确认后端数据正确性
2. **A1 + A2 + A3**(前端数据转换修复)→ 页面能正确展示数据
3. **A4**(列表格式对齐 task-detail→ UI 一致性
4. **A5**(加速加载)→ 去掉多余 API 请求
5. **B**task-detail 折前时长)→ 小改动
6. **C**performance 页面)→ 独立改动
7. **D**(头像修复)→ 独立改动
8. **E1 + E2**(后端补充优化)→ 可选
---
## 四、验证方式
每步完成后:
1. 微信开发者工具编译Ctrl+B
2. 操作路径:任务列表 → 轩哥 → 任务详情 → 查看全部服务记录
3. 用 weixin-devtools MCP power 的 `evaluate_script` 读取页面 data 验证字段值
4.`list_console_messages` 检查是否有错误
5. 后端改动通过 uvicorn `--reload` 自动加载,看后端窗口有无报错
---
## 五、参考文档
- `docs/guides/FRONTEND-BACKEND-INTEGRATION.md` — 联调规范字段命名、到手收入口径、ETL 连接复用等)
- `.kiro/steering/frontend-backend-integration.md` — 联调规范摘要
- `.kiro/steering/feiqiu-data-rules.md` — 飞球数据规范索引
- `apps/backend/app/schemas/xcx_customers.py` — 后端响应 SchemaCamelModel 自动转 camelCase
- `apps/miniprogram/miniprogram/utils/request.ts` — 前端请求封装ResponseWrapper 解包逻辑)