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 de6938d5d7
commit 6de039d518
27 changed files with 6156 additions and 23 deletions

View file

@ -78,6 +78,7 @@ class TranslationsEs extends Translations with BaseTranslations<AppLocale, Trans
@override late final _Translations$wot$es wot = _Translations$wot$es._(_root);
@override late final _Translations$notifications$es notifications = _Translations$notifications$es._(_root);
@override late final _Translations$plantare$es plantare = _Translations$plantare$es._(_root);
@override late final _Translations$sale$es sale = _Translations$sale$es._(_root);
}
// Path: app
@ -160,6 +161,7 @@ class _Translations$menu$es extends Translations$menu$en {
@override String get wishlist => 'Lista de deseos';
@override String get following => 'Siguiendo';
@override String get plantares => 'Plantares';
@override String get sales => 'Ventas';
@override String get settings => 'Ajustes';
}
@ -841,6 +843,31 @@ class _Translations$plantare$es extends Translations$plantare$en {
@override String get removeConfirm => '¿Quitar este compromiso?';
}
// Path: sale
class _Translations$sale$es extends Translations$sale$en {
_Translations$sale$es._(TranslationsEs root) : this._root = root, super.internal(root);
final TranslationsEs _root; // ignore: unused_field
// Translations
@override String get title => 'Ventas';
@override String get help => 'Registra semilla vendida o comprada — dinero, Ğ1 o cualquier moneda. Un modelo aparte del regalo y del Plantare. Nunca se cobra comisión por las semillas.';
@override String get add => 'Registrar venta';
@override String get empty => 'Aún no hay ventas. Anota aquí lo que vendes o compras.';
@override String get iSold => 'Vendí';
@override String get iBought => 'Compré';
@override String get direction => '¿Vendes o compras?';
@override String get counterparty => '¿Con quién?';
@override String get counterpartyHint => 'Una persona o un colectivo (opcional)';
@override String get amount => 'Importe';
@override String get currency => 'Moneda';
@override String get currencyHint => '€, Ğ1, horas… (opcional)';
@override String get note => 'Nota (opcional)';
@override String get save => 'Guardar';
@override String get delete => 'Quitar';
@override String get removeConfirm => '¿Quitar esta venta?';
}
// Path: intro.slides
class _Translations$intro$slides$es extends Translations$intro$slides$en {
_Translations$intro$slides$es._(TranslationsEs root) : this._root = root, super.internal(root);
@ -1210,6 +1237,7 @@ extension on TranslationsEs {
'menu.wishlist' => 'Lista de deseos',
'menu.following' => 'Siguiendo',
'menu.plantares' => 'Plantares',
'menu.sales' => 'Ventas',
'menu.settings' => 'Ajustes',
'settings.language' => 'Idioma',
'settings.systemLanguage' => 'Idioma del sistema',
@ -1623,6 +1651,22 @@ extension on TranslationsEs {
'plantare.openSection' => 'Pendientes',
'plantare.settledSection' => 'Hechos',
'plantare.removeConfirm' => '¿Quitar este compromiso?',
'sale.title' => 'Ventas',
'sale.help' => 'Registra semilla vendida o comprada — dinero, Ğ1 o cualquier moneda. Un modelo aparte del regalo y del Plantare. Nunca se cobra comisión por las semillas.',
'sale.add' => 'Registrar venta',
'sale.empty' => 'Aún no hay ventas. Anota aquí lo que vendes o compras.',
'sale.iSold' => 'Vendí',
'sale.iBought' => 'Compré',
'sale.direction' => '¿Vendes o compras?',
'sale.counterparty' => '¿Con quién?',
'sale.counterpartyHint' => 'Una persona o un colectivo (opcional)',
'sale.amount' => 'Importe',
'sale.currency' => 'Moneda',
'sale.currencyHint' => '€, Ğ1, horas… (opcional)',
'sale.note' => 'Nota (opcional)',
'sale.save' => 'Guardar',
'sale.delete' => 'Quitar',
'sale.removeConfirm' => '¿Quitar esta venta?',
_ => null,
};
}