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-ledgerreturns 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:
- Create a new journal entry with mirror debits and credits
- Link the reversal to the original entry via
source_type/source_id - Post the reversal
- 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¶
- Payment Processing --- Payment allocation GL entries
- Disbursements --- Disbursement GL entries
- Fee Management --- Fee assessment and waiver GL entries
- Servicing Operations --- Modification and forgiveness GL entries
- Collections --- Non-accrual interest reversal
- GL Chart of Accounts --- Complete account reference