580 lines
33 KiB
Markdown
580 lines
33 KiB
Markdown
# M002 V4 真实目录与 Lookup API 设计
|
||
|
||
## 文档信息
|
||
|
||
| 项目 | 内容 |
|
||
| --- | --- |
|
||
| 文档版本 | 0.4 |
|
||
| 日期 | 2026-07-20 |
|
||
| 状态 | CP11 已落地第一版数据库目录与 lookup API;目录管理后台 CP1 已落地后端接口;Account + booking type 过滤 Rate Code 和 Room Information 早餐派生已确认为下一阶段待实现契约;真实 PMS 同步继续后置 |
|
||
| 适用范围 | M002 V4 Account、Market、Source、Room Type、Rate Code 目录来源、Room Information 早餐派生、数据模型、前端 lookup、缓存、酒店隔离、权限和失败兜底 |
|
||
| 不适用范围 | 真实 OPERA / OHIP 写操作、真实价格计算、房型 / 日期 / 价格级 Rate Code 适用规则、前端页面实现、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 当前 CP11 实现仍是酒店级目录校验和字段选项提示;下一阶段已确认需要按订单级 Account + `booking_type`(`GROUP` / `FIT`)过滤候选和校验适用性。
|
||
- Room Information 卡中 Breakfast 第一版由后端派生:Group 固定含早;Fit 按 Rate Code 中 `RB` / `RO` 判断,无法派生时要求用户在卡片中必填确认。
|
||
- 目录错误会让对应 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` | 已进入数据库初始化目录;当前实现仍是酒店级目录,下一阶段改为 Account + booking type 适用范围过滤;不按房型、日期或价格过滤;后台 CP1 可新增、启用 / 停用 |
|
||
|
||
当前固定种子只能支撑开发和演示闭环,不能作为生产长期事实源。
|
||
|
||
## 3. 设计目标
|
||
|
||
真实目录能力要解决以下问题:
|
||
|
||
1. 前端不再硬编码 Account、Room Type、Rate Code 选项。
|
||
2. 后端确认和复核仍然是最终校验者,前端 lookup 只用于选择体验。
|
||
3. 每个目录都按 `hotel_id` 隔离,不能跨酒店泄露目录。
|
||
4. 目录有来源、版本、更新时间和启停状态,便于排查 SuperAgent 输出 code 与系统目录不一致的问题。
|
||
5. PMS / OPERA / OHIP 不稳定或暂未接入时,系统仍可使用最后一次成功目录快照或系统管理目录兜底。
|
||
6. 固定种子目录可以作为 dev/test 或导入初始化兜底,但生产不应默认靠代码固定值。
|
||
7. Rate Code 不再作为全酒店通用下拉;业务卡 Rate Code 候选必须先受当前订单 Basic Information 的 Account 和 event `booking_type` 限定。
|
||
|
||
## 4. 目录来源分层
|
||
|
||
目录来源按阶段分三层,后续可以逐步切换,不要求一步接真实 PMS。
|
||
|
||
| 阶段 | 来源 | 中文说明 | 适用目录 |
|
||
| --- | --- | --- | --- |
|
||
| Phase 0 | `FIXED_SEED_IMPORT` | CP11 已将固定种子通过 Flyway 和启动补种子流程导入数据库,不再作为运行时代码全局 Map | 当前 Account、Market、Source、Room Type、Rate Code |
|
||
| Phase 1 | `SYSTEM_MANAGED` / `IMPORT_FILE` | 本系统数据库目录,由初始化脚本、管理后台或受控导入文件维护;`IMPORT_FILE` 用于 OWNER RATE 等人工确认过的目录 / 适用关系导入 | Account、Market、Source;也可临时维护 Room Type、Rate Code 和 Account + booking 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 同步目录优先;在 PMS 未接前可由系统管理或导入文件维护 Account 适用关系 | 同步有效 Rate Plan / Rate Code;Account、booking type、房型、日期和价格适用规则逐步扩展 | 是,New Booking 选择 Rate Code | 下一阶段先按 Account + GROUP/FIT 过滤候选,只选 code,不做价格计算 |
|
||
|
||
Department 目录在 V4 字段契约中也会被 Trace 使用。当前任务详情页第一版先固定 `FO`、`HSK`、`FO+HSK` 三个 Department code,用于 Trace 卡下拉和 SuperAgent 输出约束;这不是正式数据库目录。后续可沿用本文模型扩展 `DEPARTMENT`、lookup API、系统管理维护和目录校验,不放入本 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_rate_code_applicability`
|
||
|
||
Rate Code 本身仍保存在 `workflow_reservation_catalog_code` 中,`catalog_type=RATE_CODE` 表示该 code 是当前酒店已知的 Rate Code;是否对某个 Account、GROUP/FIT 可用,由适用关系表表达。这样可以避免在 `metadata_json` 中写不可查询的业务规则,也避免前端硬编码 OWNER RATE Excel。
|
||
|
||
本 checkpoint 只确认 Account + booking type 粒度,不纳入房型、日期和价格规则。后续如果需要按 Room Type 或入住日期进一步过滤,应在此表或独立价格规则表上扩展,不改变前端只提交稳定 `rate_code` 的基本原则。
|
||
|
||
| 字段 | 中文说明 |
|
||
| --- | --- |
|
||
| `id` | 内部主键 ID |
|
||
| `hotel_id` | 酒店 ID,必须和 Account、Rate Code 同酒店 |
|
||
| `account_code` | Reservation Account 稳定 code,引用当前酒店 Account 目录 |
|
||
| `booking_type` | `GROUP` / `FIT`;用于区分团队价和散客价候选 |
|
||
| `rate_code` | Rate Code 稳定 code,引用当前酒店 `RATE_CODE` 目录 |
|
||
| `status` | `ACTIVE` / `DISABLED`;普通 lookup 只返回 `ACTIVE` |
|
||
| `source_system` | `SYSTEM_MANAGED` / `IMPORT_FILE` / `PMS_SYNC` / `FIXED_SEED_IMPORT` |
|
||
| `catalog_version` | 适用关系版本,用于排查和前端非阻塞提示 |
|
||
| `sort_order` | 同一 Account + booking type 下的默认展示排序 |
|
||
| `metadata_json` | 扩展说明,例如导入文件行号、业务备注;不得作为唯一可查询条件 |
|
||
| `version` | 乐观锁版本 |
|
||
| `created_at` / `updated_at` | UTC 创建和更新时间 |
|
||
| `logic_deleted_at` / `logic_deleted_reason` | 逻辑删除时间和原因 |
|
||
|
||
建议唯一约束:
|
||
|
||
```text
|
||
uk_reservation_rate_code_applicability(hotel_id, account_code, booking_type, rate_code)
|
||
```
|
||
|
||
建议外键 / 逻辑校验:
|
||
|
||
- `account_code` 必须存在于同酒店 `workflow_reservation_catalog_account` 且为 `ACTIVE`,否则不进入普通 lookup。
|
||
- `rate_code` 必须存在于同酒店 `workflow_reservation_catalog_code` 且 `catalog_type=RATE_CODE`、`status=ACTIVE`,否则不进入普通 lookup。
|
||
- 停用 Account 或 Rate Code 后,普通 Rate Code lookup 不再返回对应适用关系;已确认历史卡片不回滚。
|
||
|
||
### 6.4 Rate Code 早餐派生
|
||
|
||
Room Information 卡需要展示 `breakfast_included` 布尔字段,但该字段不由 SuperAgent 输出。
|
||
|
||
第一版派生规则:
|
||
|
||
- Group 固定含早,`breakfast_included=true`,前端显示勾选且只读。
|
||
- Fit 根据最终 Rate Code 派生:Rate Code 中包含 `RB` 时 `breakfast_included=true`,包含 `RO` 时 `breakfast_included=false`。
|
||
- Fit Rate Code 同时无法命中 `RB` / `RO` 时,后端应把 `breakfast_included` 标为未解决,前端显示必填勾选框,由用户确认是否含早。
|
||
- 该规则只用于 Room Information 卡展示和确认,不代表真实价格计算,也不替代 PMS Rate Plan 规则。
|
||
- 后续如果目录表显式维护早餐属性,可优先使用目录元数据;但普通前端仍消费布尔 `breakfast_included`,不要自行按 Rate Code 字符串猜测。
|
||
|
||
### 6.5 推荐表:`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)
|
||
- isRateCodeApplicable(hotelId, accountCode, bookingType, rateCode)
|
||
|
||
workflows.reservation.repository
|
||
ReservationV4CatalogRepository
|
||
- 只封装本地目录表查询,不直接调用 PMS
|
||
|
||
workflows.reservation.service
|
||
ReservationV4CatalogLookupService
|
||
- listAccounts(...)
|
||
- listRoomTypes(...)
|
||
- listRateCodes(...)
|
||
- listRateCodesByAccountAndBookingType(...)
|
||
|
||
integrations.ohip / integrations.pms
|
||
CatalogSyncAdapter
|
||
- 后续从 PMS / OPERA / OHIP 拉取目录并转换为本系统目录草稿
|
||
```
|
||
|
||
规则:
|
||
|
||
- V4 入站、确认、复核只调用 `ReservationV4DirectoryService`。
|
||
- Basic Information 的 `account_code` 先确认或在同次复核中修正后,业务卡 Rate Code 才能按该 Account + `booking_type` 做适用性校验。
|
||
- 前端 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 |
|
||
| `account_code` | 是 | 已选择或已确认的 Reservation Account code;必须属于当前酒店 ACTIVE Account 目录 |
|
||
| `booking_type` | 是 | `GROUP` / `FIT`;取自当前业务 event 的 `target_order.booking_type` |
|
||
| `keyword` | 否 | 匹配 Rate Code 或显示名 |
|
||
| `page_num` / `page_size` | 否 | 分页 |
|
||
|
||
下一阶段 Rate Code lookup 必须按 `account_code + booking_type` 返回 `ACTIVE` 且适用的 Rate Code。`account_code` 未传、无效或不属于当前酒店时返回 400;`booking_type` 非 `GROUP` / `FIT` 时返回 400;Account 合法但当前无适用 Rate Code 时返回空 `items[]`。前端在 Account 未选时不应拉全酒店 Rate Code。`arrival_date` / `departure_date`、房型和价格过滤后置。
|
||
|
||
响应草案:
|
||
|
||
```json
|
||
{
|
||
"hotel_id": "HOTEL-TEST",
|
||
"catalog_type": "RATE_CODE",
|
||
"account_code": "QBD_TRAVEL",
|
||
"booking_type": "GROUP",
|
||
"catalog_source": "IMPORT_FILE",
|
||
"catalog_version": "owner-rate-20260720-v1",
|
||
"stale": false,
|
||
"items": [
|
||
{
|
||
"code": "GRPA1",
|
||
"display_name": "GRPA1",
|
||
"status": "ACTIVE",
|
||
"catalog_source": "IMPORT_FILE",
|
||
"pricing_available": false
|
||
}
|
||
],
|
||
"page": {
|
||
"page_num": 1,
|
||
"page_size": 20,
|
||
"total": 1
|
||
},
|
||
"warnings": []
|
||
}
|
||
```
|
||
|
||
前端用途:
|
||
|
||
- `fields[].options_source=reservation_v4_rate_code_catalog` 时调用。
|
||
- 只能在当前订单 Basic Information 已有有效 Account,且当前业务 event 有 `booking_type` 时调用;Account 切换后必须清空或重新校验已选 Rate Code。
|
||
- 下一阶段只选 `rate_code`,不展示或计算真实价格。
|
||
- V4 契约仍禁止 `UPDATE_BOOKING` 携带 Rate Code;lookup 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?account_code=...&booking_type=...` | 渲染当前 Account + GROUP/FIT 适用的 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` | 按用户可访问酒店校验;`account_code` 和 `booking_type` 只能用于当前酒店目录过滤 | 只读不写业务审计 |
|
||
|
||
复用 `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. Rate Code 字段必须先取得当前订单 Basic Information 的 Account 和当前业务 event 的 `booking_type`;缺任一条件时禁用或显示空态,不调用全酒店 Rate Code 全量查询。
|
||
6. 搜索输入做 debounce,不一次性拉全量。
|
||
7. 显示 `stale=true` 或 `warnings[]` 时给用户非阻塞提醒。
|
||
8. 用户提交确认或复核时只提交 code,不提交显示名、Market / Source 派生值或目录完整对象。
|
||
9. 确认成功后以后端返回的 `confirmed_payload_json` / 刷新详情为准更新页面。
|
||
|
||
前端禁止:
|
||
|
||
- 硬编码 PMS 房型或 Rate Code 全集。
|
||
- 硬编码 OWNER RATE Excel 中 Account 到 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-CP14.5 | Account 范围 Rate Code Lookup | 待实现:新增 Account + booking type 适用关系模型 / 导入种子,Rate Code lookup 接收 `account_code`、`booking_type`,确认和复核校验 Rate Code 适用性,前端联动 Account 后展示候选 |
|
||
| 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 已确认下一阶段按 `account_code + booking_type` 过滤;入住日期、房型和价格是否也要进入过滤仍待后续确认。
|
||
6. 生产是否允许 `FIXED_SEED` 作为兜底,还是只允许 dev/test 使用。
|
||
7. SuperAgent 是否需要读取目录;如果需要,是离线给目录包,还是新增 HMAC 机器接口。
|
||
|
||
在这些问题未确认前,当前实现仍按 DB 管理目录 + 前端 lookup 查询 + 后台 CP1 手工维护闭环推进,不接真实 PMS,也不替换 SuperAgent 输入契约。
|