# Feature: Loan Management

> **Status:** Specified, not built · **Phase:** 3 · **Depends on:** posting engine (2.1), members (2.2)
> **Primary source:** Microfinance (Non-Deposit Taking Microfinance Service Providers) Regulations 2019, **GN No. 679**, Part IV — read from the Bank of Tanzania's published text, not from secondary summaries.

---

## ⚠️ Corrections to the original specification

The rules below were verified against the actual regulation. **Three of them contradict `REGULATORY_COMPLIANCE.md` and `FEATURES.md`, and the errors all understate risk.** They are the reason this document exists.

### 1. The classification buckets were wrong

| | Original spec | **GN 679, Reg 45(1)** |
|---|---|---|
| Bucket 1 | Performing — 0 days | **Current — 0 to 5 days** |
| Bucket 2 | Watch — ≤30 days | **Especially Mentioned — 6 to 30 days** |
| Bucket 3 | Substandard — ≤60 days | **Substandard — 31 to 60 days** |
| Bucket 4 | Doubtful — ≤180 days | **Doubtful — 61 to 90 days** |
| Bucket 5 | Loss — 180+ days | **Loss — more than 90 days** |

The spec put Doubtful at up to 180 days and Loss beyond 180. The regulation puts **Loss at anything over 90 days**. A loan 120 days overdue is a **100% provision** under the real rule and would have been provisioned at 50% under the spec. On a portfolio of any size that is a material misstatement of the loan-loss provision — exactly the kind of thing a BoT inspection is designed to catch.

Provision rates themselves (1 / 5 / 25 / 50 / 100 percent, Reg 45(2)) were correct.

### 2. Housing microfinance has its own, more lenient schedule — Reg 45(3)–(4)

Entirely absent from the original spec:

| Days past due | Classification | Provision |
|---|---|---|
| 91–180 | Substandard | 25% |
| 180–360 | Doubtful | 50% |
| 361+ | Loss | 100% |

So `loan_products` needs an `is_housing_microfinance` flag, and the classification engine must branch on it. Applying the standard schedule to a housing loan would over-provision and understate earnings.

### 3. Past due is measured on the **whole loan**, not the missed instalment — Reg 44

> "Loans which are payable in installments shall be considered past due in their entirety if any of the installments has become due and unpaid for one day or more."

This is the single most consequential rule in the whole feature, and it is easy to get wrong because the intuitive implementation is wrong.

- Overdue by **one day** puts the **entire outstanding balance** past due — not just the arrears amount.
- Reg 45(5): if the missed payment is **interest only**, the entire balance is still in arrears.
- Reg 44(3): a **group loan** is past due in its entirety when any member defaults and the other members do not cover the shortfall.

PAR therefore uses the full outstanding balance as its numerator. A naïve implementation that ages only the overdue instalment will report a PAR figure several times smaller than the true one, and will pass its own tests while doing so.

---

## Purpose

Manage the full loan lifecycle — product definition, application, appraisal, approval, disbursement, servicing, collection, classification, restructuring and recovery — with every state change producing correct double-entry postings and a complete audit trail.

---

## Regulatory basis

| Area | Regulation | What it requires |
|---|---|---|
| Lending policy | Reg 37 | 17 mandatory contents; review at least every 3 years |
| Loan application | Reg 38 | 15 mandatory fields + CRB consent declaration |
| Loan agreement | Reg 39 | 12 mandatory disclosures |
| Refusal | Reg 40 | Reasons must be communicated |
| Collateral | Reg 41 | Security interest, valuation |
| Repayment | Reg 42 | Allocation and receipting |
| Restructuring | Reg 43 | Permitted for cash-flow distress, per lending policy |
| Past due | Reg 44 | Whole-loan basis (see above) |
| Classification | Reg 45 | 5 buckets + provisions; separate housing schedule |
| Credit info | Reg 35, 36 | Submission to bureaus; borrower consent |
| Disclosure | Reg 53 | Pre-contract transparency |
| Guarantors | Reg 55 | Disclosures to guarantors |
| Collection | Reg 56 | Conduct limits on debt recovery |

---

## Data model

### `loan_products`

| Column | Type | Notes |
|---|---|---|
| `tenant_id` | uuid | scoped |
| `name`, `code` | string | |
| `type` | enum | individual, group, asset, emergency, agricultural, business, housing |
| `is_housing_microfinance` | bool | **drives the alternate classification schedule** |
| `interest_method` | enum | `reducing_balance`, `flat` |
| `interest_rate` | decimal(8,4) | per period |
| `rate_period` | enum | monthly, annual |
| `day_count` | enum | `actual_365`, `actual_360`, `30_360` |
| `min_amount`, `max_amount` | bigint | **cents** |
| `min_term`, `max_term` | int | periods |
| `repayment_frequency` | enum | weekly, biweekly, monthly, quarterly |
| `grace_period_days` | int | 0–90 |
| `max_pct_of_savings`, `max_pct_of_income` | decimal | eligibility limits (Reg 37(d),(g),(h)) |
| `requires_collateral` | bool | |
| `requires_guarantors` | int | count |

