56 lines
2.6 KiB
Markdown
56 lines
2.6 KiB
Markdown
# 架构决策: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` | 本店关系首次/最近触达时间 |
|
||
|
||
同店分别对 `(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 才可发布新后端。
|
||
|
||
## 6. 非目标
|
||
|
||
本阶段不引入会员等级、积分、储值、次卡、标签群发、跨店共享或客户合并后台。这些需要独立的权限、资产和审计模型。
|