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

@@ -26,7 +26,7 @@ SCHEMA_ETL=meta
# API 配置(上游 SaaS API
# ------------------------------------------------------------------------------
API_BASE=https://pc.ficoo.vip/apiprod/admin/v1/
API_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnQtdHlwZSI6IjQiLCJ1c2VyLXR5cGUiOiIxIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiMTIiLCJyb2xlLWlkIjoiMTIiLCJ0ZW5hbnQtaWQiOiIyNzkwNjgzMTYwNzA5OTU3Iiwibmlja25hbWUiOiLnp5_miLfnrqHnkIblkZjvvJrmganmgakxIiwic2l0ZS1pZCI6IjAiLCJtb2JpbGUiOiIxMzgxMDUwMjMwNCIsInNpZCI6IjI5NTA0ODk2NTgzOTU4NDUiLCJzdGFmZi1pZCI6IjMwMDk5MTg2OTE1NTkwNDUiLCJvcmctaWQiOiIwIiwicm9sZS10eXBlIjoiMyIsInJlZnJlc2hUb2tlbiI6IncxTkhzalRIeHU1b0Ric0hnQXp6SUgrU2Q2d2M3YndUTTU1ZTZnSXg0RTQ9IiwicmVmcmVzaEV4cGlyeVRpbWUiOiIyMDI2LzMvMTkg5LiK5Y2IMToxNzowMyIsIm5lZWRDaGVja1Rva2VuIjoiZmFsc2UiLCJleHAiOjE3NzM4NTQyMjMsImlzcyI6InRlc3QiLCJhdWQiOiJVc2VyIn0.E7oh3g_-3fyeC1-oHsJXZ-tTGqcvUCMDrd9TifJAs4U
API_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnQtdHlwZSI6IjQiLCJ1c2VyLXR5cGUiOiIxIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiMTIiLCJyb2xlLWlkIjoiMTIiLCJ0ZW5hbnQtaWQiOiIyNzkwNjgzMTYwNzA5OTU3Iiwibmlja25hbWUiOiLnp5_miLfnrqHnkIblkZjvvJrmganmgakxIiwic2l0ZS1pZCI6IjAiLCJtb2JpbGUiOiIxMzgxMDUwMjMwNCIsInNpZCI6IjI5NTA0ODk2NTgzOTU4NDUiLCJzdGFmZi1pZCI6IjMwMDk5MTg2OTE1NTkwNDUiLCJvcmctaWQiOiIwIiwicm9sZS10eXBlIjoiMyIsInJlZnJlc2hUb2tlbiI6InhZbFozZDN1ekR3cnpkaFNUeVpFYnJVQUc5ZEtrZDVrK1FUWEd0Ym9LQkU9IiwicmVmcmVzaEV4cGlyeVRpbWUiOiIyMDI2LzMvMjYg5LiK5Y2IMTozMTo1MCIsIm5lZWRDaGVja1Rva2VuIjoiZmFsc2UiLCJleHAiOjE3NzQ0NTk5MTAsImlzcyI6InRlc3QiLCJhdWQiOiJVc2VyIn0.yNtsQIIQPVoFkjPUHWVsi9nRGc_lSgFGerxOfJSDOcc
API_TIMEOUT=20
API_PAGE_SIZE=200
API_RETRY_MAX=3

View File

@@ -202,13 +202,17 @@ card_type_id
2791990152417157 → “台费卡”7 条)
2791987095408517 → “年卡”7 条)
2793306611533637 → “月卡”12 条)
含义:卡种类型 ID用于区分不同卡种。
memberCardTypeName
类型string
值:"储值卡", "活动抵用券", "酒水卡", "台费卡"
值:"储值卡", "活动抵用券", "酒水卡", "台费卡", "年卡", "月卡"
含义:卡种名称,与 card_type_id 一一对应,是一个 卡种枚举名称。
@@ -560,7 +564,7 @@ payment_method 决定是否有外部支付以及大致支付渠道;
卡种类型在本表中已经完全可识别
通过 card_type_id ↔ memberCardTypeName本表已经给出了储值卡、酒水卡、台费卡、活动抵用券种卡型及各自的 ID
通过 card_type_id ↔ memberCardTypeName本表已经给出了储值卡、酒水卡、台费卡、活动抵用券、年卡、月卡六种卡型及各自的 ID
与“会员档案”里 member_card_grade_code / member_card_grade_name 可以配套构成更完整的“卡种维度”。
@@ -587,3 +591,12 @@ remark 与 from_type 的配合使用
整体来看,余额变更记录.json 是会员卡层面的“总账/明细账表”,与“充值记录”“消费结算记录”“会员档案”“卡类型、卡实例”之间,通过一整套 ID 和枚举字段建立了清晰的结构关系,而本次你给的这家门店只是该结构在一个门店上的数据切片。
<!--
AI_CHANGELOG:
- 日期: 2026-03-19
- Prompt: card_type_id 年卡/月卡映射同步
- 直接原因: 用户确认 2791987095408517=年卡、2793306611533637=月卡,同步到所有涉及 card_type_id 的文档
- 变更摘要: card_type_id 枚举补充年卡/月卡2 条memberCardTypeName 枚举补充"年卡""月卡""四种卡型"改为"六种卡型"
- 风险与验证: 纯文档变更,无运行时影响
-->

