Skip to content

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.tsALLOWED_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)

  1. Admin → People → Employees (or /people/employees). Requires admin.view.
  2. Pick the staff user row, click Edit.
  3. Change their role to one of:
  4. FINANCE_MANAGER — full finance + approvals + config.
  5. ACCOUNTANT — full finance, no approvals / config.
  6. CASHIER — cash/bank postings only, no config or reports.
  7. AUDITOR — read-only + export (no create/edit).
  8. Save. The user's JWT refreshes on next login (or immediately if using the session-refresh hook).

Custom role

  1. Admin → Permissions. Requires admin.permissions.view (or admin.view while granular perms are still migrating).
  2. Create a new role (e.g. FINANCE_REPORTER).
  3. Toggle the finance.reports.*.view + finance.view rows on. Leave everything else off.
  4. 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 ACCOUNTANT temporary finance.journals.approve_own while 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):

  1. Explicit user-level deny → denied.
  2. Explicit user-level allow → granted.
  3. Role grant → granted.
  4. Otherwise → denied.

5. Verifying the grant took effect

After changing a user's role or permissions:

  1. UI check. Log in as that user. Navigate to /finance — the sidebar item is visible only if the user has finance.view.
  2. API check. Call a gated endpoint and confirm the response: POST /finance/journals → 200 means finance.create took effect; 403 means it did not.
  3. Audit check. The next action that user performs writes an AuditLog row with their actorId — confirms JWT resolved correctly.
  4. Drift test. npm test runs src/test/permissions.matrix.test.ts. It fails the build if code references a permission not in docs/PERMISSIONS.md or 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 trailadmin.audit.view access 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.