预约系统测试环境文档

当前页面用于 beta 文档访问与部署验收。

SSOT.md

# SSOT

## 1. 产品目标

V1 只做一个完整闭环:宠主预约、前台接诊、今日队列、小暖反写。  
V1 不做小程序、不做复杂报表、不做多租户、不做超出当前业务边界的扩展能力。

本系统是预约主系统。运行时 SSOT 是我们自己的 Postgres。小暖是外部系统,不是主系统。

## 2. 主键和身份

系统内部主键全部由我们生成:

- `owner_id`
- `pet_id`
- `appointment_id`
- `doctor_id`
- `slot_id`
- `queue_entry_id`

手机号只用于:

- 登录
- 查询
- 建立宠主的主要联系方式

手机号不是内部主键,不允许作为跨表主关联键。

## 3. 核心业务对象

### 3.1 宠主

- 一个宠主可拥有多个宠物
- 宠主档案与宠物档案分离
- 宠主可被多个渠道复用,但内部仍是同一个 `owner_id`

### 3.2 宠物

- 一个宠物当前只归属一个宠主
- 宠物档案独立维护
- 所有预约最终归属于某个 `pet_id`

### 3.3 预约

- 预约是业务主对象
- 宠主端和前台端共用同一预约模型
- 差异只体现在 `channel`、权限、可选字段和可见操作

### 3.4 队列

- 队列是今日现场执行对象
- 预约和队列不是同一个概念
- 队列项必须关联预约,但生命周期独立记录

## 4. 渠道和类型

### 4.1 预约渠道

- `owner_online`
- `front_desk_phone`
- `front_desk_walk_in`
- `manual_vip`

### 4.2 预约类型

V1 最少支持:

- 普通门诊
- 疫苗预防
- 复诊复查
- 手术

说明:

- `手术` 是预约类型,不是渠道
- 宠主端默认不开放手术
- 手术只允许前台端创建

## 5. 预约状态

V1 只保留以下状态:

- `pending_arrival_nocontact`
  - 中文:待到店-未联系
  - 说明:预约已创建,但前台未联系,或未写联系备注

- `pending_arrival_withcontact`
  - 中文:待到店-已联系
  - 说明:预约已创建,前台已联系过,或已写联系备注

- `queued`
  - 中文:候诊中
  - 说明:预约已进入今日队列,等待医生接诊

- `in_service`
  - 中文:就诊中
  - 说明:当前正在医生接诊中

- `completed`
  - 中文:已完成
  - 说明:本次预约对应的现场服务已经完成

- `cancelled`
  - 中文:已取消
  - 说明:本次预约不再继续执行

## 6. 明确不保留的状态

- `arrived`
  - 原因:宠物医院场景下一到店通常直接排队,没有独立保留价值

- `no_show`
  - 原因:客户不来时,前台直接取消并写备注即可

- `pending_contact`
  - 原因:联系不是必经步骤

- `rescheduled`
  - 原因:改约不作为状态,统一按“取消原单,再重新约”处理

## 7. 状态迁移规则

允许迁移:

- `pending_arrival_nocontact -> pending_arrival_withcontact`
- `pending_arrival_nocontact -> queued`
- `pending_arrival_nocontact -> cancelled`
- `pending_arrival_withcontact -> pending_arrival_nocontact`
- `pending_arrival_withcontact -> queued`
- `pending_arrival_withcontact -> cancelled`
- `queued -> in_service`
- `queued -> pending_arrival_nocontact`
- `queued -> pending_arrival_withcontact`
- `queued -> cancelled`
- `in_service -> completed`
- `in_service -> queued`
- `in_service -> cancelled`
- `completed -> in_service`
- `completed -> queued`
- `cancelled -> pending_arrival_nocontact`
- `cancelled -> pending_arrival_withcontact`

禁止迁移:

- 任意状态直接跳成不存在的状态
- `completed` 直接恢复为 `pending_arrival_*`
- `cancelled` 自动恢复

## 8. 前台动作

允许动作:

- 通知/备注
- 排队
- 上移
- 下移
- 完成
- 取消
- 回退
- 恢复

约束:

- 通知/备注不单独形成流程状态
- 取消必须二次确认
- 回退必须二次确认
- 恢复必须二次确认
- 完成必须二次确认

## 9. 今日队列规则

- 同一医生同一时刻只允许一条 `in_service`
- `queued -> in_service -> completed` 是今日主链路
- 当前 `in_service` 完成后,如果队列还有下一位,则按顺序自动进入 `in_service`
- 队列支持上移、下移
- 已完成可回退到 `in_service` 或 `queued`
- 回退后不得自动打断当前已有的 `in_service`

