# Step 13 — Loan Top-Up — Implementation Report

**Status:** COMPLETED  
**Verified:** 12 August 2026

## What was implemented

- Complete loan top-up workflow for active loans.
- A top-up creates a linked replacement loan application for the same customer and branch.
- The replacement loan reuses the existing centralized loan calculation and repayment-schedule engine.
- Existing contract, document, approval, maker-checker and manager-threshold controls remain in force for the replacement loan.
- Top-up replacement loans are excluded from normal loan disbursement and protected by a backend service guard so the full new principal cannot be paid out accidentally.
- Configurable business-wide eligibility percentage with common values of 50%, 70% and 80% plus any custom value from 1% to 100%.
- Eligibility is calculated from the repaid share of the original scheduled principal, interest and fees. Penalties do not increase the eligibility percentage.
- Eligibility percentage and configured threshold are snapshotted onto the replacement application.
- Authorized eligibility override with required reason, user and timestamp.
- Permanent audit log for top-up application creation and execution.
- Final execution recalculates the previous loan balance under database locks instead of trusting a stale application-time quote.
- Penalties due through the execution date are assessed before the old balance is settled.
- The previous loan is closed with the explicit status `Closed Through Top-Up`.
- The previous loan record, repayment history, statement history and status history remain permanently preserved.
- Permanent old-loan ↔ replacement-loan relationship.
- Permanent `loan_top_ups` execution record containing the old outstanding components, internally settled amount, new loan values and actual customer cash.
- The replacement loan keeps its full approved new principal. It is never reduced to the net cash amount.
- Only `new principal - current previous-loan outstanding` is debited from the branch funding account and given to the customer.
- The new loan becomes active with the full approved principal, interest, fees and total repayment outstanding.
- The old loan customer statement receives an internal-settlement credit; the replacement loan statement receives the full new loan obligation.
- Atomic accounting entries distinguish the new principal receivable, the previous principal settlement, recognized interest/fees/penalties settlement and net cash movement.
- Top-up execution is fully transactional: loan state, schedules, statements, accounting and account balance either all post or all roll back.
- Duplicate execution, concurrent state changes, insufficient funds, wrong branch, missing signed contract and non-ready replacement loans are rejected.
- Loan-number generation was hardened to self-heal if the sequence counter is ever behind actual branch/year loan records.
- Branch-scoped Loan Top-Ups workspace showing active-loan eligibility, replacement applications in progress and completed top-up history.
- Backend-generated top-up preview showing new principal, interest, fees, total repayment, current internal settlement estimate, net cash estimate and repayment schedule.
- Dedicated execution form and permanent completed-top-up detail page.
- Sidebar navigation and dedicated top-up permissions.

## Required example verified

The implementation covers the specified example exactly:

- Previous loan total: `100,000`
- Amount already paid: `80,000`
- Previous outstanding: `20,000`
- New principal: `120,000`
- New interest: `10,000`
- New total repayment: `130,000`
- Amount settled internally: `20,000`
- Cash actually given to customer: `100,000`
- Replacement loan principal remains: `120,000`

## Database changes

### `businesses`

Added:

- `top_up_eligibility_percentage`

Default: `70.00`

### `loans`

Added:

- `top_up_source_loan_id`
- `top_up_eligibility_percentage`
- `top_up_paid_percentage_at_application`
- `top_up_override_used`
- `top_up_override_reason`
- `top_up_override_by`
- `top_up_override_at`

Added lifecycle status:

- `closed_through_top_up`

### `loan_top_ups`

Stores:

- System-generated top-up number
- Branch
- Previous loan
- Replacement loan
- Funding account
- Previous total outstanding
- Previous principal outstanding
- Previous interest outstanding
- Previous fees outstanding
- Previous penalties outstanding
- Amount settled internally
- New principal
- New interest
- New fees
- New total repayment
- Cash given to customer
- Execution date
- Reference
- Notes
- Executing employee
- Related financial-account transaction

Previous and replacement loan links are unique so the same executed relationship cannot be posted twice.

## Main services

- `LoanTopUpService`
  - eligibility
  - replacement application creation
  - override enforcement
  - locked balance recalculation
  - previous-loan internal settlement
  - replacement-loan activation
  - permanent status and statement history
  - duplicate/concurrency protection
- `LoanTopUpAccountingService`
  - net-cash account movement
  - balanced accounting journal
  - old principal/interest/fees/penalties settlement
  - full new principal receivable
- `LoanApplicationService`
  - existing calculation/schedule engine reused
  - loan sequence generation hardened against stale counters
- `LoanDisbursementService`
  - rejects top-up replacement loans from ordinary disbursement

## Routes/endpoints added

- `GET /loan-top-ups`
- `GET /loans/{loan}/top-up`
- `POST /loans/{loan}/top-up/preview`
- `POST /loans/{loan}/top-up`
- `GET /loans/{loan}/top-up/execute`
- `POST /loans/{loan}/top-up/execute`
- `GET /loan-top-ups/{topUp}`

## Permissions

Added:

- `loans.top-up.view`
- `loans.top-up.create`
- `loans.top-up.execute`
- `loans.top-up.override`

Role defaults:

- Branch Administrator: view, create, execute, override
- Branch Manager: view, create, execute, override
- Loan Officer: view, create
- Loan Inspector: view
- Cashier: view, execute
- Accountant: view, execute
- Auditor: view
- Super Administrator: all through the existing super-admin bypass

## Automated coverage

Step 13 tests verify:

- The full 100,000 / 80,000 / 20,000 / 120,000 / 100,000 example.
- Replacement principal stays at the full new principal.
- Only net cash leaves the funding account.
- Old loan closes specifically through top-up.
- New loan activates with full principal, interest, fees and total outstanding.
- Balanced top-up accounting journal.
- Permanent customer statements and loan-status histories.
- Configurable and custom eligibility thresholds.
- Penalties are excluded from eligibility progress.
- Unauthorized override rejection.
- Authorized override reason/user/time persistence and auditing.
- Execution uses the current locked old-loan balance rather than a stale quote.
- Signed-contract requirement.
- Insufficient-net-funds atomic rollback.
- Duplicate top-up execution prevention.
- Ordinary-disbursement bypass prevention for top-up replacement loans.
- Branch isolation and permission enforcement.
- Standardized top-up UI and business-setting rendering.

## Verification result

On the audited Step 13 implementation head before this documentation commit:

- **106 tests passed**
- **932 assertions passed**
- Fresh migrations passed.
- All seeders passed.
- All previously implemented modules remained green.

A final CI run is required on the documentation head before merge, following the repository's standard merge gate.

## Remaining for Step 13

- None.
- Early settlement and restructuring remain Step 14 and are intentionally not implemented here.
