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,168 @@
# Implementation Plan: RNS1.1 任务与绩效接口
## Overview
基于 design.md 的 7 个组件结构,增量扩展现有后端路由和服务层,新增 FDW 查询封装、绩效服务、Pydantic Schema并完成前端适配。所有 FDW 查询集中在 `fdw_queries.py`,服务层分为 `task_manager.py`(扩展)和 `performance_service.py`(新增)。
## Tasks
- [x] 1. Pydantic Schema 定义(组件 6
- [x] 1.1 扩展 `apps/backend/app/schemas/xcx_tasks.py`,新增 `PerformanceSummary`15+ 字段tierNodes, basicHours, bonusHours, currentTier, nextTierHours, tierCompleted, bonusMoney, incomeTrend, incomeTrendDir, prevMonth, currentTierLabel 等)、`TaskItem`(扩展版,含 lastVisitDays, balance, aiSuggestion 可选字段)、`TaskListResponse`items + total + page + pageSize + performance
- 新增 `RetentionClue``ServiceRecord`(含 courseTypeClass 枚举)、`AiAnalysis``NoteItem`(含 score 可选字段)、`ServiceSummary``TaskDetailResponse` 模型
- _Requirements: 1.1, 1.6, 2.1, 2.2, 2.4, 2.9_
- [x] 1.2 新建 `apps/backend/app/schemas/xcx_performance.py`,定义 `DateGroupRecord`(含可选 avatarChar/avatarColor`DateGroup``TierInfo``IncomeItem`(含 desc`CustomerSummary``NewCustomer``RegularCustomer``PerformanceOverviewResponse` 模型
- 定义 `RecordsSummary`totalCount, totalHours, totalHoursRaw, totalIncome`PerformanceRecordsResponse`summary + dateGroups + hasMore模型
- _Requirements: 3.1, 3.3, 3.4, 3.5, 4.1, 4.2, 4.3_
- [x] 2. FDW 查询封装服务(组件 3 — 新增 fdw_queries.py
- [x] 2.1 新建 `apps/backend/app/services/fdw_queries.py`,实现 `_fdw_context(conn, site_id)` 上下文管理器BEGIN + SET LOCAL app.current_site_id`get_member_info(conn, site_id, member_ids)` 批量查询会员信息
- ⚠️ DQ-6通过 member_id JOIN fdw_etl.v_dim_member取 scd2_is_current=1
- _Requirements: 8.7_
- [x] 2.2 实现 `get_member_balance(conn, site_id, member_ids)` 批量查询会员储值卡余额
- ⚠️ DQ-7通过 member_id JOIN fdw_etl.v_dim_member_card_account取 scd2_is_current=1
- _Requirements: 1.8, 8.7_
- [x] 2.3 实现 `get_last_visit_days(conn, site_id, member_ids)` 批量查询客户距上次到店天数
- 来源fdw_etl.v_dwd_assistant_service_logWHERE is_trash = false
- _Requirements: 1.7_
- [x] 2.4 实现 `get_salary_calc(conn, site_id, assistant_id, year, month)` 查询助教绩效/档位/收入数据
- ⚠️ DWD-DOC 规则 1收入使用 items_sum 口径
- ⚠️ DWD-DOC 规则 2费用使用 assistant_pd_money + assistant_cx_money
- _Requirements: 1.2, 1.3, 3.7, 8.5, 8.6_
- [x] 2.5 实现 `get_service_records(conn, site_id, assistant_id, year, month, limit, offset)` 查询助教服务记录明细
- ⚠️ 废单排除WHERE is_trash = falseDQ-6客户姓名通过 member_id JOIN dim_member
- _Requirements: 2.11, 2.12, 3.5, 4.7, 4.8, 8.8_
- [x] 2.6 实现 `get_service_records_for_task(conn, site_id, assistant_id, member_id, limit)` 查询特定客户的服务记录TASK-2 用)
- _Requirements: 2.1, 2.3_
- [x] 3. Checkpoint — Schema 与 FDW 查询层验证
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. task_manager 服务扩展(组件 4 — 扩展现有 task_manager.py
- [x] 4.1 在 `apps/backend/app/services/task_manager.py` 中实现 `get_task_list_v2(user_id, site_id, status, page, page_size)`
- 逻辑_get_assistant_id() → 查询 coach_tasks 带分页 → fdw_queries 批量获取会员信息/余额/lastVisitDays → fdw_queries.get_salary_calc() 获取绩效概览 → 查询 ai_cache 获取 aiSuggestion → 组装 TaskListResponse
- 扩展字段lastVisitDays/balance/aiSuggestion采用优雅降级单个查询失败返回 null不影响整体响应
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10_
- [x] 4.2 在 `apps/backend/app/services/task_manager.py` 中实现 `get_task_detail(task_id, user_id, site_id)`
- 逻辑_get_assistant_id() + _verify_task_ownership() 权限校验 → 查询 coach_tasks 基础信息 → 查询 member_retention_clue 维客线索 → 查询 ai_cacheapp5_talking_points → talkingPoints, app4_analysis → aiAnalysis→ fdw_queries.get_service_records_for_task() 服务记录(最多 20 条)→ 查询 notes最多 20 条)→ 组装 TaskDetailResponse
- tag 字段净化:去除换行符 \n多行标签使用空格分隔
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 2.10, 2.11, 2.12, 2.13, 2.14_
- [x] 4.3 Write property test: 收入趋势计算正确性
- **Property 1: 收入趋势计算正确性**
- 生成器st.floats(min_value=0, max_value=1e6) × 2
- 验证incomeTrendDir 方向正确、差值正确、前缀符号一致
- **Validates: Requirements 1.4**
- [x] 4.4 Write property test: lastVisitDays 计算正确性
- **Property 2: lastVisitDays 计算正确性**
- 生成器st.dates(max_value=date.today())
- 验证:天数差 = (today - date).days非负
- **Validates: Requirements 1.7**
- [x] 4.5 Write property test: 维客线索 tag 净化与 source 枚举
- **Property 3: 维客线索 tag 净化与 source 枚举**
- 生成器st.text() 生成含 \n 的 tag + st.sampled_from source
- 验证tag 无换行、source 在枚举内
- **Validates: Requirements 2.4, 2.5**
- [x] 4.6 Write property test: courseTypeClass 枚举映射
- **Property 4: courseTypeClass 枚举映射**
- 生成器st.sampled_from(ALL_COURSE_TYPES)
- 验证:结果在 {basic, vip, tip, recharge, incentive} 内、无 tag- 前缀
- **Validates: Requirements 2.9, 4.4**
- [x] 5. performance_service 服务(组件 5 — 新增 performance_service.py
- [x] 5.1 新建 `apps/backend/app/services/performance_service.py`,实现 `get_overview(user_id, site_id, year, month)`
- 逻辑:获取 assistant_id → fdw_queries.get_salary_calc() 档位/收入/费率 → fdw_queries.get_service_records() 按日期分组为 DateGroup → 聚合新客/常客列表 → 计算 incomeItems含 desc 费率描述)→ 查询上月收入 lastMonthIncome
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13_
- [x] 5.2 实现 `get_records(user_id, site_id, year, month, page, page_size)`
- 逻辑:获取 assistant_id → fdw_queries.get_service_records() 带分页 → 按日期分组为 dateGroups → 计算 summary 汇总 → 返回 { summary, dateGroups, hasMore }
- PERF-2 不返回 avatarChar/avatarColor前端自行计算
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8_
- [x] 5.3 Write property test: 分页与排序
- **Property 5: 列表分页与排序**
- 生成器st.lists(st.fixed_dictionaries(...)) + st.integers(1,10) page/pageSize
- 验证:记录数 ≤ pageSize、hasMore 正确、排序正确
- **Validates: Requirements 2.3, 4.2**
- [x] 5.4 Write property test: DateGroup 分组正确性
- **Property 6: DateGroup 分组正确性**
- 生成器st.lists(st.fixed_dictionaries({date, hours, income}))
- 验证:日期唯一、组内日期一致、汇总正确、按日期倒序
- **Validates: Requirements 3.3, 4.3**
- [x] 5.5 Write property test: incomeItems desc 格式化
- **Property 7: incomeItems desc 格式化**
- 生成器st.floats(min_value=0.01) rate × st.floats(min_value=0) hours
- 验证desc 包含费率值和工时值,格式为 "{rate}元/h × {hours}h"
- **Validates: Requirements 3.9**
- [x] 5.6 Write property test: 月度汇总聚合正确性
- **Property 8: 月度汇总聚合正确性**
- 生成器st.lists(st.fixed_dictionaries({hours, hours_raw, income}))
- 验证totalCount/totalHours/totalHoursRaw/totalIncome 聚合正确
- **Validates: Requirements 4.6**
- [x] 6. Checkpoint — 服务层验证
- Ensure all tests pass, ask the user if questions arise.
- [x] 7. xcx_tasks Router 扩展(组件 1
- [x] 7.1 修改 `apps/backend/app/routers/xcx_tasks.py`:将 `GET /api/xcx/tasks` 响应从 `list[TaskListItem]` 改为 `TaskListResponse`(含 items + performance + 分页),新增 status/page/pageSize 查询参数,调用 `task_manager.get_task_list_v2()`
- _Requirements: 1.1, 1.2_
- [x] 7.2 在 `routers/xcx_tasks.py` 中新增 `GET /api/xcx/tasks/{task_id}` 端点,调用 `task_manager.get_task_detail()`,含 require_approved() 权限校验
- _Requirements: 2.1, 2.13, 2.14_
- [x] 7.3 修改 `routers/xcx_tasks.py``POST .../pin``POST .../unpin` 端点,响应对齐契约格式 `{ isPinned: bool }`
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5_
- [x] 7.4 修改 `POST /api/xcx/notes` 端点(`xcx_notes.py`),接受请求体中的可选 `score` 字段number, 1-5存入 `biz.coach_notes.score` 列;超出 1-5 范围返回 422
- _Requirements: 6.3_
- [x] 7.5 Write property test: Pin/Unpin 状态往返
- **Property 9: Pin/Unpin 状态往返**
- 生成器st.booleans() 初始状态
- 验证pin→true、unpin→false、往返恢复
- **Validates: Requirements 5.1, 5.2, 5.3**
- [x] 7.6 Write property test: 权限与数据隔离
- **Property 10: 权限与数据隔离**
- 生成器st.sampled_from(INVALID_USER_SCENARIOS)
- 验证:未审核/无绑定/无权限/非本人任务 → 所有端点返回 403
- **Validates: Requirements 2.13, 8.1, 8.2, 8.4**
- [x] 7.7 Write property test: 备注 score 输入验证
- **Property 12: 备注 score 输入验证**
- 生成器st.integers()
- 验证1-5 接受、超范围拒绝 422、null 接受
- **Validates: Requirements 6.3**
- [x] 8. xcx_performance Router组件 2 — 新增)
- [x] 8.1 新建 `apps/backend/app/routers/xcx_performance.py`,实现 `GET /api/xcx/performance` 端点(接受 year/month 参数),调用 `performance_service.get_overview()`,含 require_approved() 权限校验
- _Requirements: 3.1, 3.2_
- [x] 8.2 在 `routers/xcx_performance.py` 中实现 `GET /api/xcx/performance/records` 端点(接受 year/month/page/pageSize 参数),调用 `performance_service.get_records()`
- _Requirements: 4.1, 4.2_
- [x] 8.3 修改 `apps/backend/app/main.py`,导入并注册 `xcx_performance.router`
- _Requirements: 3.1, 4.1_
- [x] 9. Checkpoint — 后端路由层验证
- Ensure all tests pass, ask the user if questions arise.
- [x] 10. 前端适配(组件 7
- [x] 10.1 修改 `apps/miniprogram/miniprogram/services/api.ts`:新增 `pinTask()``unpinTask()``fetchTaskDetail()``fetchPerformanceOverview(year, month)``fetchPerformanceRecords(year, month, page)` 函数;`createNote()` 增加可选 `score` 参数
- _Requirements: 5.6, 6.1, 6.2, 7.1_
- [x] 10.2 修改 `apps/miniprogram/miniprogram/pages/task-list/task-list.ts`:消费 `TaskListResponse` 新结构items + performance`buildPerfData()` 使用 15+ 字段绩效数据,移除 mock 数据
- _Requirements: 6.4, 6.5_
- [x] 10.3 修改 task-detail 页面:调用 `fetchTaskDetail()`;根据 `balance` 前端本地计算 `storageLevel`,根据 `heartScore` 计算 `relationLevel`/`relationLevelText`/`relationColor`
- _Requirements: 6.4, 6.5_
- [x] 10.4 修改 `apps/miniprogram/miniprogram/pages/performance/performance.ts`:添加月份切换控件(左右箭头 + 月份标签),实现 `switchMonth()` 调用 `fetchPerformanceOverview`,含加载状态管理
- _Requirements: 7.1, 7.2, 7.3_
- [x] 10.5 修改 `apps/miniprogram/miniprogram/pages/performance-records/performance-records.ts`:修复 `switchMonth()` 中 page 未重置的 Bug切换月份时清空记录列表并重新加载page 重置为 1
- _Requirements: 7.4, 7.5_
- [x] 10.6 在 performance 和 performance-records 页面中,使用 `nameToAvatarColor()``customerName` 计算 `avatarChar`/`avatarColor`,不依赖后端
- _Requirements: 7.6_
- [x] 10.7 Write property test: 前端派生字段计算
- **Property 11: 前端派生字段计算**
- 生成器st.text(min_size=1) name + st.floats(0, 1e6) balance + st.floats(0, 10) heartScore
- 验证avatarChar=name[0]、确定性、storageLevel/relationLevel 单调性
- **Validates: Requirements 6.4, 6.5, 7.6**
- [x] 11. Final checkpoint — 全量验证
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- 组件 3fdw_queries.py是所有 FDW 查询的集中封装,组件 4/5 通过调用它访问 ETL 数据
- 组件 4 扩展现有 task_manager.py不新建 task_perf_service.py组件 5 新建 performance_service.py
- Schema 文件命名:`xcx_tasks.py`(扩展)+ `xcx_performance.py`(新增),与 design.md 一致
- 所有 12 个正确性属性P1-P12均有对应的属性测试任务
- DWD-DOC 强制规则在 fdw_queries.py 中统一实施items_sum 口径、费用拆分、DQ-6/DQ-7 JOIN、废单排除