# Objects（实体）

> 持久名词 + 字段 + 归属系统。以 `graph/ontology.jsonl` 为机器可审计真源。

## entity:store

**门店**。宠物店主体，所有业务数据的归属范围。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| name | VARCHAR | 店铺名称 |
| logo | VARCHAR | 店铺 Logo |
| phone | VARCHAR | 联系电话 |
| address | VARCHAR | 地址 |
| latitude | Double | 纬度（公开报告页不返回） |
| longitude | Double | 经度（公开报告页不返回） |
| intro | TEXT | 简介 |
| owner_id | BIGINT | 老板用户 ID |
| invite_code | VARCHAR(8) | legacy 兼容字段；不返回客户端、不再作为员工注册凭证 |
| booking_day_start | LocalTime | 预约首号 |
| booking_last_slot_start | LocalTime | 预约末号 |
| booking_capacity | INT | 门店承诺的并发接待数（1～10） |
| onboarding_status | VARCHAR(16) | unknown（历史不可推断）/ in_progress / completed |
| onboarding_completed_at | DATETIME | 老板显式完成开通时间 |
| onboarding_completed_by_user_id | BIGINT | 显式完成开通的老板 |
| create_time / update_time | DATETIME | 时间戳 |
| deleted | TINYINT(1) | 软删 |

- **归属系统**：Backend Core（`StoreController` / `StoreService` / `StoreMapper`）
- **代码**：`backend/src/main/java/com/petstore/entity/Store.java`
- **文档**：`docs/产品设计文档.md §三 t_store`
- **测试**：`StoreControllerTest`
- **证据**：`anchored`

## entity:staff_invitation

**一次性员工邀请**。老板按员工本人手机号创建，1～30 天有效、可撤销且只能接受一次。

| 字段 | 类型 | 说明 |
|------|------|------|
| invitation_id | VARCHAR(36) | 对外随机 ID，管理端撤销使用 |
| token_hash | VARCHAR(64) | 256-bit 原始 token 的 SHA-256；原始 token 不落库 |
| store_id | BIGINT | 受邀门店 |
| invited_name / invited_phone | VARCHAR | 受邀姓名 / 服务端核验手机号；API 只返回脱敏手机号 |
| status | VARCHAR(16) | pending / accepted / revoked / expired |
| expires_at | DATETIME | 到期时间 |
| created_by_user_id | BIGINT | 创建邀请的老板 |
| accepted_by_user_id / accepted_at | BIGINT / DATETIME | 首次成功接受回执 |
| revoked_by_user_id / revoked_at | BIGINT / DATETIME | 撤销回执 |
| create_time / update_time | DATETIME | 时间戳 |

- **安全边界**：原始 token 仅在创建响应返回一次；列表/预览不返回 `token_hash`、原始 token 或完整手机号。
- **代码**：`backend/src/main/java/com/petstore/entity/StaffInvitation.java`、`service/StaffInvitationService.java`
- **测试**：`StaffInvitationServiceTest`、`StoreOnboardingMigrationTest`
- **证据**：`anchored`

## entity:audit_log

**不可变操作审计**。记录开店、门店配置和员工权限类操作，与可统计经营事实 `BusinessEvent` 分开。

| 字段 | 类型 | 说明 |
|------|------|------|
| audit_id | VARCHAR(36) | 随机审计 ID |
| store_id | BIGINT | 强制门店范围 |
| actor_user_id / actor_role | BIGINT / VARCHAR | 操作人快照 |
| action | VARCHAR(64) | 受控动作名 |
| target_type / target_id | VARCHAR | 目标类型 / 安全引用 ID |
| outcome | VARCHAR(16) | success 等受控结果 |
| metadata_json | TEXT | 服务端白名单低敏标量 JSON |
| occurred_at / create_time | DATETIME | 发生 / 入库时间 |

