# Relations（关系）

> 实体/动作/事件/规则之间的边。ER 关系 + 动作-事件 + 规则-动作适用。

## ER 关系（实体间）

| 关系 | 基数 | 说明 |
|------|------|------|
| `Store` 1—N `User` | 一个门店有多个用户（boss 1 + staff N + customer 不挂门店） | `User.store_id` → `Store.id` |
| `User` N—1 `Store` | boss/staff 归属一个门店；customer 可无门店 | — |
| `Store` 1—N `Appointment` | 一个门店有多个预约 | `Appointment.store_id` → `Store.id` |
| `User` 1—N `Appointment` | 一个客户拥有多次预约；一个账号可创建多次预约；一个技师服务多次预约 | `Appointment.customer_user_id` / `created_by_user_id` / `assigned_user_id` → `User.id` |
| `Appointment` 1—1 `Report` | 一约一份报告（`uk_report_appointment` 唯一约束） | `Report.appointment_id` → `Appointment.id` |
| `Report` N—1 `Store` | 报告归属门店 | `Report.store_id` → `Store.id` |
| `Report` N—1 `User` | 报告分别归属客户与提交作者；服务技师由预约承载 | `Report.customer_user_id` / `author_staff_id` → `User.id` |
| `Report` 1—N `ReportImage` | 报告含多张照片（before/after/during） | `ReportImage.report_id` → `Report.id` |
| `Report` 1—N `ReportLead` | 一份报告可多次留资（同手机号去重） | `ReportLead.report_id` → `Report.id` |
| `ReportLead` N—1 `Store` | 留资归属门店（回访池范围） | `ReportLead.store_id` → `Store.id` |
| `ReportLead` 1—N `FollowUpTask` | 同一留资可跨多个终结轮次形成任务，但至多一个开放任务 | `FollowUpTask.source_lead_id` → `ReportLead.id` |
| `StoreCustomer` 1—N `FollowUpTask` | 回访任务归属 canonical 门店客户 | `FollowUpTask.store_customer_id` → `StoreCustomer.id` |
| `FollowUpTask` N—0..1 `Appointment` | 仅真实再次预约成功后引用 | `FollowUpTask.rebooked_appointment_id` → `Appointment.id` |
| `Store` 1—N `BusinessEvent` | 所有可统计事实强制归属门店 | `BusinessEvent.store_id` → `Store.id` |
| `StoreCustomer` 1—N `BusinessEvent` | 客户时间线；无客户语义事件可空 | `BusinessEvent.store_customer_id` → `StoreCustomer.id` |
| `Store` 1—N `StaffInvitation` | 一个门店可签发多次限时邀请 | `StaffInvitation.store_id` → `Store.id` |
| `Store` 1—N `AuditLog` | 安全/配置审计强制归属门店 | `AuditLog.store_id` → `Store.id` |

## 动作—事件（触发）

| 动作 | 触发事件 |
|------|----------|
| `action:register_boss` | `event:store_registered`、`event:session_issued` |
| `action:login` | `event:session_issued` |
| `action:wx_phone_login` | `event:session_issued`、`event:wx_bound`（若 loginCode 有） |
| `action:create_appointment` | `event:appointment_created` |
| `action:start_service` | `event:appointment_status_changed`（new→doing）、`event:service_started` |
| `action:transition_appointment_status` | `event:appointment_status_changed` |
| `action:submit_report` | `event:report_submitted`、`event:appointment_status_changed`（doing→done）、`event:service_completed` |
| `action:confirm_report_sent` | `event:report_sent`（仅首次确认） |
| `action:track_report_open` | `event:report_opened` / `event:report_reopened` |
| `action:submit_lead` | `event:lead_submitted` |
| `action:submit_lead` | `event:follow_up_created`（首次或终态后新轮次） |
| `action:start_follow_up_task` | `event:follow_up_started` |
| `action:reschedule_follow_up_task` | `event:follow_up_rescheduled` |
| `action:close_follow_up_task` | `event:follow_up_completed` |
| `action:unsubscribe` | `event:follow_up_canceled` |
| `action:create_appointment` | `event:follow_up_completed`、`event:rebook_created`（携带任务且真实保存） |
| `action:accept_staff_invitation` | `event:staff_invitation_accepted`、`event:session_issued` |

## 规则—动作（适用）