## 10. 排班和号源

- 未来排班可维护
- 当天 `00:00` 后锁定当天排班和普通号源
- 已被占用的普通号源不能直接删除
- 允许追加人工号源
- 允许临时停诊,但必须记录审计和影响范围

## 11. 医生临时请假

当医生临时请假:

- 不允许批量改约
- 不允许批量取消
- 必须由前台逐单处理
- 对未到店预约:
  - 逐单取消
  - 如需继续就诊,走人工号源重新约
- 对已排队和 `in_service`:
  - 只能人工逐单处理
  - 不允许系统自动吞掉

## 12. 数据流

### 12.1 开发前

- 小暖是唯一业务入口
- 我们系统负责导入、归集、验证、反写链路准备

### 12.2 过渡期

- 小暖入口和我们入口并存
- 任何入口产生的数据都必须归集到我们 SSOT
- 然后再反写小暖

### 12.3 最终期

- 我们系统成为唯一入口
- 小暖只接收反写

## 13. 反写小暖

- 每次有效业务变更都要反写小暖
- 反写必须异步
- 目标时延 1 分钟内
- 失败要重试
- 重试后仍失败要进入死信
- 必须支持幂等

## 14. 审计

以下动作必须审计:

- 创建预约
- 修改预约
- 取消预约
- 恢复预约
- 完成预约
- 回退预约
- 排队
- 队列顺序调整
- 临时停诊
- 人工号源重约
- 小暖导入
- 小暖反写

SDD.md

# SDD

## 1. 总体架构

V1 采用单仓后端优先架构:

```text
Frontend HTML/JS
  -> Fastify API
  -> PostgreSQL
  -> Sync/Outbox Worker
  -> XiaoNuan Adapter
```

说明:

- 后端是主系统
- 前台端和宠主端都只调用我们的 API
- 小暖只通过导入和反写适配层接入

## 2. 目录边界

建议目录:

```text
docs/
src/api/
src/domain/
src/db/
src/sync/
src/ui/
src/lib/
migrations/
tests/
ops/
```

## 3. 模块 0 基座

范围:

- 医院配置
- 服务鉴权
- 基础错误码
- 验证码占位服务
- 审计日志

实现要点:

- 验证码逻辑先固定 `888888`
- 但服务接口必须可替换,不能把 `888888` 硬编码到业务流里
- 审计日志单独建表,供预约、队列、同步复用

## 4. 模块 1 排班与号源

范围:

- 医生档案
- 周排班模板
- 每日号源展开
- 普通号源和人工号源
- 锁定规则
- 临时停诊

实现要点:

- 号源是“未来承诺资源”,不是医生完整真实日程
- 普通号源支持按小时生成
- 手工号源单独标记,不占普通容量
- 当天 00:00 后锁定
- 临时停诊必须产生影响清单

## 5. 模块 2 宠主端

范围:

- 手机号登录
- 首登即注册
- 宠主档案维护
- 宠物档案维护
- 预约创建
- 我的预约查询

实现要点:

- 宠主和宠物分表
- 一人多宠
- 宠主端只能看到自己的数据
- 图片上传只做占位字段,不做真实上传服务

## 6. 模块 3 前台端

范围:

- 前台建预约
- 通知/备注
- 排队
- 调整顺序
- 完成
- 取消
- 回退
- 恢复

实现要点:

- 与宠主端共用预约主模型
- 只在权限和操作上区分
- 取消、回退、恢复都需要二次确认

## 7. 模块 4 今日队列

范围:

- 医生分组队列
- 当前就诊
- 队列推进
- 队列顺序调整

实现要点:

- 队列项独立于预约记录
- 同一医生只能存在一条 `in_service`
- 完成后自动推进下一条
- 回退不自动抢占已有 `in_service`

## 8. 模块 5 小暖同步与反写

范围:

- 小暖导入
- 外部映射
- Outbox
- Worker
- 反写确认
- 重试
- 死信

实现要点:

- 导入和反写分开建模
- 使用 outbox 表保存待发送事件
- worker 异步投递
- 幂等键必须稳定
- 所有失败都必须可追踪
- 导入应兼容 DoctorWarm 原始预约字段;如果原始数据缺医生标识,先落到占位医生,后续再人工修正

## 9. 数据模型建议

V1 至少需要:

- `owners`
- `owner_profiles`
- `pets`
- `pet_profiles`
- `doctors`
- `schedule_rules`
- `appointment_slots`
- `appointments`
- `queue_entries`
- `audit_logs`
- `external_links`
- `sync_outbox`
- `sync_attempts`
- `sync_dead_letters`

## 10. API 设计原则

- 先 API,再页面
- 宠主端和前台端共享核心读写接口
- 页面不直接写数据库
- 所有接口都要有明确中文文档

## 11. 实施顺序

1. 文档和数据模型
2. 后端基座和迁移
3. 预约与队列闭环
4. 前端页面联调
5. 小暖导入与反写
6. CI 与部署脚本

TDD.md

# TDD

## 1. 原则

按 `karpathy-guidelines` 执行:

- 先定义可验证目标
- 先写失败测试
- 再写最小实现
- 不提前抽象
- 每一步都能用测试证明

## 2. 第一批必须先写的测试

### 2.1 身份与档案

- 手机号首次登录即注册
- 同手机号再次登录命中同一宠主
- 同手机号可创建多个宠物
- 第二只宠物不影响第一只宠物

### 2.2 预约创建

- 宠主端创建普通预约成功
- 前台电话预约成功
- 前台现场预约成功
- 手术预约只能前台创建
- 宠主端不能创建手术预约

### 2.3 状态机

- `pending_arrival_nocontact` 可排队
- `pending_arrival_withcontact` 可排队
- `queued` 可回退到两种待到店状态
- `in_service` 完成后自动推进下一位
- `completed` 可回退到 `queued`
- `completed` 可回退到 `in_service`

### 2.4 取消与恢复

- 取消必须二次确认
- 取消后释放普通号源
- 取消后删除队列项
- 恢复必须二次确认
- 恢复后不自动重新占号

### 2.5 排班与号源

- 当天 00:00 后修改排班被拒绝
- 号源满时普通预约失败
- 号源满时人工号源仍可重约
- 临时请假不能批量改约

### 2.6 同步与反写

- 导入幂等
- 反写幂等
- 反写失败重试
- 超过 1 分钟未确认触发告警
- 多次失败进入死信

## 3. 测试顺序

1. 单元测试:状态机、号源、队列推进
2. 集成测试:API + DB
3. 同步测试:Outbox/Worker/小暖适配层
4. UI 冒烟测试:主链路

TEST_MATRIX.md

# TEST MATRIX

## 1. 医生临时请假

- 有未来预约时新增停诊记录
- 不允许批量取消
- 不允许批量改约
- 必须逐单取消
- 取消后如需继续就诊,只能人工号源重约

## 2. 已排队取消

- 从 `queued` 取消
- 队列项删除
- 顺序自动归一
- 如存在 `in_service`,不得受影响

## 3. 已完成回退

- `completed -> in_service`
- `completed -> queued`
- 回退后不得破坏当前已有 `in_service`

## 4. 取消恢复

- `cancelled -> pending_arrival_nocontact`
- `cancelled -> pending_arrival_withcontact`
- 恢复后不自动占号
- 恢复后不自动进队列

## 5. 自动进入 in_service

- 当前一位完成,下一位自动进入 `in_service`
- 同一医生同时只能有一条 `in_service`
- 最后一位完成后,不产生新的 `in_service`

## 6. 人工号源重约

- 普通号满后允许前台走人工号源
- 人工号源不占普通容量
- 重约后保留审计链路

## 7. 当天锁定后改排班

- 当天 00:00 后普通修改被拒绝
- 强制调整必须带审计
- 强制调整不得删除已占用号

## 8. 号源满后加号

- 普通号约满
- 宠主端不可继续占普通号
- 前台端允许人工号源处理

## 9. 前台和宠主端同模型不同权限

- 同一个预约主模型
- 宠主端权限受限
- 前台端多出取消、回退、恢复、队列操作

## 10. 小暖导入和我们导入并存

- 导入冲突去重
- 外部映射稳定
- 重复事件幂等
- 反写不重复制造脏数据

CI.md

# CI

## 1. 门禁目标

所有变更必须在本地可验证,再进入部署。

## 2. 最小门禁顺序

1. `doctor`
2. `lint`
3. `test`
4. `ssot-check`
5. `check-no-secrets`
6. `check-no-data`
7. `schema-check`
8. `schema-drift-test`
9. `db-migrate-test`
10. `api-contract-test`
11. `api-docs-check`
12. `sync-job-test`
13. `sync-db-write-test`
14. `sync-rate-limit-test`
15. `write-*`
16. `ui-smoke-test`
17. `deploy-config-check`
18. `ci-local`