- **禁止字段**：手机号、token/URL、openid/unionid、密码/密钥、IP、地址/坐标、备注/内容和原始请求体。
- **代码**：`backend/src/main/java/com/petstore/entity/AuditLog.java`、`service/AuditLogService.java`
- **测试**：`AuditLogServiceTest`、`StoreOnboardingMigrationTest`
- **证据**：`anchored`

## entity:user

**用户**。三角色：`boss`（老板）/ `staff`（员工）/ `customer`（宠主）。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| username | VARCHAR | 用户名 |
| password | VARCHAR | 密码（`@JsonIgnore` 不返回；登录成功 `setPassword(null)`） |
| name | VARCHAR | 姓名/技师名 |
| phone | VARCHAR | 手机号 |
| avatar | VARCHAR | 头像 |
| store_id | BIGINT | 所属店铺（customer 可空） |
| role | VARCHAR | boss / staff / customer |
| wechat_openid | VARCHAR(64) | 微信小程序 openid（`@JsonIgnore`，仅服务端使用） |
| wechat_unionid | VARCHAR(64) | 微信 unionid（`@JsonIgnore`） |
| create_time / update_time | DATETIME | 时间戳 |
| deleted | TINYINT(1) | 软删 |

- **归属系统**：Backend Core（`UserController` / `UserService` / `UserMapper`）
- **代码**：`backend/src/main/java/com/petstore/entity/User.java`
- **文档**：`docs/产品设计文档.md §三 t_user`、`docs/.agents/common-context.md 术语规则`
- **测试**：`UserServiceLoginStrategyTest`
- **证据**：`anchored`

## entity:appointment

**预约**。状态机：`new`（待开始）→ `doing`（服务中）→ `done`（已完成）/ `cancel`（已取消）。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| pet_name | VARCHAR | 宠物名称 |
| pet_type | VARCHAR | 宠物类型（猫/狗/其他） |
| service_type | VARCHAR | 服务类型 |
| appointment_time | DATETIME | 预约开始时间（半小时粒度，按服务时长检查跨桶容量） |
| duration_minutes | INT | 下单时的服务时长快照（30～480，30 分钟粒度） |
| status | VARCHAR | new / doing / done / cancel |
| store_id | BIGINT | 所属门店 |
| customer_user_id | BIGINT | 被服务客户账号；新业务归属主字段 |
| created_by_user_id | BIGINT | 实际创建预约的登录账号（customer/boss/staff） |
| user_id | BIGINT | legacy 兼容；新数据镜像 customer_user_id |
| assigned_user_id | BIGINT | 服务技师 ID（开始服务时赋值，API 别名 assignedStaffId） |
| pet_id | BIGINT | 宠物档案 ID |
| remark | TEXT | 备注 |
| create_time / update_time | DATETIME | 时间戳 |
| deleted | TINYINT(1) | 软删 |

- **归属系统**：Backend Core（`AppointmentController` / `AppointmentService` / `AppointmentMapper`）
- **代码**：`backend/src/main/java/com/petstore/entity/Appointment.java`、`backend/src/main/java/com/petstore/service/AppointmentService.java`
- **文档**：`docs/产品设计文档.md §三 t_appointment §5.2 预约管理`
- **测试**：`AppointmentServiceTest`、`AppointmentControllerDetailTest`
- **证据**：`anchored`

## entity:report

