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

2.9 KiB
Raw Blame History

架构决策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-customersStoreCustomer 为列表入口,再聚合宠物、预约、报告和留资,返回稳定 storeCustomerId

  • lastVisitAt 只计算已开始/已完成的历史服务;未来预约不算到店。
  • lastReportIdleadStatuspets 仍从事实表实时读取,避免将易变经营指标重复写入主档。
  • 返回 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. 非目标

本阶段不引入会员等级、积分、储值、次卡、标签群发、跨店共享或客户合并后台。这些需要独立的权限、资产和审计模型。