Balance Sheet Report
Balance Sheet Report
Component file: src/features/reports/components/BalanceSheetReport.tsx Data layer: src/lib/db/financial-reports.ts → fetchBalanceSheetData() 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_dataget_balance_sheet_net_incomefor fiscal-YTDget_balance_sheet_net_incomeagain withp_fiscal_year_start = '1900-01-01'for cumulative net activity through the as-of dateget_balance_sheet_imbalance- Parent-org views always pass the actual parent-org UUID via
getOrgId(entityId) || getActualOrgId(entityId).nullmust 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 indocumentation/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:
ASSETSLIABILITIESNET 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.00instead of disappearing from the list.
Header metadata:
ReportHeadershows the organization, report title, as-of date, and accounting basis.- When available, the page also shows
fiscal_year_start_labeland 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_incomeTOTAL LIABILITIES & NET ASSETS = total_liabilities + total_equity + all_time_net_incomeequation_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
Fromto blank, which means all-time. - Bank-register-eligible rows default
Fromto 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: trueon the initial loadlimit = 20for the first pagelimit = 50for 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
Fromwhen 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, andBalancecolumns for both bank and non-bank accounts. Bank-register-eligible rows no longer collapse grouped activity into a single signedAmountcolumn.
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.mddocumentation/pages/reports/14-BALANCE-SHEET-BY-FUND.mddocumentation/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