# Acquisition

How a car's purchase (M-Compra) is saved as an append-only header and ledger entries, why filling in a blank is not a correction, how the amount financed opens a Loan, and why a replay never saves twice.

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

An Acquisition is how a car's Ownership Period began (RF06). `acquisition.save` writes, in one transaction:

- a ★ `acquisition` header: the purchase date, down payment and amount financed in the car's currency, and the Loan it opened;
- ★ `ledger_entry` rows of kind `acquisition_price` and `acquisition_cost` (the purchase base, RN06), dated the purchase date;
- above zero financed, a `loan` whose principal is the amount financed and an opening ★ `loan_snapshot`. The principal is never a ledger entry or an expense (RN05);
- with an invoice, a `vehicle_document` of kind `purchase_invoice` for the uploaded file; new entries get trust `with_proof`;
- the Ownership Period's `start_date`, which follows the purchase date. A car in the garage without a period gets one; a wished-for car without one is `BAD_REQUEST`, and so is a sold car's date after its sale.

Every field is optional (US06). An omitted field keeps its value, so a partial save is fine and the rest can be filled in later. The first save makes `recorded` true, which removes the garage's "Next step" card.

## Corrections (ADR-01, RNF12) [#corrections-adr-01-rnf12]

Nothing is updated. Changing a recorded value (price, costs, date, down payment, amount financed) is a Correction:

- a changed price or cost, or a moved date, writes a `correction` entry naming the latest entry of that kind, with the `reason`;
- the new header names the one it replaces (`corrects_id`), so a period's headers form a chain and the latest is the purchase as it stands;
- a changed amount financed opens a new Loan with the old one's terms and settles the old one with a zero-balance snapshot (a Loan's principal is fixed).

Without a `reason`, a save that would change a recorded value writes nothing and answers `{ status: "reason_required" }`. Filling in a blank needs no reason. A save that changes nothing writes nothing.

## Trade-in (US08) [#trade-in-us08]

`tradeIn: { vehicleId, valueMinor }` names one of the household's current cars. The same transaction sells it on the purchase date for `valueMinor` through Close sale's `recordSale` (Disposal header, `sale_proceeds` entry, a remaining Loan paid off, period closed, Sold), so the old car's deal result uses the trade-in value. The header stores `trade_in_vehicle_id` and `trade_in_entry_id`, the old car's `sale_proceeds` entry, which is the money; nothing is copied into the new car's ledger and its purchase base stays the full price. Corrections carry the link forward.

`acquisition.get` answers `tradeIn` (car and value) once linked, and `tradeInOptions`: the other cars in the garage with an open period in the same currency, while the purchase has no trade-in and is not a sold car's. A trade-in is `BAD_REQUEST` when it is this car, not in the garage, in another currency, owned only after the purchase date, or when the purchase already has one; another Organization's car is `NOT_FOUND`. Nothing is written then. A sale is never undone (RN01), so a linked trade-in stays.

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

The client generates the save's `id` (a UUID), which becomes the new header's id. Sending it again answers the purchase as it stands and writes nothing more; saves of one car run one at a time. The same id for another car is `CONFLICT`.

## Procedures [#procedures]

| Procedure                                                                                                                                           | Who                         | Failures                                                                                                                                                     |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `acquisition.get` `{ vehicleId }`                                                                                                                   | Members of the Organization | `NOT_FOUND` (car)                                                                                                                                            |
| `acquisition.save` `{ id, vehicleId, acquiredOn?, priceMinor?, costsMinor?, downPaymentMinor?, financedMinor?, invoiceFileId?, tradeIn?, reason? }` | Members of the Organization | `INPUT_VALIDATION`, `NOT_FOUND` (car, invoice file or trade-in), `BAD_REQUEST` (no period to record, date after the sale, trade-in refused), `CONFLICT` (id) |

`acquisition.get` answers before any save too: the period's start as the date, and price and costs already in the ledger. `null` is a value not entered yet. `timeOwned(from, until)` in `@autofolio/domain/ownership` gives M-Compra's "3 years, 4 months", until today or the sale.

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

All tables have row-level security by Active Organization. `acquisition`, `ledger_entry` and `loan_snapshot` are append-only for every role. No `audit_log` event is written: the ★ rows are the purchase's audit trail, and amounts never go to logs (RNF07).

## App [#app]

M-Compra (`screen-M-Compra`, route `/purchase/[vehicleId]`) opens from the garage's "Next step" card and the vehicle profile's More menu. The date card opens a date sheet and shows the time owned; price, acquisition costs, down payment and amount financed are fields in the account locale (blank stays to fill in later); the trade-in row (`troca`) opens a sheet (`sheet-troca`) to pick a car and its value (`valor-troca`, `usar-troca`). Save (`salvar`) confirms a trade-in first, and asks for a reason when the server answers `reason_required`. A linked trade-in opens the old car's profile. More holds "Attach invoice" (a new document of kind `purchase_invoice`) or Documents. "Close sale" opens M-Fechar-Venda.

`autofolio://dev/M-Compra` opens the seeded Corolla Cross with the drawn down payment and amount financed typed in.

## Key files [#key-files]

- [`packages/api/src/features/acquisition/acquisition.contract.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/api/src/features/acquisition/acquisition.contract.ts)
- [`packages/api/src/features/acquisition/acquisition.service.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/api/src/features/acquisition/acquisition.service.ts)
- [`packages/db/src/schema/ledger.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/db/src/schema/ledger.ts)
- [`apps/mobile/src/features/acquisition/acquisition.screen.tsx`](https://github.com/CarlosZiegler/autofolio/blob/main/apps/mobile/src/features/acquisition/acquisition.screen.tsx)
- [`packages/domain/src/ownership.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/domain/src/ownership.ts)

