适配 M002 V3 字段矩阵前端交互

This commit is contained in:
andy
2026-07-12 09:23:03 +08:00
parent db95aece20
commit 9bd6bc1cd0
11 changed files with 515 additions and 66 deletions

View File

@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
import type { ReservationTaskFieldResult } from '@/types/reservation'
import {
buildInitialFieldValues,
buildEditableFieldValues,
groupReservationFields,
validateReservationFieldValues,
@@ -147,4 +148,156 @@ describe('validateReservationFieldValues', () => {
'case_keys.group_code': 'GRP-001',
})
})
it('hydrates P0 room_items fields from field_values and submits main field_path keys', () => {
const roomTypeField = createField({
field_path: 'extracted_fields.room_items.0.room_type_raw',
field_pointer: '/extracted_fields/room_items/0/room_type_raw',
legacy_field_path: 'extracted_fields.room_type',
display_name: '房型原文',
})
const pmsCodeField = createField({
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
legacy_field_path: 'extracted_fields.pms_room_type_code',
display_name: 'PMS 房型代码',
})
const values = buildInitialFieldValues(
[roomTypeField, pmsCodeField],
{
field_values: {
'extracted_fields.room_items.0.room_type_raw': 'Deluxe Q1A',
'extracted_fields.room_items.0.pms_room_type_code': 'Q1A',
},
},
'20260711-p0',
)
const payload = buildEditableFieldValues([roomTypeField, pmsCodeField], values, false)
expect(values).toMatchObject({
'extracted_fields.room_items.0.room_type_raw': 'Deluxe Q1A',
'extracted_fields.room_items.0.pms_room_type_code': 'Q1A',
})
expect(payload).toEqual({
'extracted_fields.room_items.0.room_type_raw': 'Deluxe Q1A',
'extracted_fields.room_items.0.pms_room_type_code': 'Q1A',
})
expect(payload).not.toHaveProperty('extracted_fields.room_type')
expect(payload).not.toHaveProperty('extracted_fields.pms_room_type_code')
})
it('hydrates P0 fields from nested field_values as a defensive fallback', () => {
const values = buildInitialFieldValues(
[
createField({
field_path: 'extracted_fields.room_items.0.room_quantity',
field_pointer: '/extracted_fields/room_items/0/room_quantity',
legacy_field_path: 'extracted_fields.room_quantity',
display_name: '房间数量',
}),
],
{
field_values: {
extracted_fields: {
room_items: [
{
room_quantity: 2,
},
],
},
},
},
'20260711-p0',
)
expect(values).toEqual({
'extracted_fields.room_items.0.room_quantity': 2,
})
})
it('hydrates code-v1 historical fields from legacy_field_values into P0 main keys', () => {
const values = buildInitialFieldValues(
[
createField({
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
legacy_field_path: 'extracted_fields.pms_room_type_code',
display_name: 'PMS 房型代码',
}),
],
{
field_values: {
'extracted_fields.room_items.0.pms_room_type_code': 'NEW',
},
legacy_field_values: {
'extracted_fields.pms_room_type_code': 'LEGACY',
},
},
'code-v1',
)
expect(values).toEqual({
'extracted_fields.room_items.0.pms_room_type_code': 'LEGACY',
})
})
it('hydrates code-v1 historical fields from legacy effective_payload when legacy_field_values is absent', () => {
const values = buildInitialFieldValues(
[
createField({
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
legacy_field_path: 'extracted_fields.pms_room_type_code',
display_name: 'PMS 房型代码',
}),
],
{
effective_payload: {
extracted_fields: {
pms_room_type_code: 'LEGACY-NESTED',
},
},
},
'code-v1',
)
expect(values).toEqual({
'extracted_fields.room_items.0.pms_room_type_code': 'LEGACY-NESTED',
})
})
it('does not create frontend required errors for readonly evidence fields missing in V3 payloads', () => {
const errors = validateReservationFieldValues(
[
createField({
field_path: 'visible_reason',
display_name: '生成原因',
editable: 'N',
input_editable: 'N',
required_rule: 'Y',
value: null,
}),
createField({
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
display_name: 'PMS 房型代码',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
legacy_field_path: 'extracted_fields.pms_room_type_code',
required_rule: 'Y',
value: '',
}),
],
{
relevant_message_excerpt: 'Please book a Deluxe Q1A room.',
'extracted_fields.room_items.0.pms_room_type_code': '',
},
)
expect(errors).toEqual({
'extracted_fields.room_items.0.pms_room_type_code': {
code: 'required',
field_name: 'PMS 房型代码',
},
})
})
})

View File

