diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index bad9b45..83f7889 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -5,14 +5,15 @@ | 最近更新 | 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 订单任务和 S10/S99 来源通知已补齐业务审计查询接口;后续可做前端订单详情 V4 化、测试机联调、真实 PMS / OPERA / OHIP 同步或 SuperAgent 目录供给方案 | +| 当前重点 | 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 目录供给方案 | ## 1. 当前 Checkpoint -- 名称:`M002-V4-order-detail-overview-backend` -- 状态:Done,订单详情接口已补齐 V4 订单总览数据:`GET /api/reservation/orders/{orderId}` 返回 `order_overview`、`next_v4_action`、`related_source_messages[]` 和 `v4_order_tasks[].cards[]`。`order_overview` 只从已确认 V4 卡片派生,未确认 AI 建议不作为订单事实;`next_v4_action` 复用订单列表下一步处理口径,前端仍跳 V4 订单任务详情处理。 -- 目标:支撑前端把订单详情页从旧任务处理页改成“订单总览 + 当前确认快照 + V4 任务时间线 + 下一步入口”。 -- 边界:本轮不改前端、不新增表、不改 V4 写操作、不接真实 OPERA / OHIP、不废弃旧 V2/V3 详情接口。 +- 名称:`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 总览的最新包。 ## 2. 当前优先级 @@ -35,12 +36,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 已完成前后端列表、新增、启用 / 停用闭环;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 来源通知 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 下一步订单任务、卡片、动作类型、动作状态、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 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 总览页并进行测试机联调,或推进真实 PMS / OPERA / OHIP 目录同步和 `workflow_reservation_catalog_sync_run` checkpoint。 +- 后续如继续做 M002 V4,可优先进行测试机联调,或推进真实 PMS / OPERA / OHIP 目录同步和 `workflow_reservation_catalog_sync_run` checkpoint。 - 后续新增重要功能时,优先在 `docs/project/requirements/` 或未来 `docs/specs/` 中形成 Spec,再实现代码。 - M010 后续如需预览、历史记录、OSS 下载、订单 / 任务预填或客户字段目录化,再单独开前后端 checkpoint。 - M011 CP4 暂不推进;当前停留在 CP3 边界,只增强 SuperAgent 输入,不直接落订单、任务或长期解析历史。后续如确实需要运营查询或长期追踪,再单独设计 Excel 解析批次 / 行级持久化表。 diff --git a/client/src/i18n/locales/en-US.ts b/client/src/i18n/locales/en-US.ts index 50fb3f2..e892be3 100644 --- a/client/src/i18n/locales/en-US.ts +++ b/client/src/i18n/locales/en-US.ts @@ -454,6 +454,43 @@ export default { taskQueue: 'Order task queue', v4TaskTimeline: 'V4 order task timeline', v4TaskTimelineEmpty: 'No V4 order tasks.', + v4TaskTimelineHint: 'Shows safe order-task and card summaries only. Complete actions in the V4 task detail page.', + v4OverviewTitle: 'Current V4 confirmed snapshot', + v4OverviewSubtitle: 'Derived from confirmed cards only, not from unconfirmed AI suggestions.', + v4OverviewEmpty: 'No confirmed V4 order snapshot yet.', + accountCode: 'Account Code', + accountName: 'Account Name', + marketCode: 'Market Code', + sourceCode: 'Source Code', + departureDate: 'Departure date', + rateCode: 'Rate Code', + roomItems: 'Confirmed room items', + latestConfirmedAt: 'Latest confirmed at', + traceCardStatus: 'Trace card status', + roomingListCardStatus: 'Rooming List card status', + paymentCardStatus: 'Payment card status', + nextV4ActionTitle: 'Next action', + nextV4ActionSubtitle: 'The order overview only links to the task page; it does not confirm, review, or edit cards.', + nextV4ActionType: 'Action type', + nextV4ActionStatus: 'Action status', + nextV4CardId: 'Card ID', + openOrderTaskCount: 'Open V4 tasks', + enterV4Task: 'Open task page', + nextActionConfirm: 'Confirm card', + nextActionReview: 'Manual review', + nextActionNone: 'No pending action', + nextV4ActionEmpty: 'No next V4 action.', + relatedSourceMessages: 'Related source messages', + relatedSourceMessagesHint: 'Shows headers and conversation summary only. Open the conversation page for full email content.', + relatedSourceMessagesEmpty: 'No related source messages.', + externalConversationId: 'External conversation ID', + latestActivityAt: 'Latest activity', + v4CardsEmpty: 'No card summaries.', + cardId: 'Card ID', + reviewStatus: 'Review status', + confirmedBy: 'Confirmed by', + confirmedAt: 'Confirmed at', + sourceEventIndex: 'Source event {index}', sourceMessage: 'Source message', businessKeySource: 'Business key source', updatedAt: 'Last updated', @@ -822,6 +859,12 @@ export default { INFORMATIONAL_MESSAGE: 'Message notification (legacy)', SOURCE_MESSAGE_REVIEW_NOTIFICATION: 'Source message notification', SOURCE_MESSAGE_ONLY: 'Source message only', + SOURCE_MESSAGE_DISPLAY: 'Source message display card', + SOURCE_MESSAGE_NOTIFICATION: 'Source message notification card', + BASIC_INFORMATION: 'Basic Information card', + ROOM_INFORMATION: 'Room Information card', + TRACE_RESERVATION_NOTES: 'Trace notes card', + PAYMENT: 'Payment card', MANUAL_REVIEW: 'Manual review', FALLBACK_REVIEW: 'Manual review', }, diff --git a/client/src/i18n/locales/th-TH.ts b/client/src/i18n/locales/th-TH.ts index 8037d11..2f7802b 100644 --- a/client/src/i18n/locales/th-TH.ts +++ b/client/src/i18n/locales/th-TH.ts @@ -454,6 +454,43 @@ export default { taskQueue: 'คิวงานของออเดอร์นี้', v4TaskTimeline: 'ไทม์ไลน์งานออเดอร์ V4', v4TaskTimelineEmpty: 'ยังไม่มีงานออเดอร์ V4', + v4TaskTimelineHint: 'แสดงเฉพาะสรุปงานและการ์ดที่ปลอดภัย หากต้องดำเนินการให้เข้าไปที่หน้ารายละเอียดงาน V4', + v4OverviewTitle: 'สแนปช็อต V4 ที่ยืนยันแล้ว', + v4OverviewSubtitle: 'มาจากการ์ดที่ยืนยันแล้วเท่านั้น ไม่ใช้คำแนะนำ AI ที่ยังไม่ยืนยัน', + v4OverviewEmpty: 'ยังไม่มีสแนปช็อตออเดอร์ V4 ที่ยืนยันแล้ว', + accountCode: 'Account Code', + accountName: 'Account Name', + marketCode: 'Market Code', + sourceCode: 'Source Code', + departureDate: 'วันออก', + rateCode: 'Rate Code', + roomItems: 'ประเภทห้องและจำนวนที่ยืนยันแล้ว', + latestConfirmedAt: 'เวลายืนยันล่าสุด', + traceCardStatus: 'สถานะการ์ด Trace', + roomingListCardStatus: 'สถานะการ์ด Rooming List', + paymentCardStatus: 'สถานะการ์ด Payment', + nextV4ActionTitle: 'ขั้นตอนถัดไป', + nextV4ActionSubtitle: 'หน้าสรุปออเดอร์เป็นทางเข้าเท่านั้น ไม่ยืนยัน ตรวจสอบ หรือแก้ไขการ์ดในหน้านี้', + nextV4ActionType: 'ประเภทการดำเนินการ', + nextV4ActionStatus: 'สถานะการดำเนินการ', + nextV4CardId: 'รหัสการ์ด', + openOrderTaskCount: 'งาน V4 ที่ยังไม่เสร็จ', + enterV4Task: 'ไปหน้าดำเนินการ', + nextActionConfirm: 'ยืนยันการ์ด', + nextActionReview: 'ตรวจสอบด้วยคน', + nextActionNone: 'ไม่มีงานรอดำเนินการ', + nextV4ActionEmpty: 'ยังไม่มีขั้นตอน V4 ถัดไป', + relatedSourceMessages: 'ข้อความต้นทางที่เกี่ยวข้อง', + relatedSourceMessagesHint: 'แสดงเฉพาะหัวอีเมลและสรุปเธรด หากต้องการอ่านทั้งหมดให้เปิดหน้าบทสนทนาอีเมล', + relatedSourceMessagesEmpty: 'ยังไม่มีข้อความต้นทางที่เกี่ยวข้อง', + externalConversationId: 'รหัสเธรดภายนอก', + latestActivityAt: 'กิจกรรมล่าสุด', + v4CardsEmpty: 'ยังไม่มีสรุปการ์ด', + cardId: 'รหัสการ์ด', + reviewStatus: 'สถานะตรวจสอบ', + confirmedBy: 'ผู้ยืนยัน', + confirmedAt: 'เวลายืนยัน', + sourceEventIndex: 'เหตุการณ์ต้นทาง {index}', sourceMessage: 'ข้อความต้นทาง', businessKeySource: 'แหล่งที่มาของเลขธุรกิจ', updatedAt: 'อัปเดตล่าสุด', @@ -822,6 +859,12 @@ export default { INFORMATIONAL_MESSAGE: 'แจ้งเตือนข้อความ (เดิม)', SOURCE_MESSAGE_REVIEW_NOTIFICATION: 'แจ้งเตือนข้อความต้นทาง', SOURCE_MESSAGE_ONLY: 'ข้อความต้นทางแบบอ่านอย่างเดียว', + SOURCE_MESSAGE_DISPLAY: 'การ์ดแสดงข้อความต้นทาง', + SOURCE_MESSAGE_NOTIFICATION: 'การ์ดแจ้งเตือนข้อความต้นทาง', + BASIC_INFORMATION: 'การ์ดข้อมูลพื้นฐาน', + ROOM_INFORMATION: 'การ์ดข้อมูลห้อง', + TRACE_RESERVATION_NOTES: 'การ์ดหมายเหตุติดตาม', + PAYMENT: 'การ์ดการชำระเงิน', MANUAL_REVIEW: 'ตรวจสอบด้วยคน', FALLBACK_REVIEW: 'ตรวจสอบด้วยคน', }, diff --git a/client/src/i18n/locales/zh-CN.ts b/client/src/i18n/locales/zh-CN.ts index ba65784..82e15bf 100644 --- a/client/src/i18n/locales/zh-CN.ts +++ b/client/src/i18n/locales/zh-CN.ts @@ -454,6 +454,43 @@ export default { taskQueue: '同订单任务队列', v4TaskTimeline: 'V4 订单任务时间线', v4TaskTimelineEmpty: '暂无 V4 订单任务。', + v4TaskTimelineHint: '只展示订单任务和任务卡安全摘要;处理动作请进入 V4 任务详情页完成。', + v4OverviewTitle: 'V4 当前确认快照', + v4OverviewSubtitle: '仅来自已确认任务卡,不使用未确认 AI 建议。', + v4OverviewEmpty: '暂无已确认的 V4 订单快照。', + accountCode: 'Account 代码', + accountName: 'Account 名称', + marketCode: 'Market 代码', + sourceCode: 'Source 代码', + departureDate: '离店日期', + rateCode: 'Rate Code', + roomItems: '已确认房型房量', + latestConfirmedAt: '最近确认时间', + traceCardStatus: 'Trace 卡片状态', + roomingListCardStatus: 'Rooming List 卡片状态', + paymentCardStatus: 'Payment 卡片状态', + nextV4ActionTitle: '下一步处理', + nextV4ActionSubtitle: '订单总览只提供入口,不直接确认、复核或编辑任务卡。', + nextV4ActionType: '动作类型', + nextV4ActionStatus: '动作状态', + nextV4CardId: '任务卡编号', + openOrderTaskCount: '未完成 V4 任务数', + enterV4Task: '进入处理页', + nextActionConfirm: '确认任务卡', + nextActionReview: '人工复核', + nextActionNone: '无待处理动作', + nextV4ActionEmpty: '暂无下一步 V4 处理动作。', + relatedSourceMessages: '关联来源消息', + relatedSourceMessagesHint: '只展示邮件头和会话摘要;全文请进入邮件会话页查看。', + relatedSourceMessagesEmpty: '暂无关联来源消息。', + externalConversationId: '外部会话编号', + latestActivityAt: '最近活动时间', + v4CardsEmpty: '暂无任务卡摘要。', + cardId: '任务卡编号', + reviewStatus: '复核状态', + confirmedBy: '确认人', + confirmedAt: '确认时间', + sourceEventIndex: '来源事件 {index}', sourceMessage: '来源消息', businessKeySource: '业务号来源', updatedAt: '最近更新', @@ -822,6 +859,12 @@ export default { INFORMATIONAL_MESSAGE: '消息通知(历史 Message Notification)', SOURCE_MESSAGE_REVIEW_NOTIFICATION: '来源消息通知', SOURCE_MESSAGE_ONLY: '来源消息只读', + SOURCE_MESSAGE_DISPLAY: '来源消息展示卡', + SOURCE_MESSAGE_NOTIFICATION: '来源消息通知卡', + BASIC_INFORMATION: '基础信息卡', + ROOM_INFORMATION: '房型信息卡', + TRACE_RESERVATION_NOTES: '追踪备注卡', + PAYMENT: '付款信息卡', MANUAL_REVIEW: '人工复核', FALLBACK_REVIEW: '人工复核', }, diff --git a/client/src/tests/reservationViews.spec.ts b/client/src/tests/reservationViews.spec.ts index b66d3df..651ee8a 100644 --- a/client/src/tests/reservationViews.spec.ts +++ b/client/src/tests/reservationViews.spec.ts @@ -11,6 +11,7 @@ import ReservationSourceMessageConversationView from '@/views/reservation/Reserv import ReservationTaskListView from '@/views/reservation/ReservationTaskListView.vue' import { useAuthStore } from '@/stores/authStore' import type { + ReservationOrderDetailResult, ReservationOrderListResult, ReservationV4OrderTaskListResult, ReservationV4WorkbenchListResult, @@ -221,7 +222,7 @@ function createV4OrderTaskListResult( } } -function createOrderDetailResult(displayName = 'GRP-001') { +function createOrderDetailResult(displayName = 'GRP-001'): ReservationOrderDetailResult { return { order: { order_id: displayName === 'GRP-002' ? '20002' : '20001', @@ -286,6 +287,49 @@ function createOrderDetailResult(displayName = 'GRP-001') { review_required_count: 1, confirmed_count: 0, }, + cards: [ + { + card_id: '9100', + card_type: 'SOURCE_MESSAGE_DISPLAY', + event_type: null, + source_event_index: 0, + card_sort_order: 0, + card_status: 'READONLY', + review_status: null, + confirmed_by: null, + confirmed_at: null, + latest_activity_at: '2026-07-08T05:05:00Z', + ai_payload_json: 'unsafe ai payload must stay hidden', + display_payload_json: 'unsafe display payload must stay hidden', + confirmed_payload_json: 'unsafe confirmed payload must stay hidden', + text_body: 'unsafe mail body must stay hidden', + attachment_url: 'https://oss.example.test/private.xlsx', + }, + { + card_id: '9101', + card_type: 'BASIC_INFORMATION', + event_type: 'NEW_BOOKING', + source_event_index: 1, + card_sort_order: 10, + card_status: 'CONFIRMED', + review_status: null, + confirmed_by: 'frontend-query-admin', + confirmed_at: '2026-07-08T11:10:00Z', + latest_activity_at: '2026-07-08T11:10:00Z', + }, + { + card_id: '9104', + card_type: 'PAYMENT', + event_type: 'PAYMENT', + source_event_index: 3, + card_sort_order: 60, + card_status: 'REVIEW_REQUIRED', + review_status: 'PENDING', + confirmed_by: null, + confirmed_at: null, + latest_activity_at: '2026-07-08T11:20:00Z', + }, + ], source_message_summary: { source_message_id: '30003', hotel_id: 'HOTEL-TEST', @@ -303,8 +347,54 @@ function createOrderDetailResult(displayName = 'GRP-001') { latest_activity_at: '2026-07-08T05:10:00Z', }, ], + order_overview: { + account_code: 'QBD_TRAVEL', + account_name: 'Q.B.D. TRAVEL GROUP CO., LTD', + market_code: 'LEISURE', + source_code: 'TRAVEL_AGENT', + arrival_date: '2026-07-26', + departure_date: '2026-07-29', + rate_code: 'BAR', + room_items: [ + { + room_type_code: 'RM1', + room_count: 2, + }, + { + room_type_code: 'RM2', + room_count: 1, + }, + ], + trace_card_status: 'CONFIRMED', + rooming_list_card_status: 'PENDING_CONFIRM', + payment_card_status: 'REVIEW_REQUIRED', + latest_confirmed_at: '2026-07-08T11:20:00Z', + ai_payload_json: 'unsafe overview payload must stay hidden', + }, + next_v4_action: { + order_task_id: '9001', + card_id: '9104', + action_type: 'REVIEW', + action_status: 'REVIEW_REQUIRED', + open_order_task_count: 1, + }, + related_source_messages: [ + { + source_message_id: '30003', + hotel_id: 'HOTEL-TEST', + external_message_id: 'm-v4-1', + external_conversation_id: 'thread-v4-1', + subject: 'V4 Booking Package', + sender_summary: 'v4@example.test', + received_at: '2026-07-08T05:00:00Z', + source_sent_at: null, + conversation_message_count: 1, + html_body: 'unsafe related mail body', + attachment_url: 'https://oss.example.test/related.xlsx', + }, + ], warnings: [], - } + } as unknown as ReservationOrderDetailResult } function createSourceMessageConversationResult( @@ -480,28 +570,65 @@ describe('reservation P0 views', () => { vi.mocked(service.fetchSourceMessageConversation).mockReset() }) - it('renders active order task source evidence without the stale pending copy', async () => { + it('renders the V4 order overview, next action, related source messages and safe card timeline', async () => { vi.mocked(service.fetchReservationOrderDetail).mockResolvedValue(createOrderDetailResult()) - const wrapper = await mountWithPlugins(ReservationOrderDetailView, '/reservation/orders/20001', { - ReservationTaskDetailPanel: { - template: '', - }, - }) + const wrapper = await mountWithPlugins(ReservationOrderDetailView, '/reservation/orders/20001') await vi.dynamicImportSettled() - expect(wrapper.text()).toContain('Booking Request') - expect(wrapper.text()).toContain('guest@example.test') - expect(wrapper.text()).toContain('2') - expect(wrapper.text()).toContain('前序任务未完成') + expect(wrapper.text()).toContain('V4 当前确认快照') + expect(wrapper.text()).toContain('QBD_TRAVEL') + expect(wrapper.text()).toContain('Q.B.D. TRAVEL GROUP CO., LTD') + expect(wrapper.text()).toContain('2026-07-26') + expect(wrapper.text()).toContain('2026-07-29') + expect(wrapper.text()).toContain('RM1 × 2') + expect(wrapper.text()).toContain('下一步处理') + expect(wrapper.find('a[href="/reservation/order-tasks/9001"]').text()).toContain('进入处理页') + expect(wrapper.find('.primary-link[href="/reservation/order-tasks/9001"]').exists()).toBe(true) + expect(wrapper.text()).toContain('关联来源消息') + expect(wrapper.text()).toContain('v4@example.test') expect(wrapper.text()).toContain('查看邮件会话') + expect(wrapper.find('a[href="/reservation/source-messages/30003/conversation"]').exists()).toBe(true) expect(wrapper.text()).toContain(zhCN.order.v4TaskTimeline) expect(wrapper.text()).toContain('order-ref-1') expect(wrapper.text()).toContain('V4 Booking Package') - expect(wrapper.text()).not.toContain('PREVIOUS_TASK_NOT_FINISHED') + expect(wrapper.find('.v4-timeline-item__main[href="/reservation/order-tasks/9001"]').exists()).toBe(true) + expect(wrapper.text()).toContain('基础信息卡') + expect(wrapper.text()).toContain('frontend-query-admin') + expect(wrapper.text()).toContain('待复核') + expect(wrapper.text()).not.toContain('unsafe ai payload must stay hidden') + expect(wrapper.text()).not.toContain('unsafe display payload must stay hidden') + expect(wrapper.text()).not.toContain('unsafe confirmed payload must stay hidden') + expect(wrapper.text()).not.toContain('unsafe mail body must stay hidden') + expect(wrapper.text()).not.toContain('https://oss.example.test') + expect(wrapper.findComponent({ name: 'ReservationTaskDetailPanel' }).exists()).toBe(false) expect(wrapper.text()).not.toContain('来源邮件摘要字段待后端补充') }) + it('shows empty states when an order has no V4 overview, next action, source messages or task timeline', async () => { + const result = createOrderDetailResult() + result.order_overview = null + result.next_v4_action = { + order_task_id: null, + card_id: null, + action_type: 'NONE', + action_status: null, + open_order_task_count: 0, + } + result.related_source_messages = [] + result.v4_order_tasks = [] + vi.mocked(service.fetchReservationOrderDetail).mockResolvedValue(result) + + const wrapper = await mountWithPlugins(ReservationOrderDetailView, '/reservation/orders/20001') + await vi.dynamicImportSettled() + + expect(wrapper.text()).toContain('暂无已确认的 V4 订单快照') + expect(wrapper.text()).toContain('暂无下一步 V4 处理动作') + expect(wrapper.text()).toContain('暂无关联来源消息') + expect(wrapper.text()).toContain(zhCN.order.v4TaskTimelineEmpty) + expect(wrapper.find('a[href^="/reservation/order-tasks/"]').exists()).toBe(false) + }) + it('renders order list items returned by the backend', async () => { vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderListResult()) diff --git a/client/src/types/reservation.ts b/client/src/types/reservation.ts index 8c3fc7d..77b72d1 100644 --- a/client/src/types/reservation.ts +++ b/client/src/types/reservation.ts @@ -246,6 +246,36 @@ export interface ReservationV4CardCounts { confirmed_count: number } +export interface ReservationOrderV4RoomSummary { + room_type_code: string | null + room_count: number | null +} + +export interface ReservationOrderV4OverviewResult { + account_code: string | null + account_name: string | null + market_code: string | null + source_code: string | null + arrival_date: string | null + departure_date: string | null + rate_code: string | null + room_items: ReservationOrderV4RoomSummary[] + trace_card_status: ReservationV4CardStatus | string | null + rooming_list_card_status: ReservationV4CardStatus | string | null + payment_card_status: ReservationV4CardStatus | string | null + latest_confirmed_at: string | null +} + +export type ReservationOrderV4NextActionType = 'CONFIRM' | 'REVIEW' | 'NONE' | string + +export interface ReservationOrderV4NextActionResult { + order_task_id: string | null + card_id: string | null + action_type: ReservationOrderV4NextActionType | null + action_status: ReservationV4CardStatus | string | null + open_order_task_count: number | null +} + export interface ReservationV4ActionAvailability { blocked: boolean read_only: boolean @@ -453,6 +483,7 @@ export interface ReservationV4OrderTaskTimelineItem { order_ref: string | null order_task_status: ReservationV4OrderTaskStatus | string card_counts: ReservationV4CardCounts + cards: ReservationOrderV4TaskCardTimelineItem[] source_message_summary: ReservationV4SourceMessageSummary | null source_received_at: string | null created_at: string | null @@ -460,8 +491,24 @@ export interface ReservationV4OrderTaskTimelineItem { latest_activity_at: string | null } +export interface ReservationOrderV4TaskCardTimelineItem { + card_id: string + card_type: ReservationV4CardType | string + event_type: string | null + source_event_index: number | null + card_sort_order: number | null + card_status: ReservationV4CardStatus | string + review_status: string | null + confirmed_by: string | null + confirmed_at: string | null + latest_activity_at: string | null +} + export interface ReservationOrderDetailResult { order: ReservationOrderSummaryResult + order_overview: ReservationOrderV4OverviewResult | null + next_v4_action: ReservationOrderV4NextActionResult | null + related_source_messages: ReservationV4SourceMessageSummary[] tasks: ReservationOrderTaskTimelineItem[] v4_order_tasks: ReservationV4OrderTaskTimelineItem[] warnings: string[] diff --git a/client/src/views/reservation/ReservationOrderDetailView.vue b/client/src/views/reservation/ReservationOrderDetailView.vue index f440ec9..77d27d6 100644 --- a/client/src/views/reservation/ReservationOrderDetailView.vue +++ b/client/src/views/reservation/ReservationOrderDetailView.vue @@ -51,110 +51,290 @@ -