停止V4入站双写旧任务

This commit is contained in:
andy
2026-07-20 17:18:28 +07:00
parent d45ff5b4ed
commit 626e87abdb
16 changed files with 438 additions and 69 deletions

View File

@@ -4,16 +4,16 @@
| --- | --- |
| 最近更新 | 2026-07-20 |
| 当前分支 | `feature/huangting` |
| 当前阶段 | M002 V4 入站、多卡模型、持久化基线、入站写入、查询接口、卡片确认、复核解阻、目录校验、订单详情 V4 总览、DB 目录、Lookup API、前端 lookup 接入、目录管理后台 CP1 前后端、订单列表 V4 继续处理入口 / open count 收口V4 业务审计查询 |
| 当前重点 | M002 V4 已完成订单列表 V4 继续处理入口字段和前端消费,`GET /api/reservation/orders` 可返回 V4 下一步订单任务、卡片、动作类型、动作状态、V4 open 数和统一展示字段 `open_work_item_count``GET /api/reservation/orders/{orderId}` 已补齐订单详情 `order_overview``next_v4_action``related_source_messages[]``v4_order_tasks[].cards[]`,前端订单详情页已改为 V4 订单总览页V4 订单任务和 S10/S99 来源通知已补齐业务审计查询接口2026-07-20 测试机 smoke 已完成主链路验证,但测试机订单详情响应疑似未部署到包含 CP15.1 的最新后端包,需要重新部署后复测订单详情 V4 总览;后续可做真实 PMS / OPERA / OHIP 同步或 SuperAgent 目录供给方案 |
| 当前阶段 | M002 V4 入站、多卡模型、持久化基线、入站写入、查询接口、卡片确认、复核解阻、目录校验、订单详情 V4 总览、DB 目录、Lookup API、前端 lookup 接入、目录管理后台 CP1 前后端、订单列表 V4 继续处理入口 / open count 收口V4 业务审计查询和停止旧任务双写 |
| 当前重点 | M002 V4 已停止普通业务入站双写旧 `workflow_reservation_task`V4 后新业务主线只写 V4 order task / cards / source notification开发阶段不维护 V2/V3 旧任务兼容,测试数据可重建,生产迁移策略后续上线前单独设计。`GET /api/reservation/orders` 可返回 V4 下一步订单任务、卡片、动作类型、动作状态、V4 open 数和统一展示字段 `open_work_item_count`;旧 `open_task_count` / `next_processable_task_id` 仅作历史诊断兼容。后续可继续做测试机 V4 smoke 复测、真实 PMS / OPERA / OHIP 同步或 SuperAgent 目录供给方案 |
## 1. 当前 Checkpoint
- 名称:`M002-V4-order-detail-overview-frontend`
- 状态Done前端订单详情页已改为“订单总览页”:展示 `order_overview` 当前确认快照、`next_v4_action` 下一步入口、`related_source_messages[]` 关联来源消息摘要,以及 `v4_order_tasks[].cards[]` V4 订单任务和任务卡安全摘要。点击下一步入口或 V4 时间线任务进入 `/reservation/order-tasks/{orderTaskId}`
- 目标:让订单详情页聚焦订单视角总览,不在该页直接确认、复核或编辑 V4 任务卡
- 边界:本轮不改后端、不改 V4 写操作、不展示 `ai_payload_json``display_payload_json``confirmed_payload_json`、邮件正文或附件 URL旧 V2/V3 详情接口和独立任务详情页继续保留
- 联调备注:2026-07-20 测试机 V4 主链路 smoke 已完成登录 / 权限 / 酒店隔离、V4 工作台、V4 订单任务列表和详情、lookup、Basic Information 确认、业务卡确认、S10/S99 来源通知详情和 ack、V4 业务审计验证;当前测试机没有待复核 `REVIEW_REQUIRED` 卡,复核写操作未覆盖。测试机 `GET /api/reservation/orders/{orderId}` 仍返回旧响应形态,缺少 CP15.1 的 `order_overview``next_v4_action``related_source_messages[]``v4_order_tasks[].cards[]`,优先排查测试机是否部署了包含订单详情 V4 总览的最新包
- 名称:`M002-V4-stop-legacy-task-dual-write-and-dev-cleanup`
- 状态Done后端已停止 V4 普通业务入站创建旧 `workflow_reservation_task`,补充开发 / 测试旧任务数据清理说明,并通过相关回归与全量后端测试
- 目标:V4 普通业务只写 V4 模型,避免 V4 order task 已完成后旧兼容任务仍作为订单列表继续处理 fallback
- 边界:不做生产数据迁移,不推进真实 PMS / OPERA / OHIP不推进 M011 CP4不删除 SourceMessage、V4 order task / cards、V4 source notification、目录、用户权限、酒店配置或 SuperAgent dispatch run
- 联调备注:开发 / 测试环境允许清理旧任务、旧任务卡、旧任务审计和旧 OPERA 模拟数据;清理 SQL 见 `docs/project/operations/dev-test-v4-legacy-task-cleanup.md`。该策略仅限开发 / 测试阶段,不代表生产迁移方案
## 2. 当前优先级
@@ -36,7 +36,7 @@
- 后续每完成一个 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 / 附加费表高亮行业务 JSONDebug 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 来源通知 ackM002 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 下一步订单任务、卡片、动作类型、动作状态、V4 open 数和统一展示计数 `open_work_item_count`,前端按 V4 优先、旧任务回退跳转,并按 `open_work_item_count` 展示待处理数量V4 业务审计查询已补齐订单任务审计和来源通知 ack 审计两个只读接口;订单详情已补齐并完成前端接入 V4 `order_overview``next_v4_action``related_source_messages[]``v4_order_tasks[].cards[]`。真实 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 来源通知 ackM002 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 下一步订单任务、卡片、动作类型、动作状态、V4 open 数和统一展示计数 `open_work_item_count`,前端按 V4 优先跳转,并按 `open_work_item_count` 展示待处理数量V4 业务审计查询已补齐订单任务审计和来源通知 ack 审计两个只读接口;订单详情已补齐并完成前端接入 V4 `order_overview``next_v4_action``related_source_messages[]``v4_order_tasks[].cards[]`V4 普通业务入站已停止双写旧 `workflow_reservation_task`。真实 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

View File

@@ -46,7 +46,7 @@
| `requirements/M002-order-task-workflow-v3.md` | 当前有效 | M002 订单任务主流程 V3基于 2026-07-11 P0 冻结基线和 2026-07-12 P0.1 Parent Group 修订,记录 S10/S99、40 路由、方案 C、type-known manual review 同卡解阻和 fail-closed 边界。 |
| `requirements/M002-task-field-control-contract-v1.md` | 当前有效 | M002 任务卡字段控件契约 V1记录任务详情 `fields[]` 控件元数据、人工复核控件复用和前后端开发边界。 |
| `requirements/M002-v4-agent-callback-field-contract.md` | 当前有效 | M002 V4 Agent 回调字段契约,基于 2026-07-18 业务基线和最新答复,冻结 `source_message``order_contexts``message_events`、订单级 Basic Information、六类 Event、S10/S99 和校验口径;后端已完成 V4 入站解析、持久化、查询、确认、复核和当前酒店数据库目录校验。 |
| `requirements/M002-v4-order-task-card-domain-model-cp2.md` | 当前有效 | M002 V4 CP2 订单任务与多卡领域模型设计,并记录 CP3-CP8 表结构、入站写入、查询、确认、复核和目录校验已落地状态CP11 已完成 DB 目录与 lookup APICP12 已完成前端 lookup 接入CP13 已完成目录管理后台 CP1CP14 已完成订单列表 V4 继续处理入口CP15 已完成 V4 业务审计查询CP15.1 已完成订单详情 V4 总览后端补齐;后续仍需前端订单详情 V4 化、测试机联调和真实 PMS / OPERA / OHIP 同步。 |
| `requirements/M002-v4-order-task-card-domain-model-cp2.md` | 当前有效 | M002 V4 CP2 订单任务与多卡领域模型设计,并记录 CP3-CP8 表结构、入站写入、查询、确认、复核和目录校验已落地状态CP11 已完成 DB 目录与 lookup APICP12 已完成前端 lookup 接入CP13 已完成目录管理后台 CP1CP14 已完成订单列表 V4 继续处理入口CP15 已完成 V4 业务审计查询CP15.1 已完成订单详情 V4 总览后端补齐,当前已停止 V4 普通业务双写旧 `workflow_reservation_task`;开发阶段不维护 V2/V3 旧任务兼容,测试数据可重建,生产迁移策略后置。 |
| `requirements/M002-v4-real-catalog-lookup-api-design.md` | 当前有效 | M002 V4 真实目录与 Lookup API 设计及 CP11 / CP13 CP1 实现记录,记录 Account、Market、Source、Room Type、Rate Code 从固定种子导入数据库、前端 lookup API、目录管理后端接口、权限、缓存后置、PMS / OPERA / OHIP 同步后置和失败兜底。 |
| `requirements/M002-v4-test-machine-smoke-checklist.md` | 当前有效 | M002 V4 测试机冒烟清单覆盖登录、酒店权限、V4 工作台、订单任务详情、lookup、确认、复核解阻、S10/S99 ack、订单详情 V4 时间线和目录管理 CP1 排查点。 |
| `requirements/M002-superagent-task-result-api-contract.md` | 阶段记录 | M002 SuperAgent 任务结果入站接口契约阶段记录;对外总契约以 `integrations/superagent-api-contract.md` 为准。 |
@@ -80,6 +80,12 @@
| `frontend-backend/frontend-to-backend-api-requests.md` | 当前有效 | 前端提醒后端需要增加或补齐的接口,已区分可用、后置和历史候选路径。 |
| `frontend-backend/debug-eml-page-integration-guide.md` | 当前有效 | Debug EML 页面前端对接指南。 |
## 运维与开发测试操作
| 文档 | 状态 | 中文说明 |
| --- | --- | --- |
| `operations/dev-test-v4-legacy-task-cleanup.md` | 当前有效 | 开发 / 测试环境 V4 停止双写旧任务后的旧 `workflow_reservation_task` 及直接依赖数据清理说明;不适用于生产迁移。 |
## 执行计划
| 文档 | 状态 | 中文说明 |