**服务报告**。一约一份，提交后锁死；必须绑定 `appointment_id`。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| appointment_id | BIGINT | 关联预约 ID（唯一约束 `uk_report_appointment`） |
| remark | TEXT | 备注 |
| customer_user_id | BIGINT | 报告归属客户，从预约派生 |
| author_staff_id | BIGINT | 实际提交报告的门店账号 |
| user_id | BIGINT | legacy 兼容；新数据镜像 author_staff_id |
| store_id | BIGINT | 所属门店 |
| report_token | VARCHAR | 公开访问令牌（UUID，不带回包给公开页） |
| pet_name | VARCHAR | 宠物名（冗余） |
| service_type | VARCHAR | 服务类型（冗余） |
| appointment_time | DATETIME | 服务时间（冗余） |
| staff_name | VARCHAR | 服务技师名快照（优先取预约 assigned_user_id） |
| send_status | VARCHAR(16) | unknown（历史不可确认）/ unsent（新报告待发送）/ sent（已显式确认） |
| sent_at | DATETIME | 首次显式确认发送时间 |
| sent_by_user_id | BIGINT | 首次确认发送的门店员工 |
| send_channel | VARCHAR(16) | wechat / qr / other；首次确认后不可覆盖 |
| highlight_video_url | VARCHAR(500) | 成片 MP4 相对 URL |
| highlight_video_status | VARCHAR(32) | processing / done / failed |
| highlight_video_error | VARCHAR(500) | 错误信息 |
| highlight_duration_sec | INT | 成片时长 |
| highlight_compose_mode | VARCHAR(16) | preset / interleave |
| highlight_fail_reason | VARCHAR(16) | material / service / network / unknown |
| highlight_share_cover_url | VARCHAR(500) | 成片首帧封面 |
| create_time / update_time | DATETIME | 时间戳 |
| deleted | TINYINT(1) | 软删 |

- **归属系统**：Report Media Backend（`ReportController` / `ReportService` / `ReportMapper`）
- **代码**：`backend/src/main/java/com/petstore/entity/Report.java`、`backend/src/main/java/com/petstore/service/ReportService.java`
- **文档**：`docs/产品设计文档.md §三 t_report §5.3 服务报告`、`docs/API-报告成片字段契约.md`
- **测试**：`ReportServiceTest`、`ReportControllerTest`
- **证据**：`anchored`

## entity:report_lead

**留资线索**。宠主在公开报告页提交手机号，进入门店回访池。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| report_id | BIGINT | 关联报告 ID |
| store_id | BIGINT | 所属门店（用于回访池范围） |
| phone | VARCHAR | 手机号 |
| pet_name | VARCHAR | 宠物名（冗余） |
| service_type | VARCHAR | 服务类型（冗余） |
| wechat_openid | VARCHAR(64) | 微信 openid（脱敏返回 `wechatOpenidMasked`） |
| wechat_unionid | VARCHAR(64) | 微信 unionid（脱敏返回 `wechatUnionidMasked`） |
| reminder_type | VARCHAR | 提醒类型 |
| remind_date | DATE | 下次建议日期 |
| remind_status | VARCHAR | pending / sent / canceled / unsubscribed |
| unsubscribe_token | VARCHAR | 退订令牌 |
| client_ip | VARCHAR | 留资 IP（合规留证） |
| create_time / update_time | DATETIME | 时间戳 |
| deleted | TINYINT(1) | 软删 |

- **归属系统**：Report Media Backend（`ReportLeadController` / `ReportLeadService`）
- **代码**：`backend/src/main/java/com/petstore/controller/ReportLeadController.java`、`backend/src/main/java/com/petstore/entity/ReportLead.java`
- **文档**：`docs/本期主线-下一步产品优化点.md §C 留资`
- **测试**：暂无（Batch D 未补 `ReportLeadControllerTest`，列为 `documented` gap）
- **证据**：`anchored`（脱敏与跨店校验已实现并测试覆盖；去重逻辑 `documented`）

## entity:follow_up_task

**门店回访执行任务**。`ReportLead` 保留宠主同意和来源事实，本实体承载员工可领取、可改期、可终结的操作状态以及真实再次预约引用。

