Skip to content

ADR-0024: Manual transaction itemization

Context

Transactions are captured automatically with a single total. Users need to break a transaction into item lines (e.g. a food order split into dishes, or a bill with a discount line) — added, edited, and deleted by hand.

Decision

Manual itemization is stored as child rows of a transaction:

  • New transaction_items table (schema v7): id, transactionId (FK → transactions(id) ON DELETE CASCADE), name, amountMillis (signed Long minor units, ADR-0009), position.
  • The transaction's amountMillis stays the authoritative total. Itemization never rewrites it.
  • Item costs are signed: a negative amount is a discount line. Zero new parser work — AmountParser already parses a leading -, and MoneyText.of renders negatives.
  • Items share the transaction's currency — no per-item currency.
  • The mismatch warning is informational and non-blocking: the item sum is computed in-memory in the ViewModel (ADR-0006) and compared against the transaction total. Save stays enabled.
  • Save replaces the item set atomically (deleteByTransaction + insertAll in a DAO @Transaction). Cascade delete comes free from the FK.
  • Export/import carry items as an additive field (export version 2); old files import fine, old readers ignore the new key.

Consequences

  • Itemization is opt-in: transactions without items behave exactly as before.
  • Validation blocks save only when an item has an unparseable cost or a name-less item carries a cost (mirrors the existing detail_cant_read_amount pattern).
  • The schema version is bumped to 7; feature work that needs its own schema change must take v8+.