View File

@@ -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_work_item_count` 是订单列表统一待处理展示数量,前端订单列表已用它展示待处理数,第一版等于 V4 未完成订单任务数;`open_task_count` 仍保留为旧 V2/V3 诊断计数,排除 `COMPLETED``FAILED`;已补齐 V4 继续处理入口字段,前端有 `next_v4_order_task_id` 时优先跳 V4 订单任务详情;隐藏技术订单不返回,因此 S10/S99 和旧 S000/S999 不会在订单列表形成订单。 |
| `GET /api/reservation/orders` | 查询订单列表 | 必须带 `Authorization: Bearer <access_token>`,需要 `RESERVATION_ORDER_READ`;默认返回全部订单状态;按后端维护的订单最近业务活动时间倒序,当前落库字段为 `workflow_reservation_order.latest_activity_at`,前端不要自行重排;`open_work_item_count` 是订单列表统一待处理展示数量,前端订单列表已用它展示待处理数,第一版等于 V4 未完成订单任务数;V4 普通业务入站已停止双写旧 `workflow_reservation_task`,开发 / 测试阶段不维护 V2/V3 旧任务兼容,测试数据可重建;`open_task_count` 仍保留为旧任务表原始诊断计数,前端不要用于展示待处理数;已补齐 V4 继续处理入口字段,前端有 `next_v4_order_task_id` 时优先跳 V4 订单任务详情;`next_processable_task_id` 仅作为历史 V2/V3 诊断兼容字段,清理旧任务数据后新 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` 只筛业务 / 可处理卡,固定来源邮件展示卡不参与筛选。 |
@@ -104,7 +104,7 @@
| 接口 | 本轮变化 | 前端接入注意 |
| --- | --- | --- |
| `GET /api/reservation/orders` | 补齐订单列表 V4 继续处理入口字段,并新增统一 open count 字段;前端展示已接入。 | `order_status` 不传时默认查询全部订单状态;`page_num` 从 1 开始;`page_size` 后端有最大值保护;`open_work_item_count` 是订单列表展示用统一待处理数量,开发阶段不考虑旧数据,第一版直接等于 `v4_open_order_task_count`;前端展示待处理数量时只读该字段,不自行计算旧任务数和 V4 数,也不使用旧 `open_task_count` 作为展示数量;旧 `open_task_count` `next_processable_task_id` 继续保留用于 V2/V3 兼容与排查。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/orders` | 补齐订单列表 V4 继续处理入口字段,并新增统一 open count 字段;前端展示已接入。 | `order_status` 不传时默认查询全部订单状态;`page_num` 从 1 开始;`page_size` 后端有最大值保护;`open_work_item_count` 是订单列表展示用统一待处理数量,开发阶段不考虑旧数据,第一版直接等于 `v4_open_order_task_count`;前端展示待处理数量时只读该字段,不自行计算旧任务数和 V4 数,也不使用旧 `open_task_count` 作为展示数量;旧 `open_task_count` 保留为旧任务表原始诊断计数。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}`;开发 / 测试清理旧任务后,新 V4 订单通常不再回退旧 `/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[]``related_source_messages[]` 都为空,`order_overview` 为空快照,`next_v4_action.action_type=NONE`;旧 `tasks[]` 顺序由后端按订单队列返回V4 `v4_order_tasks[]``source_received_at``source_message_id``order_context_index``created_at`、数字 ID 正序返回;前端不要自行重排。订单详情页只展示 V4 确认快照、下一步入口、关联来源消息和任务卡安全摘要,不在该页确认、复核或编辑任务卡,也不展示 payload、邮件正文或附件 URL。`include_source_summary` 第一版不作为前端裁剪字段的强约束,前端暂不要依赖它减少返回字段。 |
| `GET /api/reservation/tasks/{taskId}` | 补齐顶层来源邮件字段,并扩展 `fields[]` 元数据。 | 顶层来源字段用于打开邮件会话;`fields[]` 中的 `result_type``task_type``task_subtype``default_value_source` 用于前端字段分组、调试和白名单对齐。 |
@@ -190,11 +190,11 @@ POST /api/auth/logout
- `GET /api/reservation/orders` 默认返回全部订单状态,包括 `TEMPORARY``ACTIVE``ENDED``LOGIC_DELETED`
- `keyword` 会匹配订单业务号、临时订单号、展示名、订单状态,也会匹配来源消息安全摘要命中的 SourceMessage ID前端可以用邮件主题、外部消息 ID 或会话 ID 辅助查订单。
- `open_work_item_count` 是订单列表统一展示数量,第一版按 V4 未完成订单任务计算,即等于 `v4_open_order_task_count`;前端展示待处理数量时只使用它,不回退旧 `open_task_count`,也不要自行把旧任务数和 V4 数相加。
- `open_task_count` 只统计旧 V2/V3 未关闭任务,排除 `COMPLETED``FAILED`,当前保留用于兼容和问题排查。
- `next_processable_task_id` 是后端按同订单队列实时计算出的下一条可处理任务;前端可以用它做“继续处理”入口
- `open_task_count` 是旧任务表原始未关闭计数,排除 `COMPLETED``FAILED`,当前保留用于历史接口兼容和问题排查V4 普通业务入站已停止双写旧任务,开发 / 测试环境应清理旧任务数据,前端展示待处理数量不要使用它
- `next_processable_task_id` 是后端按旧 V2/V3 队列实时计算出的历史兼容字段;开发阶段不维护 V2/V3 旧任务兼容,清理旧任务数据后,新 V4 订单不应再通过它进入旧任务详情
- 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 订单任务不计入 openS10/S99 来源通知不挂订单,不进入这些订单列表字段。
- 前端订单列表“继续处理”已按该优先级接入:有 `next_v4_order_task_id` 时跳 V4 订单任务详情;否则有旧 `next_processable_task_id` 时跳旧任务详情;`next_v4_action_type=NONE` 且旧字段为空时展示无待处理状态,查看详情仍固定进入订单详情。
- 前端订单列表“继续处理”已按该优先级接入:有 `next_v4_order_task_id` 时跳 V4 订单任务详情;`next_v4_action_type=NONE` 且旧字段为空时展示无待处理状态,查看详情仍固定进入订单详情。开发 / 测试阶段旧任务数据可清理,清理后新 V4 订单不应再出现旧 fallback 入口。
- `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 目标模型已改为独立来源通知,不再挂隐藏技术订单。
@@ -248,7 +248,7 @@ POST /api/auth/logout
### 5.6 前端联调演示数据 seed 接口
后端提供一个受控的 dev/test 演示数据入口,方便前端在空库或本地环境快速看到页面效果。
后端提供一个受控的 dev/test 演示数据入口,方便前端在空库或本地环境快速看到历史 V2/V3 页面效果。
```text
POST /api/system/reservation/demo-data
@@ -265,6 +265,7 @@ Content-Type: application/json
- dev profile 默认开启test 默认关闭,需要后端环境显式设置 `reservation.demo-data.enabled=true` 或环境变量 `RESERVATION_TEST_DEMO_DATA_ENABLED=true`
- 必须配置 `reservation.demo-data.access-key`dev 优先使用 `RESERVATION_DEV_DEMO_DATA_ACCESS_KEY`test 优先使用 `RESERVATION_TEST_DEMO_DATA_ACCESS_KEY`,旧通用变量 `RESERVATION_DEMO_DATA_ACCESS_KEY` 仅作为兼容兜底。
- 该接口只用于 dev/test 联调,不允许放进生产普通页面,也不要把访问口令写进前端仓库、浏览器环境变量或构建产物。
- 该接口会创建旧 `workflow_reservation_task` 演示数据,是历史 V2/V3 页面演示入口M002 V4 smoke 不应再使用该接口造数避免重新制造旧任务残留。V4 smoke 应使用 SuperAgent V4 回调或专门 V4 fixture。
返回内容:

