实现V4订单详情总览页

This commit is contained in:
andy committed 2026-07-20 15:16:21 +07:00
1 parent e5218e10eb
commit d45ff5b4ed
10 files changed
+889 -240

No files matched your search

+140 -13
View File
@@ -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: '<strong>unsafe related mail body</strong>',
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: '<section />',
},
})
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())