# P12:赠送卡矩阵细分数据 — gift-card-breakdown > 优先级:P1(BOARD-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 任务正确填充 - AC2:BOARD-3 赠送卡矩阵「余额」行的 liquor/table_fee/voucher 列显示正确数值 - AC3:赠送卡矩阵「新增」行显示各类型赠送卡的新增金额(按充值订单的 `point_amount` 拆分) - AC4:赠送卡矩阵「消费」行显示各类型赠送卡的消费金额(如 DWD 层可追溯) - AC5:环比数据正确计算(如启用 compare 参数) - AC6:RLS 视图自动包含新字段(`CREATE OR REPLACE VIEW` 使用 `SELECT *` 或显式列名) - AC7:FDW 外部表通过 `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 ──→ 总额(无法按卡类型拆分) ↓ ETL(FinanceRechargeTask) DWS 层:dws_finance_recharge_summary(+6 字段) ↓ RLS 视图 + FDW 业务库:app.v_dws_finance_recharge_summary ↓ fdw_queries.get_finance_recharge() FastAPI:GET /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) - [ ] T1:DDL 迁移 — `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` - [ ] T2:ETL 修改 — `_extract_card_balances()` 拆分赠送卡余额 - 文件:`apps/etl/connectors/feiqiu/tasks/dws/finance_recharge_task.py` - 返回值新增 `gift_liquor_balance`、`gift_table_fee_balance`、`gift_voucher_balance` - [ ] T3:ETL 新增 — `_extract_gift_recharge_breakdown()` 拆分赠送卡新增 - 文件:同 T2 - SQL:`dwd_recharge_order JOIN dim_member_card_account` 按 `card_type_id` 分组 - [ ] T4:ETL transform — `transform()` 方法写入新增 6 个字段到 record dict ### 数据库视图层 - [ ] T5:RLS 视图更新 — `app.v_dws_finance_recharge_summary` 重建 - 迁移脚本:`db/etl_feiqiu/migrations/` 或 `db/zqyy_app/migrations/` - [ ] T6:FDW 外部表同步 — `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 汇总值 - [ ] T11:BD 手册更新 — `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` | 修改 |