Files
th-hotel-simple/docs/project/requirements/M002-v4-real-catalog-lookup-api-design.md

24 KiB
Raw Blame History

M002 V4 真实目录与 Lookup API 设计

文档信息

项目 内容
文档版本 0.2
日期 2026-07-19
状态 CP11 已落地第一版数据库目录与 lookup API后续真实 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_accountworkflow_reservation_catalog_code 两张表,并通过 Flyway 初始化 HOTEL-TESTHOTEL-DEV 以及迁移执行时已存在的 ACTIVE 平台酒店的固定种子目录;同时新增 ReservationV4CatalogBootstrapRunner,在平台默认酒店由启动流程创建后,如果该酒店目录为空,会补一份 FIXED_SEED_IMPORT 初始化目录。不再在运行时代码中把固定种子作为全局目录事实。workflow_reservation_catalog_sync_run、真实 PMS / OPERA / OHIP 同步、目录管理后台和 SuperAgent 机器目录供给接口继续后置。

后续若本文与 M002-v4-agent-callback-field-contract.md 的 Agent 输入字段冲突,以 Agent 字段契约为准;若与 security-access-control-boundary.md 的接口权限冲突,以安全边界为准。

2. 当前固定种子目录

CP11 之前固定种子实现位于后端 ReservationV4DirectoryServiceFixedReservationV4DirectoryServiceImpl。CP11 起实现改为 ReservationV4DatabaseDirectoryServiceImpl,目录读取只走数据库目录表;固定种子通过 V24__create_reservation_catalog_tables.sql 初始化导入 dev/test 基准酒店和迁移时已有 ACTIVE 酒店,并由 ReservationV4CatalogBootstrapRunner 补齐迁移后才创建的默认 ACTIVE 酒店,来源标记为 FIXED_SEED_IMPORT

目录 当前用途 当前固定值 当前限制
Account Basic Information 可选目录SuperAgent 和用户提交都使用稳定 code QBD_TRAVELLIAN_TAIHANATOUR_TD 已进入数据库初始化目录,仍不是 PMS 全量 Account管理后台后置
Market 由 Account 派生的订单级 Market 当前 Account 均派生 LEISURE 已进入通用代码目录,前端仍不直接编辑
Source 由 Account 派生的订单级 Source 当前 Account 均派生 TRAVEL_AGENT 已进入通用代码目录,前端仍不直接编辑
Room Type 房型 code 校验和字段选项提示 TWNKINGDBLSGLTRPRM1RM2RM3 已进入数据库初始化目录,仍不是 PMS 全量房型
Rate Code Rate Code 校验和字段选项提示 BARRACKPACKAGEGROUPFIT 已进入数据库初始化目录,不按日期、账号、房型过滤,不含价格

当前固定种子只能支撑开发和演示闭环,不能作为生产长期事实源。

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 逻辑删除时间和原因

建议唯一约束:

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 逻辑删除时间和原因

建议唯一约束:

uk_reservation_catalog_code(hotel_id, catalog_type, code)

6.3 推荐表:workflow_reservation_catalog_sync_run

如果接 PMS / OPERA / OHIP 同步,建议记录每次同步运行。

CP11 本轮没有创建该表。原因是当前不接真实 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 作为业务稳定端口。

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 无匹配时,items=[]page.total=0;如果当前酒店未过滤的 ACTIVE 目录仍存在,catalog_source / catalog_version 继续返回真实目录元数据,不把筛选无结果误报为 DATABASE_EMPTY

8.1 Account Lookup

GET /api/reservation/lookups/accounts

查询参数:

参数 必需 中文说明
hotel_id 当前酒店 ID未传时按当前用户默认酒店
keyword 匹配 account_codeaccount_name
page_num / page_size 分页

第一版固定只返回 ACTIVE Account不开放 active_only=false

响应草案:

{
  "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

GET /api/reservation/lookups/room-types

查询参数:

参数 必需 中文说明
hotel_id 当前酒店 ID
keyword 匹配房型 code 或显示名
page_num / page_size 分页

第一版固定只返回 ACTIVE Room Type不接 arrival_date / departure_date,日期适用范围过滤后置。

响应草案:

{
  "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_codebusiness_fields.after.room_items[].room_type_code、加床目标房型等都应从该接口选。
  • 前端不要把旧固定种子当 PMS 全量房型。

8.3 Rate Code Lookup

GET /api/reservation/lookups/rate-codes

查询参数:

参数 必需 中文说明
hotel_id 当前酒店 ID
keyword 匹配 Rate Code 或显示名
page_num / page_size 分页

第一版固定只返回 ACTIVE Rate Code不接 booking_typeaccount_codearrival_date / departure_date,适用范围和价格过滤后置。

响应草案:

{
  "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_idcatalog_typekeywordactive_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 同步失败,但有上次成功快照 返回 200stale=true,带 warning 仍可按本地快照确认,并在审计或 payload 中保留目录版本
PMS 同步失败且无本地快照 返回 200 空列表和 warning或按后续实现返回明确错误 必填目录字段 fail closed返回 V4_CATALOG_UNAVAILABLEV4_FIELD_VALIDATION_FAILED
SuperAgent 输出未知非空 code 任务卡进入 REVIEW_REQUIREDfields[].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 目录维护权限

目录维护不在本 checkpoint 实现。后续如果做系统管理维护,建议新增:

RESERVATION_CATALOG_MANAGE

用于 Account、Market、Source、临时 Room Type、Rate Code 的系统管理维护页面。写操作必须记录管理审计或业务配置审计。

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=truecontrol_type=select/lookup 时才加载 lookup。
  4. 根据 options_source 选择 lookup 接口。
  5. 搜索输入做 debounce不一次性拉全量。
  6. 显示 stale=truewarnings[] 时给用户非阻塞提醒。
  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 Account / Market / Source 管理,临时 Room Type / Rate Code 管理,RESERVATION_CATALOG_MANAGE 权限和管理审计
M002-V4-CP14 PMS / OPERA / OHIP 目录同步 同步 Adapter、同步 run 表、失败重试、最后成功快照、同步状态管理入口
M002-V4-CP15 SuperAgent 目录供给 明确目录版本如何给 SuperAgent必要时新增机器目录接口或导出包

CP11 已作为后端第一步落地,因为它不依赖真实 PMS也能让前端后续不再硬编码当前固定种子。

15. 仍需确认的问题

  1. Account 的第一版真实维护入口是否放在现有系统管理后台,还是先用导入 SQL / Excel 导入。
  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_typeaccount_code、入住日期过滤。
  6. 生产是否允许 FIXED_SEED 作为兜底,还是只允许 dev/test 使用。
  7. SuperAgent 是否需要读取目录;如果需要,是离线给目录包,还是新增 HMAC 机器接口。

在这些问题未确认前CP11 仍可以先按 DB 管理目录 + 前端 lookup 查询实现,不接真实 PMS也不替换 SuperAgent 输入契约。