3.0 KiB
架构决策:StoreCustomer 事实时间线
日期:2026-08-02 状态:Accepted / Code Complete
1. 决策
客户详情时间线以 canonical StoreCustomer.id 为唯一门店关系主键,以已持久化的 BusinessEvent 为事实源。它是只读投影,不新建可编辑“动态”表,也不从页面点击、当前对象状态或日志猜测历史。
手机号留资投影与账号主档合并时,旧投影软删除并写入 merged_into_store_customer_id,既有 BusinessEvent 继续引用旧 ID 且不可改写。时间线解析 canonical 后同时查询它和全部同店别名的事件,因此合并前后的客户事实保持连续。别名只允许单层指向同店、有效且未再合并的 canonical;生产预检拒绝活动别名、自引用、跨店、缺失或链式目标。
GET /api/admin/service-customers/timeline 只接受 storeCustomerId/page/pageSize。门店范围从 session 派生;current.storeId + storeCustomerId 不匹配时按不存在返回 404,避免枚举他店客户。
2. 展示口径
- 按
occurredAt DESC, id DESC对 canonical 与别名事件统一稳定分页;同一时刻用数据库事件 ID 决定顺序。 - 展示预约创建/状态、开始/完成服务、报告提交/确认发送/首次打开/再次打开、回访意向等已经落库的事实。
- 批量补齐预约、报告、回访对象的当前低敏摘要和详情跳转 ID;对象已删除时仍保留事件标题和发生时间,不伪造摘要。
- Admin 客户抽屉可跳到预约详情、报告列表和回访池;本批不创建回访任务或再次预约归因。
3. 隐私与数据边界
允许返回:客户称呼、脱敏手机号、宠物名、服务类型、预约时间、当前业务状态、发送渠道、提醒日期、操作人显示名/角色。
禁止返回:手机号原文、预约/报告备注、图片/视频 URL、报告或邀请 token/hash、openid/unionid、IP、请求体和原始 metadata_json。服务端只解析 fromStatus/toStatus/channel/repeatSubmit 四个白名单 metadata 字段。
时间线不承载会员等级、余额、次卡、积分、营销标签或自由文本跟进记录。这些能力若进入后续阶段,必须使用独立对象与状态机,不得改写历史事实。
4. 验收
- 同店 boss/staff 可查;customer 禁止;跨店返回 404。
- 分页上限 100,顺序稳定,返回
total/page/pageSize/hasMore。 - 预约、报告、回访聚合 ID 相同时仍按
aggregateType精确解析。 - canonical ID 与已合并别名 ID 均返回同一 canonical 客户摘要,并包含合并前后的全部事件。
- system/public 事件无操作人时正常返回。
- 自动化反例确认响应不包含完整手机号、token、备注或媒体字段。
- 本体校验与代码端点漂移均为 0;Backend 全测与 Admin production build 通过。
数据库迁移:backend/db/migrations/20260802_create_store_customer_timeline.sql,在门店开通迁移后第七个执行;末尾两项别名不变量必须为 0。