225 lines
8.0 KiB
TypeScript
225 lines
8.0 KiB
TypeScript
import { Role } from './roles';
|
|
|
|
/**
|
|
* All modules available in the permission matrix.
|
|
* Each module can have: view, create, update, archive, approve, export actions.
|
|
*/
|
|
export const MODULES = [
|
|
'dashboard',
|
|
'clients',
|
|
'subscriptions',
|
|
'invoices',
|
|
'payments',
|
|
'tickets',
|
|
'employees',
|
|
'payroll',
|
|
'expenses',
|
|
'assets',
|
|
'accounts',
|
|
'fund_transfers',
|
|
'accounting',
|
|
'reports',
|
|
'areas',
|
|
'plans',
|
|
'settings',
|
|
'users',
|
|
] as const;
|
|
|
|
export type Module = (typeof MODULES)[number];
|
|
|
|
export const MODULE_LABELS: Record<Module, string> = {
|
|
dashboard: 'Dashboard',
|
|
clients: 'Clients',
|
|
subscriptions: 'Subscriptions',
|
|
invoices: 'Invoices',
|
|
payments: 'Payments',
|
|
tickets: 'Tickets',
|
|
employees: 'Employees',
|
|
payroll: 'Payroll',
|
|
expenses: 'Expenses',
|
|
assets: 'Assets',
|
|
accounts: 'Company Accounts',
|
|
fund_transfers: 'Fund Transfers',
|
|
accounting: 'Accounting',
|
|
reports: 'Reports',
|
|
areas: 'Areas',
|
|
plans: 'Plans',
|
|
settings: 'Settings',
|
|
users: 'Users',
|
|
};
|
|
|
|
export const ACTIONS = ['canView', 'canCreate', 'canUpdate', 'canArchive', 'canApprove', 'canExport'] as const;
|
|
export type Action = (typeof ACTIONS)[number];
|
|
|
|
export const ACTION_LABELS: Record<Action, string> = {
|
|
canView: 'View',
|
|
canCreate: 'Create',
|
|
canUpdate: 'Update',
|
|
canArchive: 'Archive',
|
|
canApprove: 'Approve',
|
|
canExport: 'Export',
|
|
};
|
|
|
|
/**
|
|
* Defines which actions are applicable per module.
|
|
* Only these checkboxes should be shown/enforced in the matrix.
|
|
*/
|
|
export const MODULE_ACTIONS: Record<Module, readonly Action[]> = {
|
|
dashboard: ['canView'],
|
|
clients: ['canView', 'canCreate', 'canUpdate', 'canArchive', 'canExport'],
|
|
subscriptions: ['canView', 'canCreate', 'canUpdate', 'canArchive', 'canExport'],
|
|
invoices: ['canView', 'canCreate', 'canUpdate', 'canArchive', 'canApprove', 'canExport'],
|
|
payments: ['canView', 'canCreate', 'canUpdate', 'canApprove', 'canExport'],
|
|
tickets: ['canView', 'canCreate', 'canUpdate', 'canArchive', 'canExport'],
|
|
employees: ['canView', 'canCreate', 'canUpdate', 'canArchive', 'canExport'],
|
|
payroll: ['canView', 'canCreate', 'canUpdate', 'canApprove', 'canExport'],
|
|
expenses: ['canView', 'canCreate', 'canUpdate', 'canArchive', 'canApprove', 'canExport'],
|
|
assets: ['canView', 'canCreate', 'canUpdate', 'canArchive', 'canExport'],
|
|
accounts: ['canView', 'canCreate', 'canUpdate', 'canApprove', 'canExport'],
|
|
fund_transfers: ['canView', 'canCreate', 'canApprove', 'canExport'],
|
|
accounting: ['canView', 'canExport'],
|
|
reports: ['canView', 'canExport'],
|
|
areas: ['canView', 'canCreate', 'canUpdate', 'canArchive'],
|
|
plans: ['canView', 'canCreate', 'canUpdate', 'canArchive'],
|
|
settings: ['canView', 'canUpdate'],
|
|
users: ['canView', 'canCreate', 'canUpdate', 'canArchive'],
|
|
};
|
|
|
|
/**
|
|
* Permission matrix type — one row per module with boolean actions.
|
|
*/
|
|
export interface PermissionRow {
|
|
module: Module;
|
|
canView: boolean;
|
|
canCreate: boolean;
|
|
canUpdate: boolean;
|
|
canArchive: boolean;
|
|
canApprove: boolean;
|
|
canExport: boolean;
|
|
}
|
|
|
|
/**
|
|
* Default permission matrices for system roles seeded per tenant.
|
|
*/
|
|
function allTrue(modules: readonly Module[], actions: readonly Action[]): PermissionRow[] {
|
|
return MODULES.map((mod) => ({
|
|
module: mod,
|
|
canView: actions.includes('canView') && modules.includes(mod),
|
|
canCreate: actions.includes('canCreate') && modules.includes(mod),
|
|
canUpdate: actions.includes('canUpdate') && modules.includes(mod),
|
|
canArchive: actions.includes('canArchive') && modules.includes(mod),
|
|
canApprove: actions.includes('canApprove') && modules.includes(mod),
|
|
canExport: actions.includes('canExport') && modules.includes(mod),
|
|
}));
|
|
}
|
|
|
|
const ALL_MODULES = [...MODULES] as Module[];
|
|
|
|
export const DEFAULT_ROLE_PERMISSIONS: Record<string, PermissionRow[]> = {
|
|
tenant_admin: MODULES.map((mod) => ({
|
|
module: mod,
|
|
canView: true,
|
|
canCreate: true,
|
|
canUpdate: true,
|
|
canArchive: true,
|
|
canApprove: true,
|
|
canExport: true,
|
|
})),
|
|
|
|
manager: MODULES.map((mod) => {
|
|
const noAccess: Module[] = ['users'];
|
|
const viewOnly: Module[] = ['dashboard', 'accounting', 'settings'];
|
|
if (noAccess.includes(mod)) return { module: mod, canView: false, canCreate: false, canUpdate: false, canArchive: false, canApprove: false, canExport: false };
|
|
if (viewOnly.includes(mod)) return { module: mod, canView: true, canCreate: false, canUpdate: false, canArchive: false, canApprove: false, canExport: mod === 'accounting' };
|
|
return {
|
|
module: mod,
|
|
canView: true,
|
|
canCreate: true,
|
|
canUpdate: true,
|
|
canArchive: true,
|
|
canApprove: ['invoices', 'payments', 'expenses', 'payroll', 'fund_transfers'].includes(mod),
|
|
canExport: true,
|
|
};
|
|
}),
|
|
|
|
technician: MODULES.map((mod) => {
|
|
// Technicians can create/update tickets and record payments in the field.
|
|
// They can VIEW clients, subscriptions, invoices for context but creating/updating
|
|
// those requires manager-level API access (@Roles('manager') on POST/PATCH).
|
|
// Assets require manager via class-level @Roles('manager').
|
|
// Subscriptions require manager for all endpoints.
|
|
const viewOnly: Module[] = ['clients', 'subscriptions', 'invoices', 'dashboard'];
|
|
const fullAccess: Module[] = ['tickets', 'payments'];
|
|
if (viewOnly.includes(mod)) return { module: mod, canView: true, canCreate: false, canUpdate: false, canArchive: false, canApprove: false, canExport: false };
|
|
if (fullAccess.includes(mod)) return { module: mod, canView: true, canCreate: true, canUpdate: true, canArchive: false, canApprove: false, canExport: false };
|
|
return { module: mod, canView: false, canCreate: false, canUpdate: false, canArchive: false, canApprove: false, canExport: false };
|
|
}),
|
|
|
|
collector: MODULES.map((mod) => {
|
|
const canWrite: Module[] = ['payments'];
|
|
const canView: Module[] = ['dashboard', 'clients', 'invoices', 'payments'];
|
|
if (!canView.includes(mod)) return { module: mod, canView: false, canCreate: false, canUpdate: false, canArchive: false, canApprove: false, canExport: false };
|
|
return {
|
|
module: mod,
|
|
canView: true,
|
|
canCreate: canWrite.includes(mod),
|
|
canUpdate: false,
|
|
canArchive: false,
|
|
canApprove: false,
|
|
canExport: false,
|
|
};
|
|
}),
|
|
};
|
|
|
|
// ─── Legacy support (for super_admin which uses old UserRole system) ───
|
|
|
|
export const Permission = {
|
|
READ_DASHBOARD: 'read:dashboard',
|
|
READ_REPORTS: 'read:reports',
|
|
READ_CLIENTS: 'read:clients',
|
|
WRITE_CLIENTS: 'write:clients',
|
|
DELETE_CLIENTS: 'delete:clients',
|
|
READ_SUBSCRIPTIONS: 'read:subscriptions',
|
|
WRITE_SUBSCRIPTIONS: 'write:subscriptions',
|
|
READ_INVOICES: 'read:invoices',
|
|
WRITE_INVOICES: 'write:invoices',
|
|
VOID_INVOICES: 'void:invoices',
|
|
READ_PAYMENTS: 'read:payments',
|
|
WRITE_PAYMENTS: 'write:payments',
|
|
APPROVE_REMITTANCES: 'approve:remittances',
|
|
READ_TICKETS: 'read:tickets',
|
|
WRITE_TICKETS: 'write:tickets',
|
|
ASSIGN_TICKETS: 'assign:tickets',
|
|
READ_EMPLOYEES: 'read:employees',
|
|
WRITE_EMPLOYEES: 'write:employees',
|
|
READ_PAYROLL: 'read:payroll',
|
|
WRITE_PAYROLL: 'write:payroll',
|
|
READ_EXPENSES: 'read:expenses',
|
|
WRITE_EXPENSES: 'write:expenses',
|
|
APPROVE_EXPENSES: 'approve:expenses',
|
|
READ_ASSETS: 'read:assets',
|
|
WRITE_ASSETS: 'write:assets',
|
|
READ_ACCOUNTS: 'read:accounts',
|
|
WRITE_ACCOUNTS: 'write:accounts',
|
|
TRANSFER_ACCOUNTS: 'transfer:accounts',
|
|
READ_ACCOUNTING: 'read:accounting',
|
|
WRITE_ACCOUNTING: 'write:accounting',
|
|
READ_SETTINGS: 'read:settings',
|
|
WRITE_SETTINGS: 'write:settings',
|
|
MANAGE_USERS: 'manage:users',
|
|
MANAGE_TENANTS: 'manage:tenants',
|
|
} as const;
|
|
|
|
export type PermissionString = (typeof Permission)[keyof typeof Permission];
|
|
|
|
/**
|
|
* Get permissions for super_admin (all permissions).
|
|
*/
|
|
export function getPermissionsForRoles(roles: string[]): string[] {
|
|
if (roles.includes(Role.SUPER_ADMIN)) {
|
|
return Object.values(Permission);
|
|
}
|
|
// For tenant users, permissions come from TenantRole → RolePermission in DB
|
|
return [];
|
|
}
|