yeg
2026-09-14 de1a651a03b247f96b0d7d909f878aaed1801160
docs(scratch): 移交两份非支付业务的排查结论

01 家属卡两代实现并存(审核通过不写绑定、消费侧只认新表,导致家属卡实际不可用);02 开发库里 25 张表存在指向不存在商户的历史脏数据,附已处理的洗车 5 行与处理建议。两条均为只读排查产出,未改相关业务代码。
2个文件已添加
118 ■■■■■ 已修改文件
.scratch/non-payment-findings/issues/01-family-card-two-generations.md 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/non-payment-findings/issues/02-dev-db-orphan-merchant-rows.md 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.scratch/non-payment-findings/issues/01-family-card-two-generations.md
New file
@@ -0,0 +1,73 @@
# 家属卡:两代实现并存,审核通过后家属卡实际不可用
Status: ready-for-agent
产出于支付线的只读排查(未修改本业务任何代码/迁移/前端)。移交对象:负责家属卡业务的同事。
## 现象
1. 小程序提交家属卡申请、后台审核,都能正常完成,**但审核通过后家属卡用不了权益**:核销时直接报
   `409 亲属附属卡未找到绑定的主卡`。
2. 后台「家属卡申请」页与实际待审数量对不上:页面上有申请,工作台/待办却永远不提醒。
## 根因
同一件事有**两张表、两套接口**,且迁移没做完:
| | 旧:`dependent_card_apply` | 新:`family_card_application` |
| --- | --- | --- |
| 在迁移里 | ❌ 迁移从未创建(V141 才补上) | ✅ 有 |
| 状态类型 | `tinyint` 0待审/1通过/2驳回/3取消 | 字符串 `待审核`/`已通过`/`已驳回`/`已解绑` |
| 申请入口 | ✅ 小程序 + 后台三个页面都在用 | ❌ 唯一写入口 `FamilyCardPortalService.create` 无任何调用者 |
| 消费侧读取 | ❌ 无人读 | ✅ 额度/权益判定、洗车、票券包、影院、展示策略都读它 |
- 写入侧的绑定逻辑只在**旧表审核**里缺失:`DependentCardApplyAdminService.audit()` 仅更新 `status`
  并写 `dependent_card_apply_status_log`,**不写 `family_talent_id` / `family_card_id`**,也不在新表留记录。
- 读取侧只认新表:`BenefitQuotaService`(非 PERSONAL 卡按 `family_card_id` 找主卡,找不到即 409)、
  `BenefitQuotaService.sharedCardIds`(额度共享)、`TransportCarWashCredentialMapper`、
  `TicketPackageClaimMapper`、`CinemaPortalService`、`MerchantDisplayPolicyPortalService`。
- 新表自己的接口 `/api/admin/cards/family-applications`、`/api/admin/cards/family-cards`、
  `/api/admin/cards/family-applications/{id}/audit`(`CardAdminService`)**前端无任何调用**。
## 证据
数据(开发库 `talent_card`,2026-09-14):
```
旧表 dependent_card_apply      : 13 行,7 待审 / 2 通过 / 4 取消;family_talent_id、family_card_id 全部为 NULL
新表 family_card_application   : 5 行,3 已通过 / 1 已解绑 / 1 已驳回;其中 4 行有真实 family_talent_id + family_card_id
```
调用方(grep 结论):
- 小程序 `rencaika/services/api.uts`: `appFamilyCards` → `/api/app/family-cards/applications`(旧表,
  `DependentCardApplyController`)
- 后台 `talent-card-admin/src/api/admin.ts`: `getFamilyCardApplications` / `getFamilyCardApplicationDetail` /
  `auditFamilyCardApplication` / `unbindFamilyCard` → 全部 `/api/admin/dependent-card-applies`(旧表)
- 后台页面 `FamilyCardView.vue`、`FamilyCardApplicationDetailView.vue`、`FamilyCardDetailView.vue` 都走上面这组接口
- 工作台待办 `AdminBusinessService`:`select count(*) ... from family_card_application where status = '待审核'`
  → 数的是新表,与实际待审(旧表)不一致
## 影响
1. 旧入口审核通过后**不产生绑定**,家属卡拿卡后用不了权益(额度判定第一步 409)。
2. 洗车凭证、票券包领取、影院购票、展示策略都查不到家属卡绑定关系。
3. 主卡与家属卡的**额度共享不生效**(`sharedCardIds` 也读新表)。
4. 后台两个页面展示不同数据源;「家属卡申请待审核」待办数恒为 0(新表无待审),后台不会提醒。
5. 新表现有 5 行数据**没有可持续的来源**(写入口是死代码),只能靠直接写库产生。
## 建议改法
- **方案 A(推荐)**:把小程序提交入口 + 后台三个页面切到新表 `family_card_application`,旧表连同
  `dependent_card_apply_status_log` 一起退休;审核时写 `family_talent_id` / `family_card_id`,让
  申请→审核→绑定→权益一条线。
