feat: 增加差旅报销标准测算和财务终审流程

新增差旅报销测算接口及 Spreadsheet 规则解析,审批流程拆分
直属领导审批与财务终审两阶段并细分权限,修复 PDF 文本层
缺失时自动回退 OCR,提交后清理关联会话,前端适配审批流
交互并补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-21 09:28:33 +08:00
parent 002bf4f756
commit 8f65661809
43 changed files with 4366 additions and 410 deletions

View File

@@ -19,8 +19,9 @@ const VIEW_ROLE_RULES = {
employees: ['manager'],
settings: ['manager']
}
const CLAIM_MANAGER_ROLE_CODES = new Set(['finance', 'executive'])
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'])
function normalizedRoleCodes(user) {
if (!user) {
@@ -60,6 +61,14 @@ export function canReturnExpenseClaims(user) {
return normalizedRoleCodes(user).some((roleCode) => CLAIM_RETURN_ROLE_CODES.has(roleCode))
}
export function canApproveLeaderExpenseClaims(user) {
if (Boolean(user?.isAdmin)) {
return true
}
return normalizedRoleCodes(user).some((roleCode) => CLAIM_LEADER_APPROVAL_ROLE_CODES.has(roleCode))
}
export function canAccessAppView(user, viewId) {
if (!viewId || !user) {
return false

View File

@@ -1,5 +1,9 @@
import { mapExpenseClaimToRequest } from '../composables/useRequests.js'
import { canManageExpenseClaims } from './accessControl.js'
import {
canApproveLeaderExpenseClaims,
canManageExpenseClaims,
isFinanceUser
} from './accessControl.js'
export function canProcessApprovalRequest(request, currentUser) {
const node = String(request?.workflowNode || '').trim()
@@ -14,12 +18,18 @@ export function canProcessApprovalRequest(request, currentUser) {
return true
}
return (
if (isFinanceUser(currentUser) && node.includes('财务')) {
return true
}
const isLeaderApprovalNode = (
node.includes('直属领导')
|| node.includes('领导审批')
|| node.includes('部门负责人')
|| node.includes('负责人审批')
)
return canApproveLeaderExpenseClaims(currentUser) && isLeaderApprovalNode
}
export function listPendingApprovalRequests(claimsPayload, currentUser) {

View File

@@ -181,6 +181,21 @@ function normalizeRoleLabels(value) {
return text ? [text] : []
}
function isEmailLike(value) {
return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(String(value || '').trim())
}
function resolveDisplayName(...values) {
for (const value of values) {
const normalized = String(value || '').trim()
if (normalized && !isEmailLike(normalized)) {
return normalized
}
}
return ''
}
export function normalizeRequestForUi(request) {
if (!request) {
return null
@@ -255,7 +270,12 @@ export function normalizeRequestForUi(request) {
String(request.profilePosition || request.employeePosition || request.employee_position || request.position || '').trim()
|| '待补充',
profileGrade: String(request.profileGrade || request.employeeGrade || request.employee_grade || request.grade || '').trim() || '待补充',
profileManager: String(request.profileManager || request.managerName || request.manager_name || request.manager || '').trim() || '待补充',
profileManager: resolveDisplayName(
request.profileManager,
request.managerName,
request.manager_name,
request.manager
) || '待补充',
roleLabels,
profileAvatar:
String(request.person || request.applicant || request.employeeName || '申').trim().slice(0, 1) || '申'