## 3. 各门禁职责

- `doctor`:环境、依赖、Node 版本检查
- `lint`:代码风格和明显错误
- `test`:单元测试和基础集成测试
- `ssot-check`:文档和实现边界一致
- `check-no-secrets`:防止密钥入库
- `check-no-data`:防止真实数据入库
- `schema-check`:Schema 完整性
- `schema-drift-test`:Schema 漂移检查
- `db-migrate-test`:迁移能完整跑通
- `api-contract-test`:API 契约测试
- `api-docs-check`:API 文档生成一致
- `sync-*`:同步链路测试
- `write-*`:写入、反写、回滚、幂等
- `ui-smoke-test`:页面主链路可达
- `deploy-config-check`:部署配置正确
- `ci-local`:本地聚合门禁

DEVOPS.md

# DEVOPS

## 1. 原则

- 本地开发是唯一允许的开发方式。
- `ssh120` 只做 beta 测试环境部署,不做云端开发。
- 所有代码、迁移、前端资源、Nginx 配置模板、部署脚本都必须在仓库内维护。
- 任何部署都必须来自 git 仓库与部署脚本,不允许手工在线修代码。

## 2. 环境职责

### 本地开发机

- 写代码
- 跑数据库迁移
- 跑双前端和后端联调
- 跑 `ci-local`
- 更新 `CHANGELOG.md`
- 推送到远端仓库

### ssh120

- 拉取 `main`
- 执行 `ops/bootstrap-ssh120.sh`
- 执行 `ops/deploy-ssh120.sh`
- 提供 beta API、beta 宠主端、beta 前台端、beta 文档站

## 3. 域名职责

beta 测试环境:

- `beta-booking-api.xspet.net`:预约系统 API
- `beta-booking-web.xspet.net`:宠主网页端
- `beta-ops.xspet.net`:医院前台网页端
- `beta-docs.xspet.net`:文档站

正式环境:

- `booking-api.xspet.net`
- `booking-web.xspet.net`
- `ops.xspet.net`
- `docs.xspet.net`

规则:

- beta 部署只允许从 `main` 发起,不允许直接从 feature 分支发布
- 正式域名必须使用独立证书与独立 nginx `server_name`
- 正式域名不能复用 beta 证书

## 4. 运行架构

### API

- 进程:`schedule-booking-api.service`
- 监听:`127.0.0.1:13001`
- 环境文件:`/etc/schedule-booking-system-new-v2.env`

### Worker

- 进程:`schedule-booking-worker.service`
- 负责:小暖反写、补偿、同步健康检查

### nginx

- 负责:
  - `beta-booking-api.xspet.net` 反代到 `127.0.0.1:13001`
  - `beta-booking-web.xspet.net` 托管 `/data/schedule-booking-system-new-v2/www/owner`
  - `beta-ops.xspet.net` 托管 `/data/schedule-booking-system-new-v2/www/ops`
  - `beta-docs.xspet.net` 托管 `/data/schedule-booking-system-new-v2/www/docs`

## 5. 目录约定

- 仓库:`/data/schedule-booking-system-new-v2/repo`
- 日志:`/data/schedule-booking-system-new-v2/logs`
- 前端发布目录:`/data/schedule-booking-system-new-v2/www`
- 宠主端:`/data/schedule-booking-system-new-v2/www/owner`
- 前台端:`/data/schedule-booking-system-new-v2/www/ops`
- 文档站:`/data/schedule-booking-system-new-v2/www/docs`

## 6. 本地门禁

进入部署前,至少通过:

```bash
npm run doctor
npm test
npm run owner-web:build
npm run frontdesk-web:build
npm run ui-smoke-test
npm run ci-local
```

`ci-local` 是整仓门禁,不是纯后端门禁。

## 7. bootstrap

首次在 ssh120 执行:

```bash
cd /data/schedule-booking-system-new-v2/repo
bash ops/bootstrap-ssh120.sh
```

职责:

- 安装 Node
- 安装 nginx
- 安装 certbot
- 创建目录
- 初始化 `/etc/schedule-booking-system-new-v2.env`
- 注册 systemd 服务
- 写入 nginx 配置模板

## 8. deploy

beta 部署只执行:

```bash
cd /data/schedule-booking-system-new-v2/repo
bash ops/deploy-ssh120.sh
```

职责:

