Finance permissions matrix
Focused permissions matrix for the finance domain — what each
permission gates, which role has it, and where the enforcement lives
in code. The authoritative catalog is docs/PERMISSIONS.md; this page
is the finance-only slice with cross-references to every finance
feature.
Audience: admins granting finance access; auditors verifying segregation of duties at the permission level; developers wiring a new finance action.
1. Permission catalog (finance slice)
Source: docs/PERMISSIONS.md §4.1, §4.4. Every row below is enforced
by requirePermission('x.y') in src/lib/api.ts or a
<PermissionGate> in the UI, plus Postgres RLS policies keyed on
auth_user_has_permission('x.y') where noted.
1.1 Coarse finance permissions
| Permission | Gates | Enforcement |
|---|---|---|
finance.view |
Finance route, reports, ledgers | <ProtectedRoute>, RLS SELECT on JournalEntry / JournalLine / LedgerEntry |
finance.create |
Record payments, post manual journals, voucher forms | <PermissionGate>, requirePermission('finance.create') in handlers, RLS INSERT |
finance.edit |
Edit config, reconcile, reverse journals, chart-of-accounts CRUD | <PermissionGate>, API + RLS UPDATE |
finance.payments.record |
Post a payment receipt to ledger | requirePermission on /finance/vouchers/agent-receipt-allocate and siblings |
1.2 Finance workflow (split from coarse finance.create / approvals.approve)
| Permission | Gates |
|---|---|
finance.payments.verify |
Verify a pending payment |
finance.payments.reject |
Reject a pending payment |
finance.payments.refund |
Issue a refund |
finance.journals.approve |
Approve a manual journal entry |
finance.journals.approve_own |
Override — bypass maker-checker on self-approve/self-reject. Super-admin only. |
finance.journals.reject |
Reject a manual journal entry |
finance.journals.bulk_approve |
Bulk approve journals |
finance.journals.reverse |
Post a reversal voucher |
finance.journals.reverse_own |
Override — bypass maker-checker on self-reverse. Super-admin only. |
finance.bookings.approve_finance |
Finance-tier booking approval |
finance.bookings.reject_finance |
Finance-tier booking rejection |
finance.cancellations.approve_airline |
Approve airline cancellation refund |
finance.cancellations.approve_b2b |
Approve B2B buyer cancellation |
finance.allocations.agent_receipt |
Allocate on-account agent receipt |
finance.allocations.supplier_advance |
Allocate supplier advance |
finance.stock_adjustment.post |
Post opening/closing stock adjustment |
finance.ledger.rebuild |
Rebuild ledger from source |
finance.config.edit |
Edit finance config (prefixes, GL defaults) |
Granular workflow permissions are seeded but not fully wired
Per docs/PERMISSIONS.md §8 item 10, several of these sectional
permissions are seeded (so admins can toggle them in the
PermissionsMatrix UI) but the handlers still gate on coarse
finance.create / finance.edit. The workflow is tightening
incrementally. Check
src/test/permissions.matrix.test.ts → ALLOWED_DOC_ONLY list
for the current seeded-but-unwired set.
1.3 Finance period / year management
| Permission | Gates |
|---|---|
finance.periods.create |
Create accounting period |
finance.periods.lock |
Lock accounting period |
finance.periods.unlock |
Unlock period |
finance.periods.close |
Permanently close period (exec-level) |
finance.years.create |
Create financial year |
finance.years.close |
Close financial year (exec-level) |
1.4 TDS
| Permission | Gates |
|---|---|
finance.tds.view |
View TDS rate master + deduction ledger |
finance.tds.deduct |
Apply TDS on a supplier payment |
finance.tds.export |
Export quarterly 26Q CSV |
1.5 Reports (each has independent view + export)
| Permission | Report |
|---|---|
finance.reports.trial_balance.view/.export |
Trial Balance |
finance.reports.day_book.view/.export |
Day Book |
finance.reports.balance_sheet.view/.export |
Balance Sheet |
finance.reports.profit_loss.view/.export |
Profit & Loss (Cash Flow reuses this permission) |
finance.reports.group_profit_loss.view/.export |
Group Profitability |
finance.reports.aging.view/.export |
AR / AP Aging |
finance.reports.receivables_payables.view/.export |
Receivables & Payables summary |
finance.reports.gst_summary.view/.export |
GST Summary (GSTR-1 / GSTR-3B exports) |
finance.reports.stock_status.view/.export |
Stock Status |
finance.reports.group_status.view/.export |
Group Status |
finance.reports.settlements.view/.export |
Settlement Report |
2. Role × permission grid
Legend: ✓ granted, - not granted. Source: docs/PERMISSIONS.md
§5 and the seed migrations referenced there.
2.1 Super-admin tier
| Permission | CEO | GM | IT_ADMIN | ADMIN_HR |
|---|---|---|---|---|
finance.view / .create / .edit |
✓ | ✓ | ✓ | ✓ |
finance.payments.record |
✓ | ✓ | ✓ | ✓ |
finance.tds.* |
✓ | ✓ | ✓ | ✓ |
finance.reports.* (view + export) |
✓ | ✓ | ✓ | ✓ |
finance.periods.*, finance.years.* |
✓ | ✓ | ✓ | ✓ |
finance.config.edit |
✓ | ✓ | ✓ | ✓ |
finance.ledger.rebuild |
✓ | ✓ | ✓ | ✓ |
finance.stock_adjustment.post |
✓ | ✓ | ✓ | ✓ |
approvals.approve |
✓ | ✓ | ✓ | ✓ |
finance.journals.approve_own |
✓ | ✓ | ✓ | - |
finance.journals.reverse_own |
✓ | ✓ | ✓ | - |
ADMIN_HR does not hold the maker-checker overrides
ADMIN_HR has every other finance permission (seeded by the
migrations listed in docs/PERMISSIONS.md §3) but not the
two overrides. HR is not an approver of record. The overrides
are reserved for CEO / GM / IT_ADMIN and are seeded exclusively
to those three roles by
supabase/migrations/20260418150000_journal_maker_checker_overrides.sql.
2.2 Functional finance tier
| Permission | FINANCE_MANAGER | ACCOUNTANT | CASHIER | AUDITOR |
|---|---|---|---|---|
finance.view |
✓ | ✓ | ✓ | ✓ |
finance.create |
✓ | ✓ | ✓ | - |
finance.edit |
✓ | ✓ | - | - |
finance.payments.record |
✓ | ✓ | ✓ | - |
approvals.approve |
✓ | - | - | - |
finance.tds.view |
✓ | ✓ | - | - |
finance.tds.deduct |
✓ | ✓ | - | - |
finance.tds.export |
✓ | ✓ | - | - |
finance.reports.*.view (all) |
✓ | most | - | ✓ |
finance.reports.profit_loss.view |
✓ | - | - | ✓ |
finance.reports.*.export |
✓ | subset | - | ✓ (reports.export) |
finance.config.edit |
✓ | - | - | - |
finance.periods.* |
✓ | - | - | - |
finance.journals.approve_own |
- | - | - | - |
finance.journals.reverse_own |
- | - | - | - |
2.3 Other roles with limited finance touch
| Permission | SALES_MANAGER | OPS_MANAGER | B2B_MANAGER |
|---|---|---|---|
finance.view |
- | - | - |
finance.create |
- | - | - |
finance.edit |
- | - | - |
finance.payments.record |
- | - | - |
approvals.approve |
- | ✓ | - |
reports.export |
✓ | ✓ | ✓ |
finance.reports.* |
- | - | - |
Portal roles (AGENT, CUSTOMER) hold no finance permissions.
They access their scoped finance surfaces (own invoices, own receipts)
through ownership checks in the handler, not through the permission
matrix. See docs/PERMISSIONS.md §4.5.
3. Cross-references per feature page
Each finance feature page covers one area in depth. Use this table to jump from a permission to the page that documents its surface.
| Permission | Feature page |
|---|---|
finance.view (route) |
Overview |
finance.edit (CoA CRUD) |
Chart of accounts §7-8 |
finance.create / approvals.approve |
Journals, Vouchers, Double-entry engine §4 |
finance.journals.approve_own / reverse_own |
Maker-checker §3 |
finance.periods.* |
Overview §3 |
finance.tds.* |
TDS, India compliance |
finance.reports.gst_summary.* |
GST, India compliance |
finance.reports.* |
Reports |
finance.allocations.agent_receipt |
Vouchers receipt allocation |
finance.stock_adjustment.post |
Opening / closing stock handler (src/lib/api.ts) |
admin.audit.view |
Audit trail §4 |
4. How to grant a user finance access
Standard grant (assign role)
- Admin → People → Employees (or
/people/employees). Requiresadmin.view. - Pick the staff user row, click Edit.
- Change their role to one of:
FINANCE_MANAGER— full finance + approvals + config.ACCOUNTANT— full finance, no approvals / config.CASHIER— cash/bank postings only, no config or reports.AUDITOR— read-only + export (no create/edit).- Save. The user's JWT refreshes on next login (or immediately if using the session-refresh hook).
Custom role
- Admin → Permissions. Requires
admin.permissions.view(oradmin.viewwhile granular perms are still migrating). - Create a new role (e.g.
FINANCE_REPORTER). - Toggle the
finance.reports.*.view+finance.viewrows on. Leave everything else off. - Assign the custom role to the user via step 1 above.
Per-user override
The catalog supports user-level allow / deny via UserPermission rows
(see docs/PERMISSIONS.md §2 for precedence). This is available
through the PermissionsMatrix admin UI:
- Allow override: grant a specific permission to one user without
touching their role. Example: give an
ACCOUNTANTtemporaryfinance.journals.approve_ownwhile the FINANCE_MANAGER is on leave. Do this sparingly — overrides are the first thing an auditor will want explained. - Deny override: revoke a specific permission from one user. Useful for a super-admin who should nonetheless not post journals directly.
Precedence order (from docs/PERMISSIONS.md §2):
- Explicit user-level deny → denied.
- Explicit user-level allow → granted.
- Role grant → granted.
- Otherwise → denied.
5. Verifying the grant took effect
After changing a user's role or permissions:
- UI check. Log in as that user. Navigate to
/finance— the sidebar item is visible only if the user hasfinance.view. - API check. Call a gated endpoint and confirm the response:
POST /finance/journals→ 200 meansfinance.createtook effect; 403 means it did not. - Audit check. The next action that user performs writes an
AuditLogrow with their actorId — confirms JWT resolved correctly. - Drift test.
npm testrunssrc/test/permissions.matrix.test.ts. It fails the build if code references a permission not indocs/PERMISSIONS.mdor vice versa. Run after any permission-related migration.
6. Cross-references
docs/PERMISSIONS.md— canonical catalog (this page is the finance slice).- Overview §5 — feature → permission summary.
- Maker-checker — override permissions in detail.
- Audit trail —
admin.audit.viewaccess pattern. supabase/migrations/20260418150000_journal_maker_checker_overrides.sql— override permission seed.src/test/permissions.matrix.test.ts— drift test that keeps code and the matrix in sync.