feat(v2): lock deposit currency to machine.fiat_code (closes #26)

Each machine handles exactly one currency today (operator-set on
`dca_machines.fiat_code`). The deposit's currency is fully determined
by the machine it's recorded against, so it shouldn't be operator-
chooseable in the first place.

Surfaced during 2026-05-16 E2E testing: Jordan had a "15 USD" deposit
recorded against an EUR Sintra (operator typo in the freeform currency
input). The balance summary is currency-blind (`SUM(amount)` over
mixed currencies), so on the next cash-out the system distributed
15 EUR worth of sats on the strength of that 15 USD row. Worked out
by chance; could have over-paid by ~10% if the actual EUR/USD rate
had been further off.

Fix:
  - `CreateDepositData` / `UpdateDepositData` no longer carry a
    `currency` field. Any client-submitted value is silently dropped
    at Pydantic validation, before reaching the handler.
  - `api_create_deposit` resolves the machine's `fiat_code` and
    passes it to `create_deposit(..., currency=...)` as a required
    keyword arg. The deposit row's `currency` column always matches
    the machine going forward.
  - UI: the freeform `<q-input label="Currency">` becomes a read-only
    `<q-chip>` slot on the amount field, sourced from the new
    `depositMachineFiatCode` computed (resolves via the selected
    client's machine).
  - `m005_lock_deposit_currency_to_machine_fiat_code` migration
    backfills existing rows: every `dca_deposits.currency` gets
    rewritten to match its joined `dca_machines.fiat_code`. Greg's
    stray `15 USD` row becomes `15 EUR` (the right answer at today's
    invariant).

Multi-currency-per-machine support is explicitly out of scope here;
when hardware ships that reads multiple denominations across
currencies, the relevant changes are documented in issue #26's
"Future" section (dca_machines.fiat_codes set, currency-aware
balance summary, etc.). The current fix is "lock the input side";
that future work is "unlock it but constrained to the machine's
declared set".

3 new unit tests (`tests/test_deposit_currency.py`) lock in the
model-contract guarantees. Total suite 89 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-16 18:03:34 +02:00
commit d2e682712d
7 changed files with 151 additions and 15 deletions

View file

@ -165,10 +165,19 @@ class ClientBalanceSummary(BaseModel):
class CreateDepositData(BaseModel):
"""Operator records a fiat deposit against an LP enrolment.
`currency` is server-set from the target machine's `fiat_code` at
write time the API ignores any value the client submits. Each
machine currently handles exactly one currency (`dca_machines.
fiat_code`); allowing the operator to pick a different one at
deposit time would either be a typo or a future multi-currency
feature that doesn't exist yet (`aiolabs/satmachineadmin#26`).
"""
client_id: str
machine_id: str
amount: float
currency: str = "GTQ"
notes: Optional[str] = None
@validator("amount")
@ -192,8 +201,11 @@ class DcaDeposit(BaseModel):
class UpdateDepositData(BaseModel):
"""Operator edits on a pending deposit. `currency` removed — see
`CreateDepositData`; the currency is bound to the machine and not
editable after the row lands."""
amount: Optional[float] = None
currency: Optional[str] = None
notes: Optional[str] = None
@validator("amount")