1. `git pull main`
2. `npm ci`
3. `npm run deploy-preflight`
4. 构建 `owner-web`
5. 构建 `frontdesk-web`
6. 生成 docs 静态站
7. 发布静态资源到 `www`
8. 运行数据库迁移
9. 重启 API / worker
10. reload nginx
11. 做健康检查

部署前必须确认远端仓库当前分支为 `main`。如果 ssh120 当前 checkout 不是 `main`,先切回 `main` 再执行脚本。

## 9. HTTPS 与自动续期

- 证书工具:`certbot`
- 所有 beta 域名都必须启用 HTTPS
- 所有 HTTP 都必须 301 跳转到 HTTPS
- 使用 `certbot renew`
- 证书续期后 reload nginx

建议命令:

```bash
certbot --nginx -d beta-booking-api.xspet.net -d beta-booking-web.xspet.net -d beta-ops.xspet.net -d beta-docs.xspet.net
systemctl enable certbot-renew.timer
```

## 10. 健康检查

至少验证:

- `http://127.0.0.1:13001/health/live`
- `http://127.0.0.1:13001/health/ready`
- `http://127.0.0.1:13001/health/worker`
- `https://beta-booking-api.xspet.net/health/live`
- `https://beta-booking-web.xspet.net`
- `https://beta-ops.xspet.net`

## 11. 故障排查

### API

```bash
systemctl status schedule-booking-api.service
journalctl -u schedule-booking-api.service -n 200 --no-pager
tail -n 200 /data/schedule-booking-system-new-v2/logs/api.log
```

### Worker

```bash
systemctl status schedule-booking-worker.service
journalctl -u schedule-booking-worker.service -n 200 --no-pager
tail -n 200 /data/schedule-booking-system-new-v2/logs/worker.log
```

### nginx

```bash
systemctl status nginx
nginx -t
journalctl -u nginx -n 200 --no-pager
```

### 证书

```bash
certbot certificates
systemctl status certbot-renew.timer
```

## 12. 回滚

只允许按 git 回滚,不允许在线改代码。

标准回滚:

1. 在本地确定回滚目标 commit/tag
2. 推送回滚提交到远端 `main`
3. 在 ssh120 重新执行 `bash ops/deploy-ssh120.sh`

如果只是服务异常,不改代码:

```bash
systemctl restart schedule-booking-api.service
systemctl restart schedule-booking-worker.service
systemctl reload nginx
```

API.md

# API

## Health

- `GET /`
- `GET /version`
- `GET /app`
- `GET /health/live`
- `GET /health/ready`
- `GET /health/worker`

## Auth

- `POST /v1/auth/login/verify`

## Master Data

- `GET /v1/owners/:ownerId/profile`
- `POST /v1/owners/:ownerId/profile`
- `POST /v1/pets`
- `POST /v1/pets/:petId`
- `GET /v1/pets`
- `GET /v1/pets/:petId/profile`
- `POST /v1/pets/:petId/profile`
- `POST /v1/doctors`
- `GET /v1/doctors`
- `GET /v1/doctors/:doctorId`
- `POST /v1/doctors/:doctorId`
- `POST /v1/doctors/:doctorId/status`
- `POST /v1/imports/xiaonuan/appointments`
- `POST /v1/schedule-rules`
- `GET /v1/schedule-rules`

`POST /v1/imports/xiaonuan/appointments` 当前支持两类字段:

- 我们系统内部归一字段:`externalAppointmentId / externalOwnerId / externalPetId / phone / petName / scheduledDate ...`
- DoctorWarm 原始预约字段:`id / customer_id / pet_id / contact_phone / pet_name / reservation_type / record_type / reservation_date / remark / content`

如果小暖原始预约里缺医生编码,系统会落到占位医生:

- `doctor_code = XIAONUAN-UNASSIGNED`
- `display_name = 小暖导入待分配`

## Slots

- `POST /v1/slots/generate`
- `POST /v1/slots/manual`
- `GET /v1/slots`

## Appointments

- `POST /v1/appointments`
- `GET /v1/appointments/:appointmentId`
- `GET /v1/appointments`
- `POST /v1/appointments/:appointmentId/notes`
- `POST /v1/appointments/:appointmentId/pending-status`
- `POST /v1/appointments/:appointmentId/queue`
- `POST /v1/appointments/:appointmentId/complete`
- `POST /v1/appointments/:appointmentId/cancel`
- `POST /v1/appointments/:appointmentId/restore`
- `POST /v1/appointments/:appointmentId/rollback`
- `POST /v1/appointments/:appointmentId/move`

## Queue

- `GET /v1/queue`

