/** * Vela Permission System — Discord-style granular permissions. * Every permission is binary (on/off). View vs Manage split for every resource. * Hierarchy enforces who can manage whom. */ export type Permission = // ── Accounting & Finance ── | 'can_view_dashboard' | 'can_manage_invoices' | 'can_view_invoices' | 'can_manage_expenses' | 'can_view_expenses' | 'can_manage_bank_feeds' | 'can_view_bank_feeds' | 'can_manage_customers' | 'can_view_customers' | 'can_manage_vendors' | 'can_view_vendors' | 'can_view_reports' | 'can_export_financial_data' // ── People & HR ── | 'can_view_employees' | 'can_manage_employees' | 'can_delete_employees' | 'can_manage_departments' | 'can_manage_roles' | 'can_view_leave_balances' | 'can_approve_leave' | 'can_manage_leave_types' | 'can_view_payroll' | 'can_run_payroll' | 'can_manage_payroll_settings' | 'can_manage_performance' | 'can_view_performance' | 'can_manage_recruitment' | 'can_view_timesheets' | 'can_manage_timesheets' | 'can_submit_resignation' | 'can_review_resignation' | 'can_manage_contracts' // ── Workspace ── | 'can_view_noticeboard' | 'can_manage_noticeboard' | 'can_use_comms' | 'can_manage_comms_channels' | 'can_manage_documents' | 'can_view_documents' | 'can_manage_tasks' | 'can_manage_assets' | 'can_view_directory' | 'can_view_portal' // ── Admin & System ── | 'can_manage_org_settings' | 'can_manage_billing' | 'can_manage_integrations' | 'can_view_audit_log' | 'can_manage_webhooks' | 'can_manage_api_keys' | 'can_delete_org' | 'can_view_sent_emails' | 'can_send_emails' // ── God Mode ── | 'can_manage_admin' ; export interface PermissionDef { key: Permission; label: string; category: string; description: string; } export const PERMISSIONS_LIST: PermissionDef[] = [ // Accounting & Finance { key: 'can_view_dashboard', label: 'View dashboard', category: 'Accounting', description: 'See financial metrics and cash flow' }, { key: 'can_view_invoices', label: 'View invoices', category: 'Accounting', description: 'Read-only invoice access' }, { key: 'can_manage_invoices', label: 'Manage invoices', category: 'Accounting', description: 'Create, edit, send, delete invoices' }, { key: 'can_view_expenses', label: 'View expenses', category: 'Accounting', description: 'Read-only expense access' }, { key: 'can_manage_expenses', label: 'Manage expenses', category: 'Accounting', description: 'Submit, edit, approve expenses' }, { key: 'can_view_bank_feeds', label: 'View bank feeds', category: 'Accounting', description: 'Read bank reconciliation data' }, { key: 'can_manage_bank_feeds', label: 'Manage bank feeds', category: 'Accounting', description: 'Connect banks, reconcile transactions' }, { key: 'can_view_customers', label: 'View customers', category: 'Accounting', description: 'Read customer/client directory' }, { key: 'can_manage_customers', label: 'Manage customers', category: 'Accounting', description: 'Create, edit, delete customer records' }, { key: 'can_view_vendors', label: 'View vendors', category: 'Accounting', description: 'Read vendor/supplier directory' }, { key: 'can_manage_vendors', label: 'Manage vendors', category: 'Accounting', description: 'Add/edit vendor records' }, { key: 'can_view_reports', label: 'View reports', category: 'Accounting', description: 'Access P&L, balance sheet, financial reports' }, { key: 'can_export_financial_data', label: 'Export financial data', category: 'Accounting', description: 'Download CSV/PDF of financial records' }, // People & HR { key: 'can_view_employees', label: 'View employees', category: 'People', description: 'See employee directory & profiles' }, { key: 'can_manage_employees', label: 'Manage employees', category: 'People', description: 'Create, edit employee records' }, { key: 'can_delete_employees', label: 'Delete employees', category: 'People', description: 'Remove employees from org' }, { key: 'can_manage_departments', label: 'Manage departments', category: 'People', description: 'Create, rename, delete departments' }, { key: 'can_manage_roles', label: 'Manage roles', category: 'People', description: 'Create/edit/delete custom roles & permissions' }, { key: 'can_view_leave_balances', label: 'View leave balances', category: 'People', description: 'See team leave balances & calendars' }, { key: 'can_approve_leave', label: 'Approve leave', category: 'People', description: 'Approve/reject leave requests' }, { key: 'can_manage_leave_types', label: 'Manage leave types', category: 'People', description: 'Add/edit leave type definitions' }, { key: 'can_view_payroll', label: 'View payroll', category: 'People', description: 'See payroll runs & payslips' }, { key: 'can_run_payroll', label: 'Run payroll', category: 'People', description: 'Create & execute payroll runs' }, { key: 'can_manage_payroll_settings', label: 'Manage payroll settings', category: 'People', description: 'Edit tax rates, jurisdictions, deductions' }, { key: 'can_manage_performance', label: 'Manage performance', category: 'People', description: 'Create & manage appraisals/reviews' }, { key: 'can_view_performance', label: 'View performance', category: 'People', description: 'See performance reviews' }, { key: 'can_manage_recruitment', label: 'Manage recruitment', category: 'People', description: 'Post jobs, manage applicants, hiring' }, { key: 'can_view_timesheets', label: 'View timesheets', category: 'People', description: 'See team time entries' }, { key: 'can_manage_timesheets', label: 'Manage timesheets', category: 'People', description: 'Edit/approve time entries' }, { key: 'can_submit_resignation', label: 'Submit resignation', category: 'People', description: 'Submit own resignation' }, { key: 'can_review_resignation', label: 'Review resignations', category: 'People', description: 'Accept/reject resignation requests' }, { key: 'can_manage_contracts', label: 'Manage contracts', category: 'People', description: 'Create, edit, send employment contracts' }, // Workspace { key: 'can_view_noticeboard', label: 'View noticeboard', category: 'Workspace', description: 'Read announcements' }, { key: 'can_manage_noticeboard', label: 'Manage noticeboard', category: 'Workspace', description: 'Create, pin, delete announcements' }, { key: 'can_use_comms', label: 'Use messaging', category: 'Workspace', description: 'Send/receive team messages' }, { key: 'can_manage_comms_channels', label: 'Manage channels', category: 'Workspace', description: 'Create, edit, delete chat channels' }, { key: 'can_view_documents', label: 'View documents', category: 'Workspace', description: 'Browse & download files' }, { key: 'can_manage_documents', label: 'Manage documents', category: 'Workspace', description: 'Upload, organize, delete files' }, { key: 'can_manage_tasks', label: 'Manage tasks', category: 'Workspace', description: 'Create, assign, complete tasks' }, { key: 'can_manage_assets', label: 'Manage assets', category: 'Workspace', description: 'Track & assign assets' }, { key: 'can_view_directory', label: 'View directory', category: 'Workspace', description: 'See employee contact directory' }, { key: 'can_view_portal', label: 'View portal', category: 'Workspace', description: 'Access self-service portal' }, // Admin & System { key: 'can_manage_org_settings', label: 'Manage org settings', category: 'Admin', description: 'Edit company name, slug, country' }, { key: 'can_manage_billing', label: 'Manage billing', category: 'Admin', description: 'Change plan, manage payment methods' }, { key: 'can_manage_integrations', label: 'Manage integrations', category: 'Admin', description: 'Connect third-party apps' }, { key: 'can_view_audit_log', label: 'View audit log', category: 'Admin', description: 'See who changed what & when' }, { key: 'can_manage_webhooks', label: 'Manage webhooks', category: 'Admin', description: 'Configure outbound webhooks' }, { key: 'can_manage_api_keys', label: 'Manage API keys', category: 'Admin', description: 'Generate/revoke API access keys' }, { key: 'can_delete_org', label: 'Delete organisation', category: 'Admin', description: 'Permanently delete the entire org' }, { key: 'can_view_sent_emails', label: 'View sent emails', category: 'Admin', description: 'See email log of sent correspondence' }, { key: 'can_send_emails', label: 'Send emails', category: 'Admin', description: 'Send client-facing emails' }, // God Mode { key: 'can_manage_admin', label: 'Administrator', category: 'Admin', description: 'Grants ALL permissions automatically' }, ]; export const PERMISSION_CATEGORIES = ['Accounting', 'People', 'Workspace', 'Admin']; // Page → required permissions map (any one of these grants access) export const PAGE_PERMISSIONS: Record = { home: [], invoices: ['can_view_invoices', 'can_manage_invoices'], expenses: ['can_view_expenses', 'can_manage_expenses'], 'bank-feeds': ['can_view_bank_feeds', 'can_manage_bank_feeds'], reports: ['can_view_reports'], employees: ['can_view_employees', 'can_manage_employees'], roles: ['can_manage_roles'], payroll: ['can_view_payroll', 'can_run_payroll'], leave: ['can_view_leave_balances', 'can_approve_leave'], performance: ['can_view_performance', 'can_manage_performance'], documents: ['can_view_documents', 'can_manage_documents'], recruitment: ['can_manage_recruitment'], tasks: ['can_manage_tasks'], noticeboard: ['can_view_noticeboard'], comms: ['can_use_comms'], directory: ['can_view_directory'], assets: ['can_manage_assets'], portal: ['can_view_portal'], clients: ['can_view_customers', 'can_manage_customers'], settings: ['can_manage_org_settings'], pricing: [], privacy: [], terms: [], }; // Default hierarchy levels for built-in org roles export const BUILTIN_ROLE_LEVELS: Record = { owner: 100, admin: 99, it_team: 98, hr_manager: 5, accountant: 5, employee: 1, guest: 0, }; // Default permissions for built-in org roles // Owner, Admin, IT have bypass (no explicit perms needed) export const BUILTIN_ROLE_PERMISSIONS: Record = { owner: [], // bypass admin: [], // bypass it_team: [], // bypass accountant: [ 'can_view_dashboard', 'can_view_invoices', 'can_manage_invoices', 'can_view_expenses', 'can_manage_expenses', 'can_view_bank_feeds', 'can_view_customers', 'can_view_vendors', 'can_view_reports', 'can_export_financial_data', 'can_view_payroll', 'can_use_comms', 'can_view_noticeboard', 'can_view_directory', 'can_view_portal', 'can_view_sent_emails', 'can_send_emails', ], hr_manager: [ 'can_view_employees', 'can_manage_employees', 'can_view_leave_balances', 'can_approve_leave', 'can_manage_leave_types', 'can_manage_performance', 'can_view_performance', 'can_review_resignation', 'can_manage_contracts', 'can_use_comms', 'can_view_noticeboard', 'can_manage_noticeboard', 'can_view_directory', 'can_view_portal', 'can_submit_resignation', 'can_manage_departments', 'can_view_timesheets', ], employee: [ 'can_use_comms', 'can_view_noticeboard', 'can_view_directory', 'can_view_portal', 'can_submit_resignation', 'can_view_performance', 'can_view_leave_balances', 'can_manage_timesheets', ], guest: [], // newly joined users — no access until reassigned }; /** Roles that bypass permission checks entirely */ export function hasBypassRole(role: string): boolean { return role === 'owner' || role === 'admin' || role === 'it_team'; } export function hasBuiltInPermission(builtInRole: string, permission: Permission): boolean { if (hasBypassRole(builtInRole)) return true; return (BUILTIN_ROLE_PERMISSIONS[builtInRole] || []).includes(permission); } export function getHierarchyLevel(role: string): number { return BUILTIN_ROLE_LEVELS[role] || 0; } /** * If a role has can_manage_admin, resolve to ALL permissions. * This is the "god mode" — one permission that grants everything. */ export function resolvePermissions(raw: Set): Set { if (raw.has('can_manage_admin')) { return new Set(PERMISSIONS_LIST.map((p) => p.key)); } return raw; } /** * Permission dependency map. * Key = higher-power permission, value = permissions it depends on. * Enabling the key auto-enables all dependencies. * Disabling a dependency auto-disables the key. */ export const PERMISSION_DEPENDENCIES: Record = { can_manage_invoices: ['can_view_invoices'], can_manage_expenses: ['can_view_expenses'], can_manage_bank_feeds: ['can_view_bank_feeds'], can_manage_customers: ['can_view_customers'], can_manage_vendors: ['can_view_vendors'], can_manage_employees: ['can_view_employees'], can_delete_employees: ['can_view_employees'], can_manage_leave_types: ['can_view_leave_balances'], can_run_payroll: ['can_view_payroll'], can_manage_payroll_settings: ['can_view_payroll'], can_manage_performance: ['can_view_performance'], can_manage_recruitment: ['can_view_employees'], can_manage_timesheets: ['can_view_timesheets'], can_review_resignation: ['can_view_employees'], can_manage_contracts: ['can_view_employees'], can_manage_noticeboard: ['can_view_noticeboard'], can_manage_documents: ['can_view_documents'], can_manage_comms_channels: ['can_use_comms'], }; /** * Given a set of permissions, enforce dependency rules: * - If a parent is enabled, auto-enable all its dependencies * - If a dependency is disabled, auto-disable any parent that needs it * - If can_manage_admin is present, return ALL permissions */ export function enforceDependencies(raw: Set): Set { const result = new Set(raw); // God mode — skip all dependency logic if (result.has('can_manage_admin')) { return new Set(PERMISSIONS_LIST.map((p) => p.key)); } let changed = true; while (changed) { changed = false; // Pass 1: enable dependencies for parents for (const [parent, deps] of Object.entries(PERMISSION_DEPENDENCIES)) { if (result.has(parent as Permission)) { for (const dep of deps) { if (!result.has(dep)) { result.add(dep); changed = true; } } } } // Pass 2: disable parents whose dependencies were removed for (const [parent, deps] of Object.entries(PERMISSION_DEPENDENCIES)) { if (result.has(parent as Permission)) { for (const dep of deps) { if (!result.has(dep)) { result.delete(parent as Permission); changed = true; } } } } } return result; } /** * Build a map of dependency → [parents] for UI. * When toggling off a dependency, we need to know which parents to auto-disable. */ export const DEPENDENT_MAP: Record = {}; for (const [parent, deps] of Object.entries(PERMISSION_DEPENDENCIES)) { for (const dep of deps) { if (!DEPENDENT_MAP[dep]) DEPENDENT_MAP[dep] = []; DEPENDENT_MAP[dep].push(parent as Permission); } } // Default custom roles to seed on org creation export interface DefaultRoleDef { name: string; hierarchyLevel: number; color: string; permissions: Permission[]; canManageLower: boolean; } export const DEFAULT_CUSTOM_ROLES: DefaultRoleDef[] = [ { name: 'Finance', hierarchyLevel: 5, color: '#10B981', permissions: [ 'can_view_dashboard', 'can_view_invoices', 'can_manage_invoices', 'can_view_expenses', 'can_manage_expenses', 'can_view_bank_feeds', 'can_view_customers', 'can_manage_customers', 'can_view_vendors', 'can_view_reports', 'can_export_financial_data', 'can_use_comms', 'can_view_noticeboard', 'can_view_directory', 'can_view_portal', 'can_view_sent_emails', 'can_send_emails', ], canManageLower: false, }, { name: 'HR', hierarchyLevel: 5, color: '#8B5CF6', permissions: [ 'can_view_employees', 'can_manage_employees', 'can_view_leave_balances', 'can_approve_leave', 'can_manage_leave_types', 'can_manage_performance', 'can_view_performance', 'can_review_resignation', 'can_manage_contracts', 'can_use_comms', 'can_view_noticeboard', 'can_manage_noticeboard', 'can_view_directory', 'can_view_portal', 'can_manage_departments', 'can_view_timesheets', ], canManageLower: false, }, { name: 'Operations', hierarchyLevel: 5, color: '#F59E0B', permissions: [ 'can_view_dashboard', 'can_view_invoices', 'can_view_expenses', 'can_use_comms', 'can_view_noticeboard', 'can_manage_noticeboard', 'can_view_directory', 'can_view_portal', 'can_manage_documents', 'can_view_documents', 'can_manage_tasks', ], canManageLower: false, }, ];