Fees live in a separate `loan_product_fees` table — a product has many fees, each with `type` (processing, insurance, legal, appraisal, penalty), `calculation` (fixed / percentage), `amount`, and `charged_at` (application / disbursement / repayment / late).

Separating fees matters because **Reg 39(d) requires the effective annual interest rate including all fees** to be disclosed. EIR cannot be computed from a single rate column.

### `loans`

Key columns beyond the obvious: `application_number`, `account_number`, `status`, `principal_applied`, `principal_approved`, `principal_disbursed`, `interest_method`, `interest_rate`, `nominal_annual_rate`, `effective_annual_rate` (both disclosed per Reg 39), `disbursed_at`, `first_payment_date`, `maturity_date`, `classification`, `classified_at`, `provision_rate`, `provision_amount`, `days_past_due`, `total_outstanding`.

All money columns are `bigint` **cents**.

### `loan_schedules`

One row per instalment: `due_date`, `principal_due`, `interest_due`, `fees_due`, `penalty_due`, and the matching `*_paid` columns, plus `status` and `days_overdue`.

Generated on disbursement. Regenerated only on restructuring — and the old schedule is retained, never overwritten, because the original terms are evidence.

### Supporting tables

`loan_applications` (workflow state, appraisal, decisions), `loan_collaterals` (type, description, valuation, `locked_until`), `loan_guarantors`, `loan_repayments`, `loan_classifications` (history — one row per daily classification run, so the trail is reconstructable), `loan_restructurings`, `loan_writeoffs`.

---

## Workflow

```
DRAFT ──▶ SUBMITTED ──▶ APPRAISED ──▶ APPROVED ──▶ DISBURSED ──▶ ACTIVE
             │              │             │                          │
             ▼              ▼             ▼                          ▼
          WITHDRAWN      REJECTED     REJECTED              CLOSED / WRITTEN_OFF
```

Four stages, each with a distinct actor — maker-checker separation is what makes the audit trail meaningful.

**1. Submit.** Capture all 15 Reg 38 fields plus the CRB consent declaration. Consent is stored with timestamp, IP and the exact wording shown — a consent record that cannot reproduce what the borrower agreed to is not evidence.

**2. Appraise.** Credit officer performs field verification, capacity analysis (income vs obligations), collateral valuation, guarantor capacity, and a CRB report pull. Produces a recommendation, not a decision.

**3. Approve.** Routed by amount through a configurable approval matrix (loan officer → branch manager → credit committee → board). **The approver must not be the appraiser** — enforced in the application, not merely in the UI.

**4. Disburse.** Posts to the GL, generates the schedule, locks pledged collateral, and issues the Reg 39 agreement. Channels: M-Pesa, Airtel, Mixx, HaloPesa, bank transfer, cash.

---

## Interest and schedules

**Reducing balance** is the default and the regulator's expectation. Flat rate is supported only where a product explicitly declares it, because flat-rate pricing understates the true cost to the borrower and its EIR must still be disclosed truthfully.

Instalment for reducing balance:

```
        P × r
A = ─────────────
    1 − (1 + r)⁻ⁿ
```

where `P` = principal in cents, `r` = periodic rate, `n` = instalments.

**Rounding is the whole problem.** Working in integer cents, the computed instalment rarely divides evenly. The rule: round each instalment to the cent, then force the **final** instalment to absorb the residual so that principal repaid equals principal advanced exactly. Any other approach leaves a few cents outstanding on a closed loan, which then ages, classifies, and shows up in PAR.

The invariant is asserted in tests for every generated schedule:

```
Σ principal_due == principal_disbursed     // exactly, in cents
```

### EIR

Reg 39(d) requires the effective annual rate **including all fees**. Computed by solving for the rate at which the net cash flow (disbursement minus up-front fees, against all scheduled payments) has zero present value — Newton-Raphson, since there is no closed form. This number appears on the agreement and in the pre-contract disclosure.

---

## Repayment allocation

Order: **fees → penalties → interest → principal**.

Penalties before interest, and interest before principal, so that a partial payment never silently reduces principal while charges accumulate behind it. The order is configurable per tenant, but the default is what the regulations' disclosure requirements assume.

Every repayment posts:

```
Dr  Cash / Mobile Money / Bank
    Cr  Loan Fees Income
    Cr  Penalty Income
    Cr  Interest Income
    Cr  Loans Receivable        (principal portion only)
```