V1_PLAN.md

# V1 完整开发计划

## 1. 目标与边界
V1 只做一件事:把“宠主预约、前台接诊、今日队列、小暖反写”做成可本地跑通、可部署的完整系统。  
不做小程序、不做复杂报表、不做多租户花活。  
原则按 `karpathy-guidelines`:先定 SSOT,再写最小实现,只做必须功能,不做未来假设。

## 2. SSOT
系统主键全部由我们生成:
- `owner_id`
- `pet_id`
- `appointment_id`

手机号只用于登录和查询,不是主键。  
唯一外部系统是小暖,所有数据最终归集到我们库。  
数据流分三段:
- 开发前:小暖是唯一入口,我们只导入和反写
- 过渡期:小暖入口和我们入口并存,所有数据最终进我们 SSOT,再反写小暖
- 最终期:只有我们是入口,小暖只接收反写

状态只保留:
- `pending_arrival_nocontact`
- `pending_arrival_withcontact`
- `queued`
- `in_service`
- `completed`
- `cancelled`

其中:
- `通知/备注` 只是记录,不是状态
- `arrived / no_show / rescheduled / pending_contact` 不保留
- 取消、回退、完成、重约都要有审计

## 3. SDD,按模块开发
**模块 0 基座**
- 医院配置
- 登录与服务鉴权
- 审计日志
- 验证码占位 `888888`
- 基础字典和通用错误码

**模块 1 排班与号源**
- 医生档案
- 周排班
- 每日号源生成
- 当天 00:00 锁定
- 人工号源
- 临时停诊
- 号源关闭与影响提示

**模块 2 宠主端**
- 手机号登录
- 首登即注册
- 宠主档案
- 宠物档案
- 一人多宠
- 预约创建
- 预约查询
- 预约状态查看
- 图片上传先占位,不接 OSS

**模块 3 前台端**
- 与宠主端共用预约模型
- 多渠道录入
- 通知与备注
- 排队
- 上移、下移
- 完成
- 取消
- 回退
- 取消与回退都要二次确认

**模块 4 今日队列**
- 今日候诊队列
- `queued -> in_service -> completed`
- 完成后自动推进下一位
- 当前就诊只允许一条 `in_service`
- 队列动作和前台动作共用同一数据模型

**模块 5 小暖同步与反写**
- 小暖导入
- 我们系统写入后异步反写小暖
- 目标 1 分钟内完成
- 失败重试
- 幂等控制
- 死信与人工处理

**模块 6 DevOps**
- 本地一键启动
- 迁移
- 健康检查
- CI
- ssh120 仅部署,不开发

## 4. TDD,按场景驱动
第一批必须先写测试,再实现:
- 手机号首次登录即注册
- 同手机号多宠物可并存
- `pending_arrival_*` 都可排队
- `queued` 可回退到两种待到店状态
- `in_service` 完成后自动推进下一位
- `completed` 可回退到 `queued / in_service`
- 取消必须二次确认
- 恢复必须二次确认
- 临时请假不能批量改约,只能逐单取消后重约
- 反写小暖必须异步,失败重试,1 分钟告警
- 重复投递必须幂等

测试矩阵要覆盖:
- 医生临时请假
- 已排队取消
- 已完成回退
- 取消恢复
- 自动进入 `in_service`
- 人工号源重约
- 当天锁定后改排班
- 号源满后加号
- 前台和宠主端同模型不同权限
- 小暖导入和我们导入并存时的冲突去重

## 5. CI 门禁
最小门禁顺序:
- `doctor`
- `lint`
- `test`
- `ssot-check`
- `check-no-secrets`
- `check-no-data`
- `schema-check`
- `schema-drift-test`
- `db-migrate-test`
- `api-contract-test`
- `api-docs-check`
- `sync-job-test`
- `sync-db-write-test`
- `sync-rate-limit-test`
- `write-*`
- `ui-smoke-test`
- `deploy-config-check`
- `ci-local`

## 6. 开发节奏
第 1 步,先落文档和数据模型。  
第 2 步,建后端基座和数据库迁移。  
第 3 步,做预约与队列闭环。  
第 4 步,做前端页面联调。  
第 5 步,接小暖导入与反写。  
第 6 步,补齐 CI 和部署脚本。  

## 7. 完成标准
V1 完成必须满足:
- 本地能跑
- 前后端能联通
- 宠主端和前台端能完成预约闭环
- 今日队列可操作
- 小暖能导入、能反写、能重试
- CI 全绿
- 可部署到 ssh120