# Requirements Document

## Introduction

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` 区分三种赠送卡类型（酒水卡、台费卡、抵用券），数据源完备。

本需求在 DWS 层新增 6 个字段（3 种卡类型 × 余额+新增），修改 ETL 拆分填充逻辑，更新数据库视图层，修改后端接口返回细分数据，并完成小程序联调，使赠送卡矩阵正确展示按卡类型拆分的数据。

关键约束：消费行无法按卡类型拆分（上游飞球 API 的结算单 `dwd_settlement_head.gift_card_amount` 仅提供赠送卡消费总额，不提供按卡类型的消费明细）。

## Glossary

- **DWS_Finance_Recharge_Summary**: DWS 层充值汇总表（`dws.dws_finance_recharge_summary`），按 `site_id` + `summary_date` 存储每日充值/余额汇总数据
- **ETL_Finance_Recharge_Task**: ETL 任务类 `FinanceRechargeTask`，负责从 DWD 层提取数据并写入 DWS 层充值汇总表
- **Gift_Card_Matrix**: 赠送卡矩阵，BOARD-3 财务看板中的 3×4 数据表格（行：新增/消费/余额；列：合计/酒水卡/台费卡/抵用券）
- **DWD_Dim_Member_Card_Account**: DWD 层会员卡账户维度表（`dwd.dim_member_card_account`），通过 `card_type_id` 区分卡类型
- **DWD_Recharge_Order**: DWD 层充值订单表（`dwd.dwd_recharge_order`），包含充值金额 `point_amount`（赠送部分）
- **RLS_View**: 行级安全视图 `app.v_dws_finance_recharge_summary`，基于 `site_id` 实现多门店数据隔离
- **FDW_Foreign_Table**: 通过 `postgres_fdw` 从 ETL 库映射到业务库的外部表
- **Finance_Board_API**: FastAPI 接口 `GET /api/xcx/board/finance`，返回财务看板数据（含 `RechargePanel.gift_rows`）
- **Board_Finance_Page**: 微信小程序页面 `board-finance`，渲染财务看板赠送卡矩阵
- **FDW_Queries_Service**: 后端服务 `fdw_queries.py`，封装 FDW 外部表的 SQL 查询逻辑
- **Card_Type_ID**: 飞球系统中区分卡类型的标识符，三种赠送卡 ID 已在 ETL 中硬编码为 `GIFT_CARD_TYPE_IDS`

## Requirements

### Requirement 1: DWS 层赠送卡细分字段扩展

**User Story:** 作为数据工程师，我需要在 DWS 充值汇总表中新增赠送卡按卡类型拆分的字段，以便下游查询能获取细分数据。

#### Acceptance Criteria

1. WHEN DDL 迁移脚本执行完成, THE DWS_Finance_Recharge_Summary SHALL 包含以下 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)）
2. THE DWS_Finance_Recharge_Summary SHALL 对所有 6 个新增字段设置默认值为 0
3. WHEN 新增字段写入完成, THE DWS_Finance_Recharge_Summary SHALL 满足余额恒等式：`gift_card_balance = gift_liquor_balance + gift_table_fee_balance + gift_voucher_balance`
4. WHEN 新增字段写入完成, THE DWS_Finance_Recharge_Summary SHALL 满足新增恒等式：`recharge_gift = gift_liquor_recharge + gift_table_fee_recharge + gift_voucher_recharge`

### Requirement 2: ETL 赠送卡余额按卡类型拆分

**User Story:** 作为数据工程师，我需要 ETL 任务按卡类型拆分赠送卡余额，以便 DWS 层存储各类型赠送卡的当日末余额。

#### Acceptance Criteria

1. WHEN ETL_Finance_Recharge_Task 执行余额提取, THE ETL_Finance_Recharge_Task SHALL 从 DWD_Dim_Member_Card_Account 按 `card_type_id` 分组查询三种赠送卡（酒水卡 2794699703437125、台费卡 2791990152417157、抵用券 2793266846533445）的余额合计
2. WHEN ETL_Finance_Recharge_Task 完成余额提取, THE ETL_Finance_Recharge_Task SHALL 将三种赠送卡余额分别写入 `gift_liquor_balance`、`gift_table_fee_balance`、`gift_voucher_balance` 字段
3. IF DWD_Dim_Member_Card_Account 中某种赠送卡类型无记录, THEN THE ETL_Finance_Recharge_Task SHALL 将该类型余额字段写入 0

### Requirement 3: ETL 赠送卡新增充值按卡类型拆分

**User Story:** 作为数据工程师，我需要 ETL 任务按卡类型拆分赠送卡新增充值金额，以便 DWS 层存储各类型赠送卡的每日新增数据。

#### Acceptance Criteria

1. WHEN ETL_Finance_Recharge_Task 执行新增充值提取, THE ETL_Finance_Recharge_Task SHALL 通过 DWD_Recharge_Order JOIN DWD_Dim_Member_Card_Account（关联 `tenant_member_card_id` → `tenant_member_id`）按 `card_type_id` 分组查询三种赠送卡的 `point_amount` 合计
2. WHEN ETL_Finance_Recharge_Task 完成新增充值提取, THE ETL_Finance_Recharge_Task SHALL 将三种赠送卡新增金额分别写入 `gift_liquor_recharge`、`gift_table_fee_recharge`、`gift_voucher_recharge` 字段
3. IF DWD_Recharge_Order 中某种赠送卡类型在指定日期无充值记录, THEN THE ETL_Finance_Recharge_Task SHALL 将该类型新增字段写入 0

### Requirement 4: ETL Transform 阶段写入细分字段

**User Story:** 作为数据工程师，我需要 ETL 的 transform 阶段将提取到的细分数据正确写入 record dict，以便 load 阶段持久化到 DWS 表。

#### Acceptance Criteria

1. WHEN ETL_Finance_Recharge_Task 执行 transform, THE ETL_Finance_Recharge_Task SHALL 将余额提取结果（3 个字段）和新增充值提取结果（3 个字段）合并写入当日 record dict
2. WHEN ETL_Finance_Recharge_Task 执行 transform 且提取方法返回空结果, THE ETL_Finance_Recharge_Task SHALL 将对应字段填充为 0
3. THE ETL_Finance_Recharge_Task SHALL 使用与现有字段一致的 delete-before-insert 幂等策略写入 6 个新增字段

### Requirement 5: RLS 视图同步新增字段

**User Story:** 作为数据工程师，我需要 RLS 视图包含新增的 6 个字段，以便业务库通过视图访问细分数据并保持行级安全隔离。

#### Acceptance Criteria

1. WHEN RLS_View 重建完成, THE RLS_View SHALL 包含 DWS_Finance_Recharge_Summary 的全部 6 个新增字段
2. THE RLS_View SHALL 对新增字段保持与现有字段一致的 `site_id` 行级安全过滤策略
3. WHEN 非授权门店用户查询 RLS_View, THE RLS_View SHALL 不返回其他门店的赠送卡细分数据

### Requirement 6: FDW 外部表同步

**User Story:** 作为数据工程师，我需要 FDW 外部表同步新增字段，以便后端应用通过业务库访问 DWS 层的赠送卡细分数据。

#### Acceptance Criteria

1. WHEN `IMPORT FOREIGN SCHEMA` 重新执行, THE FDW_Foreign_Table SHALL 包含 DWS_Finance_Recharge_Summary 的全部 6 个新增字段
2. THE FDW_Foreign_Table SHALL 通过幂等脚本完成同步，支持重复执行不报错

### Requirement 7: 后端接口返回赠送卡细分数据

**User Story:** 作为门店管理者，我需要财务看板接口返回赠送卡按卡类型拆分的数据，以便小程序展示完整的赠送卡矩阵。

#### Acceptance Criteria

1. WHEN Finance_Board_API 被调用, THE FDW_Queries_Service SHALL 在 SQL 查询中包含 6 个新增字段的 SUM 聚合
2. WHEN Finance_Board_API 返回 `gift_rows`, THE Finance_Board_API SHALL 在余额行的 `liquor`、`table_fee`、`voucher` 列返回对应的细分余额数值（非 0）
3. WHEN Finance_Board_API 返回 `gift_rows`, THE Finance_Board_API SHALL 在新增行的 `liquor`、`table_fee`、`voucher` 列返回对应的细分新增数值（非 0，当日有充值时）
4. WHEN Finance_Board_API 返回 `gift_rows` 且消费行无法按卡类型拆分, THE Finance_Board_API SHALL 在消费行的 `liquor`、`table_fee`、`voucher` 列返回 0，仅 `total` 列返回消费总额
5. WHEN Finance_Board_API 被调用且无数据, THE FDW_Queries_Service SHALL 返回所有新增字段默认值为 0 的空数据结构
6. WHEN Finance_Board_API 被调用且 compare 参数启用, THE Finance_Board_API SHALL 对赠送卡细分字段正确计算环比数据

### Requirement 8: 消费行数据处理策略

**User Story:** 作为门店管理者，我需要了解赠送卡消费行的数据展示策略，以便正确理解矩阵中消费数据的含义。

#### Acceptance Criteria

1. THE Finance_Board_API SHALL 在赠送卡矩阵消费行的 `total` 列返回赠送卡消费总额（来源 `dwd_settlement_head.gift_card_amount` 汇总）
2. WHILE 上游飞球 API 不提供按卡类型拆分的消费明细, THE Finance_Board_API SHALL 在消费行的 `liquor`、`table_fee`、`voucher` 列返回 0
3. IF 未来上游 API 提供按卡类型拆分的消费明细, THEN THE ETL_Finance_Recharge_Task SHALL 扩展消费拆分字段（不在本次需求范围内）

### Requirement 9: 小程序赠送卡矩阵渲染

**User Story:** 作为门店管理者，我需要在小程序财务看板中看到赠送卡矩阵正确展示后端返回的细分数据，以便了解各类赠送卡的使用情况。

#### Acceptance Criteria

1. WHEN Board_Finance_Page 加载完成, THE Board_Finance_Page SHALL 从 Finance_Board_API 获取真实数据替换当前 mock 数据
2. WHEN Board_Finance_Page 渲染赠送卡矩阵, THE Board_Finance_Page SHALL 按字段映射关系正确展示数据：`liquor` → 酒水卡列、`table_fee` → 台费卡列、`voucher` → 抵用券列
3. WHEN Finance_Board_API 返回环比数据, THE Board_Finance_Page SHALL 在对应单元格展示环比变化标识
4. IF Finance_Board_API 返回错误或超时, THEN THE Board_Finance_Page SHALL 展示加载失败提示，不显示 mock 数据

### Requirement 10: 数据一致性验证

**User Story:** 作为数据工程师，我需要验证 ETL 写入的细分数据与 DWD 层源数据一致，以便确保数据准确性。

#### Acceptance Criteria

1. WHEN ETL 跑数完成, THE DWS_Finance_Recharge_Summary SHALL 满足：三种赠送卡余额之和（`gift_liquor_balance + gift_table_fee_balance + gift_voucher_balance`）等于 DWD_Dim_Member_Card_Account 中对应三种 `card_type_id` 的 `balance` 之和
2. WHEN ETL 跑数完成, THE DWS_Finance_Recharge_Summary SHALL 满足：三种赠送卡新增之和（`gift_liquor_recharge + gift_table_fee_recharge + gift_voucher_recharge`）等于 DWD_Recharge_Order 中对应三种卡类型当日 `point_amount` 之和
3. THE DWS_Finance_Recharge_Summary SHALL 满足：`gift_card_balance` 字段值等于 `gift_liquor_balance + gift_table_fee_balance + gift_voucher_balance`（总额与细分之和恒等）