View File

@@ -133,17 +133,9 @@
有效期、最近消费时间等状态信息。
根据字段值,这一页数据中主要有类卡:
根据字段值,这一页数据中主要有类卡:
储值卡
活动抵用券
台费卡
酒水卡
月卡
储值卡、活动抵用券、台费卡、酒水卡、年卡、月卡
因此,这个 JSON 更准确地理解为:门店下所有储值/次卡/券类会员卡的列表视图。
@@ -163,15 +155,14 @@ card_type_id
枚举(按数据分布):
2793249295533893
2793266846533445
2791990152417157
2794699703437125
2793306611533637
| card_type_id | 卡种名称 |
|---|---|
| 2793249295533893 | 储值卡 |
| 2793266846533445 | 活动抵用券 |
| 2791990152417157 | 台费卡 |
| 2794699703437125 | 酒水卡 |
| 2791987095408517 | 年卡 |
| 2793306611533637 | 月卡 |
这些 ID 对应不同的卡种配置,具体含义在系统内部的“卡种配置表”中。
@@ -183,15 +174,15 @@ member_card_grade_code
枚举:
2790683528022853 → 储值卡
| member_card_grade_code | 卡种名称 |
|---|---|
| 2790683528022853 | 储值卡 |
| 2790683528022856 | 活动抵用券 |
| 2790683528022855 | 台费卡 |
| 2790683528022858 | 酒水卡 |
| 2790683528022857 | 月卡 |
2790683528022856 → 活动抵用券
2790683528022855 → 台费卡
2790683528022858 → 酒水卡
2790683528022857 → 月卡
> 注意年卡card_type_id `2791987095408517`)对应的 member_card_grade_code 尚未在样本数据中出现。
member_card_grade_code_name
@@ -201,15 +192,7 @@ member_card_grade_code_name
枚举值(与上面 code 一一对应):
"储值卡"
"活动抵用券"
"台费卡"
"酒水卡"
"月卡"
"储值卡"、"活动抵用券"、"台费卡"、"酒水卡"、"月卡"、"年卡"
member_card_type_name
@@ -808,4 +791,10 @@ AI_CHANGELOG:
- 直接原因: 合并 `electricityCardDeduct`/`rechargeFreezeBalance` 驼峰变体与已有小写字段去重,补充 5 个真正新字段描述
- 变更摘要: 去除大小写重复行,补充 able_share_member_discount/electricity_deduct_radio/electricity_discount/member_grade/principal_balance 的中文描述
- 风险与验证: 纯文档文案修正无运行时影响验证grep "新发现字段" 返回 0 结果
- 日期: 2026-03-19
- Prompt: card_type_id 年卡/月卡映射同步
- 直接原因: 用户确认 2791987095408517=年卡、2793306611533637=月卡,同步到所有涉及 card_type_id 的文档
- 变更摘要: 补充年卡/月卡的 card_type_id 映射,将枚举列表改为表格形式并标注中文名称
- 风险与验证: 纯文档变更,无运行时影响
-->

View File