| 字段 | 类型 | 说明 |
|------|------|------|
| id / task_id | BIGINT / VARCHAR(36) | 内部主键 / Admin 对外随机 ID |
| store_id / store_customer_id | BIGINT | 同店范围 / canonical 客户主档 |
| source_lead_id / source_report_id | BIGINT | 来源留资 / 报告 |
| status | VARCHAR(16) | pending / in_progress / completed / canceled |
| outcome | VARCHAR(24) | rebooked / not_interested / invalid_contact / unsubscribed |
| due_date / assigned_user_id | DATE / BIGINT | 回访日 / 当前领取人 |
| attempt_count / last_attempt_outcome | INT / VARCHAR(24) | 已联系次数 / no_answer 或 follow_later |
| last_contacted_at / last_contacted_by_user_id | DATETIME / BIGINT | 最近联系回执 |
| closed_at / closed_by_user_id | DATETIME / BIGINT | 终态回执；退订取消无员工关闭人 |
| rebooked_appointment_id | BIGINT | 仅真实同店预约保存成功后写入 |
| create_time / update_time / version | DATETIME / DATETIME / BIGINT | 时间戳与并发版本 |

- **归属系统**：Backend Core + Store Admin FE
- **代码**：`backend/src/main/java/com/petstore/entity/FollowUpTask.java`、`service/FollowUpTaskService.java`
- **文档**：`docs/架构决策-FollowUpTask回访状态机与再次预约归因-2026-08-02.md`
- **测试**：`FollowUpTaskServiceTest`、`FollowUpTaskMigrationTest`、`AdminFollowUpTaskControllerTest`
- **证据**：`anchored`

## entity:business_event

**不可变业务事件**。预约、履约、报告打开与留资等经营事实的统一持久化入口，为报告漏斗和 StoreCustomer 时间线提供可统计事实源。

| 字段 | 类型 | 说明 |
|------|------|------|
| id / event_id | BIGINT / VARCHAR(36) | 内部主键 / 随机事件 ID |
| event_type / event_version | VARCHAR(64) / INT | 事件类型与 schema 版本 |
| store_id / store_customer_id | BIGINT | 门店范围 / 可选客户关系 |
| aggregate_type / aggregate_id | VARCHAR(32) / BIGINT | 关联业务对象 |
| actor_user_id / actor_role | BIGINT / VARCHAR(16) | 操作人快照，匿名事件可空 |
| source | VARCHAR(32) | customer / admin / public_report / system / migration |
| occurred_at / create_time | DATETIME | 发生时间 / 入库时间 |
| metadata_json | TEXT | 服务端白名单低敏标量 JSON |
| idempotency_key | VARCHAR(128) | 可选幂等键 |

- **隐私**：不保存手机号、报告 token/hash、媒体 URL、IP、openid/unionid、备注或原始请求体
- **不变量**：只追加，不更新、不软删；通用事件明细不直接暴露给前端
- **归属系统**：Backend Core（跨 Report Media Backend 写入点）
- **代码**：`backend/src/main/java/com/petstore/entity/BusinessEvent.java`、`BusinessEventService.java`
- **测试**：`BusinessEventServiceTest`、`BusinessEventMigrationTest`
- **证据**：`anchored`

---

## Batch 2 录入实体

## entity:pet

**宠物档案**。归属某客户（`ownerUserId`）；商家/员工通过预约关联查看本店服务过的宠物。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| name | VARCHAR | 昵称 |
| pet_type | VARCHAR | 猫/狗/其他 |
| breed | VARCHAR | 品种（可选） |
| avatar | VARCHAR | 头像相对路径 |
| owner_user_id | BIGINT | 所属用户（客户） |
| remark | TEXT | 备注 |
| create_time / update_time | DATETIME | 时间戳 |
| deleted | TINYINT(1) | 软删 |

- **归属系统**：Backend Core（`PetController` / `PetService` / `PetMapper`）
- **代码**：`backend/src/main/java/com/petstore/entity/Pet.java`、`controller/PetController.java`、`service/PetService.java`
- **文档**：`docs/产品设计文档.md §三 t_pet（隐含，预约含 pet_name/pet_type）`
- **测试**：暂无 `PetControllerTest`（`gap`，建议补）
- **证据**：`anchored`（实体与跨用户/跨店校验已实现；测试覆盖为 `gap`）

## entity:service_type

