实现V4 Trace事项卡前端展示
This commit is contained in:
@@ -104,7 +104,7 @@ describe('reservation V4 pages', () => {
|
||||
|
||||
it('keeps V4 task card layout classes for styled card metadata and payload summary', async () => {
|
||||
const detail = createOrderTaskDetail()
|
||||
detail.business_cards[0]!.card_type = 'TRACE_RESERVATION_NOTES'
|
||||
detail.business_cards[0]!.card_type = 'VOUCHER'
|
||||
detail.business_cards[0]!.display_payload = {
|
||||
booking_scenario: 'STANDARD',
|
||||
relevant_message_excerpt: 'Please keep this visible.',
|
||||
@@ -514,6 +514,178 @@ describe('reservation V4 pages', () => {
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('https://oss.example')
|
||||
})
|
||||
|
||||
it('renders GENERAL Trace cards with fixed department options and confirms safe trace fields only', async () => {
|
||||
const detail = createOrderTaskDetail()
|
||||
useTraceBusinessCard(detail, 'GENERAL')
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
|
||||
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 traceSection = wrapper
|
||||
.findAll('.task-card-section')
|
||||
.find((section) => section.find('[data-testid="trace-card"]').exists())
|
||||
expect(traceSection).toBeTruthy()
|
||||
expect((traceSection!.find('textarea[name="/trace_items/0/text"]').element as HTMLTextAreaElement).value)
|
||||
.toBe('Late arrival note')
|
||||
expect(traceSection!.text()).not.toContain('CONTENT_SHOULD_NOT_RENDER')
|
||||
expect(traceSection!.text()).not.toContain('TARGET_ORDER_SHOULD_NOT_RENDER')
|
||||
expect(traceSection!.text()).not.toContain('AI_PAYLOAD_SHOULD_NOT_RENDER')
|
||||
expect(traceSection!.text()).not.toContain('https://oss.example/private/trace.pdf')
|
||||
expect(traceSection!.find('details.safe-payload').exists()).toBe(false)
|
||||
|
||||
const departmentOptions = traceSection!
|
||||
.find('select[name="/trace_items/0/department_code"]')
|
||||
.findAll('option')
|
||||
.map((option) => option.attributes('value'))
|
||||
expect(departmentOptions).toEqual(['', 'FO', 'HSK', 'FO+HSK'])
|
||||
|
||||
await traceSection!.find('textarea[name="/trace_items/0/text"]').setValue('Arrange late arrival follow-up')
|
||||
await traceSection!.find('select[name="/trace_items/0/department_code"]').setValue('HSK')
|
||||
await traceSection!.find('button.primary-button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(service.confirmReservationV4OrderTaskCard).toHaveBeenCalledWith('9001', 'card-trace', {
|
||||
version: 10,
|
||||
confirmed_payload: {
|
||||
trace_items: [
|
||||
{
|
||||
text: 'Arrange late arrival follow-up',
|
||||
department_code: 'HSK',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const submittedRequest = vi.mocked(service.confirmReservationV4OrderTaskCard).mock.calls[0]?.[2]
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('content')
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('target_order')
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('AI_PAYLOAD_SHOULD_NOT_RENDER')
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('https://oss.example')
|
||||
})
|
||||
|
||||
it('renders EXTRA_BED Trace cards with room type lookup, positive count input and confirms safe fields only', async () => {
|
||||
const detail = createOrderTaskDetail()
|
||||
useTraceBusinessCard(detail, 'EXTRA_BED')
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
|
||||
vi.mocked(service.confirmReservationV4OrderTaskCard).mockResolvedValue(detail)
|
||||
|
||||
const wrapper = await mountWithPlugins(
|
||||
ReservationV4OrderTaskDetailView,
|
||||
'/reservation/order-tasks/9001',
|
||||
)
|
||||
await flushPromises()
|
||||
|
||||
const traceSection = wrapper
|
||||
.findAll('.task-card-section')
|
||||
.find((section) => section.find('[data-testid="trace-card"]').exists())
|
||||
expect(traceSection).toBeTruthy()
|
||||
expect(traceSection!.text()).toContain('SET EXTRA BED')
|
||||
expect(traceSection!.find('select[name="/trace_items/0/target_room_type_code"]').text()).toContain('TWN - Twin')
|
||||
const roomCountInput = traceSection!.find('input[name="/trace_items/0/extra_bed_room_count"]')
|
||||
expect(roomCountInput.attributes('type')).toBe('number')
|
||||
expect(roomCountInput.attributes('min')).toBe('1')
|
||||
expect(roomCountInput.attributes('step')).toBe('1')
|
||||
|
||||
await traceSection!.find('select[name="/trace_items/0/target_room_type_code"]').setValue('TWN')
|
||||
await roomCountInput.setValue('0')
|
||||
await traceSection!.find('select[name="/trace_items/0/department_code"]').setValue('FO+HSK')
|
||||
await traceSection!.find('button.primary-button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(service.confirmReservationV4OrderTaskCard).not.toHaveBeenCalled()
|
||||
expect(traceSection!.text()).toContain('Extra Bed Room Count must be a positive integer')
|
||||
|
||||
await roomCountInput.setValue('2')
|
||||
await traceSection!.find('button.primary-button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(service.confirmReservationV4OrderTaskCard).toHaveBeenCalledWith('9001', 'card-trace', {
|
||||
version: 10,
|
||||
confirmed_payload: {
|
||||
trace_items: [
|
||||
{
|
||||
target_room_type_code: 'TWN',
|
||||
extra_bed_room_count: 2,
|
||||
department_code: 'FO+HSK',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const submittedRequest = vi.mocked(service.confirmReservationV4OrderTaskCard).mock.calls[0]?.[2]
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('content')
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('target_order')
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('https://oss.example')
|
||||
})
|
||||
|
||||
it('resolves REVIEW_REQUIRED Trace cards with field errors and safe review overrides', async () => {
|
||||
const detail = createOrderTaskDetail()
|
||||
useTraceBusinessCard(detail, 'GENERAL', 'REVIEW_REQUIRED')
|
||||
detail.order_task.order_id = 'order-2001'
|
||||
detail.order_task.target_resolution_status = 'RESOLVED'
|
||||
vi.mocked(service.fetchReservationV4OrderTaskDetail).mockResolvedValue(detail)
|
||||
vi.mocked(service.resolveReservationV4OrderTaskCardReview).mockResolvedValue({
|
||||
...detail,
|
||||
business_cards: [
|
||||
{
|
||||
...detail.business_cards[0]!,
|
||||
card_status: 'PENDING_CONFIRM',
|
||||
review_status: 'RESOLVED',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const wrapper = await mountWithPlugins(
|
||||
ReservationV4OrderTaskDetailView,
|
||||
'/reservation/order-tasks/9001',
|
||||
)
|
||||
await flushPromises()
|
||||
|
||||
const traceSection = wrapper
|
||||
.findAll('.task-card-section')
|
||||
.find((section) => section.find('[data-testid="trace-card"]').exists())
|
||||
expect(traceSection).toBeTruthy()
|
||||
expect(traceSection!.find('.trace-field__error').text()).toContain('Department is required')
|
||||
|
||||
await traceSection!.find('textarea[name="/trace_items/0/text"]').setValue('Confirm trace review text')
|
||||
await traceSection!.find('select[name="/trace_items/0/department_code"]').setValue('FO')
|
||||
await traceSection!.find('textarea[name="v4_review_reason"]').setValue('confirmed trace note')
|
||||
await traceSection!.find('button.primary-button').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(service.confirmReservationV4OrderTaskCard).not.toHaveBeenCalled()
|
||||
expect(service.resolveReservationV4OrderTaskCardReview).toHaveBeenCalledWith('9001', 'card-trace', {
|
||||
version: 10,
|
||||
confirmed_order_id: 'order-2001',
|
||||
reason: 'confirmed trace note',
|
||||
field_overrides: [
|
||||
{
|
||||
field_pointer: '/trace_items/0/text',
|
||||
value: 'Confirm trace review text',
|
||||
},
|
||||
{
|
||||
field_pointer: '/trace_items/0/department_code',
|
||||
value: 'FO',
|
||||
},
|
||||
],
|
||||
})
|
||||
const submittedRequest = vi.mocked(service.resolveReservationV4OrderTaskCardReview).mock.calls[0]?.[2]
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('content')
|
||||
expect(JSON.stringify(submittedRequest)).not.toContain('target_order')
|
||||
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',
|
||||
@@ -1557,6 +1729,124 @@ function useBoundOrder(detail: ReservationV4OrderTaskDetailResult): void {
|
||||
}
|
||||
}
|
||||
|
||||
function useTraceBusinessCard(
|
||||
detail: ReservationV4OrderTaskDetailResult,
|
||||
itemType: 'GENERAL' | 'EXTRA_BED',
|
||||
cardStatus = 'PENDING_CONFIRM',
|
||||
): void {
|
||||
const isReviewRequired = cardStatus === 'REVIEW_REQUIRED'
|
||||
detail.business_cards = [
|
||||
createCard('card-trace', 'TRACE_RESERVATION_NOTES', cardStatus, {
|
||||
event_type: 'TRACE_RESERVATION_NOTES',
|
||||
version: 10,
|
||||
availability: createAvailability({
|
||||
confirmable: cardStatus === 'PENDING_CONFIRM',
|
||||
reviewable: isReviewRequired,
|
||||
editable: true,
|
||||
read_only: false,
|
||||
}),
|
||||
display_payload: {
|
||||
trace_items: [
|
||||
itemType === 'EXTRA_BED'
|
||||
? {
|
||||
item_type: 'EXTRA_BED',
|
||||
target_room_type_code: 'TWN',
|
||||
extra_bed_room_count: 1,
|
||||
department_code: 'FO',
|
||||
content: 'CONTENT_SHOULD_NOT_RENDER',
|
||||
}
|
||||
: {
|
||||
item_type: 'GENERAL',
|
||||
text: 'Late arrival note',
|
||||
department_code: 'FO',
|
||||
content: 'CONTENT_SHOULD_NOT_RENDER',
|
||||
},
|
||||
],
|
||||
target_order: 'TARGET_ORDER_SHOULD_NOT_RENDER',
|
||||
ai_payload_json: 'AI_PAYLOAD_SHOULD_NOT_RENDER',
|
||||
attachment_url: 'https://oss.example/private/trace.pdf',
|
||||
},
|
||||
fields: itemType === 'EXTRA_BED'
|
||||
? [
|
||||
createField('/trace_items/0/target_room_type_code', {
|
||||
display_name: 'Target Room Type',
|
||||
value: 'TWN',
|
||||
options_source: 'RESERVATION_V4_ROOM_TYPE_CATALOG',
|
||||
control_type: 'SELECT',
|
||||
edit_scope: isReviewRequired ? 'REVIEW' : 'CONFIRM',
|
||||
write_target: isReviewRequired ? 'REVIEW_RESOLUTION_FIELD_OVERRIDES' : 'CONFIRMED_PAYLOAD_JSON',
|
||||
}),
|
||||
createField('/trace_items/0/extra_bed_room_count', {
|
||||
display_name: 'Extra Bed Room Count',
|
||||
value: 1,
|
||||
control_type: 'NUMBER',
|
||||
edit_scope: isReviewRequired ? 'REVIEW' : 'CONFIRM',
|
||||
write_target: isReviewRequired ? 'REVIEW_RESOLUTION_FIELD_OVERRIDES' : 'CONFIRMED_PAYLOAD_JSON',
|
||||
}),
|
||||
createTraceDepartmentField(isReviewRequired),
|
||||
createField('/trace_items/0/content', {
|
||||
display_name: 'Legacy content',
|
||||
value: 'CONTENT_SHOULD_NOT_RENDER',
|
||||
editable: true,
|
||||
edit_scope: isReviewRequired ? 'REVIEW' : 'CONFIRM',
|
||||
write_target: isReviewRequired ? 'REVIEW_RESOLUTION_FIELD_OVERRIDES' : 'CONFIRMED_PAYLOAD_JSON',
|
||||
}),
|
||||
]
|
||||
: [
|
||||
createField('/trace_items/0/text', {
|
||||
display_name: 'Trace Text',
|
||||
value: 'Late arrival note',
|
||||
control_type: 'TEXTAREA',
|
||||
edit_scope: isReviewRequired ? 'REVIEW' : 'CONFIRM',
|
||||
write_target: isReviewRequired ? 'REVIEW_RESOLUTION_FIELD_OVERRIDES' : 'CONFIRMED_PAYLOAD_JSON',
|
||||
}),
|
||||
createTraceDepartmentField(isReviewRequired, isReviewRequired ? ['Department is required'] : []),
|
||||
createField('/trace_items/0/content', {
|
||||
display_name: 'Legacy content',
|
||||
value: 'CONTENT_SHOULD_NOT_RENDER',
|
||||
editable: true,
|
||||
edit_scope: isReviewRequired ? 'REVIEW' : 'CONFIRM',
|
||||
write_target: isReviewRequired ? 'REVIEW_RESOLUTION_FIELD_OVERRIDES' : 'CONFIRMED_PAYLOAD_JSON',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]
|
||||
detail.card_counts = {
|
||||
...detail.card_counts,
|
||||
pending_confirm_count: cardStatus === 'PENDING_CONFIRM' ? 2 : 1,
|
||||
review_required_count: isReviewRequired ? 1 : 0,
|
||||
}
|
||||
}
|
||||
|
||||
function createTraceDepartmentField(
|
||||
isReviewRequired: boolean,
|
||||
validationErrors: string[] = [],
|
||||
): ReservationV4TaskCardResult['fields'][number] {
|
||||
return createField('/trace_items/0/department_code', {
|
||||
display_name: 'Department',
|
||||
value: 'FO',
|
||||
control_type: 'SELECT',
|
||||
options_source: 'reservation_v4_trace_department_fixed',
|
||||
fixed_options: [
|
||||
{
|
||||
value: 'FO',
|
||||
label: 'FO',
|
||||
},
|
||||
{
|
||||
value: 'HSK',
|
||||
label: 'HSK',
|
||||
},
|
||||
{
|
||||
value: 'FO+HSK',
|
||||
label: 'FO+HSK',
|
||||
},
|
||||
],
|
||||
edit_scope: isReviewRequired ? 'REVIEW' : 'CONFIRM',
|
||||
write_target: isReviewRequired ? 'REVIEW_RESOLUTION_FIELD_OVERRIDES' : 'CONFIRMED_PAYLOAD_JSON',
|
||||
validation_errors: validationErrors,
|
||||
})
|
||||
}
|
||||
|
||||
function createPaymentConversationMedia(): SourceMessageOriginalMedia[] {
|
||||
return [
|
||||
{
|
||||
@@ -1830,6 +2120,7 @@ function createField(
|
||||
edit_scope: overrides.edit_scope ?? 'NORMAL_TASK',
|
||||
write_target: overrides.write_target ?? 'CONFIRMED_PAYLOAD_JSON',
|
||||
options_source: overrides.options_source ?? null,
|
||||
fixed_options: overrides.fixed_options ?? null,
|
||||
raw_readonly: overrides.raw_readonly ?? false,
|
||||
validation_errors: overrides.validation_errors ?? [],
|
||||
control_hint: overrides.control_hint ?? null,
|
||||
|
||||
Reference in New Issue
Block a user