feat: 新增预算费控模型与报销审批流引擎

后端新增预算费控服务和报销单审批流模块,引入申请人费用画像
算法,优化知识库 RAG 运行时和同步逻辑,完善报销单工作流常
量和明细同步,更新差旅报销规则电子表格,前端新增预算分析
组件和数字员工模型,完善审批对话框和洞察面板交互,优化侧
边栏和顶栏样式,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-27 17:31:27 +08:00
parent cbb98f4469
commit d4d5d40569
75 changed files with 5393 additions and 686 deletions

View File

@@ -57,6 +57,7 @@ const REIMBURSEMENT_PROGRESS_LABELS = [
const APPLICATION_PROGRESS_LABELS = [
'创建申请',
'直属领导审批',
'预算管理者审批',
'审批完成'
]
@@ -386,10 +387,13 @@ function resolveApplicationProgressCurrentIndex(approvalMeta, workflowNode) {
const normalizedNode = String(workflowNode || '').trim()
if (approvalMeta.key === 'completed') {
return 2
return 3
}
if (normalizedNode.includes('审批完成') || normalizedNode.includes('申请完成')) {
return 3
}
if (normalizedNode.includes('预算')) {
return 2
}
if (
@@ -437,15 +441,42 @@ function resolveApplicationApproverName(claim) {
) || '直属领导'
}
function resolveApplicationBudgetApproverName(claim) {
const routeEvent = findApprovalEventForStep(claim, '直属领导审批')
return resolveDisplayName(
routeEvent?.next_approver_name,
routeEvent?.nextApproverName,
routeEvent?.budget_approver_name,
routeEvent?.budgetApproverName
) || 'P8预算监控者'
}
function resolveProgressDisplayLabel(label, documentTypeCode, claim, approvalMeta) {
const normalizedLabel = normalizeText(label)
const workflowNode = normalizeText(claim?.approval_stage || claim?.workflowNode)
if (
documentTypeCode === DOCUMENT_TYPE_APPLICATION
&& approvalMeta.key !== 'completed'
&& normalizeText(label) === '直属领导审批'
&& normalizedLabel === '直属领导审批'
&& (
workflowNode.includes('直属领导')
|| workflowNode.includes('领导审批')
|| workflowNode.includes('部门负责人')
|| workflowNode.includes('负责人审批')
)
) {
return `等待 ${resolveApplicationApproverName(claim)} 批复`
}
if (
documentTypeCode === DOCUMENT_TYPE_APPLICATION
&& approvalMeta.key !== 'completed'
&& normalizedLabel === '预算管理者审批'
&& workflowNode.includes('预算')
) {
return `等待 ${resolveApplicationBudgetApproverName(claim)} 批复`
}
return label
}
@@ -471,7 +502,7 @@ function findApprovalEventForStep(claim, label) {
}
const source = normalizeText(flag.source)
if (!['manual_approval', 'finance_approval'].includes(source)) {
if (!['manual_approval', 'budget_approval', 'finance_approval'].includes(source)) {
return false
}
@@ -482,10 +513,19 @@ function findApprovalEventForStep(claim, label) {
return (
previousStage.includes('直属领导')
|| previousStage.includes('领导审批')
|| nextStage.includes('预算')
|| nextStage.includes('财务')
)
}
if (stepLabel === '预算管理者审批') {
return (
source === 'budget_approval'
|| previousStage.includes('预算')
|| nextStage.includes('审批完成')
)
}
if (stepLabel === '财务审批') {
return (
previousStage.includes('财务')
@@ -557,15 +597,16 @@ function buildCompletedStepMeta(claim, label) {
return buildProgressStepMeta('AI预审通过', reviewedAt)
}
if (stepLabel === '直属领导审批' || stepLabel === '财务审批') {
if (stepLabel === '直属领导审批' || stepLabel === '预算管理者审批' || stepLabel === '财务审批') {
const approvalEvent = findApprovalEventForStep(claim, stepLabel)
if (approvalEvent) {
const operator = resolveDisplayName(
approvalEvent.operator,
approvalEvent.operator_name,
approvalEvent.operatorName,
stepLabel === '直属领导审批' ? claim?.manager_name : ''
) || (stepLabel === '财务审批' ? '财务' : '直属领导')
stepLabel === '直属领导审批' ? claim?.manager_name : '',
stepLabel === '预算管理者审批' ? approvalEvent.next_approver_name : ''
) || (stepLabel === '财务审批' ? '财务' : stepLabel === '预算管理者审批' ? '预算监控者' : '直属领导')
const approvedAt = formatDateTime(approvalEvent.created_at || approvalEvent.createdAt)
return buildProgressStepMeta(`${operator}通过`, approvedAt, `${operator}审批通过 ${approvedAt}`.trim())
}
@@ -626,6 +667,10 @@ function resolveCurrentStepStartedAt(claim, label) {
if (stepLabel === '直属领导审批') {
return claim?.submitted_at || claim?.updated_at || claim?.created_at
}
if (stepLabel === '预算管理者审批') {
const leaderApprovalEvent = findApprovalEventForStep(claim, '直属领导审批')
return leaderApprovalEvent?.created_at || leaderApprovalEvent?.createdAt || claim?.updated_at || claim?.submitted_at
}
if (stepLabel === '财务审批') {
const leaderApprovalEvent = findApprovalEventForStep(claim, '直属领导审批')
return leaderApprovalEvent?.created_at || leaderApprovalEvent?.createdAt || claim?.updated_at || claim?.submitted_at