**服务类型**。`storeId` 为空表示系统默认（不可改删）；非空为门店自定义。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| store_id | BIGINT | 门店 ID（NULL=系统默认） |
| name | VARCHAR | 服务名称 |
| duration_minutes | INT | 预计服务时长（30～480，30 分钟粒度） |
| create_time | DATETIME | 创建时间 |

- **归属系统**：Backend Core（`ServiceTypeController` / `ServiceTypeService` / `ServiceTypeMapper`）
- **代码**：`backend/src/main/java/com/petstore/entity/ServiceType.java`、`controller/ServiceTypeController.java`
- **文档**：`docs/产品设计文档.md §三 t_service_type §5.2 服务类型`
- **测试**：`ServiceTypeControllerTest`（12 用例）
- **证据**：`anchored`

## entity:schedule_block

**门店日程手动占用**。以半小时为粒度连续占用，用于到店客（`walk_in`，消耗一个容量）或暂停预约（`blocked`，关闭覆盖范围内全部容量）。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| store_id | BIGINT | 所属门店（非空） |
| slot_start | DATETIME | 半小时档起始时刻 |
| duration_minutes | INT | 连续占用时长（30～480，30 分钟粒度） |
| block_type | VARCHAR(32) | walk_in / blocked |
| note | VARCHAR(500) | 备注 |
| created_by_user_id | BIGINT | 创建人（从上下文派生） |
| create_time / update_time | DATETIME | 时间戳 |
| deleted | TINYINT(1) | 软删 |

- **归属系统**：Backend Core（`ScheduleController` / `ScheduleService` / `ScheduleBlockMapper`）
- **代码**：`backend/src/main/java/com/petstore/entity/ScheduleBlock.java`、`controller/ScheduleController.java`、`service/ScheduleService.java`
- **文档**：`docs/产品设计文档.md §5.2（隐含，预约时段占用）`
- **测试**：`ScheduleControllerTest`、`BookingCapacityServiceTest`
- **证据**：`anchored`

## entity:report_image

**报告媒体**。`photo_type` 分组：`before`（服务前）/ `after`（服务后）/ `during`（服务过程中）。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| report_id | BIGINT | 关联报告 ID |
| photo_url | VARCHAR(500) | 媒体 URL |
| photo_type | VARCHAR(20) | before / after / during |
| media_type | VARCHAR(20) | photo / video（默认 photo） |
| sort_order | INT | 同类型内排序 |
| create_time | DATETIME | 创建时间 |

- **归属系统**：Report Media Backend（`ReportImageMapper`）
- **代码**：`backend/src/main/java/com/petstore/entity/ReportImage.java`、`mapper/ReportImageMapper.java`
- **文档**：`docs/产品设计文档.md §三 t_report_image`
- **测试**：间接（`ReportServiceTest#createWithImagesSavesEachImage`）
- **证据**：`anchored`

## entity:highlight_video

**服务回顾短片**。当前作为 `entity:report` 的 `highlight_*` 字段承载，非独立 JPA 实体；状态机 `processing` / `done` / `failed`。

| 字段 | 类型 | 说明 |
|------|------|------|
| report_id | BIGINT | 关联报告（即 Report.id） |
| highlight_video_url | VARCHAR(500) | 成片 MP4 相对 URL |
| highlight_video_status | VARCHAR(32) | processing / done / failed |
| highlight_video_error | VARCHAR(500) | 错误信息 |
| highlight_duration_sec | INT | 成片时长 |
| highlight_compose_mode | VARCHAR(16) | preset / interleave |
| highlight_fail_reason | VARCHAR(16) | material / service / network / unknown |
| highlight_share_cover_url | VARCHAR(500) | 成片首帧封面 |

- **归属系统**：Report Media Backend（`ReportHighlightVideoService`）
- **代码**：`backend/src/main/java/com/petstore/entity/Report.java`（字段承载）、`service/ReportHighlightVideoService.java`
- **文档**：`docs/API-报告成片字段契约.md`、`docs/洗美报告短视频成片方案.md`
- **测试**：暂无独立测试（`gap`，建议补成片三态用例）
- **证据**：`anchored`（字段与三态已实现；FFmpeg 集成测试为 `gap`）