@@ -82,8 +82,8 @@
| `tenant_member_id` | int | `2799212845565701` | 租户内会员主键 ID。对应会员档案表的 `id` |
| `system_member_id` | int | `2799212844549893` | 系统级会员 ID全局唯一 |
| `tenant_member_card_id` | int | `2799219999295237` | 会员卡账户 ID指明本次变更针对哪一张卡。对应储值卡列表的 `id` |
| `card_type_id` | int | `2793249295533893` | 卡种类型 ID。枚举`2793249295533893` = 储值卡,`2793266846533445` = 活动抵用券,`2794699703437125` = 酒水卡,`2791990152417157` = 台费卡 |
| `memberCardTypeName` | string | `"储值卡"` | 卡种名称,与 `card_type_id` 一一对应。枚举值:`"储值卡"``"活动抵用券"``"酒水卡"``"台费卡"` |
| `card_type_id` | int | `2793249295533893` | 卡种类型 ID。枚举`2793249295533893` = 储值卡,`2793266846533445` = 活动抵用券,`2794699703437125` = 酒水卡,`2791990152417157` = 台费卡`2791987095408517` = 年卡,`2793306611533637` = 月卡 |
| `memberCardTypeName` | string | `"储值卡"` | 卡种名称,与 `card_type_id` 一一对应。枚举值:`"储值卡"``"活动抵用券"``"酒水卡"``"台费卡"``"年卡"``"月卡"` |
| `memberName` | string | `"曾丹烨"` | 会员姓名/称呼 |
| `memberMobile` | string | `"13922213242"` | 会员手机号 |
@@ -220,4 +220,10 @@ AI_CHANGELOG:
- 直接原因: 4.8"时间"组混入 principal_after/principal_before/principal_data 三个本金字段
- 变更摘要: 新增 4.8"本金明细"组3 字段);原 4.8 时间组重编号为 4.9
- 风险与验证: 纯文档分组调整,无运行时影响;验证:统计各组字段数总和 = 25+3 = 28含本金字段
- 日期: 2026-03-19
- Prompt: card_type_id 年卡/月卡映射同步
- 直接原因: 用户确认 2791987095408517=年卡、2793306611533637=月卡,同步到所有涉及 card_type_id 的文档
- 变更摘要: card_type_id 枚举和 memberCardTypeName 枚举补充年卡/月卡
- 风险与验证: 纯文档变更,无运行时影响
-->

View File

