Files
th-hotel-simple/docs/project/requirements/M002-v4-real-catalog-lookup-api-design.md
2026-07-20 01:54:38 +07:00

520 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# M002 V4 真实目录与 Lookup API 设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.3 |
| 日期 | 2026-07-19 |
| 状态 | CP11 已落地第一版数据库目录与 lookup API目录管理后台 CP1 已落地后端接口;真实 PMS 同步继续后置 |
| 适用范围 | M002 V4 Account、Market、Source、Room Type、Rate Code 目录来源、数据模型、前端 lookup、缓存、酒店隔离、权限和失败兜底 |
| 不适用范围 | 真实 OPERA / OHIP 写操作、真实价格计算、前端页面实现、SuperAgent Prompt 修改、目录同步任务、SuperAgent 机器目录接口 |
## 1. 文档定位
M002 V4 CP8 已实现第一版固定种子目录校验M002 V4 CP11 已把该目录迁移为数据库目录和前端 lookup API
- Basic Information 的 `account_code` 必须存在于当前酒店数据库 Account 目录。
- Market / Source 由 Account 派生,不由 SuperAgent 输出。
- Room Type / Rate Code 第一版使用当前酒店数据库目录校验和字段选项提示。
- 目录错误会让对应 V4 卡片进入 `REVIEW_REQUIRED`,用户通过复核解阻选择合法 code。
本文记录真实目录和 lookup API 的设计与 CP11 第一版实现。CP11 新增 `workflow_reservation_catalog_account``workflow_reservation_catalog_code` 两张表,并通过 Flyway 初始化 `HOTEL-TEST``HOTEL-DEV` 以及迁移执行时已存在的 `ACTIVE` 平台酒店的固定种子目录;同时新增 `ReservationV4CatalogBootstrapRunner`,在平台默认酒店由启动流程创建后,如果该酒店目录为空,会补一份 `FIXED_SEED_IMPORT` 初始化目录。不再在运行时代码中把固定种子作为全局目录事实。目录管理后台 CP1 已新增 Account、Room Type、Rate Code 后端查询、新增、启用 / 停用接口;`workflow_reservation_catalog_sync_run`、真实 PMS / OPERA / OHIP 同步、Market / Source 独立管理页面和 SuperAgent 机器目录供给接口继续后置。
后续若本文与 `M002-v4-agent-callback-field-contract.md` 的 Agent 输入字段冲突,以 Agent 字段契约为准;若与 `security-access-control-boundary.md` 的接口权限冲突,以安全边界为准。
## 2. 当前固定种子目录
CP11 之前固定种子实现位于后端 `ReservationV4DirectoryService``FixedReservationV4DirectoryServiceImpl`。CP11 起实现改为 `ReservationV4DatabaseDirectoryServiceImpl`,目录读取只走数据库目录表;固定种子通过 `V24__create_reservation_catalog_tables.sql` 初始化导入 dev/test 基准酒店和迁移时已有 `ACTIVE` 酒店,并由 `ReservationV4CatalogBootstrapRunner` 补齐迁移后才创建的默认 `ACTIVE` 酒店,来源标记为 `FIXED_SEED_IMPORT`
| 目录 | 当前用途 | 当前固定值 | 当前限制 |
| --- | --- | --- | --- |
| Account | Basic Information 可选目录SuperAgent 和用户提交都使用稳定 code | `QBD_TRAVEL``LIAN_TAI``HANATOUR_TD` | 已进入数据库初始化目录,仍不是 PMS 全量 Account后台 CP1 可新增、启用 / 停用 |
| Market | 由 Account 派生的订单级 Market | 当前 Account 均派生 `LEISURE` | 已进入通用代码目录,前端仍不直接编辑 |
| Source | 由 Account 派生的订单级 Source | 当前 Account 均派生 `TRAVEL_AGENT` | 已进入通用代码目录,前端仍不直接编辑 |
| Room Type | 房型 code 校验和字段选项提示 | `TWN``KING``DBL``SGL``TRP``RM1``RM2``RM3` | 已进入数据库初始化目录,仍不是 PMS 全量房型;后台 CP1 可新增、启用 / 停用 |
| Rate Code | Rate Code 校验和字段选项提示 | `BAR``RACK``PACKAGE``GROUP``FIT` | 已进入数据库初始化目录,不按日期、账号、房型过滤,不含价格;后台 CP1 可新增、启用 / 停用 |
当前固定种子只能支撑开发和演示闭环,不能作为生产长期事实源。
## 3. 设计目标
真实目录能力要解决以下问题:
1. 前端不再硬编码 Account、Room Type、Rate Code 选项。
2. 后端确认和复核仍然是最终校验者,前端 lookup 只用于选择体验。
3. 每个目录都按 `hotel_id` 隔离,不能跨酒店泄露目录。
4. 目录有来源、版本、更新时间和启停状态,便于排查 SuperAgent 输出 code 与系统目录不一致的问题。
5. PMS / OPERA / OHIP 不稳定或暂未接入时,系统仍可使用最后一次成功目录快照或系统管理目录兜底。
6. 固定种子目录可以作为 dev/test 或导入初始化兜底,但生产不应默认靠代码固定值。
## 4. 目录来源分层
目录来源按阶段分三层,后续可以逐步切换,不要求一步接真实 PMS。
| 阶段 | 来源 | 中文说明 | 适用目录 |
| --- | --- | --- | --- |
| Phase 0 | `FIXED_SEED_IMPORT` | CP11 已将固定种子通过 Flyway 和启动补种子流程导入数据库,不再作为运行时代码全局 Map | 当前 Account、Market、Source、Room Type、Rate Code |
| Phase 1 | `SYSTEM_MANAGED` | 本系统数据库目录,由初始化脚本、管理后台或导入文件维护 | Account、Market、Source也可临时维护 Room Type、Rate Code |
| Phase 2 | `PMS_SYNC` | 后端从 PMS / OPERA / OHIP 同步目录到本系统本地表,业务查询只读本地快照 | Room Type、Rate Code 优先Account、Market、Source 视 PMS 能力再接 |
原则:
- 业务确认接口只依赖本系统目录服务,不直接调用外部 PMS。
- 真实 PMS / OHIP Adapter 只负责同步目录快照,不把厂商 DTO 直接暴露给业务服务或前端。
- 同一目录可有 `source_system` 标记,但业务接口只使用稳定 `code`
## 5. 目录归属建议
| 目录 | 第一版真实来源建议 | 未来 PMS / OPERA / OHIP 方向 | 前端是否可编辑 | 说明 |
| --- | --- | --- | --- | --- |
| Account | 本系统管理目录优先 | 可选同步 PMS profile / company / travel agent 主数据,但本系统仍维护映射 code | 是Basic Information 选择 Account | Account 影响 Market / Source 派生,第一版建议先由系统管理维护,避免 PMS profile 字段未确认导致业务不可用 |
| Market | 本系统管理目录 | 可选同步 PMS market code 配置 | 否,随 Account 派生展示 | 前端不直接改 Market修改 Account 后后端派生 Market |
| Source | 本系统管理目录 | 可选同步 PMS source code 配置 | 否,随 Account 派生展示 | 前端不直接改 Source修改 Account 后后端派生 Source |
| Room Type | PMS / OPERA / OHIP 同步目录优先 | 同步酒店有效房型、展示名、人数、启停状态 | 是,业务卡选择房型 | 若 PMS 未接入,可临时由系统管理维护或固定种子初始化 |
| Rate Code | PMS / OPERA / OHIP 同步目录优先 | 同步有效 Rate Plan / Rate Code价格和日期适用规则后置 | 是New Booking 选择 Rate Code | 第一版 lookup 只选 code不做价格计算 |
Department 目录在 V4 字段契约中也会被 Trace 使用,但当前固定种子 CP8 尚未实现。后续可沿用本文模型扩展 `DEPARTMENT`,不放入本 checkpoint。
## 6. 数据模型草案
### 6.1 推荐表:`workflow_reservation_catalog_account`
Account 单独建表,原因是它不仅有显示名称,还要派生 Market / Source。
| 字段 | 中文说明 |
| --- | --- |
| `id` | 内部主键 ID |
| `hotel_id` | 酒店 ID目录按酒店隔离 |
| `account_code` | Account 稳定 code大小写敏感 |
| `account_name` | Account 显示名称 |
| `market_code` | 该 Account 派生的 Market code |
| `source_code` | 该 Account 派生的 Source code |
| `status` | `ACTIVE` / `DISABLED` |
| `source_system` | `SYSTEM_MANAGED` / `PMS_SYNC` / `FIXED_SEED_IMPORT` |
| `external_account_id` | 外部 PMS profile 或 account ID可为空 |
| `catalog_version` | 目录版本,用于前端缓存和排查 |
| `last_synced_at` | 外部同步成功 UTC 时间;系统管理数据可为空 |
| `metadata_json` | 扩展元数据,不替代可查询字段 |
| `version` | 乐观锁版本 |
| `created_at` / `updated_at` | UTC 创建和更新时间 |
| `logic_deleted_at` / `logic_deleted_reason` | 逻辑删除时间和原因 |
建议唯一约束:
```text
uk_reservation_catalog_account_code(hotel_id, account_code)
```
### 6.2 推荐表:`workflow_reservation_catalog_code`
Market、Source、Room Type、Rate Code 可先使用统一 code 表。
| 字段 | 中文说明 |
| --- | --- |
| `id` | 内部主键 ID |
| `hotel_id` | 酒店 ID目录按酒店隔离 |
| `catalog_type` | `MARKET` / `SOURCE` / `ROOM_TYPE` / `RATE_CODE` |
| `code` | 稳定目录 code大小写敏感 |
| `display_name` | 前端显示名称 |
| `status` | `ACTIVE` / `DISABLED` |
| `source_system` | `SYSTEM_MANAGED` / `PMS_SYNC` / `FIXED_SEED_IMPORT` |
| `external_id` | 外部 PMS / OPERA / OHIP ID可为空 |
| `sort_order` | 前端默认排序 |
| `effective_from` / `effective_to` | 生效日期范围,可为空;酒店本地业务日期语义 |
| `catalog_version` | 目录版本,用于前端缓存和排查 |
| `last_synced_at` | 外部同步成功 UTC 时间 |
| `metadata_json` | 扩展元数据例如房型人数、Rate Code 适用说明 |
| `version` | 乐观锁版本 |
| `created_at` / `updated_at` | UTC 创建和更新时间 |
| `logic_deleted_at` / `logic_deleted_reason` | 逻辑删除时间和原因 |
建议唯一约束:
```text
uk_reservation_catalog_code(hotel_id, catalog_type, code)
```
### 6.3 推荐表:`workflow_reservation_catalog_sync_run`
如果接 PMS / OPERA / OHIP 同步,建议记录每次同步运行。
CP11 本轮没有创建该表。原因是当前不接真实 PMS / OPERA / OHIP 同步暂时没有同步运行事实可记录CP13 目录管理后台 CP1 也只维护系统管理目录项,不产生外部同步运行事实。后续做 PMS / OPERA / OHIP 同步 worker 时,再新增该表和对应 Repository。
| 字段 | 中文说明 |
| --- | --- |
| `id` | 同步运行 ID |
| `hotel_id` | 酒店 ID |
| `catalog_type` | 同步目录类型 |
| `source_system` | 外部来源系统 |
| `sync_status` | `SUCCESS` / `FAILED` / `PARTIAL_SUCCESS` |
| `started_at` / `finished_at` | UTC 开始和结束时间 |
| `catalog_version` | 本次生成的目录版本 |
| `items_seen_count` | 外部返回数量 |
| `items_upserted_count` | 本系统写入或更新数量 |
| `items_disabled_count` | 本系统停用数量 |
| `safe_error_summary` | 安全错误摘要,不保存 Secret 或完整外部响应 |
| `created_at` / `updated_at` | UTC 创建和更新时间 |
同步 run 是技术追踪,不直接给普通业务前端展示完整细节。管理后台后续如展示同步记录,应单独登记权限和脱敏规则。
## 7. 后端服务边界
CP11 已替换当前 `FixedReservationV4DirectoryServiceImpl`,保留 `ReservationV4DirectoryService` 作为业务稳定端口。
```text
workflows.reservation.service
ReservationV4DirectoryService
- findAccount(hotelId, accountCode)
- isKnownRoomTypeCode(hotelId, roomTypeCode)
- isKnownRateCode(hotelId, rateCode)
workflows.reservation.repository
ReservationV4CatalogRepository
- 只封装本地目录表查询,不直接调用 PMS
workflows.reservation.service
ReservationV4CatalogLookupService
- listAccounts(...)
- listRoomTypes(...)
- listRateCodes(...)
integrations.ohip / integrations.pms
CatalogSyncAdapter
- 后续从 PMS / OPERA / OHIP 拉取目录并转换为本系统目录草稿
```
规则:
- V4 入站、确认、复核只调用 `ReservationV4DirectoryService`
- 前端 lookup Controller 调用 `ReservationV4CatalogLookupService`,该服务同样只读本地目录表。
- PMS / OHIP 同步 Adapter 只能写本地目录表或同步 run不直接参与用户确认事务。
- 如果目录服务不可用,确认接口 fail closed不接受自由文本。
## 8. Lookup API 草案
Lookup API 属于前端业务查询接口,不给 SuperAgent 或 AgentBus 调用。
统一要求:
- 分类:`FRONTEND_USER`
- 鉴权Bearer session token。
- 权限:第一版建议复用 `RESERVATION_TASK_READ`;目录维护后台另行使用管理权限。
- 酒店隔离:`hotel_id` 可选;不传时按当前用户默认酒店解析,传入时必须校验用户可访问。
- 返回:只返回目录 code、显示名、状态、来源和安全元数据不返回外部 PMS 原始响应。
- 分页:`page_num` 从 1 开始,`page_size` 后端限制最大值。
- 搜索:`keyword` 匹配稳定 code 时,后端先按 `Locale.ROOT` 大写归一化后查询,因此前端或管理后台传小写 code 也能命中;匹配显示名时仍按数据库比较规则,不额外做大小写归一化。
- `keyword` 无匹配时,`items=[]``page.total=0`;如果当前酒店未过滤的 ACTIVE 目录仍存在,`catalog_source` / `catalog_version` 继续返回真实目录元数据,不把筛选无结果误报为 `DATABASE_EMPTY`
### 8.1 Account Lookup
```text
GET /api/reservation/lookups/accounts
```
查询参数:
| 参数 | 必需 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | 否 | 当前酒店 ID未传时按当前用户默认酒店 |
| `keyword` | 否 | 匹配 `account_code``account_name` |
| `page_num` / `page_size` | 否 | 分页 |
第一版固定只返回 `ACTIVE` Account不开放 `active_only=false`
响应草案:
```json
{
"hotel_id": "HOTEL-TEST",
"catalog_type": "ACCOUNT",
"catalog_source": "FIXED_SEED_IMPORT",
"catalog_version": "seed-20260719-v1",
"stale": false,
"items": [
{
"code": "QBD_TRAVEL",
"display_name": "Q.B.D. TRAVEL GROUP CO., LTD",
"status": "ACTIVE",
"catalog_source": "FIXED_SEED_IMPORT",
"market_code": "LEISURE",
"market_name": "LEISURE",
"source_code": "TRAVEL_AGENT",
"source_name": "TRAVEL_AGENT"
}
],
"page": {
"page_num": 1,
"page_size": 20,
"total": 1
},
"warnings": []
}
```
前端用途:
- `fields[].options_source=reservation_v4_account_catalog` 时调用。
- 用户选择 Account 后,前端可以立即展示响应中的 Market / Source最终以后端确认接口派生结果为准。
- 不允许用户自由输入 Account code。
### 8.2 Room Type Lookup
```text
GET /api/reservation/lookups/room-types
```
查询参数:
| 参数 | 必需 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | 否 | 当前酒店 ID |
| `keyword` | 否 | 匹配房型 code 或显示名 |
| `page_num` / `page_size` | 否 | 分页 |
第一版固定只返回 `ACTIVE` Room Type不接 `arrival_date` / `departure_date`,日期适用范围过滤后置。
响应草案:
```json
{
"hotel_id": "HOTEL-TEST",
"catalog_type": "ROOM_TYPE",
"catalog_source": "FIXED_SEED_IMPORT",
"catalog_version": "seed-20260719-v1",
"stale": false,
"items": [
{
"code": "RM2",
"display_name": "RM2",
"status": "ACTIVE",
"catalog_source": "FIXED_SEED_IMPORT",
"adult_capacity": 2
}
],
"page": {
"page_num": 1,
"page_size": 20,
"total": 1
},
"warnings": []
}
```
前端用途:
- `fields[].options_source=reservation_v4_room_type_catalog` 时调用。
- V4 业务卡里的 `room_items[].room_type_code``business_fields.after.room_items[].room_type_code`、加床目标房型等都应从该接口选。
- 前端不要把旧固定种子当 PMS 全量房型。
### 8.3 Rate Code Lookup
```text
GET /api/reservation/lookups/rate-codes
```
查询参数:
| 参数 | 必需 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | 否 | 当前酒店 ID |
| `keyword` | 否 | 匹配 Rate Code 或显示名 |
| `page_num` / `page_size` | 否 | 分页 |
第一版固定只返回 `ACTIVE` Rate Code不接 `booking_type``account_code``arrival_date` / `departure_date`,适用范围和价格过滤后置。
响应草案:
```json
{
"hotel_id": "HOTEL-TEST",
"catalog_type": "RATE_CODE",
"catalog_source": "FIXED_SEED_IMPORT",
"catalog_version": "seed-20260719-v1",
"stale": false,
"items": [
{
"code": "GROUP",
"display_name": "GROUP",
"status": "ACTIVE",
"catalog_source": "FIXED_SEED_IMPORT",
"pricing_available": false
}
],
"page": {
"page_num": 1,
"page_size": 20,
"total": 1
},
"warnings": []
}
```
前端用途:
- `fields[].options_source=reservation_v4_rate_code_catalog` 时调用。
- 第一版只选 `rate_code`,不展示或计算真实价格。
- V4 契约仍禁止 `UPDATE_BOOKING` 携带 Rate Codelookup API 不改变该规则。
### 8.4 Market / Source Lookup
Market / Source 第一版不作为用户可编辑字段,不建议给普通业务表单单独开放选择。
如果后续系统管理后台需要维护 Market / Source可使用管理接口或通用目录接口但不应让 V4 Basic Information 页面绕过 Account 派生规则。
## 9. `fields[]` 与 lookup 的关系
后端任务详情字段仍是前端渲染白名单。Lookup API 只解决选项来源,不决定字段是否展示或可编辑。
| `options_source` | 对应 lookup | 前端行为 |
| --- | --- | --- |
| `reservation_v4_account_catalog` | `GET /api/reservation/lookups/accounts` | 渲染 Account 下拉 / 搜索选择,展示派生 Market / Source |
| `reservation_v4_room_type_catalog` | `GET /api/reservation/lookups/room-types` | 渲染房型搜索选择 |
| `reservation_v4_rate_code_catalog` | `GET /api/reservation/lookups/rate-codes` | 渲染 Rate Code 搜索选择 |
| `static_enum` | 使用 `fields[].enum_options` | 不调用 lookup |
| `system_case_lookup` | 后续订单 / 任务对象 lookup | 不属于本目录 checkpoint |
前端提交时仍按卡片确认或复核接口提交字段值。后端确认前再次校验目录,不能因为前端选项来自 lookup 就跳过后端校验。
## 10. 缓存设计
缓存是优化,不是事实源。本系统本地目录表才是业务查询事实源。
后续建议。CP11 第一版暂不加内存缓存,直接读取本系统本地目录表;原因是当前种子目录规模很小,先保证目录事实源、酒店隔离和确认校验一致。
- 目录 Service 增加内存缓存,缓存 key 包含 `hotel_id``catalog_type``keyword``active_only`、分页和过滤参数。
- 精确校验类查询,例如 `findAccount(hotelId, accountCode)``isKnownRoomTypeCode(hotelId, code)`,使用单独短 TTL 缓存。
- TTL 建议 5 分钟;真实同步后可根据 `catalog_version` 主动失效。
- 管理后台修改目录或同步 run 成功后,清理对应酒店和目录类型缓存。
- 多节点部署时,第一版可以依赖短 TTL后续如目录变更频繁再接 Redis 或事件广播失效。
响应应返回:
| 字段 | 中文说明 |
| --- | --- |
| `catalog_version` | 当前目录版本,前端可用于调试和避免重复请求 |
| `catalog_source` | 当前目录主要来源,例如 `SYSTEM_MANAGED` / `PMS_SYNC` |
| `stale` | 当前是否为过期但可用的最后成功快照 |
| `warnings[]` | 非阻塞警告,例如 PMS 同步失败但仍返回本地快照 |
## 11. 失败兜底
| 场景 | Lookup API 行为 | 确认 / 复核行为 |
| --- | --- | --- |
| 目录有本地 Active 快照 | 返回 200 和可选项 | 按目录校验,通过后确认 |
| PMS 同步失败,但有上次成功快照 | 返回 200`stale=true`,带 warning | 仍可按本地快照确认,并在审计或 payload 中保留目录版本 |
| PMS 同步失败且无本地快照 | 返回 200 空列表和 warning或按后续实现返回明确错误 | 必填目录字段 fail closed返回 `V4_CATALOG_UNAVAILABLE``V4_FIELD_VALIDATION_FAILED` |
| SuperAgent 输出未知非空 code | 任务卡进入 `REVIEW_REQUIRED``fields[].validation_errors` 指向该字段 | 用户必须选择已知 code不能自由输入原值 |
| 目录 code 后续停用 | 已确认卡保持历史确认快照不回滚 | 新确认不能选择停用 code除非后续设计允许历史兼容选择 |
| 前端提交未返回字段或自由 code | 后端忽略未开放字段;目录字段校验失败 | 不写入 `confirmed_payload_json` |
生产建议:
- `FIXED_SEED` 可用于 dev/test 和初始化导入。
- 生产如果真实目录为空,不应静默接受固定种子;应让卡片进入复核或目录不可用错误,避免写入错误 PMS code。
## 12. 权限、酒店隔离和审计
### 12.1 Lookup 查询权限
第一版 lookup 查询建议:
| 接口 | 分类 | 权限 | 酒店隔离 | 审计 |
| --- | --- | --- | --- | --- |
| `GET /api/reservation/lookups/accounts` | `FRONTEND_USER` | `RESERVATION_TASK_READ` | 按用户可访问酒店校验 | 只读不写业务审计 |
| `GET /api/reservation/lookups/room-types` | `FRONTEND_USER` | `RESERVATION_TASK_READ` | 按用户可访问酒店校验 | 只读不写业务审计 |
| `GET /api/reservation/lookups/rate-codes` | `FRONTEND_USER` | `RESERVATION_TASK_READ` | 按用户可访问酒店校验 | 只读不写业务审计 |
复用 `RESERVATION_TASK_READ` 的原因:
- Lookup 是任务详情表单选项的辅助查询能力。
- 目录值本身不包含邮件正文、附件、AI payload 或 PMS Secret。
- 可以避免第一版为了表单下拉再新增一个普通用户权限码,降低前端角色配置复杂度。
### 12.2 目录维护权限
目录管理后台 CP1 已实现后端接口。该能力属于 `FRONTEND_ADMIN`,使用管理后台错误响应和管理审计,不复用普通 lookup 权限。
```text
RESERVATION_CATALOG_MANAGE
```
已实现接口:
| 接口 | 中文说明 | 权限 | 酒店隔离 | 审计 |
| --- | --- | --- | --- | --- |
| `GET /api/admin/reservation/catalogs/accounts` | Account 管理列表 | `RESERVATION_CATALOG_MANAGE` | `hotel_id` 必须在当前用户可访问酒店内;不传时使用默认酒店 | 只读不写审计 |
| `POST /api/admin/reservation/catalogs/accounts` | 新增 Account | `RESERVATION_CATALOG_MANAGE` | 按请求 `hotel_id` 校验 | 写平台管理审计 |
| `PUT /api/admin/reservation/catalogs/accounts/{accountId}/status` | 启用 / 停用 Account | `RESERVATION_CATALOG_MANAGE` | 按记录所属酒店校验 | 状态实际变化时写平台管理审计;相同状态幂等返回,不新增审计 |
| `GET /api/admin/reservation/catalogs/room-types` | Room Type 管理列表 | `RESERVATION_CATALOG_MANAGE` | 同上 | 只读不写审计 |
| `POST /api/admin/reservation/catalogs/room-types` | 新增 Room Type | `RESERVATION_CATALOG_MANAGE` | 按请求 `hotel_id` 校验 | 写平台管理审计 |
| `PUT /api/admin/reservation/catalogs/room-types/{catalogId}/status` | 启用 / 停用 Room Type | `RESERVATION_CATALOG_MANAGE` | 按记录所属酒店校验 | 状态实际变化时写平台管理审计;相同状态幂等返回,不新增审计 |
| `GET /api/admin/reservation/catalogs/rate-codes` | Rate Code 管理列表 | `RESERVATION_CATALOG_MANAGE` | 同上 | 只读不写审计 |
| `POST /api/admin/reservation/catalogs/rate-codes` | 新增 Rate Code | `RESERVATION_CATALOG_MANAGE` | 按请求 `hotel_id` 校验 | 写平台管理审计 |
| `PUT /api/admin/reservation/catalogs/rate-codes/{catalogId}/status` | 启用 / 停用 Rate Code | `RESERVATION_CATALOG_MANAGE` | 按记录所属酒店校验 | 状态实际变化时写平台管理审计;相同状态幂等返回,不新增审计 |
CP1 限制:
- 新增目录默认 `ACTIVE``source_system=SYSTEM_MANAGED`
- 管理列表可按 `status=ACTIVE/DISABLED` 过滤;普通 lookup 仍只返回 `ACTIVE`
- 管理列表 `keyword` 搜索稳定 code 时大小写不敏感,显示名仍按数据库比较规则。
- Account 新增时 `market_code``source_code` 必须是当前酒店 `ACTIVE` 的 Market / Source code。
- Market / Source 独立管理页面暂不做,仍沿用当前初始化目录。
- 停用目录不会回滚已确认历史卡片,但新确认 / 复核会按当前 ACTIVE 目录校验。
- 后续如做前端管理页面,应在系统设置下使用该权限码控制入口。
### 12.3 第三方接口边界
SuperAgent 当前不调用本 lookup API。SuperAgent 目录供给后续有两种方式:
1. 线下或配置文件方式把目录版本给 SuperAgent。
2. 单独设计机器接口,例如 `GET /api/integrations/superagent/catalogs/...`,使用 HMAC 鉴权和最小字段。
不得让 SuperAgent 使用前端 Bearer token 或前端 lookup API。
## 13. 前端使用方式
前端建议:
1. 先读取 V4 订单任务详情。
2. 遍历每张卡 `fields[]`
3. 只有字段 `editable=true``control_type=select/lookup` 时才加载 lookup。
4. 根据 `options_source` 选择 lookup 接口。
5. 搜索输入做 debounce不一次性拉全量。
6. 显示 `stale=true``warnings[]` 时给用户非阻塞提醒。
7. 用户提交确认或复核时只提交 code不提交显示名、Market / Source 派生值或目录完整对象。
8. 确认成功后以后端返回的 `confirmed_payload_json` / 刷新详情为准更新页面。
前端禁止:
- 硬编码 PMS 房型或 Rate Code 全集。
- 把目录显示名当业务 code 提交。
- 绕过 `fields[]` 自行补业务字段。
- 使用 lookup API 给 SuperAgent、AgentBus 或 Debug 链路拼接输入。
- 在浏览器保存目录中的外部 PMS ID、同步错误详情或任何 Secret。
## 14. 后续开发 checkpoint
| Checkpoint | 目标 | 主要交付 |
| --- | --- | --- |
| M002-V4-CP11 | DB 管理目录与 Lookup API V1 | 新增 Account / Code 目录表、Repository、DirectoryService DB 实现、Account / Room Type / Rate Code lookup 查询接口、权限和测试 |
| M002-V4-CP12 | 前端 Lookup 接入 | V4 卡片字段渲染按 `options_source` 调用 lookup替换固定种子硬编码选项处理 stale / warning / 空目录 |
| M002-V4-CP13 | 目录管理后台 V1 | CP1 已完成 Account / Room Type / Rate Code 前后端列表、新增、启用 / 停用闭环、`RESERVATION_CATALOG_MANAGE` 权限和管理审计Market / Source 独立管理后置 |
| M002-V4-CP14 | 订单列表 V4 继续处理入口 | 已完成:订单列表返回 V4 下一步订单任务、卡片、动作类型、动作状态和 open 数,前端可优先跳 V4 订单任务详情 |
| M002-V4-CP15 | PMS / OPERA / OHIP 目录同步 | 同步 Adapter、同步 run 表、失败重试、最后成功快照、同步状态管理入口 |
| M002-V4-CP16 | SuperAgent 目录供给 | 明确目录版本如何给 SuperAgent必要时新增机器目录接口或导出包 |
CP11 已作为后端第一步落地,因为它不依赖真实 PMS也能让前端后续不再硬编码当前固定种子。CP13 CP1 继续沿用本地目录表,不接真实 PMS也不改变 SuperAgent 输入契约。
## 15. 仍需确认的问题
1. Account 的第一版真实维护入口已放在系统设置 `/system/reservation-catalogs`;前端第一版固定使用 `market_code=LEISURE``source_code=TRAVEL_AGENT`Market / Source 独立管理仍需后续确认。
2. Account code 是否继续使用本系统定义的稳定 code例如 `QBD_TRAVEL`,还是必须对齐 PMS profile code。
3. Market / Source 是否只允许随 Account 派生,还是未来允许用户在 Basic Information 中单独改选。
4. Room Type 第一版后端已支持系统管理维护;后续是否仍要接 PMS / OHIP 同步替换为主来源待确认。
5. Rate Code 第一版是否只校验 code还是需要按 `booking_type``account_code`、入住日期过滤。
6. 生产是否允许 `FIXED_SEED` 作为兜底,还是只允许 dev/test 使用。
7. SuperAgent 是否需要读取目录;如果需要,是离线给目录包,还是新增 HMAC 机器接口。
在这些问题未确认前,当前实现仍按 DB 管理目录 + 前端 lookup 查询 + 后台 CP1 手工维护闭环推进,不接真实 PMS也不替换 SuperAgent 输入契约。