Skip to main content

Balance Sheet Report

Balance Sheet Report

Component file: src/features/reports/components/BalanceSheetReport.tsx Data layer: src/lib/db/financial-reports.tsfetchBalanceSheetData() Route / navigation: /reports with reportTool = 'balance-sheet' Access: Parent-org users and fund users with report access Last verified against implementation: April 13, 2026

Locked Parity Notice

This page is a mirror of the live implementation, not a design document.

  • Navigation hub: [documentation/contracts/BALANCE-SHEET-PARITY.md](../../contracts/BALANCE-SHEET-PARITY.md) (RPC list, verification SQL, regression gate).
  • Source-of-truth contract: [documentation/contracts/BALANCE-SHEET-INVARIANTS.md](../../contracts/BALANCE-SHEET-INVARIANTS.md)
  • Shared drawer contract: [documentation/contracts/LEDGER-DRAWER-INVARIANTS.md](../../contracts/LEDGER-DRAWER-INVARIANTS.md)
  • Any future balance-sheet code or doc change requires three explicit user confirmations before editing.
  • RPC or migration changes (function SQL, signatures, org scope) additionally require explicit Steve sign-off on that specific change — see BALANCE-SHEET-PARITY.md §1 and .cursor/rules/balance-sheet-rpc-lock.mdc.
  • If the implementation changes, this doc must be updated in the same task.

Recent Updates

Fund breakdown cache key scope hardening (April 13, 2026)

  • Parent-org equity fund-breakdown queries now key by selected entity as well as date (['balanceSheetFundBreakdown', asOfDate, selectedEntity]).
  • Post-save and post-void invalidations now use the same entity-scoped key.
  • This prevents stale by-fund equity expansion data when users switch entities while keeping the same as-of date.

Current Runtime Behavior

The report is a point-in-time balance sheet for the selected entity and as-of date.

  • Data loads through fetchBalanceSheetData(selectedEntity, asOfDate).
  • Required dates are normalized before RPC calls; empty or invalid dates fall back to today.
  • The data layer makes four parallel RPC calls:
  • get_balance_sheet_data
  • get_balance_sheet_net_income for fiscal-YTD
  • get_balance_sheet_net_income again with p_fiscal_year_start = '1900-01-01' for cumulative net activity through the as-of date
  • get_balance_sheet_imbalance
  • Parent-org views always pass the actual parent-org UUID via getOrgId(entityId) || getActualOrgId(entityId). null must not be passed for parent-org balance-sheet views.
  • All balance-sheet RPCs must use the same org scope: child orgs where type != 'parent_org', including inactive funds. The contract lives in documentation/contracts/BALANCE-SHEET-INVARIANTS.md.
  • Consolidated balance-sheet account rows are restricted to accounts owned by the selected org tree's chart (the parent chart owner plus any in-tree account owners). Foreign accounts with reused UUIDs or contaminated journal lines must not materialize as report rows.

Display Contract

The UI renders three sections from the RPC payload:

  • ASSETS
  • LIABILITIES
  • NET ASSETS

Within each section:

  • Accounts are grouped by classification via groupAccountsByClassification().
  • Parent accounts aggregate visible subaccounts.
  • If the RPC omits a zero-activity parent but returns subaccounts, the UI synthesizes the parent row so the grouping still renders correctly.

For parent-org views only:

  • The report also fetches fetchBalanceSheetByFundData() for equity fund-breakdown expansion.
  • Equity rows with balances in multiple funds can expand to show each fund’s amount inline.
  • The expandable fund list merges all active in-scope funds with any additional funds that still carry a non-zero balance on that equity row, so active zero-balance funds remain visible as $0.00 instead of disappearing from the list.

Header metadata:

  • ReportHeader shows the organization, report title, as-of date, and accounting basis.
  • When available, the page also shows fiscal_year_start_label and whether that boundary was inherited from the parent organization.

Totals And Equation

The consolidated Balance Sheet does not render a separate net-income row.

Instead:

  • TOTAL NET ASSETS = total_equity + all_time_net_income
  • TOTAL LIABILITIES & NET ASSETS = total_liabilities + total_equity + all_time_net_income
  • equation_difference = get_balance_sheet_imbalance

If posted_balanced is false, the report shows the red warning block with:

  • posted assets
  • posted liabilities + net assets
  • equation difference

Drawer Behavior

Clicking an account or subaccount opens the stacked transaction drawer.

  • Non-bank rows default From to blank, which means all-time.
  • Bank-register-eligible rows default From to the first day of the report month.
  • While the drawer is closed, its dates stay synced to the report as-of date.
  • While the drawer is open, changing the report date does not overwrite the in-drawer dates.

Drawer fetching uses fetchLedgerEntriesByAccountCode() with:

  • transactionOrder: 'asc'
  • includeOpeningBalanceBeforeStart: true on the initial load
  • limit = 20 for the first page
  • limit = 50 for later pages

Current drawer math:

  • The drawer lists rows chronologically within the loaded window.
  • Running balance is anchored to the book balance through the calendar day before From when available.
  • If the opening-balance fetch fails, the running-balance helper falls back to page-local math.
  • Parent-org/admin drawers use the selected parent organization’s child-fund tree only for both account lookup and journal-line aggregation. They must not aggregate unrelated organizations that happen to reuse the same account code (for example 1000).

Journal entry drill-down:

  • Clicking a ledger row opens the full journal entry through fetchJournalEntryByLineIdForEntity().
  • Donation-linked rows pass donor context into JournalEntryEditDrawer.
  • Bank-register-eligible rows default to grouped mode; non-bank rows default to ungrouped mode.
  • Grouped drawer rows use Debit, Credit, and Balance columns for both bank and non-bank accounts. Bank-register-eligible rows no longer collapse grouped activity into a single signed Amount column.

Export Behavior

The consolidated report supports:

  • report export through MultiNonprofitExportDialog
  • drawer export to csv
  • drawer export to xlsx

Report exports include:

  • assets
  • liabilities
  • net assets
  • total net assets using total_equity + all_time_net_income
  • total liabilities & net assets using total_liabilities + total_equity + all_time_net_income

The on-screen report folds cumulative net activity into the displayed total lines, and export parity should be checked against the same formulas.

What This Page Deliberately Does Not Claim

The current implementation does not provide:

  • a comparative side-by-side balance-sheet view
  • a visible dedicated net-income row in the consolidated report
  • Rails-backed report logic
  • mock-data-driven rendering

Older notes that described those behaviors were removed because they no longer matched the working report.

Related Docs

  • documentation/contracts/BALANCE-SHEET-INVARIANTS.md
  • documentation/pages/reports/14-BALANCE-SHEET-BY-FUND.md
  • documentation/pages/reports/00-REPORTS-HUB.md

Synced from IFMmvp-Frontend documentation: pages/reports/01-BALANCE-SHEET-REPORT.md

Ready to Get Started?

See how Alignmint can simplify your nonprofit's operations. Schedule a free demo with our team and we'll walk you through everything.

Questions? Email us at steven@getalignmint.org

Ready to get started?Start Plus Trial