# Loan

How a car's financing (M-Financiamento) keeps its balance as an append-only history, books interest and fees to the ledger, never counts principal as an expense, and opens or edits a Loan from the app.

## What it does [#what-it-does]

A Loan finances one car (RF07, US07). The `loan` row holds the fixed terms: lender, amount financed (`principal_minor`), currency, rate and term. What is still owed is the ★ `loan_snapshot` series, and what the Loan costs is ★ `ledger_entry` rows of kind `loan_interest` and `loan_fee`, which count in total cost (TCO). Principal is never an expense (RN05): what was repaid is only the amount financed minus the latest balance, which builds equity.

A car shows its newest Loan, the last one recorded. M-Compra opens one from the amount financed ([Acquisition](/docs/features/acquisition)); M-Financiamento opens one for a car without a balance owed.

## Procedures [#procedures]

| Procedure                                                                                 | Who                         | Failures                                                                                                                               |
| ----------------------------------------------------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `loan.get` `{ vehicleId }`                                                                | Members of the Organization | `NOT_FOUND` (car)                                                                                                                      |
| `loan.create` `{ id, vehicleId, principalMinor, startDate, lender?, rate?, termMonths? }` | Members of the Organization | `INPUT_VALIDATION`, `NOT_FOUND` (car), `BAD_REQUEST` (not in the garage, start in the future), `CONFLICT` (a balance owed, id)         |
| `loan.updateTerms` `{ loanId, lender, rate, termMonths }`                                 | Members of the Organization | `INPUT_VALIDATION`, `NOT_FOUND` (Loan)                                                                                                 |
| `loan.updateBalance` `{ id, loanId, balanceMinor, asOf, interestMinor?, feesMinor? }`     | Members of the Organization | `INPUT_VALIDATION`, `NOT_FOUND` (Loan), `BAD_REQUEST` (car not in the garage, date before the start or in the future), `CONFLICT` (id) |

Each answers the car and its newest Loan as `loan.get` does: the latest balance and its date, the history (oldest first, at most 60), principal paid, interest and fees (as they stand after corrections, since the start), and the payments made. Payments are the monthly payment days reached from the start to the latest balance, at most the term ("22/48").

A rate is basis points of its period: `{ bp: 690, period: "year" }` is "6.9% APR", `{ bp: 119, period: "month" }` is "1,19% a.m.". The app picks the period the market quotes (a month in Brazil, a year elsewhere) and the owner can switch it.

## Update balance [#update-balance]

`loan.updateBalance` writes a new snapshot and, when entered, the interest and fees paid since the last update as entries dated the balance's date in the car's open Ownership Period. A backdated balance joins the history in date order; the latest balance is the newest date's. A zero balance pays the Loan off, after which a new one can be opened.

## Offline and idempotency (RNF04) [#offline-and-idempotency-rnf04]

The client generates the Loan's id and the snapshot's id. Sending one again answers the Loan as it stands and writes nothing more; an id that names another car's Loan or another Loan's snapshot is `CONFLICT`.

## Data and isolation [#data-and-isolation]

`loan` has row-level security by Active Organization and only its terms are updatable; `loan_snapshot` and `ledger_entry` are append-only for every role. No `audit_log` event is written: the ★ rows are the trail, and amounts never go to logs (RNF07).

## App [#app]

M-Financiamento (`screen-M-Financiamento`, route `/loan/[vehicleId]`) shows the balance in `negative` with its history line, principal with payments made, interest and fees, the rate, the RN05 note and "Update balance" (`atualizar-saldo`). The More menu edits the terms, or opens a new Loan once one is paid off; a car without a Loan offers Add loan. The vehicle profile's Loan balance card opens it. `autofolio://dev/M-Financiamento` opens the seeded M3.

## Key files [#key-files]

- [`packages/api/src/features/loan/loan.contract.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/api/src/features/loan/loan.contract.ts)
- [`packages/api/src/features/loan/loan.service.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/api/src/features/loan/loan.service.ts)
- [`apps/mobile/src/features/loan/loan.screen.tsx`](https://github.com/CarlosZiegler/autofolio/blob/main/apps/mobile/src/features/loan/loan.screen.tsx)

