export const DEFAULT_APP_VIEW_ORDER = [ 'workbench', 'documents', 'budget', 'audit', 'overview', 'policies', 'digitalEmployees', 'logs', 'employees', 'settings' ] const ALWAYS_VISIBLE_VIEWS = new Set(['workbench', 'documents', 'policies']) const VIEW_ROLE_RULES = { overview: ['finance', 'executive'], budget: ['budget_monitor', 'executive'], audit: ['finance'], digitalEmployees: ['finance'], logs: ['manager'], employees: ['manager'], settings: ['manager'] } 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) { return [] } return Array.isArray(user.roleCodes) ? user.roleCodes .map((item) => normalizeRoleCode(item)) .filter(Boolean) : [] } function normalizeRoleCode(value) { const roleCode = String(value || '').trim().toLowerCase() return roleCode === 'auditor' ? 'budget_monitor' : roleCode } function normalizeComparableText(value) { return String(value || '').trim() } function collectIdentityNames(...values) { return values .map((value) => normalizeComparableText(value)) .filter(Boolean) } function identityIntersects(leftValues, rightValues) { const rightSet = new Set(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 } const username = String(user.username || user.account || '').trim().toLowerCase() const role = String(user.role || '').trim().toLowerCase() const roleCodes = normalizedRoleCodes(user) return ( Boolean(user.isAdmin) || username === 'admin' || role === 'admin' || role === '管理员' || role === '系统管理员' || roleCodes.includes('admin') ) } export function isManagerUser(user) { return hasPlatformAdminIdentity(user) || normalizedRoleCodes(user).includes('manager') } export function isPlatformAdminUser(user) { return hasPlatformAdminIdentity(user) } export function isFinanceUser(user) { return normalizedRoleCodes(user).includes('finance') } export function isExecutiveUser(user) { return normalizedRoleCodes(user).includes('executive') } export function isBudgetMonitorUser(user) { return normalizedRoleCodes(user).includes('budget_monitor') } export function canEditBudgetCenter(user) { return isPlatformAdminUser(user) || isExecutiveUser(user) } export function canSwitchBudgetDepartments(user) { return isPlatformAdminUser(user) || isExecutiveUser(user) } export function canManageExpenseClaims(user) { if (isPlatformAdminUser(user)) { return true } return normalizedRoleCodes(user).some((roleCode) => CLAIM_MANAGER_ROLE_CODES.has(roleCode)) } export function canDeleteArchivedExpenseClaims(user) { return isPlatformAdminUser(user) } export function canReturnExpenseClaims(user) { if (isPlatformAdminUser(user)) { return true } return normalizedRoleCodes(user).some((roleCode) => CLAIM_RETURN_ROLE_CODES.has(roleCode)) } export function canApproveLeaderExpenseClaims(user) { if (isPlatformAdminUser(user)) { return true } 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, request?.employeeName, request?.employee_name, request?.profileName, request?.applicant ) const currentNames = collectIdentityNames( user?.name, user?.username, user?.email, user?.employeeNo, user?.employee_no ) return applicantNames.length > 0 && identityIntersects(applicantNames, currentNames) } export function isCurrentDirectManagerForRequest(request, user) { if (isCurrentRequestApplicant(request, user)) { return false } const managerNames = collectIdentityNames( request?.profileManager, request?.managerName, request?.manager_name, request?.directManagerName, request?.direct_manager_name, request?.manager ) const currentNames = collectIdentityNames( user?.name, user?.username, user?.email, user?.employeeNo, user?.employee_no ) return managerNames.length > 0 && identityIntersects(managerNames, currentNames) } export function canAccessAppView(user, viewId) { if (!viewId || !user) { return false } if (!DEFAULT_APP_VIEW_ORDER.includes(viewId)) { return false } if (viewId === 'budget') { if (isPlatformAdminUser(user)) { return true } const roleCodes = normalizedRoleCodes(user) return VIEW_ROLE_RULES.budget.some((roleCode) => roleCodes.includes(roleCode)) } if (isManagerUser(user)) { return true } if (ALWAYS_VISIBLE_VIEWS.has(viewId)) { return true } const requiredRoles = VIEW_ROLE_RULES[viewId] || [] const roleCodes = normalizedRoleCodes(user) return requiredRoles.some((roleCode) => roleCodes.includes(roleCode)) } export function getAccessibleViewIds(user) { return DEFAULT_APP_VIEW_ORDER.filter((viewId) => canAccessAppView(user, viewId)) } export function filterNavItemsByAccess(navItems, user) { return navItems.filter((item) => canAccessAppView(user, item.id)) } export function resolveDefaultAuthorizedRoute(user) { const firstVisibleView = getAccessibleViewIds(user)[0] return { name: `app-${firstVisibleView || 'workbench'}` } }