@@ -245,13 +245,20 @@ describe('reservationService real API mode', () => {
confirmed_order_id: '20001',
field_overrides: [
{
field_pointer: '/extracted_fields/pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
legacy_field_path: 'extracted_fields.pms_room_type_code',
value: 'RM3',
},
],
},
confirmed_payload: {
'extracted_fields.pms_room_type_code': 'RM3',
field_values: {
'extracted_fields.room_items.0.pms_room_type_code': 'RM3',
},
legacy_field_values: {
'extracted_fields.pms_room_type_code': 'RM3',
},
},
opera_operations: [],
}),
@@ -262,7 +269,8 @@ describe('reservationService real API mode', () => {
reason: '确认 PMS 房型代码',
field_overrides: [
{
field_pointer: '/extracted_fields/pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
value: 'RM3',
},
],
@@ -280,7 +288,8 @@ describe('reservationService real API mode', () => {
reason: '确认 PMS 房型代码',
field_overrides: [
{
field_pointer: '/extracted_fields/pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
value: 'RM3',
},
],

View File

@@ -149,13 +149,20 @@ function createManualReviewResolutionResult(
confirmed_order_id: '20001',
field_overrides: [
{
field_pointer: '/extracted_fields/pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
legacy_field_path: 'extracted_fields.pms_room_type_code',
value: 'RM3',
},
],
},
confirmed_payload: {
'extracted_fields.pms_room_type_code': 'RM3',
field_values: {
'extracted_fields.room_items.0.pms_room_type_code': 'RM3',
},
legacy_field_values: {
'extracted_fields.pms_room_type_code': 'RM3',
},
},
opera_operations: [],
...overrides,
@@ -424,6 +431,48 @@ describe('ReservationTaskDetailPanel', () => {
expect(wrapper.text()).not.toContain('房型为必填项')
})
it('hydrates code-v1 legacy payload values but saves future drafts with P0 field paths', async () => {
vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
createTaskDetail({
field_contract_version: 'code-v1',
draft_payload: {
field_values: {
'extracted_fields.room_items.0.pms_room_type_code': 'P0-SHOULD-NOT-WIN',
},
legacy_field_values: {
'extracted_fields.pms_room_type_code': 'LEGACY-RM',
},
},
fields: [
createRequiredField({
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
legacy_field_path: 'extracted_fields.pms_room_type_code',
display_name: 'PMS 房型代码',
value: null,
}),
],
}),
)
vi.mocked(service.saveReservationTaskDraft).mockResolvedValue(createMutationResult())
const wrapper = await mountPanel()
const pmsInput = wrapper.find('textarea')
expect((pmsInput.element as HTMLTextAreaElement).value).toBe('LEGACY-RM')
expect(wrapper.text()).not.toContain('P0-SHOULD-NOT-WIN')
await pmsInput.setValue('RM4')
await findButton(wrapper, '保存草稿').trigger('click')
await flushPromises()
expect(service.saveReservationTaskDraft).toHaveBeenCalledWith('10001', {
field_values: {
'extracted_fields.room_items.0.pms_room_type_code': 'RM4',
},
})
})
it('refreshes audit records after saving a draft', async () => {
vi.mocked(service.fetchReservationTaskAudits)
.mockResolvedValueOnce({
@@ -551,7 +600,7 @@ describe('ReservationTaskDetailPanel', () => {
review_status: 'PENDING',
manual_review: {
reason_code: 'missing_room_type_code',
missing_fields: ['/extracted_fields/pms_room_type_code'],
missing_fields: ['/extracted_fields/room_items/0/pms_room_type_code'],
review_instruction: '请补充 PMS 房型代码。',
},
availability: {
@@ -565,7 +614,9 @@ describe('ReservationTaskDetailPanel', () => {
},
fields: [
createRequiredField({
field_path: 'extracted_fields.pms_room_type_code',
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
legacy_field_path: 'extracted_fields.pms_room_type_code',
display_name: 'PMS 房型代码',
editable: 'N',
input_editable: 'N',
@@ -587,7 +638,9 @@ describe('ReservationTaskDetailPanel', () => {
confirmed_order_id: '20001',
field_overrides: [
{
field_pointer: '/extracted_fields/pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
legacy_field_path: 'extracted_fields.pms_room_type_code',
value: 'RM3',
},
],
@@ -621,7 +674,7 @@ describe('ReservationTaskDetailPanel', () => {
expect(wrapper.text()).toContain('任务卡类型')
expect(wrapper.text()).toContain('NEW_BOOKING_MANUAL_REVIEW')
expect(wrapper.text()).toContain('人工复核解阻')
expect(wrapper.text()).toContain('/extracted_fields/pms_room_type_code')
expect(wrapper.text()).toContain('/extracted_fields/room_items/0/pms_room_type_code')
expect(wrapper.text()).toContain('PMS 房型代码')
expect(wrapper.text()).not.toContain('人工转换')
expect(wrapper.text()).not.toContain('保存草稿')
@@ -638,7 +691,8 @@ describe('ReservationTaskDetailPanel', () => {
reason: '确认 PMS 房型代码',
field_overrides: [
{
field_pointer: '/extracted_fields/pms_room_type_code',
field_pointer: '/extracted_fields/room_items/0/pms_room_type_code',
field_path: 'extracted_fields.room_items.0.pms_room_type_code',
value: 'RM3',
},
],