## entity:session_token

**HMAC session token**。非 JPA 实体，鉴权上下文数据结构；token 格式 `base64url(payload).base64url(sig)`，HMAC-SHA256，默认 7 天。

| 字段 | 类型 | 说明 |
|------|------|------|
| userId | Long | 用户 ID |
| storeId | Long | 门店 ID（customer 可空） |
| role | String | boss / staff / customer |
| exp | long | Unix 秒过期时间 |

- **归属系统**：Backend Core（`SessionTokenService` / `CurrentUser` / `CurrentUserContext` / `AuthInterceptor`）
- **代码**：`backend/src/main/java/com/petstore/auth/SessionTokenService.java`、`CurrentUser.java`、`CurrentUserContext.java`、`AuthInterceptor.java`
- **文档**：`docs/ontology/rules.md#rule:BR-AUTH-001`
- **测试**：`SessionTokenServiceTest`（7 用例）
- **证据**：`anchored`

---

## Batch 2.1 补录实体（audit_drift 发现的遗漏）

## entity:report_testimonial

**宠主寄语**。生成分享海报时填写的一句话（口碑素材）；同一报告保留一条，重复提交更新内容与时间。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| report_id | BIGINT | 关联报告（唯一约束，一报告一寄语） |
| store_id | BIGINT | 所属门店 |
| content | VARCHAR(200) | 寄语内容 |
| is_public | TINYINT(1) | 是否公开（默认 1） |
| create_time / update_time | DATETIME | 时间戳 |

- **归属系统**：Report Media Backend（`ReportTestimonialService`）
- **代码**：`backend/src/main/java/com/petstore/entity/ReportTestimonial.java`、`service/ReportTestimonialService.java`
- **入口**：`POST /api/report/{token}/testimonial` → `ReportController#submitTestimonial`
- **证据**：`anchored`

## entity:service_interval

**服务建议周期配置**。用于「下次该洗了」的日期预测；`store_id` 为 null 表示系统默认，老板可针对自家店覆盖。

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| store_id | BIGINT | 门店 ID（NULL=系统默认） |
| service_name | VARCHAR(64) | 与 `t_service_type.name` 对齐作为匹配键 |
| pet_type | VARCHAR(16) | 按宠物类型细分（dog/cat/other），NULL=全部 |
| interval_days_min | INT | 建议最短间隔（天） |
| interval_days_max | INT | 建议最长间隔（天） |
| create_time | DATETIME | 创建时间 |

- **归属系统**：Report Media Backend（`ReportLeadService#suggestNext` 读取）
- **代码**：`backend/src/main/java/com/petstore/entity/ServiceInterval.java`
- **证据**：`anchored`（实体存在；独立 controller 接口 `gap`，目前由 `ReportLeadService` 内部读取）

## entity:highlight_fail_reason

**成片失败原因归类**（非 JPA 枚举）。对宠主展示口径，不含技术堆栈。

| 枚举值 | code | 说明 |
|------|------|------|
| MATERIAL | material | 素材缺失/无法读取/编码失败 |
| SERVICE | service | 合成服务异常（ffmpeg/磁盘等） |
| NETWORK | network | 网络类异常（预留） |
| UNKNOWN | unknown | 未归类 |

- **归属系统**：Report Media Backend
- **代码**：`backend/src/main/java/com/petstore/entity/HighlightFailReason.java`
- **证据**：`anchored`（`jpa: false`）

## entity:customer_timeline

**门店客户事实时间线**（非 JPA 只读模型）。以 canonical `StoreCustomer.id` 为稳定客户关系键，把已经持久化的 `BusinessEvent` 投影为门店可理解的预约、履约、报告发送/打开、留资和回访任务记录；被合并投影的事件通过别名指针一并读取，不改写历史事件。

