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:
@@ -3,6 +3,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { RequireCapability } from '../../common/decorators/capability.decorator';
|
||||
import { AccountsService } from './accounts.service';
|
||||
import { CreateAccountDto } from './dto/create-account.dto';
|
||||
import { UpdateAccountDto } from './dto/update-account.dto';
|
||||
@@ -16,24 +17,28 @@ export class AccountsController {
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'List all accounts' })
|
||||
@RequireCapability('financials.accounts.view')
|
||||
findAll(@Query('fundType') fundType?: string, @Query('includeArchived') includeArchived?: string) {
|
||||
return this.accountsService.findAll(fundType, includeArchived === 'true');
|
||||
}
|
||||
|
||||
@Get('trial-balance')
|
||||
@ApiOperation({ summary: 'Get trial balance' })
|
||||
@RequireCapability('financials.accounts.view')
|
||||
getTrialBalance(@Query('asOfDate') asOfDate?: string) {
|
||||
return this.accountsService.getTrialBalance(asOfDate);
|
||||
}
|
||||
|
||||
@Put(':id/set-primary')
|
||||
@ApiOperation({ summary: 'Set account as primary for its fund type' })
|
||||
@RequireCapability('financials.accounts.edit')
|
||||
setPrimary(@Param('id') id: string) {
|
||||
return this.accountsService.setPrimary(id);
|
||||
}
|
||||
|
||||
@Post('bulk-opening-balances')
|
||||
@ApiOperation({ summary: 'Set opening balances for multiple accounts' })
|
||||
@RequireCapability('financials.accounts.edit')
|
||||
bulkSetOpeningBalances(
|
||||
@Body() dto: { asOfDate: string; entries: { accountId: string; targetBalance: number }[] },
|
||||
) {
|
||||
@@ -42,6 +47,7 @@ export class AccountsController {
|
||||
|
||||
@Post(':id/opening-balance')
|
||||
@ApiOperation({ summary: 'Set opening balance for an account at a specific date' })
|
||||
@RequireCapability('financials.accounts.edit')
|
||||
setOpeningBalance(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: { targetBalance: number; asOfDate: string; memo?: string },
|
||||
@@ -51,6 +57,7 @@ export class AccountsController {
|
||||
|
||||
@Post(':id/adjust-balance')
|
||||
@ApiOperation({ summary: 'Adjust account balance to a target amount' })
|
||||
@RequireCapability('financials.accounts.edit')
|
||||
adjustBalance(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: { targetBalance: number; asOfDate: string; memo?: string },
|
||||
@@ -60,6 +67,7 @@ export class AccountsController {
|
||||
|
||||
@Post('transfer')
|
||||
@ApiOperation({ summary: 'Transfer funds between asset accounts' })
|
||||
@RequireCapability('financials.accounts.edit')
|
||||
transferFunds(
|
||||
@Body() dto: { fromAccountId: string; toAccountId: string; amount: number; transferDate: string; memo?: string },
|
||||
) {
|
||||
@@ -68,18 +76,21 @@ export class AccountsController {
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get account by ID' })
|
||||
@RequireCapability('financials.accounts.view')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.accountsService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new account' })
|
||||
@RequireCapability('financials.accounts.edit')
|
||||
create(@Body() dto: CreateAccountDto) {
|
||||
return this.accountsService.create(dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: 'Update an account' })
|
||||
@RequireCapability('financials.accounts.edit')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateAccountDto) {
|
||||
return this.accountsService.update(id, dto);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user