feat(web): 报销单新增关联申请单门控与草稿检测流程

- 新增 travelReimbursementAssociationGateModel,查询可关联申请单/草稿报销单并生成跳过/选择/单独新建动作,区分差旅费与业务招待费类型
- travelReimbursementApplicationLinkModel 补充 buildLinkedApplicationReferenceIndex/buildRequiredApplicationActions 等关联构建逻辑
- useTravelReimbursementSuggestedActions 接入 select_required_application/skip 系列动作,'我要报销'入口改为先走关联门控
- useWorkbenchAiActionRouter 新增 SKIP_REQUIRED_APPLICATION_LINK/SKIP_REIMBURSEMENT_DRAFT_CHECK 动作分发
- useWorkbenchAiExpenseFlow 暴露 startAiReimbursementAssociationGate,stewardPlanModel 待处理流程适配
- 新增 workbench-ai-action-router、workbench-ai-reimbursement-association-gate 测试并更新 guided-flow、steward-plan 测试
This commit is contained in:
caoxiaozhu
2026-06-22 15:55:59 +08:00
parent aa965da69d
commit ba444a514f
11 changed files with 1756 additions and 25 deletions

View File

@@ -0,0 +1,138 @@
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'
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: '不关联,单独新建报销单'
})
})