feat(web): 统一平台管理员判定与 AI 工作台申请预览动作接入
- authUser 抽出 resolveAuthUserAdminFlag,统一 isAdmin 解析(含 superadmin、role_codes、中英文角色名),accessControl 复用同一逻辑 - 登录态、应用外壳路由、系统状态接入统一管理员判定,LoginView 与相关 composable 配套调整 - AI 工作台申请提交改为调用新的 /application-preview-action 接口,草稿保存仍走 orchestrator;预审模型补充重叠冲突提示与阻断判断 - 同步更新 accessControl/api-request/ai 预览动作等前端测试
This commit is contained in:
@@ -53,6 +53,7 @@ test('AI mode offers an inline application shortcut when no candidate applicatio
|
||||
assert.match(aiMode, /buildLocalApplicationPreviewMessage/)
|
||||
assert.match(aiMode, /refreshApplicationPreviewEstimate/)
|
||||
assert.match(aiMode, /applicationPreview:\s*preview/)
|
||||
assert.match(aiMode, /suggestedActions:\s*buildInlineApplicationPreviewSuggestedActions\(preview\)/)
|
||||
assert.doesNotMatch(aiMode, /function startAiApplicationDraft/)
|
||||
assert.doesNotMatch(aiMode, /buildAiApplicationStepPrompt/)
|
||||
})
|
||||
@@ -94,12 +95,143 @@ test('AI mode handles document query prompts locally before steward planning', (
|
||||
assert.match(aiMode, /emit\('open-document', buildAiDocumentDetailRequest\(detailReference\)\)/)
|
||||
})
|
||||
|
||||
test('AI mode continues required application gate decisions into table preview from steward plan', () => {
|
||||
assert.match(aiMode, /function continueAiRequiredApplicationGateFromPlan\(normalizedPlan, prompt = ''\)/)
|
||||
assert.match(aiMode, /flow\.flowId === 'travel_application'[\s\S]*void startAiApplicationPreview\('travel', '差旅费', prompt/)
|
||||
assert.match(aiMode, /flow\.flowId === 'travel_reimbursement'[\s\S]*startAiExpenseDraft\('travel', '差旅费', true/)
|
||||
assert.match(aiMode, /continueAiRequiredApplicationGateFromPlan\(normalizedPlan, prompt\)/)
|
||||
test('AI mode asks for manual confirmation before generating application preview table', () => {
|
||||
assert.match(aiMode, /function buildAiRequiredApplicationGateSuggestedActions\(flow, prompt = ''\)/)
|
||||
assert.match(aiMode, /label:\s*'确认发起出差申请'/)
|
||||
assert.match(aiMode, /action_type:\s*'ai_application_start_inline'/)
|
||||
assert.match(aiMode, /carry_text:\s*prompt/)
|
||||
assert.match(aiMode, /label:\s*'确认关联已有申请单'/)
|
||||
assert.match(aiMode, /flow_id:\s*'travel_reimbursement'/)
|
||||
assert.match(aiMode, /suggestedActions:\s*requiredApplicationContinuationFlow[\s\S]*buildAiRequiredApplicationGateSuggestedActions\(requiredApplicationContinuationFlow, prompt\)/)
|
||||
assert.doesNotMatch(aiMode, /continueAiRequiredApplicationGateFromPlan\(normalizedPlan, prompt\)/)
|
||||
assert.doesNotMatch(aiMode, /flow\.flowId === 'travel_application'[\s\S]*void startAiApplicationPreview\('travel', '差旅费', prompt\)/)
|
||||
assert.match(aiMode, /class="workbench-ai-application-preview application-preview-shell"/)
|
||||
assert.match(aiMode, /resolveInlineApplicationPreviewRows\(message\)/)
|
||||
assert.match(aiMode, /commitInlineApplicationPreviewEditor\(message\)/)
|
||||
})
|
||||
|
||||
test('AI mode shows pending feedback before async application preview estimate refresh', () => {
|
||||
const startPreviewFunction = aiMode.match(
|
||||
/async function startAiApplicationPreview[\s\S]*?\n}\n\nfunction requestDeleteCurrentConversation/
|
||||
)?.[0] || ''
|
||||
|
||||
assert.match(startPreviewFunction, /const pendingMessage = createInlineMessage\(\s*'assistant',\s*'正在生成申请核对表/)
|
||||
assert.ok(
|
||||
startPreviewFunction.indexOf('conversationMessages.value.push(pendingMessage)') <
|
||||
startPreviewFunction.indexOf('await refreshApplicationPreviewEstimate(')
|
||||
)
|
||||
assert.match(startPreviewFunction, /pending:\s*true/)
|
||||
assert.match(startPreviewFunction, /replaceInlineMessage\(\s*pendingMessage\.id/)
|
||||
})
|
||||
|
||||
test('AI mode handles application preview save and submit through buttons or text commands', () => {
|
||||
assert.match(aiMode, /AI_APPLICATION_ACTION_SAVE_DRAFT/)
|
||||
assert.match(aiMode, /AI_APPLICATION_ACTION_SUBMIT/)
|
||||
assert.match(aiMode, /runAiApplicationPreviewAction/)
|
||||
assert.match(aiMode, /buildAiApplicationPrecheck/)
|
||||
assert.match(aiMode, /buildAiApplicationSubmitConflictMessage/)
|
||||
assert.match(aiMode, /isAiApplicationPrecheckBlocking/)
|
||||
assert.match(aiMode, /applicationSubmitConfirmOpen/)
|
||||
assert.match(aiMode, /确认直接提交申请/)
|
||||
assert.match(aiMode, /function buildInlineApplicationPreviewSuggestedActions\(applicationPreview = \{\}, draftPayload = null\)/)
|
||||
assert.match(aiMode, /label:\s*'直接提交'/)
|
||||
assert.match(aiMode, /function resolveInlineApplicationPreviewActionFromText\(text = ''\)/)
|
||||
assert.match(aiMode, /function executeInlineApplicationPreviewAction\(actionType, sourceMessage = null, options = \{\}\)/)
|
||||
assert.match(aiMode, /function confirmInlineApplicationSubmit\(\)/)
|
||||
assert.match(aiMode, /function cancelInlineApplicationSubmitConfirm\(\)/)
|
||||
assert.match(aiMode, /function handleInlineApplicationPreviewTextAction\(prompt\)/)
|
||||
assert.match(aiMode, /if \(handleInlineApplicationPreviewTextAction\(cleanPrompt\)\) \{[\s\S]*return[\s\S]*\}/)
|
||||
assert.match(aiMode, /\[AI_APPLICATION_ACTION_SAVE_DRAFT, AI_APPLICATION_ACTION_SUBMIT\]\.includes\(actionType\)/)
|
||||
assert.match(aiMode, /normalizedPreview\.readyToSubmit/)
|
||||
assert.match(aiMode, /fetchExpenseClaims\(\{ page: 1, pageSize: 100 \}\)/)
|
||||
assert.match(aiMode, /skipUserMessage/)
|
||||
assert.match(aiMode, /暂不能提交申请/)
|
||||
assert.match(aiMode, /#ai-open-application-detail:/)
|
||||
})
|
||||
|
||||
test('AI mode waits for submit confirmation before adding submit action to the conversation', () => {
|
||||
const executeStart = aiMode.indexOf('async function executeInlineApplicationPreviewAction')
|
||||
const executeEnd = aiMode.indexOf('\nfunction handleInlineApplicationPreviewTextAction', executeStart)
|
||||
const executeBlock = aiMode.slice(executeStart, executeEnd)
|
||||
const confirmGateIndex = executeBlock.indexOf('if (isSubmit && !options.confirmed)')
|
||||
const requestConfirmIndex = executeBlock.indexOf('requestInlineApplicationSubmitConfirmation', confirmGateIndex)
|
||||
const confirmedActionPushIndex = executeBlock.indexOf('pushInlineApplicationActionUserMessage(userText)', requestConfirmIndex)
|
||||
|
||||
assert.ok(confirmGateIndex >= 0, '直接提交应先进入确认分支')
|
||||
assert.ok(requestConfirmIndex > confirmGateIndex, '直接提交确认分支应先打开确认弹窗')
|
||||
assert.ok(confirmedActionPushIndex > requestConfirmIndex, '确认弹窗打开前不应追加“直接提交”用户消息')
|
||||
assert.match(
|
||||
executeBlock,
|
||||
/requestInlineApplicationSubmitConfirmation\(targetMessage,\s*\{\s*\.\.\.options,\s*userText\s*\}\)/
|
||||
)
|
||||
|
||||
const confirmStart = aiMode.indexOf('function confirmInlineApplicationSubmit()')
|
||||
const confirmEnd = aiMode.indexOf('\nasync function runInlineApplicationSubmitPrecheck', confirmStart)
|
||||
const confirmBlock = aiMode.slice(confirmStart, confirmEnd)
|
||||
assert.match(confirmBlock, /userText:\s*context\.userText \|\| '直接提交'/)
|
||||
assert.match(confirmBlock, /skipUserMessage:\s*false/)
|
||||
|
||||
const cancelStart = aiMode.indexOf('function cancelInlineApplicationSubmitConfirm()')
|
||||
const cancelEnd = aiMode.indexOf('\nfunction confirmInlineApplicationSubmit', cancelStart)
|
||||
const cancelBlock = aiMode.slice(cancelStart, cancelEnd)
|
||||
assert.doesNotMatch(cancelBlock, /pushInlineUserMessage|pushInlineApplicationActionUserMessage/)
|
||||
})
|
||||
|
||||
test('AI mode formats saved application draft as a detail table without continuing submit flow', () => {
|
||||
assert.match(aiMode, /function buildInlineApplicationResultTable\(draftPayload = \{\}, options = \{\}\)/)
|
||||
assert.match(aiMode, /\| 单据类型 \| 单据编号 \| 单据状态 \| 当前节点 \| 操作 \|/)
|
||||
assert.match(aiMode, /\[查看\]\(\$\{href\}\)/)
|
||||
|
||||
const resultStart = aiMode.indexOf('function buildInlineApplicationPreviewActionResultText')
|
||||
const resultEnd = aiMode.indexOf('\nfunction buildInlineApplicationDetailAction', resultStart)
|
||||
const resultBlock = aiMode.slice(resultStart, resultEnd)
|
||||
const submitBranchIndex = resultBlock.indexOf('actionType === AI_APPLICATION_ACTION_SUBMIT')
|
||||
const saveBranchIndex = resultBlock.indexOf("'### 申请草稿已保存'")
|
||||
const saveBranch = resultBlock.slice(saveBranchIndex)
|
||||
|
||||
assert.ok(submitBranchIndex >= 0)
|
||||
assert.ok(saveBranchIndex > submitBranchIndex, '保存草稿结果应走非提交分支')
|
||||
assert.match(
|
||||
saveBranch,
|
||||
/buildInlineApplicationResultTable\(draftPayload,\s*\{[\s\S]*statusLabel:\s*'草稿'[\s\S]*stageLabel:\s*'待提交'/
|
||||
)
|
||||
assert.doesNotMatch(saveBranch, /进入审批流程/)
|
||||
|
||||
const executeStart = aiMode.indexOf('async function executeInlineApplicationPreviewAction')
|
||||
const executeEnd = aiMode.indexOf('\nfunction handleInlineApplicationPreviewTextAction', executeStart)
|
||||
const executeBlock = aiMode.slice(executeStart, executeEnd)
|
||||
assert.match(executeBlock, /targetMessage\.suggestedActions = \[\]/)
|
||||
assert.doesNotMatch(
|
||||
executeBlock,
|
||||
/targetMessage\.suggestedActions = isSubmit[\s\S]*buildInlineApplicationPreviewSuggestedActions\(targetMessage\.applicationPreview, draftPayload\)/
|
||||
)
|
||||
assert.match(executeBlock, /suggestedActions:\s*isSubmit\s*\?\s*buildInlineApplicationDetailAction\(draftPayload\)\s*:\s*\[\]/)
|
||||
})
|
||||
|
||||
test('AI mode locks application preview actions while estimate refresh is pending', () => {
|
||||
assert.match(aiMode, /function isApplicationPreviewEstimatePendingPreview\(applicationPreview = \{\}\)/)
|
||||
assert.match(
|
||||
aiMode,
|
||||
/function buildInlineApplicationPreviewSuggestedActions\(applicationPreview = \{\}, draftPayload = null\) \{[\s\S]*if \(isApplicationPreviewEstimatePendingPreview\(applicationPreview\)\) \{[\s\S]*return \[\]/
|
||||
)
|
||||
assert.match(aiMode, /const isAiModeInputLocked = computed\(\(\) => applicationPreviewEstimatePending\.value\)/)
|
||||
assert.match(aiMode, /:disabled="isAiModeInputLocked"/)
|
||||
assert.match(aiMode, /v-if="canShowInlineSuggestedActions\(message\)"/)
|
||||
assert.match(aiMode, /:disabled="isInlineSuggestedActionDisabled\(action, message\)"/)
|
||||
assert.match(
|
||||
aiMode,
|
||||
/message\.suggestedActions = \[\][\s\S]*const committed = await commitApplicationPreviewEditor\(message\)/
|
||||
)
|
||||
assert.match(
|
||||
aiMode,
|
||||
/if \(applicationPreviewEstimatePending\.value\) \{[\s\S]*toast\('请等待费用测算完成后再继续操作。'\)[\s\S]*return true/
|
||||
)
|
||||
assert.match(
|
||||
aiMode,
|
||||
/row\.editable && !isApplicationPreviewEstimatePending\(message\) \? 0 : -1/
|
||||
)
|
||||
assert.match(
|
||||
aiMode,
|
||||
/费用测算正在同步,请稍等,完成后才能保存草稿或直接提交。/
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user