Skip to content

hamnuscode/npo-financial-module

Repository files navigation

NPO Financial Management Module

A production-quality front end for a non-profit financial-management module whose entire job is to produce five trustworthy, auditable headline numbers — Total Raised, Expensed, Blocked, Reserved and Free Cash — plus the workflows and drill-downs that make those numbers true.

Built to the NPO Financial Management Module — Design & Development Specification.

The one rule that matters

The original mockup computed Free Cash = Raised − Expenses. That is wrong — it overstates spendable cash by ignoring money that is legally or strategically off-limits. The only correct derivation, implemented everywhere here, is:

Free Cash = Total Raised − Expensed − Blocked − Reserved
  • Blocked = unspent donor-restricted balances (restricted donations − restricted expenses)
  • Reserved = board-designated amounts (designations + allocations − un-reserves − reserve spend)
  • Non-cash = in-kind fair value (an extra − NonCash term so in-kind, which is raised but never cash, can't inflate Free Cash; it is 0 in the baseline)

Single source of truth

No number on screen is hand-keyed or stored denormalised. Every headline, project and liquidity figure is a pure function over one ledger. Each journal line is tagged with a fund_bucket and a cash_account_id, so the bucket view and the liquidity view are just different group-bys of the same data — no parallel bookkeeping (§7). The derivation logic lives in a framework-agnostic module (src/domain) with the UI talking to it only through a swappable DataProvider.

Reconciliation guard (§8/§10)

Free Cash is derived two independent ways and they must agree:

  1. Formula: Raised − Expensed − Blocked − Reserved − NonCash
  2. Cash identity: (Σ cash-account current balances) − Blocked − Reserved

If they disagree the app shows a loud “ledger out of balance” alert instead of silently displaying a number. Flip the Out-of-balance ledger scenario on the dashboard to see it fire.

Everything drills down (§6.6)

Every headline number is clickable and lands on the exact filtered list that produces it — e.g. Blocked → unspent restricted balances by project. This is what makes the dashboard trustworthy to an accountant.

Edge cases handled (§9)

Pending/bounced donations excluded until cleared · refunds that reverse a donation and restore bucket + account · restricted overspend that warns and requires explicit approval · negative Free Cash shown in red with an explanation (never hidden) · in-kind donations recorded at fair value but flagged and excluded from cash · multi-currency modelled in the types (PKR rendered in v1).

The first four are switchable live via the scenario switcher on the dashboard; pending/bounced/refunded/draft records are present in the baseline (and correctly excluded) without moving the headline numbers.

Tech

  • React 19 + TypeScript (strict) + Vite
  • Tailwind CSS v4
  • Vitest for the derivation & reconciliation tests
  • All monetary math in integer minor units (no floats), currency-aware types

Project layout

src/
  domain/          # pure, framework-agnostic — the source of truth
    money.ts         integer minor-unit money (multi-currency-ready)
    types.ts         the six entities (§3) + buckets (§4) + journal (§7)
    predicates.ts    the single definition of what "counts" (§9)
    ledger.ts        double-entry journal + cash balances (§7, §6.2)
    derive.ts        §8 formulas + per-project + drill-down breakdowns
    reconcile.ts     §8/§10 reconciliation guard
    seed.ts          mock data reconciling exactly to §6.1, + scenarios
    *.test.ts        31 unit tests of the correctness claims
  data/            # the swappable service seam
    DataProvider.ts        interface
    InMemoryDataProvider.ts implementation over the seed
    context.tsx            React context + scenario switch
  components/      # UI surfaces (§6)
  ui/              # formatting helpers + design primitives

Develop

npm install
npm run dev        # start the app
npm test           # run the derivation/reconciliation suite
npm run build      # typecheck + production build

What the baseline proves

The seed reconciles exactly to the spec example:

Total Raised Expensed Blocked Reserved Free Cash
800,000 320,000 150,000 80,000 250,000

Liquidity: lifetime raised Cash 200,000 + Bank 600,000 = 800,000; current balances 480,000 after 320,000 spent — and 480,000 − 150,000 − 80,000 = 250,000, matching the formula.

About

NPO Financial Management Module — five auditable headline numbers (Total Raised, Expensed, Blocked, Reserved, Free Cash) derived from a single ledger. React+TS+Vite.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors