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

@@ -19,9 +19,10 @@ const VIEW_ROLE_RULES = {
employees: ['manager'],
settings: ['manager']
}
const CLAIM_MANAGER_ROLE_CODES = new Set(['executive'])
const CLAIM_RETURN_ROLE_CODES = new Set(['finance', 'executive', 'manager', 'approver'])
const CLAIM_LEADER_APPROVAL_ROLE_CODES = new Set(['manager', 'approver'])
const CLAIM_MANAGER_ROLE_CODES = new Set(['executive'])
const CLAIM_RETURN_ROLE_CODES = new Set(['finance', 'executive', 'manager', 'approver', 'budget_monitor'])
const CLAIM_LEADER_APPROVAL_ROLE_CODES = new Set(['manager', 'approver'])
const CLAIM_BUDGET_APPROVAL_GRADE = 'P8'
function normalizedRoleCodes(user) {
if (!user) {
@@ -55,6 +56,25 @@ function identityIntersects(leftValues, rightValues) {
return leftValues.some((item) => rightSet.has(item))
}
function normalizedGrade(user) {
return String(user?.grade || user?.employeeGrade || '').trim().toUpperCase()
}
function departmentIntersects(request, user) {
const requestDepartments = collectIdentityNames(
request?.dept,
request?.departmentName,
request?.department_name
)
const currentDepartments = collectIdentityNames(
user?.department,
user?.departmentName,
user?.department_name
)
return requestDepartments.length > 0 && identityIntersects(requestDepartments, currentDepartments)
}
function hasPlatformAdminIdentity(user) {
if (!user) {
return false
@@ -130,6 +150,25 @@ export function canApproveLeaderExpenseClaims(user) {
return normalizedRoleCodes(user).some((roleCode) => CLAIM_LEADER_APPROVAL_ROLE_CODES.has(roleCode))
}
export function canApproveBudgetExpenseApplications(user, request = null) {
if (isPlatformAdminUser(user)) {
return true
}
const roleCodes = normalizedRoleCodes(user)
if (roleCodes.includes('executive')) {
return true
}
if (!roleCodes.includes('budget_monitor')) {
return false
}
if (normalizedGrade(user) !== CLAIM_BUDGET_APPROVAL_GRADE) {
return false
}
return request ? departmentIntersects(request, user) : true
}
export function isCurrentRequestApplicant(request, user) {
const applicantNames = collectIdentityNames(
request?.person,