Overpayment goes to a suspense account, never to an implicit early settlement — the member must be asked.

---

## Classification and provisioning

A daily scheduled job classifies every active loan. It is deliberately **idempotent** and writes a new `loan_classifications` row each run rather than mutating the loan, so the history is auditable and a bad run can be reconstructed.

```php
$daysPastDue = $loan->daysPastDue();   // whole-loan basis, Reg 44

$classification = $loan->product->is_housing_microfinance
    ? match (true) {                          // Reg 45(3)
        $daysPastDue <= 90  => 'current',
        $daysPastDue <= 180 => 'substandard',
        $daysPastDue <= 360 => 'doubtful',
        default             => 'loss',
    }
    : match (true) {                          // Reg 45(1)
        $daysPastDue <= 5   => 'current',
        $daysPastDue <= 30  => 'especially_mentioned',
        $daysPastDue <= 60  => 'substandard',
        $daysPastDue <= 90  => 'doubtful',
        default             => 'loss',
    };

$provisionRate = [
    'current' => 0.01, 'especially_mentioned' => 0.05,
    'substandard' => 0.25, 'doubtful' => 0.50, 'loss' => 1.00,
][$classification];
```

A change in classification posts the provision movement to the GL. Provisions are never reversed silently — a reversal requires an approved recovery or write-off record behind it.

### PAR

PAR buckets (1–30, 31–60, 61–90, 91–180, 180+) use the **full outstanding balance** of any loan with an overdue instalment, per Reg 44. This is the number the executive dashboard leads with and the number the regulator asks about first.

---

## Screens

| Screen | Contents |
|---|---|
| Loan products | List, create/edit with fee schedule and an EIR preview |
| Application pipeline | Kanban by stage, filterable by officer/branch/product |
| Application form | Multi-step covering all Reg 38 fields, documents, CRB consent |
| Appraisal | Capacity analysis, collateral, guarantors, CRB report, recommendation |
| Approval queue | Amount-routed, with the appraisal pack attached |
| Loan detail | Summary, schedule, repayments, documents, classification history, audit trail |
| Repayment entry | Channel, amount, allocation preview **before** posting |
| Arrears / collections | PAR ageing, officer assignment, promise-to-pay, field visits |
| Classification review | Daily run output, manual override with mandatory reason |
| Restructuring | Reschedule / refinance / consolidate, with before-and-after comparison |

---

## Edge cases that must be handled

- **Overpayment** → suspense, then an explicit decision. Never auto-settle.
- **Payment on a written-off loan** → recovery income, not a principal reduction.
- **Disbursement fails at the gateway** → loan must not become active; the whole thing rolls back in one transaction.
- **Backdated repayment** → recompute the classification history from that date forward, not just today's.
- **Loan matures with a residual balance** → stays active and continues ageing; maturity is not closure.
- **Group loan, one member defaults** → whole group loan past due unless the others cover (Reg 44(3)).
- **Interest-only payment missed** → whole balance in arrears (Reg 45(5)).
- **Collateral pledged to two loans** → prohibited; enforced by a lock, not a warning.
- **Restructuring** → does **not** reset days-past-due for classification. Restructured loans are tracked separately, because resetting the clock is precisely the abuse the classification rules exist to prevent.

---

## Test checklist

- [ ] Schedule principal sums exactly to principal disbursed, across randomised inputs
- [ ] Closing balance is exactly zero on the final instalment
- [ ] Reducing-balance schedule matches hand-calculated worked examples
- [ ] EIR matches an independently computed value including fees
- [ ] Classification boundaries: 5/6, 30/31, 60/61, 90/91 days
- [ ] Housing loans use the alternate schedule at 90/91, 180/181, 360/361
- [ ] One day overdue puts the **entire balance** past due
- [ ] Missed interest-only payment puts the entire balance in arrears
- [ ] Group loan classification cascades per Reg 44(3)
- [ ] Allocation order: fees → penalties → interest → principal
- [ ] Every state change posts balanced journal entries
- [ ] Approver ≠ appraiser is enforced server-side
- [ ] Pledged collateral cannot be withdrawn or re-pledged
- [ ] Tenant isolation on every loan endpoint
- [ ] Restructuring does not reset the classification clock

---

## Build order

1. Loan products + fee schedule
2. `LoanCalculator` — schedules, EIR *(test-heavy; nothing else starts until this is right)*
3. Application + Reg 38 fields + consent
4. Appraisal
5. Approval matrix
6. Disbursement + GL posting
7. Repayment + allocation
8. Classification job + provisioning
9. PAR + collections
10. Restructuring, write-off, recovery
