feat: add flexible capability-based RBAC with per-tenant customization
Introduces a capability layer on top of existing roles that controls feature visibility and access. Capabilities follow an area.feature.action taxonomy (~35 capabilities) with sensible defaults per role. Tenant admins can customize via grant/revoke overrides stored in org settings JSONB. Key changes: - Add vice_president role to DB schema - Backend: capability constants, resolution logic, CapabilityGuard (global), @RequireCapability decorator on all 16 tenant controllers - Frontend: permission hooks (useCanEdit, useHasCapability), CapabilityGate component, sidebar filtering by capability, all 17 pages migrated from useIsReadOnly to capability-based checks - New admin UI: /settings/permissions matrix page for per-tenant role customization with grant/revoke delta model - GET /organizations/my-capabilities endpoint for capability refresh - Validation of permissionOverrides in settings updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { Controller, Get, Put, Post, Body, Param, Query, Res, UseGuards, ParseIn
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { RequireCapability } from '../../common/decorators/capability.decorator';
|
||||
import { BudgetsService } from './budgets.service';
|
||||
import { UpsertBudgetDto } from './dto/upsert-budget.dto';
|
||||
|
||||
@@ -14,6 +15,7 @@ export class BudgetsController {
|
||||
|
||||
@Post(':year/import')
|
||||
@ApiOperation({ summary: 'Import budget data from parsed CSV/XLSX lines' })
|
||||
@RequireCapability('financials.budgets.edit')
|
||||
importBudget(
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Body() lines: any[],
|
||||
@@ -23,6 +25,7 @@ export class BudgetsController {
|
||||
|
||||
@Get(':year/template')
|
||||
@ApiOperation({ summary: 'Download budget CSV template for a fiscal year' })
|
||||
@RequireCapability('financials.budgets.view')
|
||||
async getTemplate(
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Res() res: Response,
|
||||
@@ -37,6 +40,7 @@ export class BudgetsController {
|
||||
|
||||
@Get(':year/vs-actual')
|
||||
@ApiOperation({ summary: 'Budget vs actual comparison' })
|
||||
@RequireCapability('financials.budgets.view')
|
||||
budgetVsActual(
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Query('month') month?: string,
|
||||
@@ -46,12 +50,14 @@ export class BudgetsController {
|
||||
|
||||
@Get(':year')
|
||||
@ApiOperation({ summary: 'Get budgets for a fiscal year' })
|
||||
@RequireCapability('financials.budgets.view')
|
||||
findByYear(@Param('year', ParseIntPipe) year: number) {
|
||||
return this.budgetsService.findByYear(year);
|
||||
}
|
||||
|
||||
@Put(':year')
|
||||
@ApiOperation({ summary: 'Upsert budgets for a fiscal year' })
|
||||
@RequireCapability('financials.budgets.edit')
|
||||
upsert(
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Body() budgets: UpsertBudgetDto[],
|
||||
|
||||
Reference in New Issue
Block a user