Skip to content

General Ledger

The apps.ledger module implements a full double-entry general ledger that records every financial event as balanced journal entries.

Core Principles

The ledger follows standard double-entry accounting:

  • Every transaction has two sides: debits and credits that must balance
  • Account balances are derived: computed from journal lines, never stored directly
  • Posted entries are immutable: corrections only via reversing entries
  • Complete audit trail: every entry traces back to its source (payment, disbursement, fee, etc.)

Data Model

Account

The chart of accounts with hierarchical structure:

Field Description
code Unique account code (e.g., "1100")
name Account name (e.g., "Loans Receivable")
account_type Asset, liability, equity, revenue, or expense
parent Self-FK for hierarchy (parent/child accounts)
is_system Protected system accounts (cannot be deleted)
loan Optional FK for loan-specific sub-accounts

Journal Entry

Entry headers that group related debit and credit lines:

Field Description
entry_number Auto-incrementing sequential number
entry_date When the entry was recorded
effective_date When the entry takes effect
description Human-readable description
posted Whether the entry is finalized
source_type / source_id Generic FK to the originating entity (payment, disbursement, etc.)

Journal Line

Individual debit or credit lines within a journal entry:

Field Description
journal_entry FK to the parent entry
account FK to the GL account
debit Debit amount (MoneyField)
credit Credit amount (MoneyField)
loan Optional FK for sub-ledger tagging

Invariants

These rules are enforced at the service level --- violations are bugs:

Every JournalEntry must balance

SUM(debits) = SUM(credits) --- enforced before posting. An unbalanced entry is rejected.

Posted entries are immutable

Once posted = True, no modifications are allowed. Corrections are made only via reversing entries.

Balances are always derived

Account balances are computed from journal lines on demand. There is no cached balance field.

Default Chart of Accounts

Code Account Type Purpose
1100 Loans Receivable Asset Outstanding loan principal
1110 Interest Receivable Asset Accrued but unpaid interest
1120 Fees Receivable Asset Assessed but unpaid fees
1150 Suspense Asset Unapplied funds
1200 Cash / Bank Asset Cash and bank accounts
1300 Allowance for Losses Asset (contra) Reserve for expected losses
2100 Unearned Revenue Liability Deferred revenue
4100 Interest Income Revenue Earned interest income
4200 Fee Income Revenue Earned fee income
4300 Recovery Income Revenue Post-charge-off recoveries
5100 Provision for Losses Expense Charge-off provision
5200 Fee Waiver Expense Expense Cost of waived fees
5300 Forgiveness Expense Expense Cost of forgiven principal

System accounts are auto-created on first use.

Journal Entries by Event

Every financial event produces balanced journal entries:

Event Debit Credit
Disbursement Loans Receivable Cash
Payment (principal) Cash Loans Receivable
Payment (interest) Cash Interest Receivable
Payment (fees) Cash Fees Receivable
Interest accrual Interest Receivable Interest Income
Fee assessment Fees Receivable Fee Income
Fee waiver Fee Waiver Expense Fees Receivable
Principal forgiveness Forgiveness Expense Loans Receivable
Non-accrual reversal Interest Income Interest Receivable
Charge-off provision Provision for Losses Allowance for Losses
Write-off Allowance for Losses Loans Receivable
Recovery Cash Recovery Income
Refund Loans/Fees Receivable Cash
Suspense hold Suspense Cash
Suspense release Cash Suspense

Sub-Ledger

Journal lines carry an optional loan_id foreign key, enabling per-loan accounting views:

  • GET /api/v1/ledger/loans/{id}/sub-ledger returns all journal lines for a specific loan
  • Provides a complete financial history of every transaction affecting a loan
  • Used for loan-level reconciliation and audit

Reversing Entries

Since posted entries are immutable, corrections are made via reversing entries:

  1. Create a new journal entry with mirror debits and credits
  2. Link the reversal to the original entry via source_type/source_id
  3. Post the reversal
  4. Both entries remain in the ledger for audit trail

Reports and Views

View Description
Trial balance Summarizes all account balances as of a given date
GL detail Full journal entry listing for a date range
Loan sub-ledger All GL entries filtered to a specific loan
Account balance Balance for any account as of any date

See Reporting for the trial balance and GL detail report generators.

See Also