| 规则 | 适用动作 |
|------|----------|
| `rule:BR-AUTH-001` | 所有 protected 接口 |
| `rule:BR-AUTH-002` | `action:login`、`action:wx_phone_login` |
| `rule:BR-STORE-001` | `action:update_store`、`action:delete_store` |
| `rule:BR-STORE-002` | legacy 永久邀请码查询/注册停用入口 |
| `rule:BR-APPT-001` | `action:start_service`、`action:transition_appointment_status`、`action:submit_report` |
| `rule:BR-APPT-002` | `action:list_appointments`、`action:start_service`、`action:transition_appointment_status`、`action:get_appointment_detail`、`delete` |
| `rule:BR-APPT-003` | `action:get_appointment_detail`、`action:transition_appointment_status`（仅 cancel） |
| `rule:BR-APPT-004` | `action:create_appointment` |
| `rule:BR-RPT-001` | `action:submit_report` |
| `rule:BR-RPT-002` | `action:submit_report` |
| `rule:BR-RPT-003` | `action:submit_report` |
| `rule:BR-RPT-004` | `action:submit_report` |
| `rule:BR-RPT-005` | `action:submit_report`、`action:delete_report` |
| `rule:BR-RPT-006` | `action:get_report_by_token` |
| `rule:BR-RPT-007` | `action:track_report_open` |
| `rule:BR-LEAD-001` | `action:submit_lead` |
| `rule:BR-LEAD-002` | `action:list_leads` |
| `rule:BR-LEAD-003` | `action:list_leads` |
| `rule:BR-BE-001` | 所有已落库事件触发动作、`action:view_workbench` |
| `rule:BR-FU-001` | 回访任务创建、列表、领取、改期、关闭与退订 |
| `rule:BR-FU-002` | `action:create_appointment`、`action:close_follow_up_task` |

## 实体—归属系统

| 实体 | 归属系统（Agent 写入锁） |
|------|--------------------------|
| `entity:store` | Backend Core |
| `entity:user` | Backend Core |
| `entity:appointment` | Backend Core |
| `entity:report` | Report Media Backend |
| `entity:report_lead` | Report Media Backend |
| `entity:business_event` | Backend Core（跨域写入底座） |
| `entity:follow_up_task` | Backend Core + Store Admin FE |

---

## Batch 2 录入关系

## ER 关系（实体间，续）

| 关系 | 基数 | 说明 |
|------|------|------|
| `User` 1—N `Pet` | 一个客户有多个宠物 | `Pet.owner_user_id` → `User.id` |
| `Pet` 1—N `Appointment` | 一只宠物可多次预约 | `Appointment.pet_id` → `Pet.id`（可空） |
| `Store` 1—N `ServiceType` | 一个门店有多个自定义服务类型（`store_id` 为空=系统默认） | `ServiceType.store_id` → `Store.id` |
| `Store` 1—N `ScheduleBlock` | 一个门店有多个排班占用 | `ScheduleBlock.store_id` → `Store.id` |
| `User` 1—N `ScheduleBlock` | 一个用户创建多个排班占用 | `ScheduleBlock.created_by_user_id` → `User.id` |
| `Report` 1—1 `HighlightVideo` | 一份报告对应一个成片（当前作为 `Report.highlight_*` 字段承载） | — |
| `User` 1—N `SessionToken` | 一个用户可有多个签发的 session token（每次登录/注册签发） | token payload 含 `userId` |

## 动作—事件（触发，续）

| 动作 | 触发事件 |
|------|----------|
| `action:generate_highlight` | `event:highlight_succeeded` / `event:highlight_failed`（异步） |
| `action:upload_image` | `event:upload_completed` |
| `action:create_schedule_block` | `event:schedule_block_created` |
| `action:delete_schedule_block` | `event:schedule_block_deleted` |
| `action:create_service_type` | `event:service_type_created` |
| `action:delete_service_type` | `event:service_type_deleted` |
| `action:create_pet` | `event:pet_created` |
| `action:accept_staff_invitation` | `event:staff_invitation_accepted` |
| `action:update_user` | `event:user_updated` |
| `action:bind_wechat` | `event:wx_bound`（已在 Batch 1 录入） |
| `action:submit_testimonial` | `event:testimonial_saved` |
| `action:track_video_play` | `event:video_play` |
| `action:track_video_save` | `event:video_save` |
| `action:logout` | `event:session_revoked`（gap） |

## 规则—动作（适用，续）

| 规则 | 适用动作 |
|------|----------|
| `rule:BR-UPLOAD-001` | `action:upload_image`、`action:get_upload_image` |
| `rule:BR-UPLOAD-002` | `action:upload_image` |
| `rule:BR-HL-001` | `action:generate_highlight` |
| `rule:BR-HL-002` | `action:generate_highlight` |
| `rule:BR-SCH-001` | `action:day_agenda`、`action:create_schedule_block`、`action:delete_schedule_block` |
| `rule:BR-ST-001` | `action:create_service_type`、`action:update_service_type`、`action:delete_service_type`、`action:init_service_types` |
| `rule:BR-PET-001` | `action:list_pets`、`action:create_pet`、`action:update_pet`、`action:delete_pet`、`action:get_pet_history` |
| `rule:BR-USER-001` | 员工列表/删除与邀请创建/列表/撤销 |
| `rule:BR-ONBOARD-001` | 老板入驻、查看/完成开通清单 |
| `rule:BR-INVITE-001` | 员工邀请创建/列表/撤销/预览/接受 |
| `rule:BR-AUDIT-001` | 门店和员工权限类敏感操作 |
| `rule:BR-USER-002` | `action:update_user`、`action:get_user_info` |
| `rule:BR-USER-003` | 返回 `User` 的登录/注册/信息类动作 |
| `rule:BR-STORE-003` | `action:get_store`、`action:list_stores` |
| `rule:BR-TST-001` | `action:submit_testimonial` |
| `rule:BR-UNSUB-001` | `action:unsubscribe` |
| `rule:BR-INT-001` | `action:get_report_suggestion` |
| `rule:BR-CONFIG-001` | —（全局约束） |
| `rule:BR-CONFIG-002` | —（全局约束） |
| `rule:BR-CONFIG-003` | —（发布全局约束） |

