apps/finance-portal, oi-sys-finance) is a dedicated Next.js application for the Finance / Accounting team. It surfaces TigerBeetle double-entry ledger data, accounts receivable aging, statutory financials, reconciliation workflows, and a flexible report builder — all scoped behind the finance_analyst JWT role.
Note:
The Finance Portal is intentionally isolated from the Admin and Compliance portals. A
finance_analyst JWT cannot access platform ops routes (rate tables, programs, rules) or
compliance data (producer licensing, filings).
URL & Access
| Environment | URL |
|---|---|
| Production | https://finance.openinsure.dev |
| Local dev | http://localhost:3006 |
Sign In
Use the standard OpenInsure sign-in flow. The Finance Portal sets theoi_finance_token cookie, verified against FINANCE_JWT_SECRET.
Roles & Authorization
| JWT role | SpiceDB org relation | Description |
|---|---|---|
finance_analyst | finance | Standard finance team access |
superadmin | — | Full access |
system | — | Machine-to-machine (M2M) |
finance_analyst role is enforced in apps/finance-portal/lib/auth.ts. The middleware rejects any JWT that does not carry one of these three roles and redirects unauthenticated requests to /login.
Navigation
| Section | Page | Route |
|---|---|---|
| Overview | Dashboard | / |
| Analytics | Entities | /analytics/entities |
| Analytics | Pipeline | /analytics/pipeline |
| Analytics | Captive | /analytics/captive |
| Reports | AR Aging | /reports/ar-aging |
| Reports | Financials | /reports/financials |
| Reports | Reconciliation | /reports/reconciliation |
| Reports | Statutory | /reports/statutory |
| Reports | Chart of Accounts | /reports/chart-of-accounts |
| Reports | Report Builder | /reports/builder |
Pages
Dashboard (/)
Three-pane layout (flex h-full overflow-hidden) with period toggle and live non-pay queue:
Left pane (w-64) — KPI sidebar:
- Period toggle — YTD / MTD / QTD links (
?period=ytdURL param), defaultytd KPICardfor GWP (Gross Written Premium) — neutral statusKPICardfor AR Outstanding —status='warn'if balance > 0KPICardfor Loss Ratio —status='warn'if > 65%,status='critical'if > 85%- Navigation links to A/R Aging, Pipeline, and Financials
- Fixed header with total outstanding balance
- CSS horizontal bar chart — 5 aging buckets (Current / 31–60 / 61–90 / 91–120 / 120+), bars sized relative to the largest bucket, no chart library
- Top 5 overdue accounts mini-table — Invoice #, Insured, Outstanding (cents), Days overdue
> 90d→ red,> 30d→ amber, else muted- “View all N invoices →” link to full AR Aging report
NonPayQueue client component:
- TanStack Query polling (
refetchInterval: 60_000) withinitialDatafrom server render - Groups entries by dunning stage: Stage 3 Critical (red) first, Stage 2 Warning (amber) next
- Each card shows policy number, insured name, amount due, and days remaining on grace period
- Clicking a card opens a shadcn Dialog with amount due, grace period end, dunning stage, and optional note input
- If queue empty: green checkmark (“Queue is clear”)
- “Full A/R Aging Report” link at bottom
Promise.all):
GET /v1/analytics/mga?period=${period}— GWP + loss ratioGET /v1/reports/ar-aging— bucket bars + top 5 overdue rows (amounts in cents)GET /v1/billing/non-pay-queue— non-pay entries for right pane (amountDue in dollars)
apps/finance-portal/app/(dashboard)/non-pay-queue.tsx (client island)
AR Aging (/reports/ar-aging)
Accounts receivable aging report grouped into 30/60/90/90+ day buckets. Fetches from GET /v1/reports/ar-aging. Exportable as CSV via the ExportToolbar component.
Financials (/reports/financials)
Income statement and balance sheet views sourced from the TigerBeetle ledger (GET /v1/reports/financials). Supports period selection (MTD, QTD, YTD, custom range). Income statement and balance sheet views are backed by getTrialBalance() from the TigerBeetle ledger client, which queries all 9 account types concurrently and returns debit/credit totals with a balanced boolean.
The 9 account types queried are: Carrier Payable (1), MGA Fiduciary (2), MGA Revenue (3), Producer Payable (4), Tax Authority Payable (5), Loss Fund (6), Claims Paid (7), Reserves (8), and Reinsurer Payable (9). Each row in the trial balance reports total debits, total credits, and net balance in integer cents.
Reconciliation (/reports/reconciliation)
Displays unreconciled ledger entries with status badges. Individual entries can be marked reconciled via PATCH /v1/reports/reconciliation/:id. Changes optimistically update the UI.
Statutory (/reports/statutory)
State statutory financial exhibits for regulatory filing. Data from GET /v1/reports/statutory. Read-only; the actual filing workflow lives in the Compliance Portal.
Chart of Accounts (/reports/chart-of-accounts)
Full account hierarchy from TigerBeetle. Sortable and searchable.
Trial Balance & Journal Entries
The TigerBeetle ledger client provides two audit-grade reporting methods:-
Trial Balance (
getTrialBalance()) — Queries all 9 account types concurrently viaPromise.all. Each row contains the account ID, account type code (1-9), human-readable name, total credits, total debits, and net balance — all in integer cents. Returns abalancedflag confirming debits equal credits across the organization’s subledger.Code Account Name 1 Carrier Payable 2 MGA Fiduciary 3 MGA Revenue 4 Producer Payable 5 Tax Authority Payable 6 Loss Fund 7 Claims Paid 8 Reserves 9 Reinsurer Payable -
Journal Entries (
getJournalEntries()) — Filtered transfer history through the MGA fiduciary hub account. Supportsfrom/todate-range filtering (converted to TigerBeetle nanosecond timestamps) and alimitparameter (default 100). Each entry includes the transfer ID, debit/credit account IDs, amount in cents, transfer code, and timestamp.
Report Builder (/reports/builder)
Flexible pivot-style report builder. Saved report configurations are persisted via POST /v1/reports/builder. Results can be exported to CSV or PDF.
API Proxy Allowlist
The Finance Portal proxies API requests throughapps/finance-portal/app/api/[...path]/route.ts, gated by lib/proxy-allowlist.ts:
403 Forbidden.
Environment Variables
| Variable | Description |
|---|---|
FINANCE_JWT_SECRET | JWT signing secret — set via wrangler secret put |
API_URL | API worker base URL (e.g., https://api.openinsure.dev) |
NEXT_PUBLIC_APP_NAME | "Finance Portal" — set in wrangler.toml [vars] |
Deployment
The Finance Portal deploys as an OpenNext Cloudflare Worker (oi-sys-finance):
master when apps/finance-portal/ or shared packages change. See CI/CD for the full pipeline.
Period Close Workflow
The period close process manages the month-end accounting close lifecycle. It is implemented in@openinsure/billing/period-close and exposed through apps/api/src/routes/period-close.ts under /v1/period-close.
Close Lifecycle
| Status | Description |
|---|---|
open | Close created, checklist items being worked |
in_progress | Close work underway |
pending_review | All items complete, awaiting dual-control approval |
closed | Approved and locked — no further changes |
reopened | Closed period reopened for adjustments |
Creating a Period Close
YYYY-MM format. The system rejects duplicate closes for the same org and period (HTTP 409). On creation, a default 15-step checklist is automatically generated.
Default Checklist
The standard month-end checklist covers seven categories with dependency ordering (later items are blocked until predecessors complete):| Order | Category | Title |
|---|---|---|
| 1 | Premium | Premium reconciliation |
| 2 | Premium | Earned premium calculation review |
| 3 | Premium | UPR balance verification |
| 4 | Loss | Loss reserve review |
| 5 | Loss | IBNR reserve review |
| 6 | Commission | Commission calculation and posting |
| 7 | Commission | Producer statement reconciliation |
| 8 | Reinsurance | Ceded premium calculation |
| 9 | Reinsurance | Reinsurer settlement |
| 10 | Reconciliation | Bank reconciliation |
| 11 | Reconciliation | Carrier statement reconciliation |
| 12 | Accrual | Expense accruals |
| 13 | Accrual | Revenue accruals |
| 14 | Review | Trial balance review |
| 15 | Review | Management sign-off |
pending, in_progress, completed, skipped, blocked), assignee, completion metadata, and notes.
Updating Checklist Items
completed, the system verifies that all blockedBy dependencies are already complete. If any dependency is incomplete, the request returns HTTP 409.
Progress Tracking
progress object calculated by calculateCloseProgress():
- Total items, completed count, and completion percentage
- Breakdown by category
- List of blocked items
Accrual Entries
Generate month-end accrual journal entries for expenses and estimated losses:generateAccrualEntries() function produces double-entry journal entries suitable for posting to the TigerBeetle ledger.
Reversing Accruals
Reverse prior period accruals at the start of the new period:Initiating Close
Move the period topending_review status:
open or in_progress state. Records the initiating user for dual-control purposes.
Dual-Control Approval
closed and a closedAt timestamp is recorded.
All period close endpoints require org_admin or billing_admin role.
Reconciliation
The reconciliation module matches carrier statement data against the system’s policy records to identify discrepancies. The API is at/v1/reconciliation.
Upload Carrier Statement
Carrier statements can be uploaded as JSON or CSV: JSON upload:policyNumber and premium columns (header row required). An optional insuredName column is supported. The CSV file is archived to R2 storage for audit purposes.
Auto-Matching Logic
On upload, the system automatically matches carrier rows against all org policies:- Exact match — Policy number matches (case-insensitive) and premium amounts agree within $0.01.
- Discrepancy — Policy number matches but premium amounts differ.
- Unmatched carrier — Carrier row has no corresponding system policy.
- Unmatched system — System policy has no corresponding carrier row.
| Field | Description |
|---|---|
matchedCount | Policies that match exactly on premium |
discrepancyCount | Policies that match on number but differ on premium |
unmatchedCarrierCount | Carrier rows with no system match |
unmatchedSystemCount | System policies missing from carrier statement |
Session Management
List sessions:Manual Override
For discrepancies that have been investigated, rows can be manually accepted or disputed:accepted (variance explained) or disputed (requires carrier follow-up). The override records the acting user for audit.
All reconciliation endpoints require org_admin or superadmin role.