feat: 增强风险规则生成引擎与预算中心页面

后端拆分风险规则生成为解释器、语义分析、本体对齐等子模块,
优化模板执行和流程图生成,完善员工种子数据和导入逻辑,增强
报销单权限策略和草稿持久化,前端新增预算中心视图和趋势图
组件,重构审计页面和风险规则测试对话框交互,完善文档中心
和报销创建页面细节,补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-26 09:15:14 +08:00
parent d0e946cf47
commit 0e861d8fa6
150 changed files with 14953 additions and 4099 deletions

View File

@@ -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 &&