View File

@@ -42,7 +42,7 @@
| `GET /api/source-messages` | 已完成安全摘要列表 | 可以 | 不能替代邮件会话全文接口。 |
| `GET /api/source-messages/{id}` | 已完成单条安全摘要 | 可以 | 不能替代邮件会话全文接口。 |
| `GET /api/source-messages/{id}/original` | 已完成单封原文权限读取 | 谨慎接入 | 必须带 Bearer token需要同时拥有 `SOURCE_MESSAGE_READ``SOURCE_MESSAGE_ORIGINAL_READ`;只能读单封邮件,不能返回同一 conversation 全量邮件。 |
| `GET /api/reservation/orders` | 已完成第一版 | 可以 | 默认查询全部订单状态;订单列表待处理展示使用 `open_work_item_count`;旧 `open_task_count` 仅作为 V2/V3 兼容诊断计数。 |
| `GET /api/reservation/orders` | 已完成第一版 | 可以 | 默认查询全部订单状态;订单列表待处理展示使用 `open_work_item_count`V4 普通业务已停止双写旧任务,`open_task_count` 仅作为历史诊断计数。 |
| `GET /api/source-messages/{sourceMessageId}/conversation` | 已完成第一版,已补 `html_body_sanitized``html_render_mode` | 可以 | 必须带 Bearer token需要同时拥有 `SOURCE_MESSAGE_READ``SOURCE_MESSAGE_ORIGINAL_READ`;返回完整 text/html、后端清洗后的 HTML、媒体外链和关联订单 / 任务摘要;前端不传原文读取 key页面展示优先使用 `html_body_sanitized`。 |
| `POST /api/system/reservation/demo-data` | 已完成 | 仅本地 / test 联调可用 | 默认关闭,必须后端配置访问口令;不能作为生产页面接口。 |
| `POST /api/system/debug/eml-superagent-runs` | 已完成第一版 | 仅 dev/test Debug 页面可用 | 默认关闭,必须后端配置访问口令、阿里云 OSS 和 SuperAgent Open API第一版只展示 SuperAgent 结果,不创建订单和任务;已能识别旧 S000/S999 和新结构化 S10/S99。 |
@@ -353,7 +353,7 @@ GET /api/reservation/orders/{orderId}
GET /api/reservation/orders
```
当前状态:后端已完成第一版。默认查询全部订单状态;`open_task_count` 排除旧 V2/V3 的 `COMPLETED``FAILED``next_processable_task_id` 按旧 V2/V3 同订单队列可处理状态实时计算。M002 V4 CP14 已补齐 V4 继续处理入口字段;后端新增 `open_work_item_count` 作为订单列表统一待处理展示数量,第一版直接等于 V4 未完成订单任务数,不叠加旧任务。前端已按 `next_v4_order_task_id` 优先进入 V4 订单任务详情,并在无 V4 入口时回退旧任务详情;订单列表待处理数量已改为只展示 `open_work_item_count`
当前状态:后端已完成第一版。默认查询全部订单状态;`open_task_count` 排除旧任务表中 `COMPLETED``FAILED`保留为历史诊断计数。M002 V4 CP14 已补齐 V4 继续处理入口字段;后端新增 `open_work_item_count` 作为订单列表统一待处理展示数量,第一版直接等于 V4 未完成订单任务数,不叠加旧任务。当前开发阶段已停止 V4 普通业务双写旧 `workflow_reservation_task`,开发 / 测试环境旧任务数据可清理且可重建;`next_processable_task_id` 仅作为历史 V2/V3 诊断兼容字段,清理后新 V4 订单不应返回旧任务入口。前端已按 `next_v4_order_task_id` 优先进入 V4 订单任务详情;订单列表待处理数量已改为只展示 `open_work_item_count`
默认排序:按后端维护的订单最近业务活动时间倒序返回,保证最近有业务活动的订单排在前面。后端当前使用 `workflow_reservation_order.latest_activity_at` 作为排序字段,并在订单创建、任务创建、草稿保存、最终确认、人工复核解阻、任务状态变更等写路径维护;前端不要再基于任务时间或更新时间自行重排。
@@ -413,7 +413,7 @@ V4 继续处理字段说明:
| `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 来源通知不创建订单,不进入订单列表字段统计。订单列表展示“待处理数量”时只使用 `open_work_item_count`,不使用旧 `open_task_count` 作为展示数量,也不在前端自行计算 `open_task_count + v4_open_order_task_count`;旧 `open_task_count``v4_open_order_task_count` 保留用于兼容与排查。
前端“继续处理”入口优先级:优先使用 `next_v4_order_task_id` 跳转 V4 订单任务详情;没有 V4 待处理`next_processable_task_id` 为空时展示无待处理状态。S10/S99 来源通知不创建订单,不进入订单列表字段统计。订单列表展示“待处理数量”时只使用 `open_work_item_count`,不使用旧 `open_task_count` 作为展示数量,也不在前端自行计算 `open_task_count + v4_open_order_task_count`;旧 `open_task_count``v4_open_order_task_count` 保留用于兼容与排查。V4 新业务主线只写 V4 模型,开发阶段不维护 V2/V3 旧任务兼容,测试数据可重建;清理旧任务数据后,新 V4 订单不应再出现旧继续处理入口。
## 6. 前端联调演示数据 seed 接口
@@ -423,7 +423,7 @@ V4 继续处理字段说明:
POST /api/system/reservation/demo-data
```
当前状态:后端已完成第一版。该接口只用于本地 / test 联调造数,默认关闭,不是生产业务页面接口。
当前状态:后端已完成第一版。该接口只用于本地 / test 联调造数,默认关闭,不是生产业务页面接口。该接口会创建旧 `workflow_reservation_task` 演示数据,是历史 V2/V3 页面演示入口M002 V4 smoke 不应再使用该接口造数避免重新制造旧任务残留。V4 smoke 应使用 SuperAgent V4 回调或专门 V4 fixture。
启用条件:

View File

@@ -120,12 +120,14 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID也不需要为
- V4 普通业务包使用 `route_code=null``source_message``order_contexts[]``message_events[]`
- `source_message.source_message_id` 是外部来源消息 ID对应 SourceMessage Inbox 的 `external_message_id`,不是本系统内部数据库 ID。
- 普通业务按 `source_message + order_ref` 创建 V4 订单任务并在订单任务下创建来源消息只读卡、Basic Information 卡和业务事件卡。
- 普通业务按 `source_message + order_ref` 创建 V4 订单任务并在订单任务下创建来源消息只读卡、Basic Information 卡和业务事件卡V4 后新业务主线不再创建旧 `workflow_reservation_task`
- Basic Information 必须先确认业务卡逐卡确认或复核解阻确认后永久锁定V4 第一版不提供前端草稿。
- `route_code=S10/S99` 使用 V4 来源通知模型,不创建隐藏技术订单,不进入订单详情时间线,不阻塞普通订单;前端只展示和 ack。
- Account / Market / Source、Room Type、Rate Code 以本系统数据库目录稳定代码为准SuperAgent 不应输出显示文案作为业务判断依据。
当前后端仍兼容 M002 V3 CP1-CP6
开发阶段不维护 V2/V3 旧任务兼容,测试数据可重建;该策略仅限开发 / 测试阶段,不代表生产迁移方案。生产数据迁移策略不在当前 checkpoint 处理,后续上线前另开迁移方案。
当前后端仍兼容 M002 V3 CP1-CP6 历史入口代码路径,但不作为开发阶段新业务验收重点:
- 已建立 40 条 P0.1 路由枚举 / 稳定配置;`route_code` 保持历史稳定,不按总数连续重编号,`R41/R42` 仍可能出现在响应和历史 transition 中。
- 已支持结构化 `S10/S99` 历史入站兼容;当前 V4 新入站使用来源通知模型。
@@ -811,6 +813,8 @@ S000/S999 不在 body 里传 `hotel_id`,后端使用平台酒店表唯一 `ACT
### 8.7 成功响应
V4 普通业务包成功响应示例。注意V4 新业务主线只写 V4 订单任务和任务卡,响应中的 `task_id``task_status``execution_order` 不再代表旧 `workflow_reservation_task`,因此返回 `null`;前端和后端内部处理应通过 V4 查询接口读取 `order_task_id` / `card_id`
```json
{
"request_id": "req-003",
@@ -827,19 +831,21 @@ S000/S999 不在 body 里传 `hotel_id`,后端使用平台酒店表唯一 `ACT
"system_process_category": "BUSINESS_TASK",
"adapter_error_code": null,
"order_id": "1900000000000000300",
"task_id": "1900000000000000400",
"task_id": null,
"system_task_type": "NEW_BOOKING",
"task_card_type": "NEW_BOOKING",
"task_status": "PENDING_CONFIRM",
"order_status": "TEMPORARY",
"execution_order": 1
"task_status": null,
"order_status": "ACTIVE",
"execution_order": null
}
],
"warnings": []
}
```
S000/S999 成功响应示例:
历史 V2/V3 兼容入口仍可能返回旧 `task_id``execution_order`;该路径仅作历史参考,不作为开发阶段 V4 新业务验收重点。
旧文本 S000/S999 成功响应示例:
```json
{

View File

@@ -0,0 +1,177 @@
# 开发 / 测试环境 V4 旧任务数据清理说明
## 1. 使用边界
本说明只适用于开发 / 测试环境,用于 M002 V4 主线停止双写旧 `workflow_reservation_task` 后,清理历史 smoke、fixture 或调试过程中遗留的旧任务数据。
项目当前决策:
- V4 后新业务主线只写 V4 模型:`workflow_reservation_v4_order_task``workflow_reservation_v4_task_card``workflow_reservation_v4_source_notification`
- 开发阶段不维护 V2/V3 旧任务兼容,测试数据可重建。
- 本清理策略不代表生产迁移方案;生产数据迁移必须在上线前单独设计、评审和演练。
## 2. 不清理的数据
以下数据不要通过本脚本清理:
- `platform_source_message_inbox`
- `workflow_reservation_v4_order_task`
- `workflow_reservation_v4_task_card`
- `workflow_reservation_v4_source_notification`
- `workflow_reservation_catalog_account`
- `workflow_reservation_catalog_code`
- 用户、角色、权限、菜单和酒店配置表
- `platform_superagent_dispatch_run`
- SourceMessage 原始链路和 SuperAgent dispatch 运行记录
## 3. 清理范围
清理目标为旧任务主模型和直接依赖旧 task 的开发测试数据:
- `workflow_reservation_opera_operation_attempt`
- `workflow_reservation_opera_operation`
- `workflow_reservation_audit_log``task_id` 指向旧任务的审计行
- `workflow_reservation_invoice_generation.task_id` 指向旧任务时置空,不删除生成记录
- `workflow_reservation_task_card`
- `workflow_reservation_task`
- `workflow_reservation_order.created_from_task_id` 指向旧任务时置空,不删除订单
`workflow_reservation_invoice_generation` 可能带可选 `task_id`。第一版只解除旧 task 引用,不默认删除催款单生成记录;如某次测试明确需要删除旧任务关联的催款单记录,应另行确认后按测试数据范围处理。
## 4. 执行前检查
先确认当前环境是开发或测试环境,并备份数据库或保留可重建脚本。
```sql
SELECT COUNT(*) AS legacy_task_count
FROM workflow_reservation_task;
SELECT hotel_id, task_status, COUNT(*) AS task_count
FROM workflow_reservation_task
GROUP BY hotel_id, task_status
ORDER BY hotel_id, task_status;
```
如只清理单酒店测试数据,先设置酒店 ID
```sql
SET @hotel_id = 'HOTEL-TEST';
```
## 5. 清理 SQL
```sql
START TRANSACTION;
CREATE TEMPORARY TABLE tmp_dev_legacy_reservation_task_ids (
id BIGINT PRIMARY KEY
);
INSERT INTO tmp_dev_legacy_reservation_task_ids (id)
SELECT id
FROM workflow_reservation_task
WHERE hotel_id = @hotel_id;
DELETE attempt
FROM workflow_reservation_opera_operation_attempt attempt
JOIN tmp_dev_legacy_reservation_task_ids legacy_task
ON legacy_task.id = attempt.task_id
WHERE attempt.hotel_id = @hotel_id;
DELETE operation_row
FROM workflow_reservation_opera_operation operation_row
JOIN tmp_dev_legacy_reservation_task_ids legacy_task
ON legacy_task.id = operation_row.task_id
WHERE operation_row.hotel_id = @hotel_id;
DELETE audit_row
FROM workflow_reservation_audit_log audit_row
JOIN tmp_dev_legacy_reservation_task_ids legacy_task
ON legacy_task.id = audit_row.task_id
WHERE audit_row.hotel_id = @hotel_id;
UPDATE workflow_reservation_invoice_generation invoice_row
JOIN tmp_dev_legacy_reservation_task_ids legacy_task
ON legacy_task.id = invoice_row.task_id
SET invoice_row.task_id = NULL,
invoice_row.updated_at = UTC_TIMESTAMP(6)
WHERE invoice_row.hotel_id = @hotel_id;
DELETE card
FROM workflow_reservation_task_card card
JOIN tmp_dev_legacy_reservation_task_ids legacy_task
ON legacy_task.id = card.task_id
WHERE card.hotel_id = @hotel_id;
UPDATE workflow_reservation_order order_row
JOIN tmp_dev_legacy_reservation_task_ids legacy_task
ON legacy_task.id = order_row.created_from_task_id
SET order_row.created_from_task_id = NULL,
order_row.updated_at = UTC_TIMESTAMP(6)
WHERE order_row.hotel_id = @hotel_id;
DELETE task
FROM workflow_reservation_task task
JOIN tmp_dev_legacy_reservation_task_ids legacy_task
ON legacy_task.id = task.id
WHERE task.hotel_id = @hotel_id;
DROP TEMPORARY TABLE tmp_dev_legacy_reservation_task_ids;
COMMIT;
```
如果确认要清理所有开发 / 测试酒店的旧任务,可去掉上述 SQL 中所有 `hotel_id = @hotel_id` 条件。不要在生产环境执行这种全量清理。
## 6. 执行后验证
单酒店清理时使用以下验证 SQL
```sql
SELECT COUNT(*) AS legacy_task_count
FROM workflow_reservation_task
WHERE hotel_id = @hotel_id;
SELECT COUNT(*) AS legacy_task_card_count
FROM workflow_reservation_task_card
WHERE hotel_id = @hotel_id;
SELECT COUNT(*) AS legacy_opera_operation_count
FROM workflow_reservation_opera_operation
WHERE hotel_id = @hotel_id;
SELECT COUNT(*) AS legacy_opera_attempt_count
FROM workflow_reservation_opera_operation_attempt
WHERE hotel_id = @hotel_id;
SELECT COUNT(*) AS legacy_invoice_task_link_count
FROM workflow_reservation_invoice_generation
WHERE hotel_id = @hotel_id
AND task_id IS NOT NULL;
```
如果执行的是全量开发 / 测试酒店清理,验证时同步去掉 `hotel_id = @hotel_id` 条件,确认全库旧任务及直接依赖已清空或解绑:
```sql
SELECT COUNT(*) AS legacy_task_count
FROM workflow_reservation_task;
SELECT COUNT(*) AS legacy_task_card_count
FROM workflow_reservation_task_card;
SELECT COUNT(*) AS legacy_opera_operation_count
FROM workflow_reservation_opera_operation;
SELECT COUNT(*) AS legacy_opera_attempt_count
FROM workflow_reservation_opera_operation_attempt;
SELECT COUNT(*) AS legacy_invoice_task_link_count
FROM workflow_reservation_invoice_generation
WHERE task_id IS NOT NULL;
```
订单列表验证口径:
- `GET /api/reservation/orders` 中新 V4 订单的 `open_work_item_count` 只看 V4 未完成订单任务。
- 清理后,新 V4 订单不应再返回旧 `next_processable_task_id`
- `platform_source_message_inbox`、V4 order task、V4 cards、V4 source notification、目录、用户权限和酒店配置应保持不变。

View File

@@ -14,9 +14,9 @@
本文把 2026-07-18 导入的业务基线、Agent 回调问题答复、草案审查答复和剩余 10 项确认回复,整理为 M002 V4 的 Agent 回调字段契约。
本契约用于后续 M002 V4 主流程设计、后端领域建模、前端页面模型、Adapter / MCP Schema 对齐和 SuperAgent 联调。当前后端已按本文完成 V4 入站解析基线:能识别 V4 包、校验关键契约、保存 AI transition / 任务卡原始 payload并把可映射的六类 event 先接入现有订单任务链路
本契约用于后续 M002 V4 主流程设计、后端领域建模、前端页面模型、Adapter / MCP Schema 对齐和 SuperAgent 联调。当前后端已按本文完成 V4 入站解析和多卡模型基线:能识别 V4 包、校验关键契约、保存 AI transition / 任务卡原始 payload并把可映射的六类 event 写入 V4 订单任务和任务卡模型
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`
V4 订单任务与多卡领域模型的 CP2 设计已经单独落到 `M002-v4-order-task-card-domain-model-cp2.md`。截至 CP14 和停止旧任务双写 checkpoint表结构、Entity、Mapper、Repository、SuperAgent V4 入站写入、V4 查询、普通卡片确认、S10/S99 ack、V4 复核解阻、数据库目录、Account / Room Type / Rate Code Lookup API、目录管理后台 CP1 后端接口订单列表 V4 继续处理入口字段,以及 V4 普通业务不再创建旧 `workflow_reservation_task` 已实现。真实 PMS 同步仍后置,方案见 `M002-v4-real-catalog-lookup-api-design.md`
当前已确认开发阶段数据可以清空,因此 M002 V4 后续可以按新模型重建,不要求兼容旧任务数据、旧草稿、旧 OPERA 模拟、旧 `S000/S999`、旧 Fallback 或旧 `case_keys`

View File

@@ -4,19 +4,19 @@
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.7 |
| 文档版本 | 0.8 |
| 日期 | 2026-07-20 |
| 状态 | CP2 设计已确认CP3-CP8、CP11、CP13、CP14V4 业务审计查询已实现 |
| 状态 | CP2 设计已确认CP3-CP8、CP11、CP13、CP14V4 业务审计查询和停止 V4 普通业务双写旧任务已实现 |
| 适用范围 | M002 V4 入站后的订单任务、多卡、状态、查询和写操作设计 |
| 不适用范围 | V4 复核接口、真实 PMS / OPERA / OHIP、前端页面视觉稿、历史数据迁移 |
| 不适用范围 | 真实 PMS / OPERA / OHIP、前端页面视觉稿、生产历史数据迁移 |
## 1. 文档定位
M002 V4 CP1 已完成 SuperAgent V4 回调包入站解析、基础校验、路由适配和 AI transition 最小落库。CP1 仍然把可映射的 V4 event 临时接入 M002 V3 的订单 / 任务 / 任务卡链路。
M002 V4 CP1 已完成 SuperAgent V4 回调包入站解析、基础校验、路由适配和 AI transition 最小落库。早期 CP1 把可映射的 V4 event 临时接入 M002 V3 的订单 / 任务 / 任务卡链路;开发阶段最新决策已停止 V4 普通业务入站双写旧 `workflow_reservation_task`
本文是 CP2 设计文档,用于把 2026-07-18 V4 字段契约落成后续可开发的数据模型和接口草案。
截至 CP14V4 业务审计查询补齐,后端已实现本文第 10、11、12 节中的持久化和查询基线,并已把 SuperAgent V4 入站结果写入新表:普通业务包创建 V4 订单任务、来源邮件展示卡、Basic Information 卡和业务卡V4 S10/S99 创建来源通知。当前已开放 V4 工作台、订单任务列表 / 详情、来源通知详情查询接口、订单详情 V4 订单任务时间线、V4 卡片确认接口、S10/S99 来源通知 ack 接口、V4 `REVIEW_REQUIRED` 卡复核解阻接口、V4 订单任务 / 来源通知审计查询接口、当前酒店数据库目录校验、卡片 `fields[]` 白名单、Account / Room Type / Rate Code lookup API、目录管理后台 CP1以及订单列表 V4 继续处理入口字段。真实 PMS 同步继续后置,设计见 `M002-v4-real-catalog-lookup-api-design.md`
截至 CP14V4 业务审计查询和停止旧任务双写补齐,后端已实现本文第 10、11、12 节中的持久化和查询基线,并已把 SuperAgent V4 入站结果写入新表:普通业务包创建 V4 订单任务、来源邮件展示卡、Basic Information 卡和业务卡,不再创建旧 `workflow_reservation_task`V4 S10/S99 创建来源通知。当前已开放 V4 工作台、订单任务列表 / 详情、来源通知详情查询接口、订单详情 V4 订单任务时间线、V4 卡片确认接口、S10/S99 来源通知 ack 接口、V4 `REVIEW_REQUIRED` 卡复核解阻接口、V4 订单任务 / 来源通知审计查询接口、当前酒店数据库目录校验、卡片 `fields[]` 白名单、Account / Room Type / Rate Code lookup API、目录管理后台 CP1以及订单列表 V4 继续处理入口字段。真实 PMS 同步继续后置,设计见 `M002-v4-real-catalog-lookup-api-design.md`
后续如本文与 `M002-v4-agent-callback-field-contract.md` 的字段契约冲突,以字段契约为准;如与安全边界冲突,以 `security-access-control-boundary.md` 为准。
@@ -27,7 +27,7 @@ M002 V4 CP1 已完成 SuperAgent V4 回调包入站解析、基础校验、路
| 入站识别 | 已识别 `route_code``source_message``order_contexts[]``message_events[]` | CP4 已把有合法 event 的 `order_ref` 建成订单任务聚合CP5 已开放 V4 安全查询接口 |
| SourceMessage | 已按 `source_message.source_message_id` 反查 SourceMessage Inbox | CP4 已固定生成普通业务包内邮件展示卡;邮件正文完整读取仍走 SourceMessage 会话接口 |
| Basic Information | 已写入 V4 Basic Information 独立卡 | CP6 已支持确认并锁定CP7 已支持复核解阻CP8 已支持目录校验和 `fields[]` 白名单CP11 已改为按当前酒店数据库 Account 目录校验并派生 Market / Source |
| 业务 Event | 可映射 event 临时创建旧 `workflow_reservation_task`,并已额外创建 V4 业务卡 | 旧任务链路仍作前端过渡兼容,后续 V4 查询和写接口完成后再逐步废弃 |
| 业务 Event | V4 新业务主线已只创建 V4 order task / cards不再双写旧 `workflow_reservation_task` | 旧 V2/V3 入站代码仍可作为历史参考保留,但开发阶段不维护旧任务兼容,测试数据可重建 |
| 技术错误 | 已落 `adapter_contract_error` transition | 已符合目标方向:不创建用户可处理卡 |
| 草稿 / READY / OPERA | 仍复用 V3 草稿、READY 和 OPERA 模拟骨架 | V4 新数据确认口径是不保存草稿、确认后锁定、当前不生成 OPERA |
| 前端查询 | 复用旧任务列表和任务详情 | CP5 已开放 V4 工作台、订单任务列表 / 详情、来源通知详情和订单详情 V4 时间线 |
@@ -661,7 +661,15 @@ GET /api/reservation/orders
- Basic 已确认后,业务卡中 `REVIEW_REQUIRED` 优先于普通 `PENDING_CONFIRM`
- `next_v4_action_type=CONFIRM` 时前端调用卡片确认接口;`REVIEW` 时调用复核解阻接口;`NONE` 表示该订单没有 V4 待处理入口。
前端订单列表“继续处理”应优先使用 `next_v4_order_task_id` 跳转 V4 订单任务详情;没有 V4 待处理入口时,再回退旧 `next_processable_task_id`
前端订单列表“继续处理”应优先使用 `next_v4_order_task_id` 跳转 V4 订单任务详情;没有 V4 待处理入口时,开发阶段通常不再回退旧任务入口。`next_processable_task_id` 仅作为历史 V2/V3 诊断兼容字段保留,测试数据清理后新 V4 订单不应返回该字段
开发 / 测试阶段旧任务口径:
- V4 普通业务入站不再创建旧 `workflow_reservation_task`,也不再创建旧 `workflow_reservation_task_card`、旧任务草稿、旧 OPERA 模拟操作。
- `open_task_count` 仍表示旧任务表原始未关闭诊断计数;开发 / 测试环境应通过专项 SQL 清理旧任务及其直接依赖数据,清理后新 V4 订单列表中该值应为 0。
- `next_processable_task_id` 仍按旧 V2/V3 队列实时计算,但开发阶段不维护旧任务兼容;清理旧任务后,新 V4 订单不应再返回旧任务入口。
- 开发阶段不维护 V2/V3 旧任务兼容,测试数据可重建。
- 该策略仅限开发 / 测试阶段,不代表生产迁移方案;生产数据迁移策略不在本 checkpoint 处理,后续上线前另开迁移方案。
### 12.7 V4 业务审计查询
@@ -749,7 +757,7 @@ S10/S99 已确认采用来源通知模型,不继续复用隐藏技术订单或
## 14. 旧 V3 / V2 兼容和废弃边界
开发阶段已确认可以清空数据,因此后续 V4 开发可以不迁移历史任务数据。
开发阶段已确认可以清空数据,因此后续 V4 开发可以不迁移历史任务数据。V4 后新业务主线只写 V4 模型;开发阶段不维护 V2/V3 旧任务兼容,测试数据可重建。该策略仅限开发 / 测试阶段,不代表生产迁移方案;生产数据迁移策略不在本 checkpoint 处理,后续上线前另开迁移方案。
### 14.1 可继续保留
@@ -776,7 +784,7 @@ S10/S99 已确认采用来源通知模型,不继续复用隐藏技术订单或
- `workflow_reservation_task` 作为 V4 多卡主模型的直接承载方式。
- 旧 V2/V3 任务详情、草稿保存和最终确认接口,待 V4 新模型落地并完成前端切换后逐步废弃。
废弃前必须先确认前端、MCP、SuperAgent 联调方和测试 fixture 都已切到 V4。
开发 / 测试环境旧任务清理参考 `../operations/dev-test-v4-legacy-task-cleanup.md`。废弃或删除生产历史链路前,必须先确认前端、MCP、SuperAgent 联调方和测试 fixture 都已切到 V4,并单独制定生产迁移方案
## 15. 安全、权限和审计

View File

@@ -109,7 +109,7 @@
| `POST /api/ai-query/v1/object-detail` | `THIRD_PARTY_SUPERAGENT` | HMAC 鉴权 | 保持 HMAC返回最小必要上下文 | 记录请求 ID、client_id 和安全错误 |
| `POST /api/ai-query/v1/conversation-tasks` | `THIRD_PARTY_SUPERAGENT` | HMAC 鉴权 | 保持 HMAC不返回邮件原文 | 记录请求 ID、client_id 和安全错误 |
| `POST /api/ai-query/v1/conversation-source` | `THIRD_PARTY_SUPERAGENT` | HMAC 鉴权 | 保持 HMAC只按契约返回需要字段 | 记录请求 ID、client_id 和安全错误 |
| `POST /api/integrations/superagent/task-results` | `THIRD_PARTY_SUPERAGENT` | HMAC + nonce + timestamp + body hash | 保持V4 / V3 / V2 共存期均必须用外部 `source_message_id` 匹配 Inbox技术契约错误只落 adapter error不创建用户可处理任务V4 普通业务包额外写入 V4 订单任务 / 多卡模型V4 S10/S99 写入来源通知且不创建旧任务 | 记录 batch、transition、adapter error、幂等结果和安全错误 |
| `POST /api/integrations/superagent/task-results` | `THIRD_PARTY_SUPERAGENT` | HMAC + nonce + timestamp + body hash | 保持V4 / V3 / V2 共存期均必须用外部 `source_message_id` 匹配 Inbox技术契约错误只落 adapter error不创建用户可处理任务V4 普通业务包只写 V4 订单任务 / 多卡模型,不再创建旧 `workflow_reservation_task`V4 S10/S99 写入来源通知且不创建旧任务 | 记录 batch、transition、adapter error、幂等结果和安全错误 |
| `/mcp` | `THIRD_PARTY_MCP` | Bearer Token提交工具可独立开关 | 保持;工具级能力限制,不暴露无关接口 | 记录工具调用结果和业务入站结果 |
| AgentBus WebSocket | `THIRD_PARTY_AGENTBUS` | AgentBus Token + capture 开关 | 保持;只入 SourceMessage不直接建业务任务 | 记录 SourceMessage、payload hash 和 dispatch run |

View File

@@ -11,7 +11,7 @@ public record ReservationV4AcceptedEventDraft(
Integer sourceEventIndex,
/** 当前 event 对应的 AI transition ID。 */
Long aiTransitionId,
/** 旧过渡链路已经解析出的本地订单投影 IDV4 第一版复用该绑定。 */
/** 入站阶段解析出的本地订单投影 IDV4 订单任务复用该绑定。 */
Long orderId,
/** 目标订单业务类型,例如 GROUP、FIT。 */
String targetBookingType,

View File

@@ -8,16 +8,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* @param sourceEventIndex AI 原始事件序号
* @param arrayIndex 当前批次中的处理顺序
* @param aiTransitionId AI 过渡记录 ID
* @param routeCode M002 V3 稳定路由码S10/S99 直接使用外部路由码
* @param routeCode M002 V3 / V4 稳定路由码S10/S99 直接使用外部路由码
* @param systemProcessCategory 系统处理分类,用于区分业务任务、来源邮件通知和契约错误
* @param adapterErrorCode Adapter 契约错误代码,正常创建任务时为空
* @param orderId 关联订单 ID仅落契约错误时为空
* @param taskId 关联任务 ID仅落契约错误为空
* @param taskId 任务 IDV4 普通业务和契约错误场景为空
* @param systemTaskType 系统主任务类型
* @param taskCardType 任务卡类型
* @param taskStatus 任务状态;仅落契约错误为空
* @param taskStatus 任务状态;V4 普通业务和契约错误场景为空
* @param orderStatus 订单状态;仅落契约错误时为空
* @param executionOrder 同订单任务执行顺序;仅落契约错误为空
* @param executionOrder 同订单任务执行顺序;V4 普通业务和契约错误场景为空
*/
public record SuperAgentTaskResultItemResponse(
@JsonProperty("source_event_index")

View File

@@ -184,7 +184,7 @@ public class ReservationAiTaskIntakeServiceImpl implements ReservationAiTaskInta
}
/**
* 接收已通过 HMAC 鉴权的 SuperAgent 任务结果。先处理旧文本结果,再分流 V3 JSON 和 V2 JSON。
* 接收已通过 HMAC 鉴权的 SuperAgent 任务结果。先处理旧文本结果,再按 V4 / V3 / V2 JSON 契约分流
*/
@Override
@Transactional
@@ -540,7 +540,7 @@ public class ReservationAiTaskIntakeServiceImpl implements ReservationAiTaskInta
}
/**
* 接收 V4 业务根。第一版按 message_events[] 顺序派生现有任务卡event 契约错误只落 transition
* 接收 V4 业务根。V4 新主线只落 AI transition 和 V4 订单任务 / 多卡模型,不再双写旧任务表
*/
private SuperAgentTaskResultResponse acceptV4BusinessRoot(
JsonNode root,
@@ -640,9 +640,22 @@ public class ReservationAiTaskIntakeServiceImpl implements ReservationAiTaskInta
continue;
}
ObjectNode item = v4CallbackRouter.normalizeEventToTaskResultItem(root, event, route, arrayIndex);
SuperAgentTaskResultItemResponse responseItem = createItem(hotelId, sourceMessageId, batchId, item, arrayIndex, now);
responseItems.add(responseItem);
acceptedEvents.add(toV4AcceptedEvent(event, route, responseItem, arrayIndex));
V4BusinessTransitionCreation transitionCreation = createV4BusinessTransition(
hotelId,
sourceMessageId,
batchId,
item,
event,
route,
arrayIndex,
now);
responseItems.add(transitionCreation.responseItem());
acceptedEvents.add(toV4AcceptedEvent(
event,
route,
transitionCreation.transitionId(),
transitionCreation.orderId(),
arrayIndex));
}
v4TaskIntakeService.createBusinessPackageTasks(sourceMessage, batchId, root, acceptedEvents, now);
@@ -1097,19 +1110,20 @@ public class ReservationAiTaskIntakeServiceImpl implements ReservationAiTaskInta
}
/**
* 将已创建 transition 的 V4 event 转成新模型写入草稿,避免 V4 多卡服务反向依赖旧任务响应细节
* 将已创建 transition 的 V4 event 转成新模型写入草稿,避免 V4 多卡服务反向依赖旧任务模型
*/
private ReservationV4AcceptedEventDraft toV4AcceptedEvent(
JsonNode event,
ReservationAiRouteDefinition route,
SuperAgentTaskResultItemResponse responseItem,
Long aiTransitionId,
Long orderId,
int arrayIndex) {
JsonNode targetOrder = event.path("target_order");
return new ReservationV4AcceptedEventDraft(
requireText(textAt(event, "order_ref"), "order_ref", LENGTH_128),
arrayIndex,
longValue(responseItem.aiTransitionId()),
longValue(responseItem.orderId()),
aiTransitionId,
orderId,
optionalText(textAt(targetOrder, "booking_type"), "target_order.booking_type", LENGTH_32),
optionalText(textAt(targetOrder, "locator_type"), "target_order.locator_type", LENGTH_64),
optionalText(textAt(targetOrder, "locator_value"), "target_order.locator_value", LENGTH_128),
@@ -1120,6 +1134,140 @@ public class ReservationAiTaskIntakeServiceImpl implements ReservationAiTaskInta
);
}
/**
* 创建 V4 业务 event 的 AI transition并解析本地订单投影不创建旧 workflow_reservation_task。
*/
private V4BusinessTransitionCreation createV4BusinessTransition(
String hotelId,
Long sourceMessageId,
Long batchId,
JsonNode item,
JsonNode event,
ReservationAiRouteDefinition route,
int arrayIndex,
LocalDateTime now) {
Integer sourceEventIndex = requirePositiveInt(item, "source_event_index");
String catalogCode = requireText(textAt(item, "catalog_code"), "catalog_code", LENGTH_32);
String skillId = requireText(textAt(item, "skill_id"), "skill_id", LENGTH_128);
String resultType = requireText(textAt(item, "result_type"), "result_type", LENGTH_64);
String aiTaskType = requireText(textAt(item, "task_type"), "task_type", LENGTH_64);
String taskSubtype = optionalText(textAt(item, "task_subtype"), "task_subtype", LENGTH_128);
String routeCode = optionalText(textAt(item, "route_code"), "route_code", LENGTH_64);
String systemProcessCategory = optionalText(
textAt(item, "system_process_category"),
"system_process_category",
LENGTH_64);
if (systemProcessCategory == null) {
systemProcessCategory = route.systemProcessCategory().name();
}
JsonNode caseKeys = item.path("case_keys");
String currentOrHistory = optionalText(textAt(item, "current_or_history"), "current_or_history", LENGTH_32);
String groupCode = optionalText(textAt(caseKeys, "group_code"), "group_code", LENGTH_128);
String confirmationNumber = optionalText(textAt(caseKeys, "confirmation_number"), "confirmation_number", LENGTH_128);
String manualReasonCode = optionalText(textAt(item.path("manual_review"), "reason_code"), "manual_review.reason_code", LENGTH_128);
String itemPayloadJson = nodeJson(item);
String itemPayloadSha256 = sha256(itemPayloadJson);
String itemIdempotencyKey = sha256(ITEM_KEY_PREFIX
+ "|" + sourceMessageId
+ "|" + sourceEventIndex
+ "|" + arrayIndex
+ "|" + catalogCode
+ "|" + skillId
+ "|" + resultType
+ "|" + aiTaskType
+ "|" + nullToEmpty(taskSubtype)
+ "|" + itemPayloadSha256);
Long transitionId = workflowRepository.insertTransition(new ReservationAiTransitionDraft(
hotelId,
batchId,
sourceMessageId,
sourceEventIndex,
arrayIndex,
arrayIndex,
catalogCode,
skillId,
resultType,
aiTaskType,
routeCode,
systemProcessCategory,
route.systemTaskType().name(),
route.taskCardType().name(),
taskSubtype,
currentOrHistory,
groupCode,
confirmationNumber,
itemPayloadSha256,
itemIdempotencyKey,
manualReasonCode,
null,
null,
null,
itemPayloadJson,
nodeJson(item.get("case_keys")),
nodeJson(item.get("extracted_fields")),
nodeJson(item.get("manual_review")),
null,
nodeJson(item.get("attachments")),
null,
null,
null,
now
));
ReservationOrderSnapshot order = resolveV4LocalOrder(hotelId, sourceMessageId, event, arrayIndex, now);
SuperAgentTaskResultItemResponse responseItem = new SuperAgentTaskResultItemResponse(
sourceEventIndex,
arrayIndex,
transitionId.toString(),
routeCode,
systemProcessCategory,
null,
order == null ? null : order.id().toString(),
null,
route.systemTaskType().name(),
route.taskCardType().name(),
null,
order == null ? null : order.orderStatus(),
null
);
return new V4BusinessTransitionCreation(
transitionId,
order == null ? null : order.id(),
responseItem);
}
/**
* V4 业务包只在当前能可靠定位 Group Code / Confirmation Number 时绑定本地订单。
*/
private ReservationOrderSnapshot resolveV4LocalOrder(
String hotelId,
Long sourceMessageId,
JsonNode event,
int arrayIndex,
LocalDateTime now) {
JsonNode targetOrder = event == null ? null : event.path("target_order");
String locatorType = trimToNull(textAt(targetOrder, "locator_type"));
String locatorValue = optionalText(textAt(targetOrder, "locator_value"), "target_order.locator_value", LENGTH_128);
BusinessKey businessKey = selectV4BusinessKey(locatorType, locatorValue);
if (businessKey == null) {
return null;
}
return workflowRepository.findActiveOrderByBusinessKey(hotelId, businessKey.keyType().name(), businessKey.value())
.orElseGet(() -> createActiveOrder(hotelId, sourceMessageId, businessKey, arrayIndex, now));
}
/**
* V4 第一版只把 Group Code 和 Confirmation Number 作为可自动落订单的业务号。
*/
private BusinessKey selectV4BusinessKey(String locatorType, String locatorValue) {
if ("GROUP_CODE".equals(locatorType) && hasText(locatorValue)) {
return new BusinessKey(ReservationOrderKeyType.GROUP_CODE, locatorValue);
}
if ("CONFIRMATION_NUMBER".equals(locatorType) && hasText(locatorValue)) {
return new BusinessKey(ReservationOrderKeyType.CONFIRMATION_NUMBER, locatorValue);
}
return null;
}
/**
* 生成 V4 业务卡 AI 原始片段,保留 event 原文和系统派生路由,不复制整封邮件正文。
*/
@@ -1623,7 +1771,7 @@ public class ReservationAiTaskIntakeServiceImpl implements ReservationAiTaskInta
true,
existingBatch.itemCount() == null ? 0 : existingBatch.itemCount(),
List.of(),
List.of(new SuperAgentTaskResultWarning("IDEMPOTENT_REPLAY", "相同请求已经处理,本次未重复创建任务"))
List.of(new SuperAgentTaskResultWarning("IDEMPOTENT_REPLAY", "相同请求已经处理,本次未重复创建业务对象"))
);
}
@@ -3011,6 +3159,16 @@ public class ReservationAiTaskIntakeServiceImpl implements ReservationAiTaskInta
) {
}
/**
* V4 业务 event 的 transition 写入结果V4 新主线不再携带旧 task ID。
*/
private record V4BusinessTransitionCreation(
Long transitionId,
Long orderId,
SuperAgentTaskResultItemResponse responseItem
) {
}
/**
* 任务创建结果,包含任务 ID 和最终写入的同订单执行序号。
*/

View File

@@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* V4 SuperAgent 回调路由器。负责把 V4 event 映射到当前系统稳定 route 和旧任务创建链路 item。
* V4 SuperAgent 回调路由器。负责把 V4 event 映射到当前系统稳定 route 和入站 transition item。
*/
final class ReservationAiV4CallbackRouter {
@@ -41,7 +41,7 @@ final class ReservationAiV4CallbackRouter {
}
/**
* 将 V4 event 归一为当前任务创建链路可消费的 item并保留 V4 原始结构。
* 将 V4 event 归一为当前入站 transition 可消费的 item并保留 V4 原始结构。
*/
ObjectNode normalizeEventToTaskResultItem(
JsonNode root,
@@ -70,7 +70,7 @@ final class ReservationAiV4CallbackRouter {
}
/**
* 将 V4 target_order 映射为旧任务链路可识别的 case_keys同时保留 Booking Code。
* 将 V4 target_order 映射为 transition 查询摘要可识别的 case_keys同时保留 Booking Code。
*/
ObjectNode buildCaseKeys(JsonNode targetOrder) {
ObjectNode caseKeys = objectMapper.createObjectNode();

View File

@@ -502,7 +502,7 @@ public class ReservationDemoDataServiceImpl implements ReservationDemoDataServic
}
/**
* 调用 AI 入站服务创建订单、任务和任务卡
* 调用历史 AI 入站服务创建 V2/V3 演示订单、任务和任务卡V4 smoke 不使用该入口造数
*/
private SuperAgentTaskResultResponse intake(String rawBody, String demoRunId, String scenarioCode) {
SuperAgentTaskResultResponse response = intakeService.accept(

View File

@@ -877,6 +877,8 @@ class ReservationFrontendQueryControllerTest {
.value(contains(0)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].next_v4_order_task_id")
.value(contains(nullValue())))
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].next_processable_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")

View File

@@ -990,7 +990,7 @@ class SuperAgentTaskResultControllerTest {
}
@Test
void shouldCreateBusinessTasksFromV4OrderContextsAndMessageEventsInOrder() throws Exception {
void shouldCreateV4OrderTaskAndCardsWithoutLegacyTaskDualWrite() throws Exception {
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-business-root-001");
String body = v4BusinessRootBody("mail-v4-business-root-001");
@@ -1003,13 +1003,17 @@ class SuperAgentTaskResultControllerTest {
.andExpect(jsonPath("$.items[0].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
.andExpect(jsonPath("$.items[0].system_task_type").value("NEW_BOOKING"))
.andExpect(jsonPath("$.items[0].task_card_type").value("NEW_BOOKING"))
.andExpect(jsonPath("$.items[0].execution_order").value(1))
.andExpect(jsonPath("$.items[0].task_id").doesNotExist())
.andExpect(jsonPath("$.items[0].task_status").doesNotExist())
.andExpect(jsonPath("$.items[0].execution_order").doesNotExist())
.andExpect(jsonPath("$.items[1].source_event_index").value(2))
.andExpect(jsonPath("$.items[1].array_index").value(2))
.andExpect(jsonPath("$.items[1].route_code").value("R10_PAYMENT_EVIDENCE_NORMAL"))
.andExpect(jsonPath("$.items[1].system_task_type").value("UPDATE_BOOKING"))
.andExpect(jsonPath("$.items[1].task_card_type").value("PAYMENT_EVIDENCE"))
.andExpect(jsonPath("$.items[1].execution_order").value(2))
.andExpect(jsonPath("$.items[1].task_id").doesNotExist())
.andExpect(jsonPath("$.items[1].task_status").doesNotExist())
.andExpect(jsonPath("$.items[1].execution_order").doesNotExist())
.andReturn();
String firstOrderId = com.jayway.jsonpath.JsonPath.read(
@@ -1069,6 +1073,12 @@ class SuperAgentTaskResultControllerTest {
)
""", Long.class, source.inboxId());
assertThat(v4CardStatusCount).isEqualTo(4L);
Long legacyTaskCount = jdbcTemplate.queryForObject("""
SELECT COUNT(*)
FROM workflow_reservation_task
WHERE source_message_id = ?
""", Long.class, source.inboxId());
assertThat(legacyTaskCount).isZero();
String sourceDisplayPayload = jdbcTemplate.queryForObject("""
SELECT display_payload_json
@@ -1115,8 +1125,8 @@ class SuperAgentTaskResultControllerTest {
mockMvc.perform(signedPost(body, "nonce-v4-two-order-refs-001"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.accepted_count").value(2))
.andExpect(jsonPath("$.items[0].execution_order").value(1))
.andExpect(jsonPath("$.items[1].execution_order").value(1));
.andExpect(jsonPath("$.items[0].execution_order").doesNotExist())
.andExpect(jsonPath("$.items[1].execution_order").doesNotExist());
List<String> orderRefs = jdbcTemplate.queryForList("""
SELECT order_ref
@@ -1148,7 +1158,8 @@ class SuperAgentTaskResultControllerTest {
mockMvc.perform(signedPost(body, "nonce-v4-fit-booking-code-001"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.accepted_count").value(1))
.andExpect(jsonPath("$.items[0].order_status").value("TEMPORARY"));
.andExpect(jsonPath("$.items[0].order_id").doesNotExist())
.andExpect(jsonPath("$.items[0].order_status").doesNotExist());
Long unresolvedCount = jdbcTemplate.queryForObject("""
SELECT COUNT(*)
@@ -1320,15 +1331,15 @@ class SuperAgentTaskResultControllerTest {
.andExpect(jsonPath("$.items[0].route_code").value("R06_CANCEL_GROUP_BLOCK_NORMAL"))
.andExpect(jsonPath("$.items[0].system_task_type").value("CANCEL_BOOKING"))
.andExpect(jsonPath("$.items[0].task_card_type").value("CANCEL_BOOKING"))
.andExpect(jsonPath("$.items[0].execution_order").value(1))
.andExpect(jsonPath("$.items[0].execution_order").doesNotExist())
.andExpect(jsonPath("$.items[1].route_code").value("R14_GENERAL_REQUEST_NORMAL"))
.andExpect(jsonPath("$.items[1].system_task_type").value("UPDATE_BOOKING"))
.andExpect(jsonPath("$.items[1].task_card_type").value("TRACE_RESERVATION_NOTES"))
.andExpect(jsonPath("$.items[1].execution_order").value(2))
.andExpect(jsonPath("$.items[1].execution_order").doesNotExist())
.andExpect(jsonPath("$.items[2].route_code").value("R11_ROOMING_LIST_NORMAL"))
.andExpect(jsonPath("$.items[2].system_task_type").value("UPDATE_BOOKING"))
.andExpect(jsonPath("$.items[2].task_card_type").value("ROOMING_LIST"))
.andExpect(jsonPath("$.items[2].execution_order").value(3))
.andExpect(jsonPath("$.items[2].execution_order").doesNotExist())
.andReturn();
String firstOrderId = com.jayway.jsonpath.JsonPath.read(
@@ -1554,8 +1565,8 @@ class SuperAgentTaskResultControllerTest {
.andExpect(jsonPath("$.items[0].adapter_error_code").value("PAYMENT_ATTACHMENT_ID_NOT_FOUND"))
.andExpect(jsonPath("$.items[0].task_id").doesNotExist())
.andExpect(jsonPath("$.items[1].route_code").value("R06_CANCEL_GROUP_BLOCK_NORMAL"))
.andExpect(jsonPath("$.items[1].task_id").exists())
.andExpect(jsonPath("$.items[1].execution_order").value(1));
.andExpect(jsonPath("$.items[1].task_id").doesNotExist())
.andExpect(jsonPath("$.items[1].execution_order").doesNotExist());
Long taskCount = jdbcTemplate.queryForObject("""
SELECT COUNT(*)
@@ -1569,7 +1580,7 @@ class SuperAgentTaskResultControllerTest {
AND system_process_category = 'ADAPTER_CONTRACT_ERROR'
AND adapter_error_code = 'PAYMENT_ATTACHMENT_ID_NOT_FOUND'
""", Long.class, source.inboxId());
assertThat(taskCount).isEqualTo(1L);
assertThat(taskCount).isZero();
assertThat(adapterErrorCount).isEqualTo(1L);
List<String> v4CardTypes = jdbcTemplate.queryForList("""