- 方案 B:反向统一到旧表——消费侧 5 处改读旧表,并补上旧表的绑定写入。
- 方案 C:两表双向同步。不推荐,双份维护迟早再裂。
决定方案前建议先确认:家属卡是否还要求「仅卓越人才卡可申请、每张主卡最多 2 个家属、累计最多 3 个申请」
(旧表 `DependentCardApplyService` 里有这套校验,新表侧没有)。
## Comments
- 2026-09-14 支付线会话只读排查后记录;同日补建了旧表迁移 `V141__create_dependent_card_apply.sql`
  (该表迁移里一直缺,缺失会导致工作台统计与家属卡申请页 500),已与用户确认保留并知会。
.scratch/non-payment-findings/issues/02-dev-db-orphan-merchant-rows.md
New file
@@ -0,0 +1,45 @@
# 开发库里大量「指向不存在商户」的历史脏数据
Status: ready-for-agent
产出于支付线的只读排查(未修改本业务任何代码/迁移/前端)。移交对象:负责对应业务的同事。
## 现象
多张业务表里的 `merchant_id` 指向 `merchant` 表里不存在的商户。全库扫描(开发库 `talent_card`,
2026-09-14)共 **25 张表**命中:
| 类型 | 表(孤儿行数) |
| --- | --- |
| 配置/内容类 | `merchant_store_consume_rule`(69)、`medical_package`(18)、`transport_parking_card_level_right`(15)、`shopping_topic_merchant`(14)、`shopping_service_detail`(14)、`premium_topic_merchant`(12)、`transport_parking_lot`(10)、`transport_fuel_store`(10)、`transport_fuel_hero_image`(10)、`cinema_coupon`(9)、`merchant_product`(5)、`merchant_product_audit_record`(5)、`transport_parking_benefit`(5)、`transport_fuel_benefit`(5)、`merchant_card_benefit`(3)、`spouse_employment_benefit`(2)、`finance_service_detail`(2)、`admin_approval_organization`(2) |
| 交易/痕迹类 | `payment_transaction`(22)、`benefit_order`(12)、`order_refund`(1)、`merchant_session`(3)、`talent_dynamic_code`(1)、`transport_parking_plate_registration`(2)、`transport_fuel_mobile_registration`(1) |
## 证据
- 不是数据导入造成的:旧快照库 `127.0.0.1:6134/talent_card` 上同样的统计结果一致
  (`merchant_store_consume_rule` 69/71、`medical_package` 18/18、`benefit_order` 15/100、
  `payment_transaction` 22/71、`transport_car_wash_service` 5/6)。
- 绝大多数孤儿行**不会报错**:列表查询用 `LEFT JOIN merchant`,查不到就不显示。
- 会报错的是带硬校验的入口("商户不存在"这类 `BusinessException`):
  - `CarWashBenefitManagementService`、`CarWashJumpConfigAdminService`×2、`CarWashJumpConfigPortalService`:洗车商家不存在
  - `ParkingPlateMerchantService`:停车商家不存在
  - `MerchantCouponManagementService`:适用商户不存在
  - `MerchantDisplayPolicyAdminService`×3、`MerchantManagementService`:商户不存在
## 已处理
- `transport_car_wash_service` 中指向不存在商户的 5 行(merchant_id 38/39/40/41/42)已**软删**
  (`deleted = 1`,可回滚)。合法商户 89(`自主洗车1` / `carWash`)保留,
  `/api/admin/car-wash-benefits/merchants/89/rules` 返回 200。
## 建议改法
1. **配置类孤儿**(第一类):若确认对应商户已下线,可逐表软删(置 `deleted = 1`)以免配置页保存时报错;
   删之前确认不是需要保留的配置样本。
2. **交易/痕迹类**(第二类):建议**不要删除**——支付流水、订单、退款是历史痕迹,删除后无法回溯;它们
   只在按 `merchant_id` 精确查询时才会暴露,列表页不受影响。
3. 更彻底的做法是在这些表上加外键或定期对账任务,避免继续产生孤儿行(需要产品/架构决策)。
## Comments
- 2026-09-14 支付线会话只读排查后记录。