Trust Ledger
The trust ledger must remain a single financial source of truth. Do not add JSON-backed trust balances or per-feature trust stores.
Canonical Tables
TrustAccount: firm-scoped IOLTA/escrow/operating trust account record and current account balance.TrustTransaction: immutable-ish ledger row for deposits, disbursements, fees, interest, transfers, adjustments, and reimbursements.TrustReconciliation: bank/book/client-ledger reconciliation receipt.
TrustTransaction.caseId is nullable and uses ON DELETE SET NULL so ledger history survives case deletion.
Posting Path
Use postTrustTransaction() from src/lib/trust/ledger.ts for every trust mutation that creates money movement. It:
- selects or creates the default firm IOLTA account when no account id is supplied,
- locks the trust account row with
FOR UPDATE, - verifies case ownership when a case id is supplied,
- fills
contactIdfrom the case client when needed, - rejects account overdrafts,
- rejects case/client-ledger overdrafts,
- rejects case disbursements, fee payments, and transfer-outs while a subrogation or medical-bill lien is still open (
TRUST_OPEN_LIENS), - writes the
TrustTransaction, - updates
TrustAccount.balance, - hops a
SETTLEMENTmatter toDISBURSEMENTon the first fail-closed debit (deposits and calculator-sheet saves do not).
Current writers using this path:
/api/dashboard/trust-account-ledger/api/cases/[caseId]/trust-ledger/api/dashboard/client-trust-dashboard/api/trust/transactions
Reconciliation
/api/trust/reconcile calculates:
- bank balance from the supplied statement value,
- book balance from real
TrustTransactionrows for the trust account, - client-ledger balance from the per-case sums of those same rows.
It persists every attempt in TrustReconciliation. TrustAccount.lastReconciledAt is stamped only when bank = book = client ledger. A failed recon is an audit row, not a successful recon.
Tests
Focused coverage lives in:
src/__tests__/api/dashboard-trust-account-ledger.test.tssrc/__tests__/api/trust-single-store-writers.test.tssrc/__tests__/lib/disbursement-gate.test.ts
Any new trust writer should add a failing test proving it calls the canonical posting path and does not write trust data into metadata JSON.