482 lines
20 KiB
Markdown
482 lines
20 KiB
Markdown
# Design Document: 赠送卡矩阵细分数据 (gift-card-breakdown)
|
||
|
||
## Overview
|
||
|
||
本设计解决 BOARD-3 财务看板赠送卡 3×4 矩阵中细分单元格全部返回 0 的问题。根因是 DWS 层 `dws_finance_recharge_summary` 仅存储赠送卡总额,未按卡类型(酒水卡/台费卡/抵用券)拆分。
|
||
|
||
改动贯穿全栈数据链路:DDL 新增 6 个字段 → ETL 拆分填充 → RLS 视图 + FDW 同步 → 后端 SQL 查询 + 接口返回 → 小程序替换 mock 数据。
|
||
|
||
关键约束:消费行无法按卡类型拆分(上游飞球 API 的 `dwd_settlement_head.gift_card_amount` 仅提供总额),消费行细分列保持 0。
|
||
|
||
## Architecture
|
||
|
||
### 数据流向
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
subgraph DWD["DWD 层(数据源)"]
|
||
DCA["dim_member_card_account<br/>card_type_id + balance"]
|
||
DRO["dwd_recharge_order<br/>point_amount"]
|
||
DSH["dwd_settlement_head<br/>gift_card_amount(仅总额)"]
|
||
end
|
||
|
||
subgraph ETL["ETL(FinanceRechargeTask)"]
|
||
ECB["_extract_card_balances()<br/>按 card_type_id 分组余额"]
|
||
EGR["_extract_gift_recharge_breakdown()<br/>JOIN 按 card_type_id 分组新增"]
|
||
TF["transform()<br/>合并 6 个新字段到 record"]
|
||
end
|
||
|
||
subgraph DWS["DWS 层"]
|
||
DFS["dws_finance_recharge_summary<br/>+6 新字段"]
|
||
end
|
||
|
||
subgraph VIEW["视图 + FDW"]
|
||
RLS["app.v_dws_finance_recharge_summary<br/>RLS 行级安全"]
|
||
FDW["FDW 外部表<br/>业务库映射"]
|
||
end
|
||
|
||
subgraph BACKEND["后端"]
|
||
FQ["fdw_queries.get_finance_recharge()<br/>SQL SUM 6 字段"]
|
||
BS["board_service._build_recharge()<br/>环比计算(自动适配)"]
|
||
end
|
||
|
||
subgraph MP["小程序"]
|
||
BF["board-finance.ts<br/>替换 mock → API 调用"]
|
||
end
|
||
|
||
DCA --> ECB
|
||
DRO -->|JOIN| EGR
|
||
DCA -->|card_type_id| EGR
|
||
DSH -.->|总额,无法拆分| TF
|
||
ECB --> TF
|
||
EGR --> TF
|
||
TF --> DFS
|
||
DFS --> RLS
|
||
RLS --> FDW
|
||
FDW --> FQ
|
||
FQ --> BS
|
||
BS --> BF
|
||
```
|
||
|
||
### 改动范围
|
||
|
||
| 层级 | 文件 | 改动类型 |
|
||
|------|------|----------|
|
||
| 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/zqyy_app/migrations/` | 新增 |
|
||
| FDW | `db/fdw/` | 新增/修改 |
|
||
| 后端 | `apps/backend/app/services/fdw_queries.py` | 修改 |
|
||
| 小程序 | `apps/miniprogram/miniprogram/pages/board-finance/board-finance.ts` | 修改 |
|
||
| BD 手册 | `apps/etl/connectors/feiqiu/docs/database/DWS/main/BD_manual_dws_finance_recharge_summary.md` | 修改 |
|
||
|
||
不需要修改的文件:
|
||
- `apps/backend/app/schemas/xcx_board.py` — `GiftCell`/`GiftRow`/`RechargePanel` 已预留 `liquor`/`table_fee`/`voucher` 字段
|
||
- `apps/backend/app/services/board_service.py` — `_build_recharge()` 环比逻辑已遍历 `gift_rows` 所有 cell,自动适配
|
||
- `apps/backend/app/routers/xcx_board.py` — 路由无需变更
|
||
|
||
## Components and Interfaces
|
||
|
||
### 1. DDL 迁移脚本
|
||
|
||
新增 6 个 `NUMERIC(14,2)` 字段到 `dws.dws_finance_recharge_summary`:
|
||
|
||
```sql
|
||
ALTER TABLE dws.dws_finance_recharge_summary
|
||
ADD COLUMN IF NOT EXISTS gift_liquor_balance NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||
ADD COLUMN IF NOT EXISTS gift_table_fee_balance NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||
ADD COLUMN IF NOT EXISTS gift_voucher_balance NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||
ADD COLUMN IF NOT EXISTS gift_liquor_recharge NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||
ADD COLUMN IF NOT EXISTS gift_table_fee_recharge NUMERIC(14,2) NOT NULL DEFAULT 0,
|
||
ADD COLUMN IF NOT EXISTS gift_voucher_recharge NUMERIC(14,2) NOT NULL DEFAULT 0;
|
||
```
|
||
|
||
### 2. ETL — `FinanceRechargeTask` 修改
|
||
|
||
#### 2a. `_extract_card_balances()` 拆分
|
||
|
||
当前实现将三种赠送卡余额合并为 `gift_balance`。修改后按 `card_type_id` 分别返回:
|
||
|
||
```python
|
||
# 现有常量(沿用)
|
||
CASH_CARD_TYPE_ID = 2793249295533893
|
||
GIFT_CARD_TYPE_IDS = [2791990152417157, 2793266846533445, 2794699703437125]
|
||
|
||
# card_type_id → 字段名映射
|
||
GIFT_TYPE_FIELD_MAP = {
|
||
2794699703437125: 'gift_liquor_balance', # 酒水卡
|
||
2791990152417157: 'gift_table_fee_balance', # 台费卡
|
||
2793266846533445: 'gift_voucher_balance', # 抵用券
|
||
}
|
||
```
|
||
|
||
返回值新增 3 个 key:`gift_liquor_balance`、`gift_table_fee_balance`、`gift_voucher_balance`。原有 `gift_balance` 保持不变(向后兼容)。
|
||
|
||
#### 2b. 新增 `_extract_gift_recharge_breakdown()`
|
||
|
||
通过 `dwd_recharge_order JOIN dim_member_card_account`(关联 `tenant_member_card_id` → `tenant_member_id`)按 `card_type_id` 分组查询赠送金额(`point_amount`):
|
||
|
||
```python
|
||
GIFT_RECHARGE_FIELD_MAP = {
|
||
2794699703437125: 'gift_liquor_recharge', # 酒水卡
|
||
2791990152417157: 'gift_table_fee_recharge', # 台费卡
|
||
2793266846533445: 'gift_voucher_recharge', # 抵用券
|
||
}
|
||
```
|
||
|
||
SQL 逻辑:
|
||
```sql
|
||
SELECT dca.card_type_id, SUM(ro.point_amount) AS gift_recharge
|
||
FROM dwd.dwd_recharge_order ro
|
||
JOIN dwd.dim_member_card_account dca
|
||
ON ro.tenant_member_card_id = dca.tenant_member_id
|
||
WHERE ro.site_id = %s
|
||
AND {biz_date_expr} >= %s AND {biz_date_expr} <= %s
|
||
AND dca.card_type_id IN (2794699703437125, 2791990152417157, 2793266846533445)
|
||
AND dca.scd2_is_current = 1
|
||
AND COALESCE(dca.is_delete, 0) = 0
|
||
GROUP BY dca.card_type_id
|
||
```
|
||
|
||
返回 `dict`:`{gift_liquor_recharge: Decimal, gift_table_fee_recharge: Decimal, gift_voucher_recharge: Decimal}`,缺失的卡类型默认 0。
|
||
|
||
#### 2c. `extract()` 调用新方法
|
||
|
||
```python
|
||
def extract(self, context):
|
||
# ... 现有逻辑 ...
|
||
recharge_summary = self._extract_recharge_summary(site_id, start_date, end_date)
|
||
card_balances = self._extract_card_balances(site_id, end_date)
|
||
gift_recharge_breakdown = self._extract_gift_recharge_breakdown(site_id, start_date, end_date)
|
||
return {
|
||
'recharge_summary': recharge_summary,
|
||
'card_balances': card_balances,
|
||
'gift_recharge_breakdown': gift_recharge_breakdown,
|
||
'start_date': start_date, 'end_date': end_date, 'site_id': site_id,
|
||
}
|
||
```
|
||
|
||
#### 2d. `transform()` 合并新字段
|
||
|
||
在 `record` dict 构建时追加 6 个字段:
|
||
|
||
```python
|
||
record = {
|
||
# ... 现有字段 ...
|
||
'gift_liquor_balance': self.safe_decimal(balance.get('gift_liquor_balance', 0)),
|
||
'gift_table_fee_balance': self.safe_decimal(balance.get('gift_table_fee_balance', 0)),
|
||
'gift_voucher_balance': self.safe_decimal(balance.get('gift_voucher_balance', 0)),
|
||
'gift_liquor_recharge': self.safe_decimal(gift_recharge.get('gift_liquor_recharge', 0)),
|
||
'gift_table_fee_recharge': self.safe_decimal(gift_recharge.get('gift_table_fee_recharge', 0)),
|
||
'gift_voucher_recharge': self.safe_decimal(gift_recharge.get('gift_voucher_recharge', 0)),
|
||
}
|
||
```
|
||
|
||
幂等策略:沿用现有 delete-before-insert(按 `site_id` + `stat_date`),新字段随整行写入。
|
||
|
||
### 3. RLS 视图重建
|
||
|
||
`app.v_dws_finance_recharge_summary` 需要 `CREATE OR REPLACE VIEW` 更新列列表,包含 6 个新字段。RLS 策略基于 `site_id`,新字段自动继承。
|
||
|
||
### 4. FDW 外部表同步
|
||
|
||
通过 `IMPORT FOREIGN SCHEMA` 重新导入,幂等脚本已支持(先 DROP 再 IMPORT)。
|
||
|
||
### 5. 后端 — `fdw_queries.get_finance_recharge()`
|
||
|
||
SQL 新增 6 个字段的 SUM 聚合:
|
||
|
||
```sql
|
||
SELECT SUM(recharge_cash) AS actual_income,
|
||
SUM(first_recharge_cash) AS first_charge,
|
||
SUM(renewal_cash) AS renew_charge,
|
||
SUM(cash_card_balance) AS card_balance,
|
||
SUM(total_card_balance) AS all_card_balance,
|
||
SUM(gift_card_balance) AS gift_balance_total,
|
||
-- 新增 6 字段
|
||
SUM(gift_liquor_balance) AS gift_liquor_balance,
|
||
SUM(gift_table_fee_balance) AS gift_table_fee_balance,
|
||
SUM(gift_voucher_balance) AS gift_voucher_balance,
|
||
SUM(gift_liquor_recharge) AS gift_liquor_recharge,
|
||
SUM(gift_table_fee_recharge) AS gift_table_fee_recharge,
|
||
SUM(gift_voucher_recharge) AS gift_voucher_recharge
|
||
FROM app.v_dws_finance_recharge_summary
|
||
WHERE stat_date >= %s::date AND stat_date <= %s::date
|
||
```
|
||
|
||
`gift_rows` 构建逻辑更新:
|
||
|
||
```python
|
||
gift_rows = [
|
||
{"label": "新增", "total": _gc(gift_liquor_recharge + gift_table_fee_recharge + gift_voucher_recharge),
|
||
"liquor": _gc(gift_liquor_recharge), "table_fee": _gc(gift_table_fee_recharge), "voucher": _gc(gift_voucher_recharge)},
|
||
{"label": "消费", "total": _gc(0.0), # 消费总额从现有字段获取
|
||
"liquor": _gc(0.0), "table_fee": _gc(0.0), "voucher": _gc(0.0)},
|
||
{"label": "余额", "total": _gc(gift_balance),
|
||
"liquor": _gc(gift_liquor_balance), "table_fee": _gc(gift_table_fee_balance), "voucher": _gc(gift_voucher_balance)},
|
||
]
|
||
```
|
||
|
||
注意:新增行的 `total` 使用三个细分字段之和(而非 `recharge_gift`),保证 `total = liquor + table_fee + voucher` 恒等。
|
||
|
||
### 6. 后端 — `_empty_recharge_data()`
|
||
|
||
空默认值结构不变(已经是全 0),无需修改。
|
||
|
||
### 7. 小程序 — `board-finance.ts`
|
||
|
||
替换 mock 数据为真实 API 调用。字段映射:
|
||
|
||
| 后端字段 | 小程序字段 |
|
||
|----------|-----------|
|
||
| `GiftRow.liquor.value` | `item.wine` |
|
||
| `GiftRow.table_fee.value` | `item.table` |
|
||
| `GiftRow.voucher.value` | `item.coupon` |
|
||
| `GiftCell.compare` | `item.*Compare` |
|
||
|
||
数据转换在 API 响应处理层完成(后端返回 camelCase,小程序按现有字段名映射)。
|
||
|
||
|
||
## Data Models
|
||
|
||
### DWS 表结构变更
|
||
|
||
`dws.dws_finance_recharge_summary` 新增字段:
|
||
|
||
| 字段名 | 类型 | 默认值 | 说明 |
|
||
|--------|------|--------|------|
|
||
| `gift_liquor_balance` | NUMERIC(14,2) | 0 | 酒水卡余额(当日末快照) |
|
||
| `gift_table_fee_balance` | NUMERIC(14,2) | 0 | 台费卡余额(当日末快照) |
|
||
| `gift_voucher_balance` | NUMERIC(14,2) | 0 | 抵用券余额(当日末快照) |
|
||
| `gift_liquor_recharge` | NUMERIC(14,2) | 0 | 酒水卡新增充值(赠送部分) |
|
||
| `gift_table_fee_recharge` | NUMERIC(14,2) | 0 | 台费卡新增充值(赠送部分) |
|
||
| `gift_voucher_recharge` | NUMERIC(14,2) | 0 | 抵用券新增充值(赠送部分) |
|
||
|
||
### 恒等式约束
|
||
|
||
1. 余额恒等:`gift_card_balance = gift_liquor_balance + gift_table_fee_balance + gift_voucher_balance`
|
||
2. 新增恒等:`recharge_gift = gift_liquor_recharge + gift_table_fee_recharge + gift_voucher_recharge`
|
||
|
||
### 卡类型 ID 映射(硬编码常量)
|
||
|
||
| card_type_id | 类型 | 余额字段 | 新增字段 |
|
||
|---|---|---|---|
|
||
| 2794699703437125 | 酒水卡 | `gift_liquor_balance` | `gift_liquor_recharge` |
|
||
| 2791990152417157 | 台费卡 | `gift_table_fee_balance` | `gift_table_fee_recharge` |
|
||
| 2793266846533445 | 抵用券 | `gift_voucher_balance` | `gift_voucher_recharge` |
|
||
|
||
### 现有 Schema(无需修改)
|
||
|
||
`GiftCell`(Pydantic):
|
||
```python
|
||
class GiftCell(CamelModel):
|
||
value: float
|
||
compare: str | None = None
|
||
down: bool | None = None
|
||
flat: bool | None = None
|
||
```
|
||
|
||
`GiftRow`(Pydantic):
|
||
```python
|
||
class GiftRow(CamelModel):
|
||
label: str # "新增" / "消费" / "余额"
|
||
total: GiftCell
|
||
liquor: GiftCell
|
||
table_fee: GiftCell
|
||
voucher: GiftCell
|
||
```
|
||
|
||
### ETL 内部数据结构
|
||
|
||
`_extract_card_balances()` 返回值扩展:
|
||
```python
|
||
{
|
||
'cash_balance': Decimal,
|
||
'gift_balance': Decimal, # 保留(向后兼容)
|
||
'total_balance': Decimal,
|
||
'gift_liquor_balance': Decimal, # 新增
|
||
'gift_table_fee_balance': Decimal, # 新增
|
||
'gift_voucher_balance': Decimal, # 新增
|
||
}
|
||
```
|
||
|
||
`_extract_gift_recharge_breakdown()` 返回值:
|
||
```python
|
||
{
|
||
'gift_liquor_recharge': Decimal,
|
||
'gift_table_fee_recharge': Decimal,
|
||
'gift_voucher_recharge': Decimal,
|
||
}
|
||
```
|
||
|
||
|
||
## Correctness Properties
|
||
|
||
*A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
||
|
||
### Property 1: 余额恒等式
|
||
|
||
*For any* DWS 记录,`gift_card_balance` 的值必须等于 `gift_liquor_balance + gift_table_fee_balance + gift_voucher_balance`。即对于任意随机生成的三种赠送卡余额,写入 DWS 后总额字段与细分字段之和恒等。
|
||
|
||
**Validates: Requirements 1.3, 10.3**
|
||
|
||
### Property 2: 新增恒等式
|
||
|
||
*For any* DWS 记录,`recharge_gift` 的值必须等于 `gift_liquor_recharge + gift_table_fee_recharge + gift_voucher_recharge`。即对于任意随机生成的三种赠送卡新增金额,写入 DWS 后总额字段与细分字段之和恒等。
|
||
|
||
**Validates: Requirements 1.4**
|
||
|
||
### Property 3: ETL 余额提取 round-trip
|
||
|
||
*For any* 一组 `dim_member_card_account` 记录(包含随机的 `card_type_id` 和 `balance`),`_extract_card_balances()` 返回的各类型余额必须等于对应 `card_type_id` 的 `balance` 之和。当某种卡类型无记录时,对应余额为 0。
|
||
|
||
**Validates: Requirements 2.1, 2.2, 2.3, 10.1**
|
||
|
||
### Property 4: ETL 新增提取 round-trip
|
||
|
||
*For any* 一组 `dwd_recharge_order` + `dim_member_card_account` 记录(包含随机的 `card_type_id` 和 `point_amount`),`_extract_gift_recharge_breakdown()` 返回的各类型新增金额必须等于对应 `card_type_id` 的 `point_amount` 之和。当某种卡类型无充值记录时,对应新增为 0。
|
||
|
||
**Validates: Requirements 3.1, 3.2, 3.3, 10.2**
|
||
|
||
### Property 5: transform 正确合并细分字段
|
||
|
||
*For any* 随机生成的 `card_balances` dict(含 3 个余额字段)和 `gift_recharge_breakdown` dict(含 3 个新增字段),`transform()` 输出的 record 必须包含这 6 个字段且值与输入一致。当输入 dict 缺少某个 key 时,对应字段为 0。
|
||
|
||
**Validates: Requirements 4.1, 4.2**
|
||
|
||
### Property 6: 后端接口返回正确的细分数据
|
||
|
||
*For any* DWS 表中的一组记录(包含随机的 6 个细分字段值),`get_finance_recharge()` 返回的 `gift_rows` 中余额行的 `liquor`/`table_fee`/`voucher` 值必须等于对应字段的 SUM,新增行同理。
|
||
|
||
**Validates: Requirements 7.2, 7.3**
|
||
|
||
### Property 7: 消费行细分列始终为 0
|
||
|
||
*For any* 调用 `get_finance_recharge()` 的返回结果,`gift_rows` 中消费行(label="消费")的 `liquor.value`、`table_fee.value`、`voucher.value` 必须为 0。`total.value` 可以为任意非负值。
|
||
|
||
**Validates: Requirements 7.4, 8.1, 8.2**
|
||
|
||
### Property 8: 环比计算对新字段正确适配
|
||
|
||
*For any* 两组随机生成的当期和上期 DWS 数据,`_build_recharge()` 在 `compare=1` 时,`gift_rows` 中每个 cell 的 `compare` 字段必须等于 `calc_compare(当期值, 上期值)` 的结果。
|
||
|
||
**Validates: Requirements 7.6**
|
||
|
||
## Error Handling
|
||
|
||
### ETL 层
|
||
|
||
| 场景 | 处理策略 |
|
||
|------|----------|
|
||
| DWD 层某种卡类型无记录 | 对应字段写入 0(`dict.get()` 默认值) |
|
||
| `_extract_gift_recharge_breakdown()` SQL 查询失败 | 由 ETL 框架统一捕获异常,任务标记失败,不写入脏数据 |
|
||
| `dim_member_card_account` 中出现未知 `card_type_id` | 忽略(仅处理 `GIFT_CARD_TYPE_IDS` 中的三种) |
|
||
| 余额恒等式不满足 | ETL 不做运行时校验(由数据质量检查任务事后验证) |
|
||
|
||
### 后端层
|
||
|
||
| 场景 | 处理策略 |
|
||
|------|----------|
|
||
| FDW 查询失败 | `_build_recharge()` 捕获异常,降级返回 `None`(现有逻辑) |
|
||
| 查询结果为空 | 返回 `_empty_recharge_data()`(全 0 结构) |
|
||
| 新字段在视图中不存在(迁移未执行) | SQL 报错,降级为 `None` |
|
||
|
||
### 小程序层
|
||
|
||
| 场景 | 处理策略 |
|
||
|------|----------|
|
||
| API 返回错误或超时 | 展示加载失败提示,不显示 mock 数据 |
|
||
| API 返回的 `gift_rows` 结构不完整 | 使用默认值 0 填充缺失字段 |
|
||
|
||
## Testing Strategy
|
||
|
||
### 属性测试(Property-Based Testing)
|
||
|
||
使用 `hypothesis` 库(项目已有依赖),测试文件放置在 `tests/` 目录(Monorepo 级属性测试)。
|
||
|
||
每个属性测试最少 100 次迭代,使用 `@settings(max_examples=100)` 配置。
|
||
|
||
#### 测试文件
|
||
|
||
`tests/test_gift_card_breakdown_properties.py`
|
||
|
||
#### 属性测试清单
|
||
|
||
| 属性 | 测试方法 | 生成器 |
|
||
|------|----------|--------|
|
||
| P1: 余额恒等式 | 生成随机三种余额,验证总额 = 三者之和 | `st.decimals(min_value=0, max_value=999999, places=2)` |
|
||
| P2: 新增恒等式 | 生成随机三种新增,验证总额 = 三者之和 | 同上 |
|
||
| P3: ETL 余额提取 | 生成随机 card_account 行,mock DB,验证提取结果 | `st.lists(st.fixed_dictionaries({...}))` |
|
||
| P4: ETL 新增提取 | 生成随机 recharge_order + card_account,mock DB,验证提取结果 | 同上 |
|
||
| P5: transform 合并 | 生成随机 extracted dict,验证 record 包含正确字段 | `st.fixed_dictionaries({...})` |
|
||
| P6: 后端接口细分数据 | 生成随机 DWS 行,mock FDW 查询,验证 gift_rows | `st.fixed_dictionaries({...})` |
|
||
| P7: 消费行始终为 0 | 生成随机数据,验证消费行细分列 = 0 | 任意输入 |
|
||
| P8: 环比适配 | 生成随机当期/上期数据,验证 compare 计算 | `st.tuples(amount_strategy, amount_strategy)` |
|
||
|
||
标签格式:`# Feature: gift-card-breakdown, Property {N}: {title}`
|
||
|
||
### 单元测试
|
||
|
||
使用 `pytest`,测试文件放置在各模块的 `tests/` 目录。
|
||
|
||
#### ETL 单元测试
|
||
|
||
`apps/etl/connectors/feiqiu/tests/unit/test_finance_recharge_task.py`
|
||
|
||
| 测试用例 | 说明 |
|
||
|----------|------|
|
||
| `test_extract_card_balances_with_all_types` | 三种卡类型都有记录时返回正确余额 |
|
||
| `test_extract_card_balances_missing_type` | 某种卡类型无记录时返回 0 |
|
||
| `test_extract_gift_recharge_breakdown_basic` | 基本新增拆分正确 |
|
||
| `test_extract_gift_recharge_breakdown_empty` | 无充值记录时全部返回 0 |
|
||
| `test_transform_includes_new_fields` | transform 输出包含 6 个新字段 |
|
||
|
||
#### 后端单元测试
|
||
|
||
`apps/backend/tests/test_fdw_queries_gift.py`
|
||
|
||
| 测试用例 | 说明 |
|
||
|----------|------|
|
||
| `test_get_finance_recharge_gift_breakdown` | 返回正确的细分数据 |
|
||
| `test_get_finance_recharge_empty` | 无数据时返回全 0 |
|
||
| `test_gift_rows_consumption_always_zero` | 消费行细分列始终为 0 |
|
||
|
||
### 集成验证(手动)
|
||
|
||
ETL 跑数后执行验证 SQL:
|
||
|
||
```sql
|
||
-- 验证余额恒等式
|
||
SELECT site_id, stat_date,
|
||
gift_card_balance,
|
||
gift_liquor_balance + gift_table_fee_balance + gift_voucher_balance AS sum_breakdown,
|
||
gift_card_balance - (gift_liquor_balance + gift_table_fee_balance + gift_voucher_balance) AS diff
|
||
FROM dws.dws_finance_recharge_summary
|
||
WHERE gift_card_balance != gift_liquor_balance + gift_table_fee_balance + gift_voucher_balance;
|
||
|
||
-- 验证新增恒等式
|
||
SELECT site_id, stat_date,
|
||
recharge_gift,
|
||
gift_liquor_recharge + gift_table_fee_recharge + gift_voucher_recharge AS sum_breakdown,
|
||
recharge_gift - (gift_liquor_recharge + gift_table_fee_recharge + gift_voucher_recharge) AS diff
|
||
FROM dws.dws_finance_recharge_summary
|
||
WHERE recharge_gift != gift_liquor_recharge + gift_table_fee_recharge + gift_voucher_recharge;
|
||
|
||
-- 验证与 DWD 源数据一致
|
||
SELECT 'DWS' AS source,
|
||
SUM(gift_liquor_balance) AS liquor,
|
||
SUM(gift_table_fee_balance) AS table_fee,
|
||
SUM(gift_voucher_balance) AS voucher
|
||
FROM dws.dws_finance_recharge_summary
|
||
WHERE stat_date = CURRENT_DATE
|
||
UNION ALL
|
||
SELECT 'DWD',
|
||
SUM(CASE WHEN card_type_id = 2794699703437125 THEN balance ELSE 0 END),
|
||
SUM(CASE WHEN card_type_id = 2791990152417157 THEN balance ELSE 0 END),
|
||
SUM(CASE WHEN card_type_id = 2793266846533445 THEN balance ELSE 0 END)
|
||
FROM dwd.dim_member_card_account
|
||
WHERE scd2_is_current = 1 AND COALESCE(is_delete, 0) = 0
|
||
AND card_type_id IN (2794699703437125, 2791990152417157, 2793266846533445);
|
||
```
|