补齐订单列表V4继续处理入口
This commit is contained in:
@@ -35,12 +35,12 @@
|
||||
- 后续每完成一个 Feature 或 Checkpoint,需要更新本文件,避免项目状态继续沉淀在聊天记录里。
|
||||
- M010 Rooming List Excel 生成后端 CP1 和前端 V1 已实现:前端 `/reservation/rooming-lists/new` 上传来源名单和手工字段,后端同步生成 `.xlsx` 直接下载,第一版不落库、不上传 OSS。
|
||||
- M011 Booking Excel 附件预处理 CP1/CP2/CP3 已实现:后端可排除人员名单类 Excel,按最近 6 个月候选窗口选择实际存在的最新 3 个业务月,抽取 Booking Update / 附加费表高亮行业务 JSON;Debug EML 和 AgentBus dispatch 在各自 include 开关与总开关同时启用时,会在调用 SuperAgent 前追加 `attachment_extractions[]`。AgentBus 生产链路默认关闭,测试机验证后再评估开启。
|
||||
- M002 V4 CP1 当前已完成入站解析和现有任务链路过渡适配;M002 V4 CP2 已完成订单任务与多卡领域模型设计;M002 V4 CP3 已完成 V4 订单任务、多卡和 S10/S99 来源通知表结构与 Repository 基线;M002 V4 CP4 已完成入站写入新模型;M002 V4 CP5 已完成前端查询接口并补齐订单详情 `v4_order_tasks[]` 时间线;M002 V4 CP6 已完成普通卡片确认和 S10/S99 来源通知 ack;M002 V4 CP7 已完成 `REVIEW_REQUIRED` 卡复核解阻和复核场景订单归属确认;M002 V4 CP8 已完成目录校验、V4 卡片 `fields[]` 字段白名单、确认写入白名单收口和嵌套业务字段目录校验;M002 V4 CP11 已完成数据库目录、初始化种子、启动补种子、Account / Room Type / Rate Code lookup API,并把 V4 入站、确认、复核目录校验切换到当前酒店数据库目录;M002 V4 CP12 已完成前端 lookup 接入第一版和 V4 订单任务时间线消费;M002 V4 CP13 目录管理后台 CP1 已完成前后端列表、新增、启用 / 停用闭环。真实 PMS 同步和 SuperAgent 目录机器接口仍未完成。
|
||||
- M002 V4 CP1 当前已完成入站解析和现有任务链路过渡适配;M002 V4 CP2 已完成订单任务与多卡领域模型设计;M002 V4 CP3 已完成 V4 订单任务、多卡和 S10/S99 来源通知表结构与 Repository 基线;M002 V4 CP4 已完成入站写入新模型;M002 V4 CP5 已完成前端查询接口并补齐订单详情 `v4_order_tasks[]` 时间线;M002 V4 CP6 已完成普通卡片确认和 S10/S99 来源通知 ack;M002 V4 CP7 已完成 `REVIEW_REQUIRED` 卡复核解阻和复核场景订单归属确认;M002 V4 CP8 已完成目录校验、V4 卡片 `fields[]` 字段白名单、确认写入白名单收口和嵌套业务字段目录校验;M002 V4 CP11 已完成数据库目录、初始化种子、启动补种子、Account / Room Type / Rate Code lookup API,并把 V4 入站、确认、复核目录校验切换到当前酒店数据库目录;M002 V4 CP12 已完成前端 lookup 接入第一版和 V4 订单任务时间线消费;M002 V4 CP13 目录管理后台 CP1 已完成前后端列表、新增、启用 / 停用闭环;M002 V4 CP14 已完成订单列表 V4 继续处理入口字段,`GET /api/reservation/orders` 返回 V4 下一步订单任务、卡片、动作类型、动作状态和 open 数。真实 PMS 同步和 SuperAgent 目录机器接口仍未完成。
|
||||
- M002 V4 CP2 已确认:V4 工作台统一列表草案为 `/api/reservation/workbench-items`,业务订单任务接口新开 `/api/reservation/order-tasks/**`,S10/S99 来源通知详情草案为 `/api/reservation/source-notifications/{notificationId}`;S10/S99 使用来源通知模型,不再挂隐藏技术订单;`FIT + BOOKING_CODE` 不建 ACTIVE 唯一约束,匹配多条进人工复核;Basic Information 必须先确认;Account / Market / Source 当前通过数据库目录读取和派生;旧 V2/V3 任务详情和草稿确认接口后续可逐步废弃。
|
||||
|
||||
## 5. Next Steps
|
||||
|
||||
- 后续如继续做 M002 V4,可优先进入测试机 V4 目录管理与多卡 lookup 联调、真实 PMS / OPERA / OHIP 目录同步和 `workflow_reservation_catalog_sync_run` checkpoint。
|
||||
- 后续如继续做 M002 V4,可优先进入测试机 V4 订单列表继续处理入口前后端联调、真实 PMS / OPERA / OHIP 目录同步和 `workflow_reservation_catalog_sync_run` checkpoint。
|
||||
- 后续新增重要功能时,优先在 `docs/project/requirements/` 或未来 `docs/specs/` 中形成 Spec,再实现代码。
|
||||
- M010 后续如需预览、历史记录、OSS 下载、订单 / 任务预填或客户字段目录化,再单独开前后端 checkpoint。
|
||||
- M011 后续如需运营查询或长期追踪,可进入 CP4:设计 Excel 解析批次 / 行级持久化表;当前 CP3 只增强 SuperAgent 输入,不直接落订单、任务或长期解析历史。
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
| `POST /api/auth/login` | 用户名密码登录 | 成功后返回 `access_token`、当前用户、可访问酒店、权限码和可见菜单;token 只放 `sessionStorage`,不要放 `localStorage`、URL、日志或错误上报。 |
|
||||
| `GET /api/auth/me` | 恢复当前登录态 | 前端启动后带 `Authorization: Bearer <access_token>` 调用;401 时清理 token 并进入登录页。 |
|
||||
| `POST /api/auth/logout` | 登出当前 session | 带 `Authorization: Bearer <access_token>`;成功后前端必须清理本地 token 和当前用户上下文。 |
|
||||
| `GET /api/reservation/orders` | 查询订单列表 | 必须带 `Authorization: Bearer <access_token>`,需要 `RESERVATION_ORDER_READ`;默认返回全部订单状态;按后端维护的订单最近业务活动时间倒序,当前落库字段为 `workflow_reservation_order.latest_activity_at`,前端不要自行重排;`open_task_count` 排除 `COMPLETED` 和 `FAILED`;隐藏技术订单不返回,因此 S10/S99 和旧 S000/S999 不会在订单列表形成订单。 |
|
||||
| `GET /api/reservation/orders` | 查询订单列表 | 必须带 `Authorization: Bearer <access_token>`,需要 `RESERVATION_ORDER_READ`;默认返回全部订单状态;按后端维护的订单最近业务活动时间倒序,当前落库字段为 `workflow_reservation_order.latest_activity_at`,前端不要自行重排;`open_task_count` 排除 `COMPLETED` 和 `FAILED`;已补齐 V4 继续处理入口字段,前端有 `next_v4_order_task_id` 时优先跳 V4 订单任务详情;隐藏技术订单不返回,因此 S10/S99 和旧 S000/S999 不会在订单列表形成订单。 |
|
||||
| `GET /api/reservation/tasks` | 查询任务列表 / 工作台 | 必须带 Bearer token,需要 `RESERVATION_TASK_READ`;未传 `order_id` 时按来源消息接收时间倒序,传 `order_id` 时按同订单队列顺序正序;用 `can_process` 和 `readonly_reason_code` 控制入口按钮;列表不返回 AI 原始 payload、邮件正文或附件 URL;已返回来源邮件会话摘要字段,并支持 `order_status` 按任务所属订单状态筛选;旧 S000/S999 和 V3 S10/S99 以 `task_type=SOURCE_MESSAGE_ONLY` 只读任务返回,列表已透出 `result_type`、`ai_task_type`、`route_code`、`system_process_category`。V4 S10/S99 不再进入该旧任务表,应从 V4 工作台来源通知接口展示。 |
|
||||
| `GET /api/reservation/workbench-items` | 查询 V4 工作台统一列表 | 必须带 Bearer token,需要 `RESERVATION_TASK_READ`;返回 V4 业务订单任务和 S10/S99 来源通知混排摘要;支持 `hotel_id`、`item_type`、`keyword`、`page_num`、`page_size`;默认按 `source_received_at` 倒序,同一来源时间下按 `updated_at`、`created_at`、数字 `target_id` 倒序;列表不返回邮件正文、附件 URL、`ai_payload_json` 或来源通知原始 payload。 |
|
||||
| `GET /api/reservation/order-tasks` | 查询 V4 业务订单任务列表 | 必须带 Bearer token,需要 `RESERVATION_TASK_READ`;只返回 V4 业务订单任务,不包含 S10/S99 来源通知;支持 `hotel_id`、`order_id`、`order_task_status`、`card_status`、`keyword`、`page_num`、`page_size`;`order_task_status` 非 `OPEN` / `COMPLETED` 返回 400,`card_status` 非 V4 卡状态返回 400;`card_status` 只筛业务 / 可处理卡,固定来源邮件展示卡不参与筛选。 |
|
||||
@@ -101,7 +101,7 @@
|
||||
|
||||
| 接口 | 本轮变化 | 前端接入注意 |
|
||||
| --- | --- | --- |
|
||||
| `GET /api/reservation/orders` | 新增订单列表接口。 | `order_status` 不传时默认查询全部订单状态;`page_num` 从 1 开始;`page_size` 后端有最大值保护;`open_task_count` 排除 `COMPLETED` 和 `FAILED`;`next_processable_task_id` 为空表示当前没有可继续处理的任务。 |
|
||||
| `GET /api/reservation/orders` | 补齐订单列表 V4 继续处理入口字段。 | `order_status` 不传时默认查询全部订单状态;`page_num` 从 1 开始;`page_size` 后端有最大值保护;`open_task_count` 排除 `COMPLETED` 和 `FAILED`;旧 `next_processable_task_id` 继续保留。V4 新增 `next_v4_order_task_id`、`next_v4_action_card_id`、`next_v4_action_type`、`next_v4_action_status`、`v4_open_order_task_count`;前端“继续处理”如存在 `next_v4_order_task_id` 应优先跳 `/reservation/order-tasks/{next_v4_order_task_id}`,否则再回退旧 `/reservation/tasks/{next_processable_task_id}`。 |
|
||||
| `GET /api/reservation/tasks` | 补齐来源邮件会话摘要字段,并新增 `order_status` 查询参数。 | `order_status` 按任务所属订单状态过滤,支持 `TEMPORARY`、`ACTIVE`、`ENDED`、`LOGIC_DELETED`;列表仍然只返回安全摘要,不返回正文、HTML、附件 URL 或 AI 原始 payload;点击邮件入口时使用 `source_message_id` 调会话详情。 |
|
||||
| `GET /api/reservation/orders/{orderId}` | 补齐旧 `tasks[]` 来源邮件会话摘要字段,并新增 V4 `v4_order_tasks[]` 订单任务时间线。 | `include_tasks=false` 可只取订单摘要,此时 `tasks[]` 和 `v4_order_tasks[]` 都为空;旧 `tasks[]` 顺序由后端按订单队列返回,V4 `v4_order_tasks[]` 按 `source_received_at`、`source_message_id`、`order_context_index`、`created_at`、数字 ID 正序返回;前端不要自行重排。`include_source_summary` 第一版不作为前端裁剪字段的强约束,前端暂不要依赖它减少返回字段。 |
|
||||
| `GET /api/reservation/tasks/{taskId}` | 补齐顶层来源邮件字段,并扩展 `fields[]` 元数据。 | 顶层来源字段用于打开邮件会话;`fields[]` 中的 `result_type`、`task_type`、`task_subtype`、`default_value_source` 用于前端字段分组、调试和白名单对齐。 |
|
||||
@@ -183,6 +183,9 @@ POST /api/auth/logout
|
||||
- `keyword` 会匹配订单业务号、临时订单号、展示名、订单状态,也会匹配来源消息安全摘要命中的 SourceMessage ID;前端可以用邮件主题、外部消息 ID 或会话 ID 辅助查订单。
|
||||
- `open_task_count` 只统计未关闭任务,排除 `COMPLETED` 和 `FAILED`。
|
||||
- `next_processable_task_id` 是后端按同订单队列实时计算出的下一条可处理任务;前端可以用它做“继续处理”入口。
|
||||
- V4 订单任务入口字段由后端实时派生:`v4_open_order_task_count` 统计当前订单下 `order_task_status!=COMPLETED` 的 V4 订单任务;`next_v4_order_task_id` 是同订单第一条仍需用户处理的 V4 订单任务;`next_v4_action_card_id` 是该订单任务下第一张待处理卡;`next_v4_action_type` 取 `CONFIRM` / `REVIEW` / `NONE`;`next_v4_action_status` 取 `PENDING_CONFIRM` / `REVIEW_REQUIRED` 或空。
|
||||
- V4 派生规则:Basic Information 必须优先于业务卡;Basic 已确认后,业务卡中 `REVIEW_REQUIRED` 优先于普通 `PENDING_CONFIRM`;`COMPLETED` 的 V4 订单任务不计入 open;S10/S99 来源通知不挂订单,不进入这些订单列表字段。
|
||||
- 前端订单列表“继续处理”建议优先级:有 `next_v4_order_task_id` 时跳 V4 订单任务详情;否则有旧 `next_processable_task_id` 时跳旧任务详情;`next_v4_action_type=NONE` 且旧字段为空时展示查看详情或无待处理状态。
|
||||
- `display_order_key` 是前端优先展示的订单业务号或临时订单号;`group_code` 和 `confirmation_number` 只有在当前订单业务号类型匹配时返回。
|
||||
- 订单 ID、任务 ID、SourceMessage ID 在这些前端接口中按字符串返回,前端不要转换成 JavaScript number。
|
||||
- 当前 V3 / 过渡实现中,源邮件只读通知卡背后有隐藏技术订单用于满足后端任务外键,但订单列表不会返回该订单;任务列表中该类任务的 `display_order_key`、`temporary_order_no`、`group_code`、`confirmation_number` 可能为空,前端不要因此隐藏整条任务。V4 S10/S99 目标模型已改为独立来源通知,不再挂隐藏技术订单。
|
||||
|
||||
@@ -279,7 +279,7 @@ GET /api/reservation/orders/{orderId}
|
||||
GET /api/reservation/orders
|
||||
```
|
||||
|
||||
当前状态:后端已完成第一版。默认查询全部订单状态;`open_task_count` 排除 `COMPLETED` 和 `FAILED`,`next_processable_task_id` 按同订单队列可处理状态实时计算。
|
||||
当前状态:后端已完成第一版。默认查询全部订单状态;`open_task_count` 排除 `COMPLETED` 和 `FAILED`,`next_processable_task_id` 按旧 V2/V3 同订单队列可处理状态实时计算。M002 V4 CP14 已补齐 V4 继续处理入口字段,前端有 `next_v4_order_task_id` 时应优先进入 V4 订单任务详情。
|
||||
|
||||
默认排序:按后端维护的订单最近业务活动时间倒序返回,保证最近有业务活动的订单排在前面。后端当前使用 `workflow_reservation_order.latest_activity_at` 作为排序字段,并在订单创建、任务创建、草稿保存、最终确认、人工复核解阻、任务状态变更等写路径维护;前端不要再基于任务时间或更新时间自行重排。
|
||||
|
||||
@@ -311,6 +311,11 @@ GET /api/reservation/orders
|
||||
"display_name": "GRP-001",
|
||||
"open_task_count": 2,
|
||||
"next_processable_task_id": "10002",
|
||||
"next_v4_order_task_id": "30001",
|
||||
"next_v4_action_card_id": "31002",
|
||||
"next_v4_action_type": "REVIEW",
|
||||
"next_v4_action_status": "REVIEW_REQUIRED",
|
||||
"v4_open_order_task_count": 1,
|
||||
"updated_at": "2026-07-08T03:10:00Z"
|
||||
}
|
||||
],
|
||||
@@ -322,6 +327,18 @@ GET /api/reservation/orders
|
||||
}
|
||||
```
|
||||
|
||||
V4 继续处理字段说明:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `next_v4_order_task_id` | 当前订单下第一条仍需用户处理的 V4 订单任务 ID;为空表示没有 V4 待处理订单任务。 |
|
||||
| `next_v4_action_card_id` | `next_v4_order_task_id` 下第一张仍需确认或复核的卡片 ID。 |
|
||||
| `next_v4_action_type` | `CONFIRM` / `REVIEW` / `NONE`。`CONFIRM` 调卡片确认接口,`REVIEW` 调复核解阻接口。 |
|
||||
| `next_v4_action_status` | `PENDING_CONFIRM` / `REVIEW_REQUIRED`;`NONE` 时为空。 |
|
||||
| `v4_open_order_task_count` | 当前订单下未完成 V4 订单任务数,`COMPLETED` 不计入。 |
|
||||
|
||||
前端“继续处理”入口建议优先级:优先使用 `next_v4_order_task_id` 跳转 V4 订单任务详情;没有 V4 待处理时再使用旧 `next_processable_task_id` 跳旧任务详情。S10/S99 来源通知不创建订单,不进入订单列表字段统计。
|
||||
|
||||
## 6. 前端联调演示数据 seed 接口
|
||||
|
||||
建议路径:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
| --- | --- |
|
||||
| 文档版本 | 1.5 |
|
||||
| 日期 | 2026-07-19 |
|
||||
| 状态 | 当前 V4 字段基线;后端已完成 CP1-CP8,真实目录与 Lookup API 设计已落文档 |
|
||||
| 状态 | 当前 V4 字段基线;后端已完成 CP1-CP8、CP11 和 CP14,真实 PMS / OPERA / OHIP 同步仍后置 |
|
||||
| 适用范围 | 0718 业务基线下,Agent → Adapter / MCP → 信息系统的业务回调字段 |
|
||||
| 不适用范围 | 数据库表设计、前端视觉细节、真实 PMS API、技术失败后台重试、旧 M002 V3 数据兼容 |
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
本契约用于后续 M002 V4 主流程设计、后端领域建模、前端页面模型、Adapter / MCP Schema 对齐和 SuperAgent 联调。当前后端已按本文完成 V4 入站解析基线:能识别 V4 包、校验关键契约、保存 AI transition / 任务卡原始 payload,并把可映射的六类 event 先接入现有订单任务链路。
|
||||
|
||||
V4 订单任务与多卡领域模型的 CP2 设计已经单独落到 `M002-v4-order-task-card-domain-model-cp2.md`。截至 CP11,表结构、Entity、Mapper、Repository、SuperAgent V4 入站写入、V4 查询、普通卡片确认、S10/S99 ack、V4 复核解阻、数据库目录和 Account / Room Type / Rate Code Lookup API 已实现。真实 PMS 同步和目录管理后台仍后置,方案见 `M002-v4-real-catalog-lookup-api-design.md`。
|
||||
V4 订单任务与多卡领域模型的 CP2 设计已经单独落到 `M002-v4-order-task-card-domain-model-cp2.md`。截至 CP14,表结构、Entity、Mapper、Repository、SuperAgent V4 入站写入、V4 查询、普通卡片确认、S10/S99 ack、V4 复核解阻、数据库目录、Account / Room Type / Rate Code Lookup API、目录管理后台 CP1 后端接口,以及订单列表 V4 继续处理入口字段已实现。真实 PMS 同步仍后置,方案见 `M002-v4-real-catalog-lookup-api-design.md`。
|
||||
|
||||
当前已确认开发阶段数据可以清空,因此 M002 V4 后续可以按新模型重建,不要求兼容旧任务数据、旧草稿、旧 OPERA 模拟、旧 `S000/S999`、旧 Fallback 或旧 `case_keys`。
|
||||
|
||||
@@ -741,7 +741,7 @@ AI 回调包
|
||||
- `POST /api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/confirm` 确认 V4 卡片,强制 Bearer 登录、`RESERVATION_TASK_CONFIRM`、酒店访问权和 version 并发校验。
|
||||
- `POST /api/reservation/source-notifications/{notificationId}/ack` 确认 V4 S10/S99 来源通知已读 / 已处理,强制 Bearer 登录、`RESERVATION_TASK_CONFIRM`、酒店访问权和 version 并发校验。
|
||||
- CP7 已完成 `POST /api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/review-resolution` 复核解阻和复核场景订单归属确认。
|
||||
- CP8 已完成 Account / Room Type / Rate Code 目录校验和 V4 任务卡 `fields[]` 白名单;CP11 已把固定种子导入数据库目录并开放 Account / Room Type / Rate Code lookup API。真实 PMS 同步和目录管理后台仍未实现。
|
||||
- CP8 已完成 Account / Room Type / Rate Code 目录校验和 V4 任务卡 `fields[]` 白名单;CP11 已把固定种子导入数据库目录并开放 Account / Room Type / Rate Code lookup API;CP13 已完成目录管理后台 CP1;CP14 已在 `GET /api/reservation/orders` 补齐 V4 下一步处理入口字段。真实 PMS 同步仍未实现。
|
||||
|
||||
当前仍未完成:
|
||||
|
||||
|
||||
@@ -619,6 +619,37 @@ GET /api/reservation/orders/{orderId}
|
||||
- `latest_activity_at` 为 V4 订单任务自身 `updated_at` 与其下卡片 `updated_at` 的最大值。
|
||||
- 接口权限仍使用 `RESERVATION_ORDER_READ`,并按订单实际所属酒店校验访问权;V4 任务读取时继续以该订单酒店过滤,避免跨酒店脏数据泄露。
|
||||
|
||||
### 12.6 订单列表 V4 继续处理入口
|
||||
|
||||
旧订单列表接口继续作为订单视角入口:
|
||||
|
||||
```text
|
||||
GET /api/reservation/orders
|
||||
分类:FRONTEND_USER
|
||||
权限:RESERVATION_ORDER_READ
|
||||
```
|
||||
|
||||
在保留旧字段 `open_task_count` 和 `next_processable_task_id` 的基础上,M002 V4 CP14 已补齐以下 V4 字段:
|
||||
|
||||
| 字段 | 中文说明 |
|
||||
| --- | --- |
|
||||
| `next_v4_order_task_id` | 当前订单下第一条仍需用户处理的 V4 订单任务 ID |
|
||||
| `next_v4_action_card_id` | 该 V4 订单任务下第一张仍需确认或复核的卡片 ID |
|
||||
| `next_v4_action_type` | `CONFIRM` / `REVIEW` / `NONE` |
|
||||
| `next_v4_action_status` | `PENDING_CONFIRM` / `REVIEW_REQUIRED`;没有待处理卡时为空 |
|
||||
| `v4_open_order_task_count` | 当前订单下未完成 V4 订单任务数,`COMPLETED` 不计入 |
|
||||
|
||||
派生规则:
|
||||
|
||||
- 只统计绑定到订单的 V4 订单任务;S10/S99 来源通知不创建订单,不进入订单列表字段统计。
|
||||
- 同订单 V4 订单任务沿用 Repository 队列顺序:`source_received_at`、`source_message_id`、`order_context_index`、`created_at`、数字 ID 正序。
|
||||
- `order_task_status=COMPLETED` 的 V4 订单任务不计入 `v4_open_order_task_count`,也不作为下一步入口。
|
||||
- 同一 V4 订单任务内,Basic Information 必须优先于业务卡。
|
||||
- Basic 已确认后,业务卡中 `REVIEW_REQUIRED` 优先于普通 `PENDING_CONFIRM`。
|
||||
- `next_v4_action_type=CONFIRM` 时前端调用卡片确认接口;`REVIEW` 时调用复核解阻接口;`NONE` 表示该订单没有 V4 待处理入口。
|
||||
|
||||
前端订单列表“继续处理”应优先使用 `next_v4_order_task_id` 跳转 V4 订单任务详情;没有 V4 待处理入口时,再回退旧 `next_processable_task_id`。
|
||||
|
||||
## 13. 前端写操作接口
|
||||
|
||||
### 13.1 确认卡片
|
||||
@@ -747,7 +778,8 @@ AI 原始 payload、邮件正文、附件 URL 和技术 trace 不应直接进入
|
||||
| M002-V4-CP11 | DB 管理目录与 Lookup API V1 | 已完成:新增 Account / Code 目录表、DirectoryService DB 实现、Account / Room Type / Rate Code lookup 查询接口、权限、酒店隔离和测试;同步 run 后置 |
|
||||
| M002-V4-CP12 | 前端 Lookup 接入 | V4 卡片字段按 `options_source` 调用 lookup,替换固定种子硬编码选项,处理 stale / warning / 空目录 |
|
||||
| M002-V4-CP13 | 目录管理后台 V1 | Account / Market / Source 管理,临时 Room Type / Rate Code 管理,目录维护权限和管理审计 |
|
||||
| M002-V4-CP14 | PMS / OPERA / OHIP 目录同步 | 同步 Adapter、同步 run、最后成功快照、失败重试和同步状态管理入口 |
|
||||
| M002-V4-CP14 | 订单列表 V4 继续处理入口 | 已完成:`GET /api/reservation/orders` 返回 V4 下一步订单任务、卡片、动作类型、动作状态和 open 数,前端可优先跳 V4 订单任务详情 |
|
||||
| M002-V4-CP15 | PMS / OPERA / OHIP 目录同步 | 同步 Adapter、同步 run、最后成功快照、失败重试和同步状态管理入口 |
|
||||
|
||||
## 17. 已确认设计决策
|
||||
|
||||
|
||||
@@ -500,8 +500,9 @@ SuperAgent 当前不调用本 lookup API。SuperAgent 目录供给后续有两
|
||||
| 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 | PMS / OPERA / OHIP 目录同步 | 同步 Adapter、同步 run 表、失败重试、最后成功快照、同步状态管理入口 |
|
||||
| M002-V4-CP15 | SuperAgent 目录供给 | 明确目录版本如何给 SuperAgent,必要时新增机器目录接口或导出包 |
|
||||
| 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 输入契约。
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
| 接口 / 能力 | 分类 | 当前管控 | 目标管控 | 审计要求 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `GET /api/reservation/tasks` | `FRONTEND_USER` | 已强制 Bearer 登录 + `RESERVATION_TASK_READ`;支持可选 `hotel_id` 并校验酒店访问权 | 保持登录 + `RESERVATION_TASK_READ` + 酒店访问权 | 只读查询默认不写业务审计 |
|
||||
| `GET /api/reservation/orders` | `FRONTEND_USER` | 已强制 Bearer 登录 + `RESERVATION_ORDER_READ`;支持可选 `hotel_id` 并校验酒店访问权 | 保持登录 + `RESERVATION_ORDER_READ` + 酒店访问权 | 只读查询默认不写业务审计 |
|
||||
| `GET /api/reservation/orders` | `FRONTEND_USER` | 已强制 Bearer 登录 + `RESERVATION_ORDER_READ`;支持可选 `hotel_id` 并校验酒店访问权;已返回 V4 继续处理入口安全字段 | 保持登录 + `RESERVATION_ORDER_READ` + 酒店访问权;V4 入口字段只返回 order task / card ID、动作类型、状态和数量摘要 | 只读查询默认不写业务审计;不得返回邮件正文、附件 URL、AI 原始 payload 或来源通知原始 payload |
|
||||
| `GET /api/reservation/orders/{orderId}` | `FRONTEND_USER` | 已强制 Bearer 登录 + `RESERVATION_ORDER_READ`;按订单实际所属酒店校验访问权;已返回旧 `tasks[]` 和 V4 `v4_order_tasks[]` 安全摘要时间线 | 保持登录 + `RESERVATION_ORDER_READ` + 订单所属酒店访问权;V4 时间线读取按订单酒店过滤 | 只读查询默认不写业务审计;不得在 `v4_order_tasks[]` 返回邮件正文、附件 URL、AI 原始 payload 或来源通知原始 payload |
|
||||
| `GET /api/reservation/tasks/{taskId}` | `FRONTEND_USER` | 已强制 Bearer 登录 + `RESERVATION_TASK_READ`;按任务实际所属酒店校验访问权 | 保持登录 + `RESERVATION_TASK_READ` + 任务所属酒店访问权 | 只读查询默认不写业务审计 |
|
||||
| `GET /api/reservation/workbench-items` | `FRONTEND_USER` | 已实现 M002 V4 CP5;强制 Bearer 登录 + `RESERVATION_TASK_READ` + 酒店访问权 | 保持登录 + `RESERVATION_TASK_READ` + 酒店访问权;统一返回 V4 业务订单任务和 S10/S99 来源通知摘要 | 只读查询默认不写业务审计;不得返回邮件正文、附件 URL、AI 原始 payload 或来源通知原始 payload;同来源时间下使用 `updated_at` / `created_at` / 数字 ID 稳定排序 |
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.enums;
|
||||
|
||||
/**
|
||||
* Reservation V4 订单列表下一步处理动作类型。
|
||||
*/
|
||||
public enum ReservationV4NextActionType {
|
||||
/** 下一张卡片可走普通确认接口。 */
|
||||
CONFIRM,
|
||||
/** 下一张卡片需要走人工复核解阻接口。 */
|
||||
REVIEW,
|
||||
/** 当前订单没有 V4 待处理卡片。 */
|
||||
NONE
|
||||
}
|
||||
@@ -16,6 +16,11 @@ import java.time.OffsetDateTime;
|
||||
* @param displayName 订单展示名
|
||||
* @param openTaskCount 未关闭任务数,排除 COMPLETED 和 FAILED
|
||||
* @param nextProcessableTaskId 下一条当前可处理任务 ID
|
||||
* @param nextV4OrderTaskId 当前订单下第一条仍需用户处理的 V4 订单任务 ID
|
||||
* @param nextV4ActionCardId 当前 V4 订单任务下第一张仍需确认或复核的卡片 ID
|
||||
* @param nextV4ActionType V4 下一步动作类型:CONFIRM / REVIEW / NONE
|
||||
* @param nextV4ActionStatus V4 下一步卡片状态:PENDING_CONFIRM / REVIEW_REQUIRED;没有待处理卡时为空
|
||||
* @param v4OpenOrderTaskCount 当前订单下未完成的 V4 订单任务数,COMPLETED 不计入
|
||||
* @param createdAt 订单创建时间,UTC
|
||||
* @param updatedAt 订单更新时间,UTC
|
||||
*/
|
||||
@@ -40,6 +45,16 @@ public record ReservationOrderListItemResult(
|
||||
Integer openTaskCount,
|
||||
@JsonProperty("next_processable_task_id")
|
||||
String nextProcessableTaskId,
|
||||
@JsonProperty("next_v4_order_task_id")
|
||||
String nextV4OrderTaskId,
|
||||
@JsonProperty("next_v4_action_card_id")
|
||||
String nextV4ActionCardId,
|
||||
@JsonProperty("next_v4_action_type")
|
||||
String nextV4ActionType,
|
||||
@JsonProperty("next_v4_action_status")
|
||||
String nextV4ActionStatus,
|
||||
@JsonProperty("v4_open_order_task_count")
|
||||
Integer v4OpenOrderTaskCount,
|
||||
@JsonProperty("created_at")
|
||||
OffsetDateTime createdAt,
|
||||
@JsonProperty("updated_at")
|
||||
|
||||
@@ -15,6 +15,9 @@ import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderKeyT
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderVisibility;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationTaskStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4CardStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4CardType;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4NextActionType;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4OrderTaskStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationOrderListQueryRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationTaskWorkbenchQueryRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderDetailResult;
|
||||
@@ -134,11 +137,15 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
|
||||
Map<Long, List<ReservationAiQueryTaskSnapshot>> tasksByOrderId = groupTasksByOrderId(orderTasks);
|
||||
Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId =
|
||||
calculateAvailabilityByTaskId(orderTasks, orderTasks);
|
||||
Map<Long, V4OrderListNextAction> v4NextActionsByOrderId = findV4NextActionsByOrderId(
|
||||
normalizedRequest.hotelId(),
|
||||
orderIds);
|
||||
List<ReservationOrderListItemResult> items = page.items().stream()
|
||||
.map(order -> toOrderListItem(
|
||||
order,
|
||||
tasksByOrderId.getOrDefault(order.id(), List.of()),
|
||||
availabilityByTaskId))
|
||||
availabilityByTaskId,
|
||||
v4NextActionsByOrderId.getOrDefault(order.id(), V4OrderListNextAction.none())))
|
||||
.toList();
|
||||
return new ReservationOrderListResult(
|
||||
items,
|
||||
@@ -555,7 +562,8 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
|
||||
private ReservationOrderListItemResult toOrderListItem(
|
||||
ReservationAiQueryOrderSnapshot order,
|
||||
List<ReservationAiQueryTaskSnapshot> tasks,
|
||||
Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId) {
|
||||
Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId,
|
||||
V4OrderListNextAction v4NextAction) {
|
||||
return new ReservationOrderListItemResult(
|
||||
order.id().toString(),
|
||||
order.hotelId(),
|
||||
@@ -567,6 +575,11 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
|
||||
order.displayName(),
|
||||
openTaskCount(tasks),
|
||||
nextProcessableTaskId(tasks, availabilityByTaskId),
|
||||
v4NextAction.orderTaskId(),
|
||||
v4NextAction.cardId(),
|
||||
v4NextAction.actionType(),
|
||||
v4NextAction.actionStatus(),
|
||||
v4NextAction.openOrderTaskCount(),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(order.createdAt()),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(order.updatedAt()));
|
||||
}
|
||||
@@ -601,6 +614,155 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
|
||||
return tasksByOrderId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量派生订单列表 V4 下一步处理入口。S10/S99 来源通知没有订单,不会进入该统计。
|
||||
*/
|
||||
private Map<Long, V4OrderListNextAction> findV4NextActionsByOrderId(String hotelId, List<Long> orderIds) {
|
||||
if (orderIds == null || orderIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
List<ReservationV4OrderTaskSnapshot> orderTasks = v4WorkflowRepository.findOrderTasksByOrderIds(
|
||||
hotelId,
|
||||
orderIds);
|
||||
if (orderTasks.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, List<ReservationV4OrderTaskSnapshot>> orderTasksByOrderId = groupV4OrderTasksByOrderId(orderTasks);
|
||||
Map<Long, List<ReservationV4TaskCardSnapshot>> cardsByOrderTaskId = findV4TaskCardsByOrderTaskId(
|
||||
hotelId,
|
||||
orderTasks);
|
||||
Map<Long, V4OrderListNextAction> result = new LinkedHashMap<>();
|
||||
for (Long orderId : orderIds) {
|
||||
result.put(orderId, toV4OrderListNextAction(
|
||||
orderTasksByOrderId.getOrDefault(orderId, List.of()),
|
||||
cardsByOrderTaskId));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按本地订单 ID 对 V4 订单任务分组,并保留 Repository 已给出的同订单队列顺序。
|
||||
*/
|
||||
private Map<Long, List<ReservationV4OrderTaskSnapshot>> groupV4OrderTasksByOrderId(
|
||||
List<ReservationV4OrderTaskSnapshot> orderTasks) {
|
||||
Map<Long, List<ReservationV4OrderTaskSnapshot>> result = new LinkedHashMap<>();
|
||||
List<ReservationV4OrderTaskSnapshot> safeOrderTasks =
|
||||
orderTasks == null ? List.of() : orderTasks;
|
||||
for (ReservationV4OrderTaskSnapshot orderTask : safeOrderTasks) {
|
||||
if (orderTask.orderId() == null) {
|
||||
continue;
|
||||
}
|
||||
result.computeIfAbsent(orderTask.orderId(), ignored -> new java.util.ArrayList<>()).add(orderTask);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从同订单 V4 队列中派生第一条仍需处理的订单任务和卡片。
|
||||
*/
|
||||
private V4OrderListNextAction toV4OrderListNextAction(
|
||||
List<ReservationV4OrderTaskSnapshot> orderTasks,
|
||||
Map<Long, List<ReservationV4TaskCardSnapshot>> cardsByOrderTaskId) {
|
||||
List<ReservationV4OrderTaskSnapshot> safeOrderTasks = orderTasks == null ? List.of() : orderTasks;
|
||||
int openOrderTaskCount = Math.toIntExact(safeOrderTasks.stream()
|
||||
.filter(this::isOpenV4OrderTask)
|
||||
.count());
|
||||
for (ReservationV4OrderTaskSnapshot orderTask : safeOrderTasks) {
|
||||
if (!isOpenV4OrderTask(orderTask)) {
|
||||
continue;
|
||||
}
|
||||
V4CardNextAction cardAction = nextV4CardAction(
|
||||
cardsByOrderTaskId.getOrDefault(orderTask.id(), List.of()));
|
||||
if (!ReservationV4NextActionType.NONE.name().equals(cardAction.actionType())) {
|
||||
return new V4OrderListNextAction(
|
||||
orderTask.id().toString(),
|
||||
cardAction.cardId(),
|
||||
cardAction.actionType(),
|
||||
cardAction.actionStatus(),
|
||||
openOrderTaskCount);
|
||||
}
|
||||
}
|
||||
return V4OrderListNextAction.none(openOrderTaskCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 V4 订单任务是否仍处于打开状态。COMPLETED 已结束,不计入订单列表 open 数。
|
||||
*/
|
||||
private boolean isOpenV4OrderTask(ReservationV4OrderTaskSnapshot orderTask) {
|
||||
return orderTask != null
|
||||
&& !ReservationV4OrderTaskStatus.COMPLETED.name().equals(orderTask.orderTaskStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* 派生单个 V4 订单任务下的下一张待处理卡:Basic 优先,业务卡内 REVIEW 高于普通确认。
|
||||
*/
|
||||
private V4CardNextAction nextV4CardAction(List<ReservationV4TaskCardSnapshot> cards) {
|
||||
List<ReservationV4TaskCardSnapshot> safeCards = cards == null ? List.of() : cards;
|
||||
ReservationV4TaskCardSnapshot basicCard = safeCards.stream()
|
||||
.filter(card -> ReservationV4CardType.BASIC_INFORMATION.name().equals(card.cardType()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
V4CardNextAction basicAction = actionableCard(basicCard);
|
||||
if (!ReservationV4NextActionType.NONE.name().equals(basicAction.actionType())) {
|
||||
return basicAction;
|
||||
}
|
||||
ReservationV4TaskCardSnapshot reviewCard = firstBusinessCardByStatus(
|
||||
safeCards,
|
||||
ReservationV4CardStatus.REVIEW_REQUIRED.name());
|
||||
if (reviewCard != null) {
|
||||
return new V4CardNextAction(
|
||||
reviewCard.id().toString(),
|
||||
ReservationV4NextActionType.REVIEW.name(),
|
||||
ReservationV4CardStatus.REVIEW_REQUIRED.name());
|
||||
}
|
||||
ReservationV4TaskCardSnapshot confirmCard = firstBusinessCardByStatus(
|
||||
safeCards,
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name());
|
||||
if (confirmCard != null) {
|
||||
return new V4CardNextAction(
|
||||
confirmCard.id().toString(),
|
||||
ReservationV4NextActionType.CONFIRM.name(),
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name());
|
||||
}
|
||||
return V4CardNextAction.none();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Basic Information 卡状态转换为订单列表可跳转动作。
|
||||
*/
|
||||
private V4CardNextAction actionableCard(ReservationV4TaskCardSnapshot card) {
|
||||
if (card == null) {
|
||||
return V4CardNextAction.none();
|
||||
}
|
||||
if (ReservationV4CardStatus.REVIEW_REQUIRED.name().equals(card.cardStatus())) {
|
||||
return new V4CardNextAction(
|
||||
card.id().toString(),
|
||||
ReservationV4NextActionType.REVIEW.name(),
|
||||
ReservationV4CardStatus.REVIEW_REQUIRED.name());
|
||||
}
|
||||
if (ReservationV4CardStatus.PENDING_CONFIRM.name().equals(card.cardStatus())) {
|
||||
return new V4CardNextAction(
|
||||
card.id().toString(),
|
||||
ReservationV4NextActionType.CONFIRM.name(),
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name());
|
||||
}
|
||||
return V4CardNextAction.none();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 Repository 排序找到第一张指定状态的业务卡,排除来源邮件展示卡和 Basic Information。
|
||||
*/
|
||||
private ReservationV4TaskCardSnapshot firstBusinessCardByStatus(
|
||||
List<ReservationV4TaskCardSnapshot> cards,
|
||||
String cardStatus) {
|
||||
return cards.stream()
|
||||
.filter(card -> !ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name().equals(card.cardType()))
|
||||
.filter(card -> !ReservationV4CardType.BASIC_INFORMATION.name().equals(card.cardType()))
|
||||
.filter(card -> cardStatus.equals(card.cardStatus()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计未关闭任务数量,排除 COMPLETED 和 FAILED。
|
||||
*/
|
||||
@@ -811,4 +973,41 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
|
||||
Long conversationMessageCount) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单列表中一个本地订单对应的 V4 下一步处理摘要。
|
||||
*/
|
||||
private record V4OrderListNextAction(
|
||||
String orderTaskId,
|
||||
String cardId,
|
||||
String actionType,
|
||||
String actionStatus,
|
||||
Integer openOrderTaskCount) {
|
||||
|
||||
private static V4OrderListNextAction none() {
|
||||
return none(0);
|
||||
}
|
||||
|
||||
private static V4OrderListNextAction none(Integer openOrderTaskCount) {
|
||||
return new V4OrderListNextAction(
|
||||
null,
|
||||
null,
|
||||
ReservationV4NextActionType.NONE.name(),
|
||||
null,
|
||||
openOrderTaskCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单张 V4 卡片对应的下一步动作摘要。
|
||||
*/
|
||||
private record V4CardNextAction(
|
||||
String cardId,
|
||||
String actionType,
|
||||
String actionStatus) {
|
||||
|
||||
private static V4CardNextAction none() {
|
||||
return new V4CardNextAction(null, ReservationV4NextActionType.NONE.name(), null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4TaskCardDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4TaskCardSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderKeyType;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderVisibility;
|
||||
@@ -601,6 +602,155 @@ class ReservationFrontendQueryControllerTest {
|
||||
.andExpect(jsonPath("$.items[0].order_status").value("ENDED"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnOrderListWithV4NextActionEntrypoint() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage(
|
||||
"mail-frontend-order-list-v4-cp14-001",
|
||||
"Frontend Query Order List V4 CP14",
|
||||
Instant.parse("2026-07-08T06:30:00Z"));
|
||||
Long basicFirstOrderId = 930000000000004001L;
|
||||
Long reviewFirstOrderId = 930000000000004002L;
|
||||
Long confirmBusinessOrderId = 930000000000004003L;
|
||||
Long completedOnlyOrderId = 930000000000004004L;
|
||||
Long noV4OrderId = 930000000000004005L;
|
||||
Long legacyTaskId = 930000000000004301L;
|
||||
insertActiveGroupOrder(basicFirstOrderId, source.inboxId(), "GRP-FRONTEND-V4-CP14-BASIC");
|
||||
insertActiveGroupOrder(reviewFirstOrderId, source.inboxId(), "GRP-FRONTEND-V4-CP14-REVIEW");
|
||||
insertActiveGroupOrder(confirmBusinessOrderId, source.inboxId(), "GRP-FRONTEND-V4-CP14-CONFIRM");
|
||||
insertActiveGroupOrder(completedOnlyOrderId, source.inboxId(), "GRP-FRONTEND-V4-CP14-COMPLETED");
|
||||
insertActiveGroupOrder(noV4OrderId, source.inboxId(), "GRP-FRONTEND-V4-CP14-NONE");
|
||||
insertTransition(930000000000004201L, source.inboxId(), 1, "GRP-FRONTEND-V4-CP14-BASIC",
|
||||
"New Booking", "NEW_BOOKING", "NEW_BOOKING");
|
||||
insertTask(legacyTaskId, basicFirstOrderId, source.inboxId(), 930000000000004201L, "New Booking",
|
||||
"NEW_BOOKING", "NEW_BOOKING", "PENDING_CONFIRM", 1);
|
||||
|
||||
ReservationV4OrderTaskSnapshot basicFirstOrderTask = insertV4OrderTask(
|
||||
930000000000004101L,
|
||||
source,
|
||||
basicFirstOrderId,
|
||||
"order-basic-first",
|
||||
1,
|
||||
"GRP-FRONTEND-V4-CP14-BASIC",
|
||||
Instant.parse("2026-07-08T06:30:00Z"),
|
||||
HOTEL_ID);
|
||||
insertV4TaskCard(basicFirstOrderTask, ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name(), null, 0, 10,
|
||||
ReservationV4CardStatus.READONLY.name(), null, "{}");
|
||||
ReservationV4TaskCardSnapshot basicFirstCard = insertV4TaskCard(
|
||||
basicFirstOrderTask, ReservationV4CardType.BASIC_INFORMATION.name(), null, 0, 20,
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name(), null, "{}");
|
||||
insertV4TaskCard(basicFirstOrderTask, ReservationV4CardType.ROOM_INFORMATION.name(), "NEW_BOOKING", 1, 30,
|
||||
ReservationV4CardStatus.REVIEW_REQUIRED.name(), "PENDING", "{}");
|
||||
ReservationV4OrderTaskSnapshot laterOrderTask = insertV4OrderTask(
|
||||
930000000000004102L,
|
||||
source,
|
||||
basicFirstOrderId,
|
||||
"order-basic-later",
|
||||
2,
|
||||
"GRP-FRONTEND-V4-CP14-BASIC",
|
||||
Instant.parse("2026-07-08T07:30:00Z"),
|
||||
HOTEL_ID);
|
||||
insertV4TaskCard(laterOrderTask, ReservationV4CardType.BASIC_INFORMATION.name(), null, 0, 20,
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name(), null, "{}");
|
||||
|
||||
ReservationV4OrderTaskSnapshot reviewOrderTask = insertV4OrderTask(
|
||||
930000000000004103L,
|
||||
source,
|
||||
reviewFirstOrderId,
|
||||
"order-review-first",
|
||||
3,
|
||||
"GRP-FRONTEND-V4-CP14-REVIEW",
|
||||
Instant.parse("2026-07-08T08:30:00Z"),
|
||||
HOTEL_ID);
|
||||
insertV4TaskCard(reviewOrderTask, ReservationV4CardType.BASIC_INFORMATION.name(), null, 0, 20,
|
||||
ReservationV4CardStatus.CONFIRMED.name(), null, "{}");
|
||||
insertV4TaskCard(reviewOrderTask, ReservationV4CardType.ROOM_INFORMATION.name(), "NEW_BOOKING", 1, 30,
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name(), null, "{}");
|
||||
ReservationV4TaskCardSnapshot reviewCard = insertV4TaskCard(
|
||||
reviewOrderTask, ReservationV4CardType.PAYMENT.name(), "PAYMENT", 2, 60,
|
||||
ReservationV4CardStatus.REVIEW_REQUIRED.name(), "PENDING", "{}");
|
||||
|
||||
ReservationV4OrderTaskSnapshot confirmOrderTask = insertV4OrderTask(
|
||||
930000000000004104L,
|
||||
source,
|
||||
confirmBusinessOrderId,
|
||||
"order-confirm-business",
|
||||
4,
|
||||
"GRP-FRONTEND-V4-CP14-CONFIRM",
|
||||
Instant.parse("2026-07-08T09:30:00Z"),
|
||||
HOTEL_ID);
|
||||
insertV4TaskCard(confirmOrderTask, ReservationV4CardType.BASIC_INFORMATION.name(), null, 0, 20,
|
||||
ReservationV4CardStatus.CONFIRMED.name(), null, "{}");
|
||||
ReservationV4TaskCardSnapshot confirmCard = insertV4TaskCard(
|
||||
confirmOrderTask, ReservationV4CardType.ROOM_INFORMATION.name(), "UPDATE_BOOKING", 1, 30,
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name(), null, "{}");
|
||||
|
||||
ReservationV4OrderTaskSnapshot completedOrderTask = insertV4OrderTask(
|
||||
930000000000004105L,
|
||||
source,
|
||||
completedOnlyOrderId,
|
||||
"order-completed-only",
|
||||
5,
|
||||
"GRP-FRONTEND-V4-CP14-COMPLETED",
|
||||
Instant.parse("2026-07-08T10:30:00Z"),
|
||||
HOTEL_ID);
|
||||
insertV4TaskCard(completedOrderTask, ReservationV4CardType.BASIC_INFORMATION.name(), null, 0, 20,
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name(), null, "{}");
|
||||
v4WorkflowRepository.updateOrderTaskStatus(
|
||||
HOTEL_ID,
|
||||
completedOrderTask.id(),
|
||||
ReservationV4OrderTaskStatus.COMPLETED.name(),
|
||||
completedOrderTask.updatedAt());
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), get("/api/reservation/orders")
|
||||
.param("hotel_id", HOTEL_ID)
|
||||
.param("keyword", "GRP-FRONTEND-V4-CP14-")
|
||||
.param("page_num", "1")
|
||||
.param("page_size", "20"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].next_processable_task_id")
|
||||
.value(contains(legacyTaskId.toString())))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].v4_open_order_task_count")
|
||||
.value(contains(2)))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].next_v4_order_task_id")
|
||||
.value(contains(basicFirstOrderTask.id().toString())))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].next_v4_action_card_id")
|
||||
.value(contains(basicFirstCard.id().toString())))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].next_v4_action_type")
|
||||
.value(contains("CONFIRM")))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].next_v4_action_status")
|
||||
.value(contains("PENDING_CONFIRM")))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + reviewFirstOrderId + "')].v4_open_order_task_count")
|
||||
.value(contains(1)))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + reviewFirstOrderId + "')].next_v4_order_task_id")
|
||||
.value(contains(reviewOrderTask.id().toString())))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + reviewFirstOrderId + "')].next_v4_action_card_id")
|
||||
.value(contains(reviewCard.id().toString())))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + reviewFirstOrderId + "')].next_v4_action_type")
|
||||
.value(contains("REVIEW")))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + reviewFirstOrderId + "')].next_v4_action_status")
|
||||
.value(contains("REVIEW_REQUIRED")))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + confirmBusinessOrderId + "')].next_v4_order_task_id")
|
||||
.value(contains(confirmOrderTask.id().toString())))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + confirmBusinessOrderId + "')].next_v4_action_card_id")
|
||||
.value(contains(confirmCard.id().toString())))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + confirmBusinessOrderId + "')].next_v4_action_type")
|
||||
.value(contains("CONFIRM")))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + confirmBusinessOrderId + "')].next_v4_action_status")
|
||||
.value(contains("PENDING_CONFIRM")))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].v4_open_order_task_count")
|
||||
.value(contains(0)))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].next_v4_order_task_id")
|
||||
.value(contains(nullValue())))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].next_v4_action_type")
|
||||
.value(contains("NONE")))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].next_v4_action_status")
|
||||
.value(contains(nullValue())))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + noV4OrderId + "')].v4_open_order_task_count")
|
||||
.value(contains(0)))
|
||||
.andExpect(jsonPath("$.items[?(@.order_id=='" + noV4OrderId + "')].next_v4_action_type")
|
||||
.value(contains("NONE")));
|
||||
}
|
||||
|
||||
private SourceMessageCaptureResult captureSourceMessage(String externalMessageId, String subject) {
|
||||
return captureSourceMessage(externalMessageId, subject, Instant.parse("2026-07-08T08:00:00Z"));
|
||||
}
|
||||
@@ -678,13 +828,25 @@ class ReservationFrontendQueryControllerTest {
|
||||
String targetLocatorValue,
|
||||
Instant sourceReceivedAt,
|
||||
String hotelId) {
|
||||
return insertV4OrderTask(aiBatchId, source, orderId, orderRef, 1, targetLocatorValue, sourceReceivedAt, hotelId);
|
||||
}
|
||||
|
||||
private ReservationV4OrderTaskSnapshot insertV4OrderTask(
|
||||
Long aiBatchId,
|
||||
SourceMessageCaptureResult source,
|
||||
Long orderId,
|
||||
String orderRef,
|
||||
Integer orderContextIndex,
|
||||
String targetLocatorValue,
|
||||
Instant sourceReceivedAt,
|
||||
String hotelId) {
|
||||
LocalDateTime now = LocalDateTime.ofInstant(sourceReceivedAt.plusSeconds(10), ZoneOffset.UTC);
|
||||
return v4WorkflowRepository.findOrCreateOrderTask(new ReservationV4OrderTaskDraft(
|
||||
hotelId,
|
||||
source.inboxId(),
|
||||
aiBatchId,
|
||||
orderRef,
|
||||
1,
|
||||
orderContextIndex,
|
||||
orderId,
|
||||
"GROUP",
|
||||
"GROUP_CODE",
|
||||
@@ -695,7 +857,7 @@ class ReservationFrontendQueryControllerTest {
|
||||
now));
|
||||
}
|
||||
|
||||
private void insertV4TaskCard(
|
||||
private ReservationV4TaskCardSnapshot insertV4TaskCard(
|
||||
ReservationV4OrderTaskSnapshot orderTask,
|
||||
String cardType,
|
||||
String eventType,
|
||||
@@ -704,7 +866,7 @@ class ReservationFrontendQueryControllerTest {
|
||||
String cardStatus,
|
||||
String reviewStatus,
|
||||
String displayPayloadJson) {
|
||||
v4WorkflowRepository.insertTaskCard(new ReservationV4TaskCardDraft(
|
||||
return v4WorkflowRepository.insertTaskCard(new ReservationV4TaskCardDraft(
|
||||
orderTask.hotelId(),
|
||||
orderTask.id(),
|
||||
orderTask.sourceMessageId(),
|
||||
|
||||
Reference in New Issue
Block a user