接入前端真实接口并标记邮件HTML清洗
This commit is contained in:
1 parent
c83935a26c
commit
a0cb3a05d7
25 files changed
+2581
-157
No files matched your search
@@ -8,6 +8,7 @@ import ReservationTaskDetailPanel from '@/components/reservation/ReservationTask
|
||||
import type {
|
||||
ReservationTaskAuditLogResult,
|
||||
ReservationTaskDetailResult,
|
||||
ReservationTaskFieldResult,
|
||||
ReservationTaskPayloadMutationResult,
|
||||
} from '@/types/reservation'
|
||||
|
||||
@@ -24,8 +25,8 @@ vi.mock('@/services/reservationService', async (importOriginal) => {
|
||||
|
||||
const service = await import('@/services/reservationService')
|
||||
|
||||
function createTaskDetail(): ReservationTaskDetailResult {
|
||||
return {
|
||||
function createTaskDetail(overrides: Partial<ReservationTaskDetailResult> = {}): ReservationTaskDetailResult {
|
||||
const detail: ReservationTaskDetailResult = {
|
||||
task_id: '10001',
|
||||
order_id: '20001',
|
||||
source_message_id: '30001',
|
||||
@@ -52,6 +53,10 @@ function createTaskDetail(): ReservationTaskDetailResult {
|
||||
fields: [],
|
||||
opera_operations: [],
|
||||
}
|
||||
return {
|
||||
...detail,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function createMutationResult(taskStatus = 'PENDING_CONFIRM'): ReservationTaskPayloadMutationResult {
|
||||
@@ -81,6 +86,34 @@ function createAudit(auditId: string, action: string): ReservationTaskAuditLogRe
|
||||
}
|
||||
}
|
||||
|
||||
function createRequiredField(overrides: Partial<ReservationTaskFieldResult> = {}): ReservationTaskFieldResult {
|
||||
return {
|
||||
row_number: 1,
|
||||
card_name: 'New Booking',
|
||||
display_area: '客人信息',
|
||||
field_path: 'guest.name',
|
||||
display_name: '客人姓名',
|
||||
visible: 'Y',
|
||||
editable: 'Y',
|
||||
input_editable: 'Y',
|
||||
select_editable: 'N',
|
||||
date_picker: 'N',
|
||||
number_input: 'N',
|
||||
file_display: 'N',
|
||||
table_editable: 'N',
|
||||
enum_options: null,
|
||||
required_rule: 'Y',
|
||||
display_condition: null,
|
||||
validation_rule: null,
|
||||
write_path: null,
|
||||
opera_write_participation: 'N',
|
||||
opera_parameter_mapping: null,
|
||||
notes: null,
|
||||
value: '',
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
async function mountPanel() {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
@@ -107,7 +140,9 @@ async function mountPanel() {
|
||||
global: {
|
||||
plugins: [i18n, router],
|
||||
stubs: {
|
||||
RouterLink: true,
|
||||
RouterLink: {
|
||||
template: '<a><slot /></a>',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -158,6 +193,59 @@ describe('ReservationTaskDetailPanel', () => {
|
||||
expect(findButton(wrapper, '确认').attributes('disabled')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('blocks confirmation when frontend field validation fails', async () => {
|
||||
vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
|
||||
createTaskDetail({
|
||||
fields: [createRequiredField()],
|
||||
}),
|
||||
)
|
||||
|
||||
const wrapper = await mountPanel()
|
||||
await findButton(wrapper, '确认').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(service.confirmReservationTask).not.toHaveBeenCalled()
|
||||
expect(wrapper.text()).toContain('请先修正字段')
|
||||
expect(wrapper.text()).toContain('客人姓名为必填项')
|
||||
})
|
||||
|
||||
it('keeps remaining validation errors and clears the summary after fields are fixed', async () => {
|
||||
vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
|
||||
createTaskDetail({
|
||||
fields: [
|
||||
createRequiredField(),
|
||||
createRequiredField({
|
||||
field_path: 'room.type',
|
||||
display_name: '房型',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
const wrapper = await mountPanel()
|
||||
await findButton(wrapper, '确认').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('请先修正字段')
|
||||
expect(wrapper.text()).toContain('客人姓名为必填项')
|
||||
expect(wrapper.text()).toContain('房型为必填项')
|
||||
|
||||
const textareas = wrapper.findAll('textarea')
|
||||
await textareas[0]!.setValue('王建国')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('请先修正字段')
|
||||
expect(wrapper.text()).not.toContain('客人姓名为必填项')
|
||||
expect(wrapper.text()).toContain('房型为必填项')
|
||||
|
||||
await textareas[1]!.setValue('豪华 Q1A')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).not.toContain('请先修正字段')
|
||||
expect(wrapper.text()).not.toContain('客人姓名为必填项')
|
||||
expect(wrapper.text()).not.toContain('房型为必填项')
|
||||
})
|
||||
|
||||
it('refreshes audit records after saving a draft', async () => {
|
||||
vi.mocked(service.fetchReservationTaskAudits)
|
||||
.mockResolvedValueOnce({
|
||||
@@ -180,4 +268,69 @@ describe('ReservationTaskDetailPanel', () => {
|
||||
expect(wrapper.text()).toContain('DRAFT_SAVED')
|
||||
expect(wrapper.text()).not.toContain('TASK_LOADED')
|
||||
})
|
||||
|
||||
it('saves only editable active fields to match backend task field validation', async () => {
|
||||
vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
|
||||
createTaskDetail({
|
||||
fields: [
|
||||
createRequiredField({
|
||||
field_path: 'visible_reason',
|
||||
display_name: '生成原因',
|
||||
editable: 'N',
|
||||
input_editable: 'N',
|
||||
required_rule: 'Y',
|
||||
value: 'AI extracted this task.',
|
||||
}),
|
||||
createRequiredField({
|
||||
field_path: 'case_keys.group_code',
|
||||
display_name: 'Group Code',
|
||||
display_condition: '对象为Group Block或Allotment时展示',
|
||||
value: 'GRP-001',
|
||||
}),
|
||||
createRequiredField({
|
||||
field_path: 'case_keys.confirmation_number',
|
||||
display_name: 'Confirmation No.',
|
||||
display_condition: '对象为FIT Reservation时展示',
|
||||
value: null,
|
||||
}),
|
||||
createRequiredField({
|
||||
field_path: 'extracted_fields.booking_object_type',
|
||||
display_name: '订单对象类型',
|
||||
select_editable: 'Y',
|
||||
input_editable: 'N',
|
||||
enum_options: 'Group Block, FIT Reservation',
|
||||
value: 'Group Block',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
vi.mocked(service.saveReservationTaskDraft).mockResolvedValue(createMutationResult())
|
||||
|
||||
const wrapper = await mountPanel()
|
||||
await findButton(wrapper, '保存草稿').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(service.saveReservationTaskDraft).toHaveBeenCalledWith(
|
||||
'10001',
|
||||
{
|
||||
field_values: {
|
||||
'case_keys.group_code': 'GRP-001',
|
||||
'extracted_fields.booking_object_type': 'Group Block',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
it('opens the source conversation by source message id without external conversation id', async () => {
|
||||
vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(
|
||||
createTaskDetail({
|
||||
external_conversation_id: null,
|
||||
}),
|
||||
)
|
||||
|
||||
const wrapper = await mountPanel()
|
||||
|
||||
expect(wrapper.text()).toContain('查看邮件会话')
|
||||
expect(wrapper.text()).not.toContain('接口待补')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user