petstore-docs/架构决策-门店客户主档-2026-08-01.md

59 lines
2.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 架构决策StoreCustomer 门店客户主档
> 日期2026-08-01
>
> 状态:已采纳,待生产数据迁移
>
> 范围Backend Core、Report Media Backend、Store Admin、Data Model
## 1. 决策背景
原「服务客户」列表每次请求时才从 User、Pet、Appointment、Report 和 ReportLead 拼装。同一个客户在不同门店没有稳定关系 ID留资手机号与后续 customer 账号也可能生成两条投影,无法可靠承载客户时间线、回访任务和再次预约。
## 2. 冻结模型
`StoreCustomer` 是「门店与客户的关系主档」,不是全局账号,也不是会员资产。
| 字段 | 语义 |
|---|---|
| `id` | 本店客户稳定 ID后续时间线/回访任务的关联键 |
| `store_id` | 门店数据范围 |
| `customer_user_id` | 全局 customer 账号;可空,便于先承接留资 |
| `phone` | 本店最后确认联系号;后台 API 默认脱敏 |
| `display_name` | 门店视角称呼 |
| `source` | 首次来源:`customer_booking` / `assisted_booking` / `report_lead` |
| `first_contact_at` / `last_contact_at` | 本店关系首次/最近触达时间 |
| `merged_into_store_customer_id` | 软删除投影并入的同店 canonical 主档;保留不可变事件归属连续性 |
同店分别对 `(store_id, customer_user_id)``(store_id, phone)` 加唯一约束。`customer_user_id` 为空时可先以手机号建档;后续识别到 customer 账号时绑定或合并,禁止同手机号指向两个 customer。
## 3. 写入入口
- 宠主自助预约:建档来源 `customer_booking`
- boss/staff 代客预约:建档来源 `assisted_booking`
- 报告留资:建档来源 `report_lead`;若手机号已对应 customer直接绑定。
- 后续触达只更新 `last_contact_at`,不覆盖可靠的首次来源。
## 4. 读模型与口径
`GET /api/admin/service-customers``StoreCustomer` 为列表入口,再聚合宠物、预约、报告和留资,返回稳定 `storeCustomerId`
- `lastVisitAt` 只计算已开始/已完成的历史服务;未来预约不算到店。
- `lastReportId`、`leadStatus`、`pets` 仍从事实表实时读取,避免将易变经营指标重复写入主档。
- 返回 `phoneMasked`,不返回明文 `phone`
## 5. 历史迁移
按顺序执行:
1. `backend/db/migrations/20260801_split_service_identity.sql`
2. `backend/db/migrations/20260801_create_store_customer.sql`
第二个脚本先从已确定客户归属的预约建档,再合并报告留资。末尾三个验证计数必须全部为 0 才可发布新后端。
事实时间线阶段追加执行 `backend/db/migrations/20260802_create_store_customer_timeline.sql`:它只增加合并别名指针与索引,不改写既有 BusinessEvent。迁移末尾两个别名验证计数必须全部为 0。
## 6. 非目标
本阶段不引入会员等级、积分、储值、次卡、标签群发、跨店共享或客户合并后台。这些需要独立的权限、资产和审计模型。