| 字段 | 说明 |
|------|------|
| customer | 客户称呼、脱敏手机号、首次来源、首次/最近接触时间 |
| total / page / pageSize / hasMore | 稳定倒序分页信息 |
| list | `eventId/eventType/occurredAt/title/description` 与可选预约/报告/留资/FollowUpTask 低敏摘要 |

- **归属系统**：Backend Core / Store Admin FE
- **代码**：`backend/src/main/java/com/petstore/service/StoreCustomerTimelineService.java`、`admin/src/views/CustomersView.vue`
- **隐私边界**：不返回手机号原文、备注、媒体、报告/邀请 token、openid/unionid、IP 或原始 `metadata_json`
- **证据**：`anchored`（`jpa: false`）

## entity:current_user

**当前登录用户上下文**（非 JPA record）。由 `AuthInterceptor` 从 `entity:session_token` 解析后写入 ThreadLocal，与令牌实体区分。

| 字段 | 类型 | 说明 |
|------|------|------|
| userId | Long | 用户 ID |
| storeId | Long | 所属门店（customer 可为 null） |
| role | String | boss / staff / customer |

- **归属系统**：Backend Core（auth 包）
- **代码**：`backend/src/main/java/com/petstore/auth/CurrentUser.java`、`CurrentUserContext.java`、`AuthInterceptor.java`
- **证据**：`anchored`（`jpa: false`）

---

## Batch：门店 Web 后台（Phase A）

> 产品依据：`docs/门店管理后台升级方案-对标宠老板.md`。  
> 各对象按实际证据标记；收银/会员资产仍见下方 gap 占位。

## entity:admin_console

**门店 Web 管理后台**（非 JPA）。与 uni-app 小程序并存的桌面优先管理端；登录落点为工作台，不做收银/库存首页。

| 字段（概念） | 说明 |
|------|------|
| surface | Web admin |
| navModules | workbench / appointments / reports / customers / leads / schedule / settings |
| storeScope | 单店 `storeId` |
| roleGate | Phase A：boss / staff 均可读写本店；customer 禁止 |

- **归属系统**：Store Admin FE + Backend Core
- **证据**：`documented`（`jpa: false`）
- **约束**：`rule:BR-ADMIN-001` … `BR-ADMIN-004`

## entity:workbench

**今日工作台**（非 JPA 聚合视图）。指标卡 + 按履约优先级排序的行动队列 + 报告漏斗摘要；行动来自 appointment/report/follow_up_task/highlight，报告漏斗来自 BusinessEvent，**无独立工作台表**。服务端按完整本店数据计算准确总数，不再由前端拉取前 200 条估算。

| 字段（概念） | 说明 |
|------|------|
| todayAppointmentCount / todayDoneCount | 今日预约 / 今日完成 |
| overdueAppointmentCount / inServiceCount / upcomingTodayCount | 逾期未开始 / 服务中 / 今日待开始 |
| dueFollowUpCount / highlightAnomalyCount / unsentReportCount | 到期回访 / 成片失败或处理超时 / 新报告待发送 |
| actionGroups | `entity:workbench_todo_item[]`（每组最多 3 条低敏预览） |
| reportFunnel | 已提交/已确认发送/已打开/已留资/回访后真实再次预约；全部来自 BusinessEvent |
| anomalyList | 成片失败或待回访最近 N 条 |

- **代码**：`admin/src/views/WorkbenchView.vue`、`AdminBusinessEventController`、`AdminWorkbenchService`、`AdminBusinessEventService`
- **测试**：`AdminBusinessEventControllerTest`、`AdminWorkbenchServiceTest`、`AdminBusinessEventServiceTest`
- **证据**：`anchored`
- **动作**：`action:view_workbench`

## entity:workbench_todo_item

**工作台待办项**。