## 实体—归属系统（续）

| 实体 | 归属系统（Agent 写入锁） |
|------|--------------------------|
| `entity:pet` | Backend Core |
| `entity:service_type` | Backend Core |
| `entity:schedule_block` | Backend Core |
| `entity:report_image` | Report Media Backend |
| `entity:highlight_video` | Report Media Backend |
| `entity:session_token` | Backend Core（auth 包） |
| `entity:report_testimonial` | Report Media Backend |
| `entity:service_interval` | Report Media Backend |
| `entity:highlight_fail_reason` | Report Media Backend（枚举） |
| `entity:current_user` | Backend Core（auth 包） |
| `entity:admin_console` / `workbench` / `workbench_todo_item` / `store_customer` | Store Admin FE + Backend Core |
| P1/P2 gap 占位 | 未实现（membership / stored_value / service_package / report_history / analytics_dashboard）；Phase A 后台禁止偷渡 |

## 其他关系（Batch 补全）

| 关系 | 说明 |
|------|------|
| `entity:highlight_video` HAS `entity:highlight_fail_reason` | 失败态归类 |
| `entity:session_token` RESOLVES_TO `entity:current_user` | 鉴权解析 |

## 门店后台关系（Phase A · documented）

| 关系 | 说明 |
|------|------|
| `admin_console` HAS `workbench` | 登录默认工作台 |
| `workbench` HAS `workbench_todo_item` | 按优先级排序的行动组（准确总数 + 低敏预览） |
| `workbench` AGGREGATES `appointment` / `report` / `follow_up_task` | 指标与异常来源 |
| `store` HAS `store_customer` | 一店多个稳定客户关系 |
| `store_customer` LINKS `user` | 可选关联全局 customer 账号；留资客户可先不关联 |
| `store_customer` HAS `pet` | 通过 customer user 关联一人多宠 |
| `view_workbench` EMITS `workbench_viewed` | 可选埋点 |
| `BR-ADMIN-001/002` APPLIES_TO `view_workbench` 等 | 角色与店范围 |
| `BR-ADMIN-004` CONSTRAINS `admin_console` | Phase A 范围闸门 |
| admin list actions READS 对应实体 | 列表读模型 |
| `drilldown_workbench_todo` ROUTES_TO `admin_list_*` | 待办下钻 |
| `store_customer` HAS_VIEW `customer_timeline` | 一个稳定门店客户主档对应一个动态事实视图 |
| `business_event` PROJECTS_TO `customer_timeline` | 多条不可变事实经低敏白名单投影为时间线 |
| `view_store_customer_timeline` READS `store_customer` / `business_event` | 同店校验后分页读取主档与事实 |
| `admin_list_leads` READS `follow_up_task` | `/leads` 导航承载真实操作任务池，不直接编辑留资事实 |

## 智能预约助手 M0 关系（Batch 0 · documented）

| 关系 | 说明 |
|------|------|
| `user` HAS `booking_agent_session` | 一个 customer 可创建多个短期会话，每次读写校验 owner |
| `store` HAS `booking_agent_session` | 每个会话在创建时固定一家门店 |
| `create_booking_agent_session` CREATES `booking_agent_session` | 创建短期草稿会话 |
| `submit_booking_agent_message` UPDATES `booking_agent_session` | 状态与草稿版本乐观更新 |
| `transcribe_booking_agent_audio` READS `booking_agent_session` | 用 session 派生 owner/store 上下文，只返回可编辑文本 |
| `handoff_booking_agent_draft` UPDATES `booking_agent_session` | 转为 fallback 并返回一次性草稿 |
| `cancel_booking_agent_session` UPDATES `booking_agent_session` | 转为 cancelled |
| `create_booking_agent_session` EMITS `booking_agent_started` | 创建成功的首次低敏事实 |
| `submit_booking_agent_message` EMITS `booking_agent_draft_ready` | 首次进入 confirmable 时记录 |
| `handoff_booking_agent_draft` EMITS `booking_agent_fallback` | 用户交接或 LLM 降级的首次事实 |