@@ -83,11 +83,17 @@ WHERE scd2_is_current = 1;
**储值卡ID**
- 储值卡 card_type_id = 2793249295533893
**赠送卡ID**
**赠送卡ID**(归入 `gift_card_balance`
- 台费卡 2791990152417157
- 酒水卡 2794699703437125
- 活动抵用券 2793266846533445
**其他卡类型**(当前未计入 `cash_card_balance``gift_card_balance`
- 年卡 2791987095408517
- 月卡 2793306611533637
> ⚠️ 年卡和月卡的余额目前未被 `_extract_card_balances()` 统计,不包含在 `total_card_balance` 中。详见 P12 PRD`docs/prd/specs/P12-gift-card-breakdown.md`)。
## 可回溯性
| 项目 | 说明 |
@@ -95,3 +101,12 @@ WHERE scd2_is_current = 1;
| 可回溯 | ✅ 完全可回溯 |
| 数据范围 | 2025-07-21 ~ 至今 |
| 依赖表 | dwd_recharge_order, dim_member_card_account |
<!--
AI_CHANGELOG:
- 日期: 2026-03-19
- Prompt: card_type_id 年卡/月卡映射同步
- 直接原因: 用户确认 2791987095408517=年卡、2793306611533637=月卡,同步到所有涉及 card_type_id 的文档
- 变更摘要: 赠送卡ID 部分添加归入说明,新增"其他卡类型"段落标注年卡/月卡未被统计的遗漏
- 风险与验证: 纯文档变更,无运行时影响
-->

View File

@@ -859,6 +859,8 @@ dim_table ────────────────────┘
| 台费卡 | `2791990152417157` | `gift_card_balance` |
| 活动抵用券 | `2793266846533445` | `gift_card_balance` |
| 酒水卡 | `2794699703437125` | `gift_card_balance` |
| 年卡 | `2791987095408517` | ⚠️ 未统计(遗漏) |
| 月卡 | `2793306611533637` | ⚠️ 未统计(遗漏) |
`total_card_balance = cash_card_balance + gift_card_balance`
@@ -1209,6 +1211,8 @@ card_consume_total = recharge_card_consume + gift_card_consume
| 台费卡 | `2791990152417157` | `gift_card_balance` |
| 酒水卡 | `2794699703437125` | `gift_card_balance` |
| 活动抵用券 | `2793266846533445` | `gift_card_balance` |
| 年卡 | `2791987095408517` | ⚠️ 未统计(遗漏) |
| 月卡 | `2793306611533637` | ⚠️ 未统计(遗漏) |
```
total_card_balance = cash_card_balance + gift_card_balance
@@ -1772,3 +1776,12 @@ dwd_table_fee_log (ledger_count) ────────────┘
6. 计算占比:`percentage = duration_seconds / total_seconds`(四位小数)
7. 占比 ≥ 0.25 标记 `is_tagged = TRUE`
8. 过滤条件:`COALESCE(is_delete, 0) = 0`,营业日切点通过 `biz_date_sql_expr` 处理
<!--
AI_CHANGELOG:
- 日期: 2026-03-19
- Prompt: card_type_id 年卡/月卡映射同步
- 直接原因: 用户确认 2791987095408517=年卡、2793306611533637=月卡,同步到所有涉及 card_type_id 的文档
- 变更摘要: 两处卡类型映射表DWS_MEMBER_CONSUMPTION 和 DWS_FINANCE_RECHARGE补充年卡/月卡行,标注"⚠️ 未统计(遗漏)"
- 风险与验证: 纯文档变更,无运行时影响
-->

View File

@@ -139,6 +139,48 @@ class FinanceRechargeTask(FinanceBaseTask):
rows = self.db.query(sql, (site_id, start_date, end_date))
return [dict(row) for row in rows] if rows else []
# CHANGE 2026-07-17 | task 2.1: card_type_id → 细分余额字段名映射
GIFT_TYPE_FIELD_MAP = {
2794699703437125: 'gift_liquor_balance', # 酒水卡
2791990152417157: 'gift_table_fee_balance', # 台费卡
2793266846533445: 'gift_voucher_balance', # 抵用券
}
# CHANGE 2026-07-18 | task 3.1: card_type_id → 细分充值字段名映射
GIFT_RECHARGE_FIELD_MAP = {
2794699703437125: 'gift_liquor_recharge', # 酒水卡
2791990152417157: 'gift_table_fee_recharge', # 台费卡
2793266846533445: 'gift_voucher_recharge', # 抵用券
}
# CHANGE 2026-07-18 | task 3.1: 按 card_type_id 拆分赠送卡新增充值
def _extract_gift_recharge_breakdown(self, site_id: int, start_date: date, end_date: date) -> Dict[str, Decimal]:
"""按卡类型拆分赠送卡新增充值JOIN 充值订单与会员卡账户维度表)"""
cutoff = self.config.get("app.business_day_start_hour", 8)
biz_expr = biz_date_sql_expr("ro.pay_time", cutoff)
sql = f"""
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_expr} >= %s AND {biz_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
"""
rows = self.db.query(sql, (site_id, start_date, end_date))
result: Dict[str, Decimal] = {
field: Decimal('0') for field in self.GIFT_RECHARGE_FIELD_MAP.values()
}
for row in (rows or []):
field_name = self.GIFT_RECHARGE_FIELD_MAP.get(row['card_type_id'])
if field_name:
result[field_name] = self.safe_decimal(row['gift_recharge'])
return result
def _extract_card_balances(self, site_id: int, stat_date: date) -> Dict[str, Decimal]:
CASH_CARD_TYPE_ID = 2793249295533893
GIFT_CARD_TYPE_IDS = [2791990152417157, 2793266846533445, 2794699703437125]
@@ -162,6 +204,10 @@ class FinanceRechargeTask(FinanceBaseTask):
cash_balance = Decimal('0')
gift_balance = Decimal('0')
# CHANGE 2026-07-17 | task 2.1: 按 card_type_id 拆分赠送卡余额
gift_breakdown: Dict[str, Decimal] = {
field: Decimal('0') for field in self.GIFT_TYPE_FIELD_MAP.values()
}
for row in (rows or []):
card_type_id = row['card_type_id']
@@ -170,11 +216,16 @@ class FinanceRechargeTask(FinanceBaseTask):
cash_balance += balance
elif card_type_id in GIFT_CARD_TYPE_IDS:
gift_balance += balance
# 写入细分字段(未知 card_type_id 不会命中 GIFT_TYPE_FIELD_MAP
field_name = self.GIFT_TYPE_FIELD_MAP.get(card_type_id)
if field_name:
gift_breakdown[field_name] += balance
return {
'cash_balance': cash_balance,
'gift_balance': gift_balance,
'total_balance': cash_balance + gift_balance
'total_balance': cash_balance + gift_balance,
**gift_breakdown,
}