| 字段 | 说明 |
|------|------|
| kind | `overdue_appointment` / `in_service` / `upcoming_today` / `highlight_anomaly` / `unsent_report` / `follow_up_due` |
| urgency | critical / high / normal |
| count | 数量 |
| items | 最多 3 条 resource ID + 宠物/服务/时间低敏预览；无手机号、token、媒体 URL |
| drilldownAction / filter | 下钻到的 admin list action 与预置筛选 |

- **代码**：`AdminWorkbenchService`、`admin/src/views/WorkbenchView.vue`
- **测试**：`AdminWorkbenchServiceTest`
- **证据**：`anchored`

## entity:store_customer

**StoreCustomer 门店客户主档（瘦身）**。门店与客户的稳定关系实体，**不是** `entity:membership`。后台读模型以它为入口，再聚合 Pet + Appointment + Report + ReportLead。

| 字段 | 说明 |
|------|------|
| id | 本店客户稳定 ID（API: storeCustomerId） |
| store_id | 本店范围 |
| customer_user_id | 可选全局 customer 账号；留资客户可先为空 |
| phone / display_name | 本店联系手机号与称呼；API 手机号默认脱敏 |
| source | 首次来源：customer_booking / assisted_booking / report_lead |
| first_contact_at / last_contact_at | 首次/最近接触时间 |
| merged_into_store_customer_id | 仅软删除投影使用；指向同店有效 canonical 主档，保持不可变事件连续性 |
| create_time / update_time / deleted | 审计与软删除 |

API 动态聚合字段：`pets`、`lastVisitAt`、`lastReportId`、`leadStatus`、`source`。`lastVisitAt` 只统计已开始/已完成的历史服务。

- **不做**：余额、押金、积分、次卡、会员等级、批量导入持卡会员
- **证据**：`anchored`（`t_store_customer` + 动态读模型）
- **动作**：`action:admin_list_service_customers`

---

## 待录入（P1/P2 方向，gap 证据）

以下已在 `graph/ontology.jsonl` 以 `evidence=gap` + `jpa=false` 占位，**不展开字段**；**Phase A 门店后台明确不做**（`rule:BR-ADMIN-004`）：

- `entity:membership`（会员卡）— P1/P2 / C 期
- `entity:stored_value`（储值）— P1/P2
- `entity:service_package`（套餐/次卡）— P1/P2
- `entity:report_history`（宠主历史报告汇总）— P2
- `entity:analytics_dashboard`（经营分析看板）— B 期；A 期仅工作台轻量指标

后续如进入实现，再补完整字段、动作与规则。

---

## 智能预约助手 M0（Batch 0 契约）

## entity:booking_agent_session

**智能预约短期会话**。它承载已登录 customer 在一个已选门店下的结构化草稿和六态状态机，不是预约，不持有任何预约写入权。

| 字段 | 说明 |
|------|------|
| `id` | BIGINT 内部主键；用于低敏 `BusinessEvent.aggregateId` |
| `session_id` | 唯一外部 UUID |
| `customer_user_id` | 创建会话的 customer，只从登录上下文派生 |
| `store_id` | 创建时必填并验证，会话期间固定 |
| `status` | `collecting / proposing / confirmable / fallback / expired / cancelled` |
| `draft_json` / `draft_version` | 最小已验证草稿与乐观版本 |
| `entry_source` | M0 固定 `appointment_create` |
| `input_modality` | `text / voice / mixed`，首次成功消息后派生 |
| `expires_at` | 创建后 30 分钟，不滑动续期 |
| `create_time / update_time` | 服务端审计时间 |

- **不存储**：原始语音、转写原文、完整对话、提示词、模型原始请求/响应、`appointment_id`。
- **归属系统**：Backend Core（计划 `bookingagent` 包）。
- **文档**：`docs/架构决策-智能预约助手M0契约与数据模型-2026-08-02.md`。
- **证据**：`documented`（尚未创建 JPA 实体，机器图临时 `jpa:false`；实现批必须切换为真实 JPA 证据）。
- **约束**：`rule:BR-BA-001` … `rule:BR-BA-005`。
