接入V4任务卡目录查询
This commit is contained in:
1 parent
8b37232ac9
commit
d808732107
16 files changed
+803
-38
No files matched your search
@@ -8,8 +8,11 @@ import {
|
||||
fetchReservationOrders,
|
||||
fetchReservationTaskDetail,
|
||||
fetchReservationTaskList,
|
||||
fetchReservationV4AccountLookups,
|
||||
fetchReservationV4OrderTaskDetail,
|
||||
fetchReservationV4OrderTasks,
|
||||
fetchReservationV4RateCodeLookups,
|
||||
fetchReservationV4RoomTypeLookups,
|
||||
fetchReservationV4SourceNotificationDetail,
|
||||
fetchReservationV4WorkbenchItems,
|
||||
fetchSourceMessageConversation,
|
||||
@@ -188,6 +191,85 @@ describe('reservationService real API mode', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('fetches V4 catalog lookups with selected hotel and query params', async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
.mockResolvedValueOnce(mockJsonResponse({
|
||||
hotel_id: 'HOTEL-BKK',
|
||||
catalog_type: 'ACCOUNT',
|
||||
catalog_source: 'SYSTEM_MANAGED',
|
||||
catalog_version: 'v1',
|
||||
stale: false,
|
||||
items: [],
|
||||
page: {
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
total: 0,
|
||||
},
|
||||
warnings: [],
|
||||
}))
|
||||
.mockResolvedValueOnce(mockJsonResponse({
|
||||
hotel_id: 'HOTEL-BKK',
|
||||
catalog_type: 'ROOM_TYPE',
|
||||
catalog_source: 'PMS_SYNC',
|
||||
catalog_version: 'v2',
|
||||
stale: true,
|
||||
items: [],
|
||||
page: {
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
total: 0,
|
||||
},
|
||||
warnings: ['PMS sync stale'],
|
||||
}))
|
||||
.mockResolvedValueOnce(mockJsonResponse({
|
||||
hotel_id: 'HOTEL-BKK',
|
||||
catalog_type: 'RATE_CODE',
|
||||
catalog_source: 'SYSTEM_MANAGED',
|
||||
catalog_version: 'v3',
|
||||
stale: false,
|
||||
items: [],
|
||||
page: {
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
total: 0,
|
||||
},
|
||||
warnings: [],
|
||||
}))
|
||||
setReservationHotelIdProvider(() => 'HOTEL-BKK')
|
||||
|
||||
await fetchReservationV4AccountLookups({
|
||||
keyword: 'QBD',
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
})
|
||||
await fetchReservationV4RoomTypeLookups({
|
||||
keyword: 'RM',
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
})
|
||||
await fetchReservationV4RateCodeLookups({
|
||||
keyword: 'BAR',
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
})
|
||||
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'/api/reservation/lookups/accounts?hotel_id=HOTEL-BKK&keyword=QBD&page_num=1&page_size=20',
|
||||
expect.objectContaining({ method: 'GET' }),
|
||||
)
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'/api/reservation/lookups/room-types?hotel_id=HOTEL-BKK&keyword=RM&page_num=1&page_size=20',
|
||||
expect.objectContaining({ method: 'GET' }),
|
||||
)
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'/api/reservation/lookups/rate-codes?hotel_id=HOTEL-BKK&keyword=BAR&page_num=1&page_size=20',
|
||||
expect.objectContaining({ method: 'GET' }),
|
||||
)
|
||||
})
|
||||
|
||||
it('fetches V4 order task and source notification details by id', async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
.mockResolvedValueOnce(mockJsonResponse({ order_task: { order_task_id: '9001' } }))
|
||||
@@ -482,6 +564,7 @@ describe('reservationService real API mode', () => {
|
||||
updated_at: '2026-07-08T03:10:00Z',
|
||||
},
|
||||
tasks: [],
|
||||
v4_order_tasks: [],
|
||||
warnings: [],
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
buildReservationV4ReviewOverrides,
|
||||
isReservationV4ConfirmWritableField,
|
||||
isReservationV4ReviewWritableField,
|
||||
reservationV4LookupKindForOptionsSource,
|
||||
stringifyReservationV4SafeDisplayValue,
|
||||
validateReservationV4Fields,
|
||||
} from '@/utils/reservationV4FieldRules'
|
||||
@@ -188,4 +189,11 @@ describe('reservationV4FieldRules', () => {
|
||||
'/basic_information/account_code': 'Account is required',
|
||||
})
|
||||
})
|
||||
|
||||
it('maps V4 options_source to lookup kinds without returning fixed catalog options', () => {
|
||||
expect(reservationV4LookupKindForOptionsSource('reservation_v4_account_catalog')).toBe('ACCOUNT')
|
||||
expect(reservationV4LookupKindForOptionsSource('RESERVATION_V4_ROOM_TYPE_CATALOG')).toBe('ROOM_TYPE')
|
||||
expect(reservationV4LookupKindForOptionsSource('reservation_v4_rate_code_catalog')).toBe('RATE_CODE')
|
||||
expect(reservationV4LookupKindForOptionsSource('static_enum')).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -20,7 +20,10 @@ vi.mock('@/services/reservationService', async (importOriginal) => {
|
||||
...actual,
|
||||
ackReservationV4SourceNotification: vi.fn(),
|
||||
confirmReservationV4OrderTaskCard: vi.fn(),
|
||||
fetchReservationV4AccountLookups: vi.fn(),
|
||||
fetchReservationV4OrderTaskDetail: vi.fn(),
|
||||
fetchReservationV4RateCodeLookups: vi.fn(),
|
||||
fetchReservationV4RoomTypeLookups: vi.fn(),
|
||||
fetchReservationV4SourceNotificationDetail: vi.fn(),
|
||||
resolveReservationV4OrderTaskCardReview: vi.fn(),
|
||||
}
|
||||
@@ -33,12 +36,16 @@ describe('reservation V4 pages', () => {
|
||||
sessionStorage.clear()
|
||||
vi.mocked(service.ackReservationV4SourceNotification).mockReset()
|
||||
vi.mocked(service.confirmReservationV4OrderTaskCard).mockReset()
|
||||
vi.mocked(service.fetchReservationV4AccountLookups).mockReset()
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockReset()
|
||||
vi.mocked(service.fetchReservationV4RateCodeLookups).mockReset()
|
||||
vi.mocked(service.fetchReservationV4RoomTypeLookups).mockReset()
|
||||
vi.mocked(service.fetchReservationV4SourceNotificationDetail).mockReset()
|
||||
vi.mocked(service.resolveReservationV4OrderTaskCardReview).mockReset()
|
||||
mockCatalogLookups()
|
||||
})
|
||||
|
||||
it('renders V4 order task cards and confirms only editable basic information fields', async () => {
|
||||
it('renders V4 order task cards, loads lookup options and confirms only selected codes', async () => {
|
||||
const detail = createOrderTaskDetail()
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
|
||||
vi.mocked(service.confirmReservationV4OrderTaskCard).mockResolvedValue({
|
||||
@@ -60,8 +67,21 @@ describe('reservation V4 pages', () => {
|
||||
expect(wrapper.text()).toContain('基础信息卡')
|
||||
expect(wrapper.text()).toContain('业务任务卡')
|
||||
expect(wrapper.text()).toContain('Backend contract issue')
|
||||
expect(service.fetchReservationV4AccountLookups).toHaveBeenCalledWith({
|
||||
hotel_id: 'HOTEL-TEST',
|
||||
page_num: 1,
|
||||
page_size: 100,
|
||||
})
|
||||
expect(service.fetchReservationV4RoomTypeLookups).toHaveBeenCalledWith({
|
||||
hotel_id: 'HOTEL-TEST',
|
||||
page_num: 1,
|
||||
page_size: 100,
|
||||
})
|
||||
|
||||
await wrapper.find('select').setValue('HANATOUR')
|
||||
await wrapper.find('select').setValue('ACC-LIVE')
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('LEISURE')
|
||||
expect(wrapper.text()).toContain('TRAVEL_AGENT')
|
||||
await wrapper.findAll('button').find((button) => button.text().includes('确认卡片'))?.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
@@ -69,7 +89,7 @@ describe('reservation V4 pages', () => {
|
||||
version: 7,
|
||||
confirmed_payload: {
|
||||
basic_information: {
|
||||
account_code: 'HANATOUR',
|
||||
account_code: 'ACC-LIVE',
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -153,6 +173,74 @@ describe('reservation V4 pages', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('shows non-blocking lookup empty, stale and warning states', async () => {
|
||||
const detail = createOrderTaskDetail()
|
||||
vi.mocked(service.fetchReservationV4AccountLookups).mockResolvedValue({
|
||||
hotel_id: 'HOTEL-TEST',
|
||||
catalog_type: 'ACCOUNT',
|
||||
catalog_source: 'PMS_SYNC',
|
||||
catalog_version: 'stale-v1',
|
||||
stale: true,
|
||||
items: [],
|
||||
page: {
|
||||
page_num: 1,
|
||||
page_size: 100,
|
||||
total: 0,
|
||||
},
|
||||
warnings: ['PMS sync is stale'],
|
||||
})
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
|
||||
|
||||
const wrapper = await mountWithPlugins(
|
||||
ReservationV4OrderTaskDetailView,
|
||||
'/reservation/order-tasks/9001',
|
||||
)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain(zhCN.taskV4.lookup.empty)
|
||||
expect(wrapper.text()).toContain(zhCN.taskV4.lookup.stale)
|
||||
expect(wrapper.text()).toContain('PMS sync is stale')
|
||||
expect(wrapper.text()).toContain('PMS_SYNC')
|
||||
expect(wrapper.text()).toContain('stale-v1')
|
||||
})
|
||||
|
||||
it('does not submit a catalog field value that is missing from the loaded lookup result', async () => {
|
||||
const detail = createOrderTaskDetail({
|
||||
basicAccountValue: 'UNKNOWN_ACCOUNT',
|
||||
basicAccountRequired: true,
|
||||
})
|
||||
vi.mocked(service.fetchReservationV4AccountLookups).mockResolvedValue({
|
||||
hotel_id: 'HOTEL-TEST',
|
||||
catalog_type: 'ACCOUNT',
|
||||
catalog_source: 'SYSTEM_MANAGED',
|
||||
catalog_version: 'catalog-v1',
|
||||
stale: false,
|
||||
items: [],
|
||||
page: {
|
||||
page_num: 1,
|
||||
page_size: 100,
|
||||
total: 0,
|
||||
},
|
||||
warnings: [],
|
||||
})
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
|
||||
|
||||
const wrapper = await mountWithPlugins(
|
||||
ReservationV4OrderTaskDetailView,
|
||||
'/reservation/order-tasks/9001',
|
||||
)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('UNKNOWN_ACCOUNT')
|
||||
expect(wrapper.text()).toContain(zhCN.taskV4.lookup.currentNotInCatalog.replace('{value}', 'UNKNOWN_ACCOUNT'))
|
||||
|
||||
await wrapper.findAll('button').find((button) => button.text().includes('确认卡片'))?.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(service.confirmReservationV4OrderTaskCard).not.toHaveBeenCalled()
|
||||
expect(wrapper.text()).toContain(zhCN.taskV4.validationFailed)
|
||||
})
|
||||
|
||||
it('requires confirmed order id before resolving an unresolved V4 review card', async () => {
|
||||
const detail = createOrderTaskDetail({
|
||||
businessCardStatus: 'REVIEW_REQUIRED',
|
||||
@@ -277,10 +365,83 @@ async function mountWithPlugins(component: object, initialPath: string) {
|
||||
})
|
||||
}
|
||||
|
||||
function mockCatalogLookups() {
|
||||
vi.mocked(service.fetchReservationV4AccountLookups).mockResolvedValue({
|
||||
hotel_id: 'HOTEL-TEST',
|
||||
catalog_type: 'ACCOUNT',
|
||||
catalog_source: 'SYSTEM_MANAGED',
|
||||
catalog_version: 'catalog-v1',
|
||||
stale: false,
|
||||
items: [
|
||||
{
|
||||
code: 'ACC-LIVE',
|
||||
display_name: 'Live Account',
|
||||
status: 'ACTIVE',
|
||||
catalog_source: 'SYSTEM_MANAGED',
|
||||
market_code: 'LEISURE',
|
||||
market_name: 'Leisure',
|
||||
source_code: 'TRAVEL_AGENT',
|
||||
source_name: 'Travel Agent',
|
||||
adult_capacity: null,
|
||||
pricing_available: null,
|
||||
},
|
||||
],
|
||||
page: {
|
||||
page_num: 1,
|
||||
page_size: 100,
|
||||
total: 1,
|
||||
},
|
||||
warnings: [],
|
||||
})
|
||||
vi.mocked(service.fetchReservationV4RoomTypeLookups).mockResolvedValue({
|
||||
hotel_id: 'HOTEL-TEST',
|
||||
catalog_type: 'ROOM_TYPE',
|
||||
catalog_source: 'SYSTEM_MANAGED',
|
||||
catalog_version: 'catalog-v1',
|
||||
stale: false,
|
||||
items: [
|
||||
{
|
||||
code: 'TWN',
|
||||
display_name: 'Twin',
|
||||
status: 'ACTIVE',
|
||||
catalog_source: 'SYSTEM_MANAGED',
|
||||
market_code: null,
|
||||
market_name: null,
|
||||
source_code: null,
|
||||
source_name: null,
|
||||
adult_capacity: 2,
|
||||
pricing_available: null,
|
||||
},
|
||||
],
|
||||
page: {
|
||||
page_num: 1,
|
||||
page_size: 100,
|
||||
total: 1,
|
||||
},
|
||||
warnings: [],
|
||||
})
|
||||
vi.mocked(service.fetchReservationV4RateCodeLookups).mockResolvedValue({
|
||||
hotel_id: 'HOTEL-TEST',
|
||||
catalog_type: 'RATE_CODE',
|
||||
catalog_source: 'SYSTEM_MANAGED',
|
||||
catalog_version: 'catalog-v1',
|
||||
stale: false,
|
||||
items: [],
|
||||
page: {
|
||||
page_num: 1,
|
||||
page_size: 100,
|
||||
total: 0,
|
||||
},
|
||||
warnings: [],
|
||||
})
|
||||
}
|
||||
|
||||
function createOrderTaskDetail(options: {
|
||||
businessCardStatus?: string
|
||||
businessCardAvailability?: Partial<ReservationV4TaskCardResult['availability']>
|
||||
sourceDisplayPayload?: ReservationV4TaskCardResult['display_payload']
|
||||
basicAccountValue?: string
|
||||
basicAccountRequired?: boolean
|
||||
} = {}): ReservationV4OrderTaskDetailResult {
|
||||
const sourceCard = createCard('card-source', 'SOURCE_MESSAGE_DISPLAY', 'READONLY', {
|
||||
fields: [],
|
||||
@@ -301,7 +462,8 @@ function createOrderTaskDetail(options: {
|
||||
fields: [
|
||||
createField('/basic_information/account_code', {
|
||||
display_name: 'Account',
|
||||
value: '',
|
||||
value: options.basicAccountValue ?? '',
|
||||
required: options.basicAccountRequired ?? false,
|
||||
options_source: 'RESERVATION_V4_ACCOUNT_CATALOG',
|
||||
control_type: 'SELECT',
|
||||
}),
|
||||
|
||||
@@ -270,6 +270,35 @@ function createOrderDetailResult(displayName = 'GRP-001') {
|
||||
created_at: '2026-07-08T04:00:00Z',
|
||||
},
|
||||
],
|
||||
v4_order_tasks: [
|
||||
{
|
||||
order_task_id: '9001',
|
||||
order_ref: 'order-ref-1',
|
||||
order_task_status: 'OPEN',
|
||||
card_counts: {
|
||||
total_count: 3,
|
||||
readonly_count: 1,
|
||||
pending_confirm_count: 1,
|
||||
review_required_count: 1,
|
||||
confirmed_count: 0,
|
||||
},
|
||||
source_message_summary: {
|
||||
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,
|
||||
},
|
||||
source_received_at: '2026-07-08T05:00:00Z',
|
||||
created_at: '2026-07-08T05:00:00Z',
|
||||
updated_at: '2026-07-08T05:10:00Z',
|
||||
latest_activity_at: '2026-07-08T05:10:00Z',
|
||||
},
|
||||
],
|
||||
warnings: [],
|
||||
}
|
||||
}
|
||||
@@ -461,6 +490,9 @@ describe('reservation P0 views', () => {
|
||||
expect(wrapper.text()).toContain('2')
|
||||
expect(wrapper.text()).toContain('前序任务未完成')
|
||||
expect(wrapper.text()).toContain('查看邮件会话')
|
||||
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.text()).not.toContain('来源邮件摘要字段待后端补充')
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user