适配 M002 V3 前端任务流与同卡复核

This commit is contained in:
andy committed 2026-07-12 00:33:15 +08:00
1 parent fd027f124c
commit 155db63028
17 files changed
+1593 -85

No files matched your search

@@ -9,6 +9,7 @@ import ReservationTaskDetailPanel from '@/components/reservation/ReservationTask
import { useAuthStore } from '@/stores/authStore'
import type {
ReservationManualReviewConversionResult,
ReservationManualReviewResolutionResult,
ReservationOrderListItem,
ReservationOrderListResult,
ReservationTaskAuditLogResult,
@@ -25,6 +26,7 @@ vi.mock('@/services/reservationService', async (importOriginal) => {
fetchReservationTaskAudits: vi.fn(),
fetchReservationOrders: vi.fn(),
convertReservationManualReviewTask: vi.fn(),
resolveReservationManualReviewTask: vi.fn(),
saveReservationTaskDraft: vi.fn(),
confirmReservationTask: vi.fn(),
}
@@ -70,8 +72,13 @@ function createSourceMessageOnlyTaskDetail(
overrides: Partial<ReservationTaskDetailResult> = {},
): ReservationTaskDetailResult {
return createTaskDetail({
system_task_type: 'SOURCE_MESSAGE_ONLY',
task_card_type: 'SOURCE_MESSAGE_ONLY',
result_type: 'source_message_review_notification',
ai_task_type: 'Message Notification',
task_subtype: 'S10',
route_code: 'S10',
system_process_category: 'SOURCE_MESSAGE_NOTIFICATION',
system_task_type: 'MESSAGE_NOTIFICATION',
task_card_type: 'MESSAGE_NOTIFICATION',
task_status: 'COMPLETED',
fields: [],
opera_operations: [],
@@ -85,11 +92,20 @@ function createSourceMessageOnlyTaskDetail(
blocked_reason: null,
},
source_message_only_result: {
entry_result_code: 'S000',
entry_result_code: 'S10',
entry_result_meaning: 'PURE_INFORMATION',
entry_result_description: '纯信息类邮件',
entry_result_source_message_id: '30001',
raw_answer: 'S000,30001',
result_type: 'source_message_review_notification',
route_code: 'S10',
agent_assessment: {
intent: 'informational',
},
notification: {
current_message_summary: '仅同步信息,不需要订单动作。',
},
manual_review: null,
raw_answer: 'S10,30001',
},
...overrides,
})
@@ -121,6 +137,31 @@ function createManualReviewConversionResult(
}
}
function createManualReviewResolutionResult(
overrides: Partial<ReservationManualReviewResolutionResult> = {},
): ReservationManualReviewResolutionResult {
return {
task_id: '10001',
order_id: '20001',
task_status: 'READY',
review_status: 'RESOLVED',
review_resolution: {
confirmed_order_id: '20001',
field_overrides: [
{
field_pointer: '/extracted_fields/pms_room_type_code',
value: 'RM3',
},
],
},
confirmed_payload: {
'extracted_fields.pms_room_type_code': 'RM3',
},
opera_operations: [],
...overrides,
}
}
function createOrderCandidate(overrides: Partial<ReservationOrderListItem> = {}): ReservationOrderListItem {
return {
order_id: '20002',
@@ -166,6 +207,20 @@ function createAudit(auditId: string, action: string): ReservationTaskAuditLogRe
}
}
function createOperaOperation(): ReservationTaskDetailResult['opera_operations'][number] {
return {
operation_id: 'op-1',
task_id: '10001',
operation_sequence: 1,
operation_code: 'CREATE_RESERVATION',
operation_name: 'Create reservation',
operation_status: 'PENDING',
attempt_count: 0,
last_error_message: null,
attempts: [],
}
}
function createRequiredField(overrides: Partial<ReservationTaskFieldResult> = {}): ReservationTaskFieldResult {
return {
row_number: 1,
@@ -280,6 +335,7 @@ describe('ReservationTaskDetailPanel', () => {
vi.mocked(service.fetchReservationTaskAudits).mockReset()
vi.mocked(service.fetchReservationOrders).mockReset()
vi.mocked(service.convertReservationManualReviewTask).mockReset()
vi.mocked(service.resolveReservationManualReviewTask).mockReset()
vi.mocked(service.saveReservationTaskDraft).mockReset()
vi.mocked(service.confirmReservationTask).mockReset()
vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(createTaskDetail())
@@ -289,6 +345,7 @@ describe('ReservationTaskDetailPanel', () => {
})
vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderCandidateResult([]))
vi.mocked(service.convertReservationManualReviewTask).mockResolvedValue(createManualReviewConversionResult())
vi.mocked(service.resolveReservationManualReviewTask).mockResolvedValue(createManualReviewResolutionResult())
sessionStorage.clear()
})
@@ -465,10 +522,11 @@ describe('ReservationTaskDetailPanel', () => {
expect(wrapper.text()).toContain('来源消息只读')
expect(wrapper.text()).toContain('来源消息入口结果')
expect(wrapper.text()).toContain('该任务仅用于展示来源消息入口结果')
expect(wrapper.text()).toContain('S000')
expect(wrapper.text()).toContain('S10')
expect(wrapper.text()).toContain('PURE_INFORMATION')
expect(wrapper.text()).toContain('纯信息类邮件')
expect(wrapper.text()).toContain('S000,30001')
expect(wrapper.text()).toContain('S10,30001')
expect(wrapper.text()).toContain('来源消息通知')
expect(wrapper.text()).toContain('查看邮件会话')
expect(wrapper.text()).not.toContain('查看订单')
expect(wrapper.text()).not.toContain('保存草稿')
@@ -480,6 +538,121 @@ describe('ReservationTaskDetailPanel', () => {
expect(wrapper.find('button[title="重试"]').exists()).toBe(false)
})
it('resolves type-known manual review on the same task card', async () => {
vi.mocked(service.fetchReservationTaskDetail)
.mockResolvedValueOnce(
createTaskDetail({
result_type: 'manual_review',
ai_task_type: 'NEW_BOOKING',
task_subtype: 'NEW_BOOKING',
route_code: 'NEW_BOOKING_MANUAL_REVIEW',
system_process_category: 'BUSINESS_TASK',
task_status: 'BLOCKED',
review_status: 'PENDING',
manual_review: {
reason_code: 'missing_room_type_code',
missing_fields: ['/extracted_fields/pms_room_type_code'],
review_instruction: '请补充 PMS 房型代码。',
},
availability: {
blocked: true,
read_only: true,
editable: false,
confirmable: false,
executable: false,
blocked_by_task_id: null,
blocked_reason: 'MANUAL_REVIEW_REQUIRED',
},
fields: [
createRequiredField({
field_path: 'extracted_fields.pms_room_type_code',
display_name: 'PMS 房型代码',
editable: 'N',
input_editable: 'N',
value: null,
}),
],
}),
)
.mockResolvedValueOnce(
createTaskDetail({
result_type: 'manual_review',
ai_task_type: 'NEW_BOOKING',
task_subtype: 'NEW_BOOKING',
route_code: 'NEW_BOOKING_MANUAL_REVIEW',
system_process_category: 'BUSINESS_TASK',
task_status: 'READY',
review_status: 'RESOLVED',
review_resolution: {
confirmed_order_id: '20001',
field_overrides: [
{
field_pointer: '/extracted_fields/pms_room_type_code',
value: 'RM3',
},
],
},
availability: {
blocked: false,
read_only: false,
editable: false,
confirmable: false,
executable: true,
blocked_by_task_id: null,
blocked_reason: null,
},
opera_operations: [createOperaOperation()],
}),
)
vi.mocked(service.fetchReservationTaskAudits)
.mockResolvedValueOnce({
task_id: '10001',
items: [createAudit('audit-1', 'TASK_LOADED')],
})
.mockResolvedValueOnce({
task_id: '10001',
items: [createAudit('audit-2', 'MANUAL_REVIEW_RESOLVED')],
})
const wrapper = await mountPanel()
expect(wrapper.text()).toContain('路由与处理信息')
expect(wrapper.text()).toContain('系统任务类型')
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('PMS 房型代码')
expect(wrapper.text()).not.toContain('人工转换')
expect(wrapper.text()).not.toContain('保存草稿')
expect(wrapper.text()).not.toContain('确认任务')
expect(wrapper.text()).not.toContain('OPERA 模拟操作')
await wrapper.find('input[name="manual_resolution_field_override_0"]').setValue('RM3')
await wrapper.find('textarea[name="manual_resolution_reason"]').setValue('确认 PMS 房型代码')
await findButton(wrapper, '提交复核结果').trigger('click')
await flushPromises()
expect(service.resolveReservationManualReviewTask).toHaveBeenCalledWith('10001', {
confirmed_order_id: '20001',
reason: '确认 PMS 房型代码',
field_overrides: [
{
field_pointer: '/extracted_fields/pms_room_type_code',
value: 'RM3',
},
],
})
expect(service.fetchReservationTaskDetail).toHaveBeenCalledTimes(2)
expect(service.fetchReservationTaskAudits).toHaveBeenCalledTimes(2)
expect(wrapper.text()).toContain('人工复核已解阻')
expect(wrapper.text()).toContain('人工复核解阻')
expect(wrapper.text()).toContain('OPERA 模拟操作')
expect(wrapper.text()).toContain('CREATE_RESERVATION')
expect(wrapper.find('button[title="执行"]').exists()).toBe(true)
expect(wrapper.text()).not.toContain('MANUAL_REVIEW_RESOLVED')
})
it('converts a manual review task to a new booking without a target order', async () => {
vi.mocked(service.fetchReservationTaskDetail)
.mockResolvedValueOnce(