Files
Neo-ZQYY/docs/prd/specs/P12-gift-card-breakdown.md

237 lines
11 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.
# P12赠送卡矩阵细分数据 — gift-card-breakdown
> 优先级P1BOARD-3 财务看板功能缺陷修复)
> 来源RNS1 系列审计遗留项 P1-6
> 预估工作量:中
> 依赖无新增依赖DWD 层数据已就绪)
---
## 背景
BOARD-3 财务看板的「预收资产」板块包含一个赠送卡 3×4 矩阵3 行:新增/消费/余额4 列:合计/酒水卡/台费卡/抵用券)。当前矩阵除余额行的 total 列外,所有细分单元格均返回 0。
根因DWS 层 `dws_finance_recharge_summary` 只存储赠送卡总额(`recharge_gift``gift_card_balance`),未按卡类型拆分。而 DWD 层 `dim_member_card_account` 已通过 `card_type_id` 区分三种赠送卡类型,数据源完备。
---
## 需求Requirements
### 用户故事
1. 作为门店管理者,我需要在财务看板中看到赠送卡按用途(酒水/台费/抵用券)拆分的余额、新增、消费数据,以便了解各类赠送卡的使用情况和资金分布。
### 验收标准
- AC1`dws_finance_recharge_summary` 新增 6 个字段3 种卡类型 × 余额+新增ETL 任务正确填充
- AC2BOARD-3 赠送卡矩阵「余额」行的 liquor/table_fee/voucher 列显示正确数值
- AC3赠送卡矩阵「新增」行显示各类型赠送卡的新增金额按充值订单的 `point_amount` 拆分)
- AC4赠送卡矩阵「消费」行显示各类型赠送卡的消费金额如 DWD 层可追溯)
- AC5环比数据正确计算如启用 compare 参数)
- AC6RLS 视图自动包含新字段(`CREATE OR REPLACE VIEW` 使用 `SELECT *` 或显式列名)
- AC7FDW 外部表通过 `IMPORT FOREIGN SCHEMA` 自动同步
- AC8`GET /api/xcx/board/finance` 接口返回的 `gift_rows` 矩阵包含正确的细分数据(非全 0
- AC9小程序 `board-finance` 页面赠送卡矩阵正确渲染后端返回的细分数据(替换 mock
---
## 数据源分析
### DWD 层现有数据
`dwd.dim_member_card_account` 通过 `card_type_id` 区分卡类型:
| card_type_id | 类型 | 当前记录数 | 当前余额 |
|---|---|---|---|
| 2793249295533893 | 储值卡 | 421 | 128,918.32 |
| 2791990152417157 | 台费卡(赠送) | 343 | 246,267.50 |
| 2793266846533445 | 活动抵用券(赠送) | 118 | 24,978.70 |
| 2794699703437125 | 酒水卡(赠送) | 49 | 3,708.95 |
| 2791987095408517 | 年卡 | 7 | 7.00 |
| 2793306611533637 | 月卡 | 12 | 4,938.00 |
ETL 任务 `FinanceRechargeTask._extract_card_balances()` 已硬编码三个赠送卡 ID
```python
GIFT_CARD_TYPE_IDS = [2791990152417157, 2793266846533445, 2794699703437125]
```
### 矩阵数据需求
| 行 | 数据来源 | 说明 |
|---|---|---|
| 余额 | `dim_member_card_account``card_type_id` 分组 | 当日末快照,已有数据 |
| 新增 | `dwd_recharge_order.point_amount` 按会员卡类型拆分 | 需要 JOIN `dim_member_card_account` 确定卡类型 |
| 消费 | `dwd_settlement_head.gift_card_amount` | 总额已有,但无法按卡类型拆分(结算单不记录具体使用哪种赠送卡) |
### 关键约束
- **消费行拆分可能不可行**`dwd_settlement_head.gift_card_amount` 是赠送卡消费总额,飞球上游 API 不提供按卡类型拆分的消费明细。需确认是否有 `dwd_settlement_head_ex` 或其他扩展表包含此信息。
- **新增行拆分**:充值订单 `dwd_recharge_order``tenant_member_card_id` 可关联 `dim_member_card_account.tenant_member_id`,从而确定充值到哪种卡类型。
- **card_type_id 硬编码**:当前 ETL 已硬编码,本次扩展沿用同一套 ID。后续可考虑迁移至配置表feiqiu-data-rules 规则 6 精神)。
---
## 设计要点
### DDL 变更(`dws_finance_recharge_summary`
新增 6 个字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| `gift_liquor_balance` | NUMERIC(14,2) | 酒水卡余额(当日末) |
| `gift_table_fee_balance` | NUMERIC(14,2) | 台费卡余额(当日末) |
| `gift_voucher_balance` | NUMERIC(14,2) | 抵用券余额(当日末) |
| `gift_liquor_recharge` | NUMERIC(14,2) | 酒水卡新增充值(赠送部分) |
| `gift_table_fee_recharge` | NUMERIC(14,2) | 台费卡新增充值(赠送部分) |
| `gift_voucher_recharge` | NUMERIC(14,2) | 抵用券新增充值(赠送部分) |
> 消费行拆分字段暂不新增(待确认上游数据可行性)。消费行 total 可通过 `gift_card_balance` 变化量 + 新增量反推(`consumed = prev_balance + recharge - current_balance`),但精度依赖余额快照的连续性。
### ETL 任务修改
修改 `FinanceRechargeTask._extract_card_balances()`
-`gift_balance` 拆分为 3 个细分余额
- 新增 `_extract_gift_recharge_breakdown()` 方法,通过 `dwd_recharge_order JOIN dim_member_card_account` 按卡类型拆分赠送金额
### 后端接口修改
接口路径:`GET /api/xcx/board/finance`BOARD-3 财务看板)
- 路由:`apps/backend/app/routers/xcx_board.py``get_finance_board()`
- 权限:`view_board_finance`
涉及文件与修改点:
| 文件 | 修改点 |
|---|---|
| `apps/backend/app/services/fdw_queries.py` | `get_finance_recharge()` — SQL 新增 6 个字段的 SUM填充 `gift_rows` 矩阵的余额行和新增行细分数据 |
| `apps/backend/app/services/fdw_queries.py` | `_empty_recharge_data()` — 空默认值同步新增字段 |
| `apps/backend/app/services/board_service.py` | `_build_recharge()` — 环比计算已覆盖 `gift_rows` 所有 cell无需额外修改自动适配 |
| `apps/backend/app/schemas/xcx_board.py` | `GiftCell` / `GiftRow` / `RechargePanel` — schema 无需修改(已预留 `liquor`/`table_fee`/`voucher` 字段) |
> 消费行:如果无法直接拆分,使用 `余额变化量反推法` 或保持 total only。
### 小程序页面影响
页面路径:`apps/miniprogram/miniprogram/pages/board-finance/`
| 文件 | 当前状态 | 修改点 |
|---|---|---|
| `board-finance.wxml` (L316-370) | 赠送卡矩阵已渲染 `recharge.giftRows`3 行 × 4 列(酒水卡/台费卡/抵用券) | 无需修改(模板已就绪,数据绑定字段已对齐) |
| `board-finance.ts` | 当前使用 mock 数据(`giftRows` 硬编码) | 联调时替换 mock 为真实 API 调用(非本 SPEC 范围,属于联调阶段) |
| `board-finance.wxss` (L643-717) | 赠送卡表格样式已完成 | 无需修改 |
字段映射关系(后端 → 小程序):
| 后端字段 | 小程序绑定 |
|---|---|
| `GiftRow.total.value` | `item.total` |
| `GiftRow.liquor.value` | `item.wine` |
| `GiftRow.table_fee.value` | `item.table` |
| `GiftRow.voucher.value` | `item.coupon` |
| `GiftRow.*.compare` | `item.*Compare` |
> 小程序页面模板和样式已在 RNS1 阶段完成,本次只需后端返回正确数据即可自动渲染。联调阶段需将 mock 数据替换为真实 API 调用。
### 管理后台
admin-web 无涉及赠送卡/充值统计的页面,不受影响。
### RLS 视图 & FDW
- `app.v_dws_finance_recharge_summary` 使用 `CREATE OR REPLACE VIEW`,需要更新列列表
- FDW 使用 `IMPORT FOREIGN SCHEMA`,需要重新导入(幂等脚本已支持)
---
## 数据流向
```
DWD 层
├─ dwd_recharge_order.point_amount ──→ JOIN dim_member_card_account ──→ 按 card_type_id 拆分赠送金额
├─ dim_member_card_account.balance ──→ 按 card_type_id 分组求和 ──→ 3 种赠送卡余额
└─ dwd_settlement_head.gift_card_amount ──→ 总额(无法按卡类型拆分)
↓ ETLFinanceRechargeTask
DWS 层dws_finance_recharge_summary+6 字段)
↓ RLS 视图 + FDW
业务库app.v_dws_finance_recharge_summary
↓ fdw_queries.get_finance_recharge()
FastAPIGET /api/xcx/board/finance → RechargePanel.gift_rows
↓ 小程序渲染
board-finance 页面:赠送卡矩阵 3×4 显示正确数值
```
---
## 待确认项
1. **消费行拆分**`dwd_settlement_head_ex` 或其他表是否包含赠送卡消费的卡类型明细?如果没有,消费行只能显示 total细分列保持 0 或使用反推法。
2. **card_type_id 2791987095408517 和 2793306611533637**:已确认为年卡和月卡,不纳入赠送卡矩阵。
3. **环比需求**:赠送卡矩阵的每个 cell 是否需要环比数据compare 字段)?当前 schema `GiftCell` 已预留 `compare: str | None`
---
## 任务清单
### 数据层ETL + DDL
- [ ] T1DDL 迁移 — `dws_finance_recharge_summary` 新增 6 个字段
- 迁移脚本:`db/etl_feiqiu/migrations/2026-xx-xx_add_gift_breakdown_fields.sql`
- DDL 基线同步:`docs/database/ddl/etl_feiqiu__dws.sql`
- [ ] T2ETL 修改 — `_extract_card_balances()` 拆分赠送卡余额
- 文件:`apps/etl/connectors/feiqiu/tasks/dws/finance_recharge_task.py`
- 返回值新增 `gift_liquor_balance``gift_table_fee_balance``gift_voucher_balance`
- [ ] T3ETL 新增 — `_extract_gift_recharge_breakdown()` 拆分赠送卡新增
- 文件:同 T2
- SQL`dwd_recharge_order JOIN dim_member_card_account``card_type_id` 分组
- [ ] T4ETL transform — `transform()` 方法写入新增 6 个字段到 record dict
### 数据库视图层
- [ ] T5RLS 视图更新 — `app.v_dws_finance_recharge_summary` 重建
- 迁移脚本:`db/etl_feiqiu/migrations/``db/zqyy_app/migrations/`
- [ ] T6FDW 外部表同步 — `IMPORT FOREIGN SCHEMA` 重新导入
### 后端接口层
- [ ] T7后端修改 — `fdw_queries.get_finance_recharge()`
- 文件:`apps/backend/app/services/fdw_queries.py`
- SQL 新增 6 个字段的 SUM
- 填充 `gift_rows` 矩阵:余额行 + 新增行的 liquor/table_fee/voucher
- [ ] T8后端修改 — `_empty_recharge_data()` 空默认值同步
### 小程序页面层
- [ ] T9小程序联调 — `board-finance.ts` 替换 mock 数据为真实 API 调用
- 文件:`apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts`
- 当前状态mock 数据硬编码,模板和样式已就绪
- 注意:字段映射 `liquor→wine``table_fee→table``voucher→coupon` 需在数据转换层处理
### 验证与文档
- [ ] T10验证 — 跑数后对比 `dim_member_card_account` 余额与 DWS 汇总值
- [ ] T11BD 手册更新 — `BD_manual_dws_finance_recharge_summary.md`
### 涉及文件汇总
| 模块 | 文件路径 | 操作 |
|---|---|---|
| DDL | `db/etl_feiqiu/migrations/2026-xx-xx_add_gift_breakdown_fields.sql` | 新增 |
| DDL 基线 | `docs/database/ddl/etl_feiqiu__dws.sql` | 修改 |
| ETL | `apps/etl/connectors/feiqiu/tasks/dws/finance_recharge_task.py` | 修改 |
| RLS 视图 | `db/etl_feiqiu/migrations/``db/zqyy_app/migrations/` | 新增 |
| 后端 | `apps/backend/app/services/fdw_queries.py` | 修改 |
| 小程序 | `apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts` | 修改 |
| Schema | `apps/backend/app/schemas/xcx_board.py` | 无需修改(已预留) |
| 路由 | `apps/backend/app/routers/xcx_board.py` | 无需修改 |
| 服务 | `apps/backend/app/services/board_service.py` | 无需修改(环比自动适配) |
| BD 手册 | `apps/etl/connectors/feiqiu/docs/database/DWS/main/BD_manual_dws_finance_recharge_summary.md` | 修改 |