Files
X-Financial/web/tests/workbench-ai-action-router.test.mjs
caoxiaozhu ee730aa31c feat(web): AI 工作台文件预览/附件关联任务与草稿分支
- 新增 WorkbenchAiFilePreviewDialog 附件预览对话框及 useWorkbenchAiFilePreview,附件支持点击预览
- 新增 attachmentAssociationJobs/linkedReimbursementDraftJobs 前端服务与对应 composable,接入后台任务轮询与状态展示
- 新增 travelReimbursementDraftBranchModel 草稿分支模型,报销关联门控支持跳过/选择草稿
- PersonalWorkbenchAiMode 及各 composable(expense/document/steward/application-preview/attachment-association)重构适配,WorkbenchAiComposer/FileStrip 样式与交互完善
- DocumentsCenter/ReceiptFolder/TravelReimbursementCreate 等视图及 scripts 重构,风险/差旅规划/审批等工具适配
- 新增/更新前端测试:application-result-card、reimbursement-list-preview-fetch、guided-flow、composer-components 等
2026-06-24 10:42:50 +08:00

248 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import test from 'node:test'
import { buildInlineApplicationPreview } from '../src/composables/workbenchAiMode/workbenchAiApplicationPreviewModel.js'
import { buildStewardSuggestedActions } from '../src/views/scripts/stewardPlanModel.js'
import { useWorkbenchAiActionRouter } from '../src/composables/workbenchAiMode/useWorkbenchAiActionRouter.js'
import {
CONTINUE_REIMBURSEMENT_DRAFT_ACTION,
CREATE_STANDALONE_REIMBURSEMENT_DRAFT_ACTION
} from '../src/views/scripts/travelReimbursementAssociationGateModel.js'
test('workbench steward application confirmation opens inline application preview directly', () => {
const [action] = buildStewardSuggestedActions({
plan_id: 'steward-plan-ready-application',
plan_status: 'ready',
tasks: [
{
task_id: 'task-application-beijing',
task_type: 'expense_application',
title: '费用申请 2026-06-23 北京',
summary: '明天前往北京出差3天支撑客户现场实施。',
assigned_agent: 'application_assistant',
ontology_fields: {
expense_type: 'travel',
time_range: '2026-06-23 至 2026-06-25',
location: '北京',
days: '3天',
reason: '支撑客户现场实施'
},
missing_fields: ['transport_mode']
}
],
confirmation_groups: [
{
confirmation_id: 'confirm-application-beijing',
action_type: 'confirm_create_application',
target_task_id: 'task-application-beijing'
}
]
})
let previewPayload = null
let fallbackConversationStarted = false
const router = useWorkbenchAiActionRouter({
aiExpenseDraft: { value: null },
applicationFlow: {
isInlineSuggestedActionDisabled: () => false,
executeInlineApplicationPreviewAction: () => {}
},
assistantDraft: { value: '' },
attachmentFlow: {
confirmAiAttachmentAssociation: () => {}
},
emit: () => {},
expenseFlow: {
linkAiExpenseApplication: () => {},
pushInlineExpenseSceneSelectionPrompt: () => {},
startAiApplicationPreviewFromAction: (payload) => {
previewPayload = payload
},
startAiExpenseDraft: () => {}
},
focusAiModeInput: () => {},
hasInlineAttachmentOcrDetails: () => false,
resolveLatestInlineUserPrompt: () => '',
selectedFiles: { value: [] },
startInlineConversation: () => {
fallbackConversationStarted = true
},
toast: () => {},
toggleInlineAttachmentOcrDetails: () => {}
})
router.handleInlineSuggestedAction(action)
assert.equal(fallbackConversationStarted, false)
assert.equal(previewPayload?.flow_id, 'travel_application')
assert.equal(previewPayload?.expense_type, 'travel')
assert.equal(previewPayload?.expense_type_label, '差旅费')
assert.match(previewPayload?.carry_text || '', /支撑客户现场实施/)
const preview = buildInlineApplicationPreview(previewPayload.expense_type_label, previewPayload.carry_text, {
name: '测试用户',
departmentName: '交付部',
position: '实施顾问',
managerName: '张经理',
grade: 'P5'
})
assert.equal(preview.fields.time, '2026-06-23 至 2026-06-25')
assert.equal(preview.fields.location, '北京')
assert.equal(preview.fields.reason, '支撑客户现场实施')
assert.equal(preview.fields.days, '3天')
assert.equal(preview.fields.transportMode, '')
})
test('workbench reimbursement skip link action opens new reimbursement flow', () => {
let sceneSelectionPayload = null
let fallbackConversationStarted = false
const router = useWorkbenchAiActionRouter({
aiExpenseDraft: { value: null },
applicationFlow: {
isInlineSuggestedActionDisabled: () => false,
executeInlineApplicationPreviewAction: () => {}
},
assistantDraft: { value: '' },
attachmentFlow: {
confirmAiAttachmentAssociation: () => {}
},
emit: () => {},
expenseFlow: {
linkAiExpenseApplication: () => {},
pushInlineExpenseSceneSelectionPrompt: (sourceText, label) => {
sceneSelectionPayload = { sourceText, label }
},
startAiApplicationPreviewFromAction: () => {},
startAiExpenseDraft: () => {}
},
focusAiModeInput: () => {},
hasInlineAttachmentOcrDetails: () => false,
resolveLatestInlineUserPrompt: () => '',
selectedFiles: { value: [] },
startInlineConversation: () => {
fallbackConversationStarted = true
},
toast: () => {},
toggleInlineAttachmentOcrDetails: () => {}
})
router.handleInlineSuggestedAction({
label: '不关联,单独新建报销单',
action_type: 'skip_required_application_link',
payload: {
original_message: '我要报销'
}
})
assert.equal(fallbackConversationStarted, false)
assert.deepEqual(sceneSelectionPayload, {
sourceText: '我要报销',
label: '不关联,单独新建报销单'
})
})
test('workbench draft continuation action asks for attachments or description', () => {
let continuationPayload = null
let fallbackConversationStarted = false
const router = useWorkbenchAiActionRouter({
aiExpenseDraft: { value: null },
applicationFlow: {
isInlineSuggestedActionDisabled: () => false,
executeInlineApplicationPreviewAction: () => {}
},
assistantDraft: { value: '' },
attachmentFlow: {
confirmAiAttachmentAssociation: () => {}
},
emit: () => {},
expenseFlow: {
linkAiExpenseApplication: () => {},
promptAiReimbursementDraftContinuation: (payload) => {
continuationPayload = payload
},
promptStandaloneReimbursementDraftCreation: () => {},
pushInlineExpenseSceneSelectionPrompt: () => {},
startAiApplicationPreviewFromAction: () => {},
startAiExpenseDraft: () => {},
startAiReimbursementAssociationGate: () => {}
},
focusAiModeInput: () => {},
hasInlineAttachmentOcrDetails: () => false,
resolveLatestInlineUserPrompt: () => '',
selectedFiles: { value: [] },
startInlineConversation: () => {
fallbackConversationStarted = true
},
toast: () => {},
toggleInlineAttachmentOcrDetails: () => {}
})
router.handleInlineSuggestedAction({
label: '继续关联草稿 RE-202606-010',
action_type: CONTINUE_REIMBURSEMENT_DRAFT_ACTION,
payload: {
claim_id: 'draft-travel-1',
claim_no: 'RE-202606-010',
original_message: '我要报销'
}
})
assert.equal(fallbackConversationStarted, false)
assert.deepEqual(continuationPayload, {
claim_id: 'draft-travel-1',
claim_no: 'RE-202606-010',
original_message: '我要报销'
})
})
test('workbench standalone draft action asks before creating a new reimbursement draft', () => {
let standalonePrompt = null
let fallbackConversationStarted = false
const router = useWorkbenchAiActionRouter({
aiExpenseDraft: { value: null },
applicationFlow: {
isInlineSuggestedActionDisabled: () => false,
executeInlineApplicationPreviewAction: () => {}
},
assistantDraft: { value: '' },
attachmentFlow: {
confirmAiAttachmentAssociation: () => {}
},
emit: () => {},
expenseFlow: {
linkAiExpenseApplication: () => {},
promptAiReimbursementDraftContinuation: () => {},
promptStandaloneReimbursementDraftCreation: (sourceText, label) => {
standalonePrompt = { sourceText, label }
},
pushInlineExpenseSceneSelectionPrompt: () => {},
startAiApplicationPreviewFromAction: () => {},
startAiExpenseDraft: () => {},
startAiReimbursementAssociationGate: () => {}
},
focusAiModeInput: () => {},
hasInlineAttachmentOcrDetails: () => false,
resolveLatestInlineUserPrompt: () => '',
selectedFiles: { value: [] },
startInlineConversation: () => {
fallbackConversationStarted = true
},
toast: () => {},
toggleInlineAttachmentOcrDetails: () => {}
})
router.handleInlineSuggestedAction({
label: '独立新建报销单',
action_type: CREATE_STANDALONE_REIMBURSEMENT_DRAFT_ACTION,
payload: {
original_message: '我要报销'
}
})
assert.equal(fallbackConversationStarted, false)
assert.deepEqual(standalonePrompt, {
sourceText: '我要报销',
label: '独立新建报销单'
})
})