feat(sales): local sales/purchase ledger (any currency)

A sale is a distinct model from a gift or a Plantare (reproduction
commitment): a recorded seed sale or purchase with an optional price in
ANY currency — €, Ğ1, time, or none yet. Mirrors the Plantare feature.

- schema v10: Sales table (SyncColumns), guarded createTable migration,
  schema dump + generated schema_v10 for the migration round-trip test
- enum SaleDirection { iSold, iBought }
- VarietyRepository: create/watch/watchForVariety/delete + backup
  export/exportForSync/import (LWW-by-HLC, tombstones)
- InventorySnapshot.sales + JSON codec encode/decode (round-trips amount,
  currency, counterparty)
- UI: SalesScreen (/sales), sale sheet, drawer entry, variety-detail
  action beside 'add Plantare'; money hides a trailing .0
- i18n sale block + menu.sales in en/es/pt/ast
- tests: sales repo test (5) incl. Ğ1/price-less/backup round-trip;
  Sales screen added to the small-screen overflow guard

Also harden the overflow guard: swallow the headless engine's image
resource service PNG-decode errors (unrelated to layout) while still
failing on real RenderFlex overflows, so home/about stop reporting
false failures.
This commit is contained in:
vjrj 2026-07-11 02:33:42 +02:00
parent 833ddaed4d
commit e2665c27d4
27 changed files with 6156 additions and 23 deletions

View file

@ -38,6 +38,7 @@
"wishlist": "Wishlist",
"following": "Following",
"plantares": "Plantares",
"sales": "Sales",
"settings": "Settings"
},
"settings": {
@ -580,5 +581,23 @@
"openSection": "Open",
"settledSection": "Done",
"removeConfirm": "Remove this commitment?"
},
"sale": {
"title": "Sales",
"help": "Record seed you sold or bought — money, Ğ1, or any currency. A separate model from a gift or a Plantare. No commission is ever taken on seeds.",
"add": "Record a sale",
"empty": "No sales yet. Note here what you sell or buy.",
"iSold": "I sold",
"iBought": "I bought",
"direction": "Sold or bought?",
"counterparty": "With whom?",
"counterpartyHint": "A person or a collective (optional)",
"amount": "Amount",
"currency": "Currency",
"currencyHint": "€, Ğ1, hours… (optional)",
"note": "Note (optional)",
"save": "Save",
"delete": "Remove",
"removeConfirm": "Remove this sale?"
}
}