实现V4 Payment附件预览前端
This commit is contained in:
@@ -12,6 +12,7 @@ import type {
|
||||
ReservationV4SourceNotificationDetailResult,
|
||||
ReservationV4TaskCardResult,
|
||||
SourceMessageConversationResult,
|
||||
SourceMessageOriginalMedia,
|
||||
} from '@/types/reservation'
|
||||
import ReservationV4OrderTaskDetailView from '@/views/reservation/ReservationV4OrderTaskDetailView.vue'
|
||||
import ReservationV4SourceNotificationDetailView from '@/views/reservation/ReservationV4SourceNotificationDetailView.vue'
|
||||
@@ -287,6 +288,104 @@ describe('reservation V4 pages', () => {
|
||||
expect(wrapper.text()).not.toContain('RAW_HTML_SHOULD_NOT_RENDER')
|
||||
})
|
||||
|
||||
it('renders Payment attachments with image preview and non-image download from current source message media', async () => {
|
||||
const detail = createOrderTaskDetail()
|
||||
usePaymentBusinessCard(detail)
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
|
||||
vi.mocked(service.fetchSourceMessageConversation).mockResolvedValue(createSourceMessageConversationResult({
|
||||
attachments: createPaymentConversationMedia(),
|
||||
}))
|
||||
|
||||
const wrapper = await mountWithPlugins(
|
||||
ReservationV4OrderTaskDetailView,
|
||||
'/reservation/order-tasks/9001',
|
||||
)
|
||||
await flushPromises()
|
||||
|
||||
expect(service.fetchSourceMessageConversation).toHaveBeenCalledWith('30001')
|
||||
const attachmentPreview = wrapper.find('[data-testid="payment-attachment-preview"]')
|
||||
expect(attachmentPreview.exists()).toBe(true)
|
||||
expect(attachmentPreview.text()).toContain('voucher-image.jpg')
|
||||
expect(attachmentPreview.text()).toContain('voucher-document.pdf')
|
||||
expect(attachmentPreview.text()).toContain(zhCN.taskV4.paymentAttachments.noMatchedMedia)
|
||||
expect(attachmentPreview.text()).not.toContain('https://oss.example')
|
||||
|
||||
expect(attachmentPreview.findAll('[data-testid="payment-attachment-thumbnail"]')).toHaveLength(1)
|
||||
const thumbnail = attachmentPreview.find('[data-testid="payment-attachment-thumbnail"]')
|
||||
expect(thumbnail.exists()).toBe(true)
|
||||
expect(thumbnail.find('img').attributes('src')).toBe('https://oss.example/private/payment-image.jpg')
|
||||
await thumbnail.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
const modal = wrapper.find('[data-testid="payment-attachment-modal"]')
|
||||
expect(modal.exists()).toBe(true)
|
||||
expect(modal.find('img').attributes('src')).toBe('https://oss.example/private/payment-image.jpg')
|
||||
expect(wrapper.text()).not.toContain('https://oss.example/private/payment-image.jpg')
|
||||
|
||||
const downloadLink = attachmentPreview.find('[data-testid="payment-attachment-download"]')
|
||||
expect(downloadLink.exists()).toBe(true)
|
||||
expect(downloadLink.attributes('href')).toBe('https://oss.example/private/payment-document.pdf')
|
||||
})
|
||||
|
||||
it('degrades Payment attachment preview when conversation media cannot be loaded', async () => {
|
||||
const detail = createOrderTaskDetail()
|
||||
usePaymentBusinessCard(detail)
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
|
||||
vi.mocked(service.fetchSourceMessageConversation).mockRejectedValue(new ApiError('Forbidden', 403, {
|
||||
code: 'SOURCE_MESSAGE_ORIGINAL_READ_FORBIDDEN',
|
||||
message: 'Forbidden',
|
||||
}))
|
||||
|
||||
const wrapper = await mountWithPlugins(
|
||||
ReservationV4OrderTaskDetailView,
|
||||
'/reservation/order-tasks/9001',
|
||||
)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('voucher-image.jpg')
|
||||
expect(wrapper.text()).toContain(zhCN.taskV4.paymentAttachments.loadFailed)
|
||||
expect(wrapper.find('[data-testid="payment-attachment-thumbnail"]').exists()).toBe(false)
|
||||
expect(wrapper.find('[data-testid="payment-attachment-download"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('confirms Payment cards with version only and never submits attachment ids or attachment URLs', async () => {
|
||||
const detail = createOrderTaskDetail()
|
||||
usePaymentBusinessCard(detail)
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
|
||||
vi.mocked(service.fetchSourceMessageConversation).mockResolvedValue(createSourceMessageConversationResult({
|
||||
attachments: createPaymentConversationMedia(),
|
||||
}))
|
||||
vi.mocked(service.confirmReservationV4OrderTaskCard).mockResolvedValue({
|
||||
...detail,
|
||||
business_cards: [
|
||||
{
|
||||
...detail.business_cards[0]!,
|
||||
card_status: 'CONFIRMED',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const wrapper = await mountWithPlugins(
|
||||
ReservationV4OrderTaskDetailView,
|
||||
'/reservation/order-tasks/9001',
|
||||
)
|
||||
await flushPromises()
|
||||
|
||||
const paymentCard = wrapper
|
||||
.findAll('.task-card-section')
|
||||
.find((section) => section.text().includes(zhCN.cardType.PAYMENT))
|
||||
expect(paymentCard).toBeTruthy()
|
||||
await paymentCard!.find('button.primary-button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(service.confirmReservationV4OrderTaskCard).toHaveBeenCalledWith('9001', 'card-payment', {
|
||||
version: 9,
|
||||
})
|
||||
const submittedRequest = vi.mocked(service.confirmReservationV4OrderTaskCard).mock.calls[0]?.[2]
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('attachment_ids')
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('https://oss.example')
|
||||
})
|
||||
|
||||
it('renders New Booking Room Information as a business form and confirms stable final values', async () => {
|
||||
const detail = createOrderTaskDetail({
|
||||
businessEventType: 'NEW_BOOKING',
|
||||
@@ -1131,6 +1230,7 @@ function createSourceMessageConversationResult(options: {
|
||||
sanitizedHtml?: string | null
|
||||
rawHtml?: string | null
|
||||
htmlRenderMode?: SourceMessageConversationResult['messages'][number]['html_render_mode']
|
||||
attachments?: SourceMessageOriginalMedia[]
|
||||
} = {}): SourceMessageConversationResult {
|
||||
const sourceMessageId = options.sourceMessageId ?? '30001'
|
||||
const currentBody = options.currentBody ?? 'Current trigger email body.'
|
||||
@@ -1157,7 +1257,7 @@ function createSourceMessageConversationResult(options: {
|
||||
html_body_sanitized: options.sanitizedHtml ?? null,
|
||||
html_render_mode: options.htmlRenderMode,
|
||||
received_at: '2026-07-08T03:00:00Z',
|
||||
attachments: [
|
||||
attachments: options.attachments ?? [
|
||||
{
|
||||
mediaType: 'ATTACHMENT',
|
||||
fileName: 'current-message.pdf',
|
||||
@@ -1200,6 +1300,95 @@ function createConversationMessage(
|
||||
}
|
||||
}
|
||||
|
||||
function usePaymentBusinessCard(detail: ReservationV4OrderTaskDetailResult): void {
|
||||
detail.business_cards = [
|
||||
createCard('card-payment', 'PAYMENT', 'PENDING_CONFIRM', {
|
||||
version: 9,
|
||||
display_payload: {
|
||||
payment_attachments: [
|
||||
{
|
||||
attachment_id: 'att-img-1',
|
||||
file_name: 'voucher-image.jpg',
|
||||
content_type: 'image/jpeg',
|
||||
size_bytes: 123456,
|
||||
is_image: true,
|
||||
preview_available: true,
|
||||
download_available: true,
|
||||
external_media_id: 'media-payment-image',
|
||||
},
|
||||
{
|
||||
attachment_id: 'att-pdf-1',
|
||||
file_name: 'voucher-document.pdf',
|
||||
content_type: 'application/pdf',
|
||||
size_bytes: 2048,
|
||||
is_image: false,
|
||||
preview_available: false,
|
||||
download_available: true,
|
||||
external_media_id: 'media-payment-document',
|
||||
},
|
||||
{
|
||||
attachment_id: 'att-missing-1',
|
||||
file_name: 'missing-voucher.jpg',
|
||||
content_type: 'image/jpeg',
|
||||
size_bytes: 4096,
|
||||
is_image: true,
|
||||
preview_available: false,
|
||||
download_available: false,
|
||||
external_media_id: 'media-payment-missing',
|
||||
unavailable_reason_code: 'MEDIA_NOT_AVAILABLE',
|
||||
},
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
createField('/payment/attachment_ids', {
|
||||
display_name: 'attachment_ids',
|
||||
value: ['att-img-1', 'att-pdf-1'],
|
||||
editable: true,
|
||||
raw_readonly: false,
|
||||
edit_scope: 'CONFIRM',
|
||||
write_target: 'CONFIRMED_PAYLOAD_JSON',
|
||||
}),
|
||||
createField('/payment/payment_note', {
|
||||
display_name: 'Payment Note',
|
||||
value: 'Should not submit for Payment confirm.',
|
||||
editable: true,
|
||||
edit_scope: 'CONFIRM',
|
||||
write_target: 'CONFIRMED_PAYLOAD_JSON',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
function createPaymentConversationMedia(): SourceMessageOriginalMedia[] {
|
||||
return [
|
||||
{
|
||||
mediaType: 'ATTACHMENT',
|
||||
fileName: 'voucher-image.jpg',
|
||||
contentType: 'image/jpeg',
|
||||
sizeBytes: 123456,
|
||||
externalUrl: 'https://oss.example/private/payment-image.jpg',
|
||||
externalMediaId: 'media-payment-image',
|
||||
},
|
||||
{
|
||||
mediaType: 'ATTACHMENT',
|
||||
fileName: 'voucher-document.pdf',
|
||||
contentType: 'application/pdf',
|
||||
sizeBytes: 2048,
|
||||
externalUrl: 'https://oss.example/private/payment-document.pdf',
|
||||
externalMediaId: 'media-payment-document',
|
||||
},
|
||||
{
|
||||
mediaType: 'ATTACHMENT',
|
||||
fileName: 'missing-voucher.jpg',
|
||||
contentType: 'image/jpeg',
|
||||
sizeBytes: 4096,
|
||||
externalUrl: 'https://oss.example/private/same-file-name-should-not-match.jpg',
|
||||
externalMediaId: 'media-different-same-file-name',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function createRoomInformationDisplayPayload(overrides: {
|
||||
event_type?: string
|
||||
booking_type?: string
|
||||
|
||||
Reference in New Issue
Block a user