initial: standalone repo from monorepo split
This commit is contained in:
224
packages/shared/src/constants/permissions.ts
Normal file
224
packages/shared/src/constants/permissions.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
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 [];
|
||||
}
|
||||
Reference in New Issue
Block a user