feat: 增强风险规则生成引擎与预算中心页面
后端拆分风险规则生成为解释器、语义分析、本体对齐等子模块, 优化模板执行和流程图生成,完善员工种子数据和导入逻辑,增强 报销单权限策略和草稿持久化,前端新增预算中心视图和趋势图 组件,重构审计页面和风险规则测试对话框交互,完善文档中心 和报销创建页面细节,补充单元测试覆盖。
This commit is contained in:
@@ -4,6 +4,18 @@ import {
|
||||
buildUnsavedDraftAttachmentConfirmationMessage
|
||||
} from './travelReimbursementAttachmentModel.js'
|
||||
import { resolveAssistantScopeGuard } from '../../utils/assistantSessionScope.js'
|
||||
import {
|
||||
applyApplicationPolicyEstimateError,
|
||||
applyApplicationPolicyEstimateResult,
|
||||
buildApplicationPolicyEstimateRequest,
|
||||
buildLocalApplicationPreview,
|
||||
buildLocalApplicationPreviewMessage,
|
||||
buildModelRefinedApplicationPreview,
|
||||
shouldUseLocalApplicationPreview
|
||||
} from '../../utils/expenseApplicationPreview.js'
|
||||
import { fetchOntologyParse } from '../../services/ontology.js'
|
||||
import { buildExpenseApplicationOntologyContext } from '../../utils/expenseApplicationOntology.js'
|
||||
import { calculateTravelReimbursement } from '../../services/reimbursements.js'
|
||||
|
||||
export function useTravelReimbursementSubmitComposer(ctx) {
|
||||
const {
|
||||
@@ -46,6 +58,7 @@ export function useTravelReimbursementSubmitComposer(ctx) {
|
||||
fetchExpenseClaims,
|
||||
fileInputRef,
|
||||
flowRunId,
|
||||
insightPanelCollapsed,
|
||||
isKnowledgeSession,
|
||||
linkedRequest,
|
||||
mergeBusinessTimeIntoExtraContext,
|
||||
@@ -281,6 +294,73 @@ export function useTravelReimbursementSubmitComposer(ctx) {
|
||||
).trim()
|
||||
}
|
||||
|
||||
function buildApplicationPreviewReviewMeta(ontology) {
|
||||
return [
|
||||
'申请核对预览',
|
||||
String(ontology?.parse_strategy || '').trim() === 'llm_primary'
|
||||
? '模型复核完成'
|
||||
: '规则兜底复核'
|
||||
]
|
||||
}
|
||||
|
||||
async function buildApplicationPreviewWithModelReview(rawText) {
|
||||
const user = currentUser.value || {}
|
||||
const localPreview = buildLocalApplicationPreview(rawText, user)
|
||||
|
||||
const enrichWithPolicyEstimate = async (preview) => {
|
||||
const estimateRequest = buildApplicationPolicyEstimateRequest(preview, user)
|
||||
if (!estimateRequest.canCalculate) {
|
||||
return preview
|
||||
}
|
||||
try {
|
||||
const result = await calculateTravelReimbursement(estimateRequest.payload)
|
||||
return applyApplicationPolicyEstimateResult(preview, result, user)
|
||||
} catch (error) {
|
||||
console.warn('Application policy estimate failed:', error)
|
||||
return applyApplicationPolicyEstimateError(preview, error, user)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const ontology = await fetchOntologyParse(
|
||||
{
|
||||
query: rawText,
|
||||
user_id: user.username || user.name || 'anonymous',
|
||||
context_json: {
|
||||
...buildExpenseApplicationOntologyContext(user),
|
||||
session_type: activeSessionType.value,
|
||||
entry_source: props.entrySource,
|
||||
user_input_text: rawText
|
||||
}
|
||||
},
|
||||
{
|
||||
timeoutMs: 45000,
|
||||
timeoutMessage: '模型抽取申请字段超时,已保留当前本地预览。'
|
||||
}
|
||||
)
|
||||
|
||||
const refinedPreview = buildModelRefinedApplicationPreview(
|
||||
localPreview,
|
||||
ontology,
|
||||
rawText,
|
||||
user
|
||||
)
|
||||
return {
|
||||
applicationPreview: await enrichWithPolicyEstimate(refinedPreview),
|
||||
meta: buildApplicationPreviewReviewMeta(ontology)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Application preview model refinement failed:', error)
|
||||
return {
|
||||
applicationPreview: await enrichWithPolicyEstimate({
|
||||
...localPreview,
|
||||
modelReviewStatus: 'failed'
|
||||
}),
|
||||
meta: ['申请核对预览', '模型复核失败']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function submitComposer(options = {}) {
|
||||
if (submitting.value || sessionSwitchBusy.value) return null
|
||||
|
||||
@@ -388,6 +468,84 @@ export function useTravelReimbursementSubmitComposer(ctx) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (shouldUseLocalApplicationPreview(rawText, {
|
||||
sessionType: activeSessionType.value,
|
||||
attachmentCount: files.length,
|
||||
reviewAction,
|
||||
systemGenerated
|
||||
})) {
|
||||
const intentStartedAt = Date.now()
|
||||
const reviewStartedAt = intentStartedAt
|
||||
resetFlowRun()
|
||||
startFlowStep('intent', {
|
||||
title: '业务意图识别',
|
||||
tool: 'ontology.intent_detection',
|
||||
detail: '正在识别是否为费用申请事项...'
|
||||
})
|
||||
startFlowStep('application-review-preview', {
|
||||
title: '申请信息核对',
|
||||
tool: 'ontology.application_review',
|
||||
detail: '正在进行申请信息模型复核...'
|
||||
})
|
||||
if (!options.skipUserMessage) {
|
||||
messages.value.push(createMessage('user', userText, fileNames))
|
||||
}
|
||||
const pendingMessage = createMessage(
|
||||
'assistant',
|
||||
'正在进行申请信息模型复核。本步骤只识别意图和抽取字段,不会创建、更新或保存草稿。',
|
||||
[],
|
||||
{
|
||||
meta: ['模型复核中']
|
||||
}
|
||||
)
|
||||
messages.value.push(pendingMessage)
|
||||
composerDraft.value = ''
|
||||
composerBusinessTimeTags.value = []
|
||||
composerBusinessTimeDraftTouched.value = false
|
||||
clearAttachedFiles()
|
||||
if (fileInputRef.value) {
|
||||
fileInputRef.value.value = ''
|
||||
}
|
||||
nextTick(() => {
|
||||
adjustComposerTextareaHeight()
|
||||
scrollToBottom()
|
||||
})
|
||||
persistSessionState()
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const { applicationPreview, meta } = await buildApplicationPreviewWithModelReview(rawText)
|
||||
const reviewStatus = String(meta?.[1] || '').trim()
|
||||
completeFlowStep('intent', '已识别为费用申请事项', Date.now() - intentStartedAt)
|
||||
completeFlowStep(
|
||||
'application-review-preview',
|
||||
reviewStatus === '模型复核完成'
|
||||
? '模型复核完成,已生成申请核对表'
|
||||
: reviewStatus === '模型复核失败'
|
||||
? '模型复核失败,已生成临时核对表'
|
||||
: '模型未返回稳定结果,已完成规则兜底核对',
|
||||
Date.now() - reviewStartedAt
|
||||
)
|
||||
replaceMessage(pendingMessage.id, createMessage(
|
||||
'assistant',
|
||||
buildLocalApplicationPreviewMessage(applicationPreview),
|
||||
[],
|
||||
{
|
||||
meta,
|
||||
applicationPreview
|
||||
}
|
||||
))
|
||||
if (insightPanelCollapsed) {
|
||||
insightPanelCollapsed.value = true
|
||||
}
|
||||
persistSessionState()
|
||||
nextTick(scrollToBottom)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const hasUnsavedReviewDraft = Boolean(
|
||||
!isKnowledgeSession.value &&
|
||||
files.length &&
|
||||
|
||||
Reference in New Issue
Block a user