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:
parent
de6938d5d7
commit
6de039d518
27 changed files with 6156 additions and 23 deletions
|
|
@ -18,6 +18,7 @@ import 'harvest_date_picker.dart';
|
|||
import 'photo_pick.dart';
|
||||
import 'plantare_sheet.dart';
|
||||
import 'quantity_kind_l10n.dart';
|
||||
import 'sale_sheet.dart';
|
||||
import 'quantity_picker.dart';
|
||||
import 'seed_glyph.dart';
|
||||
import 'theme.dart';
|
||||
|
|
@ -110,19 +111,31 @@ class _DetailView extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_SectionTitle(t.plantare.title),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: OutlinedButton.icon(
|
||||
key: const Key('detail.addPlantare'),
|
||||
icon: const Icon(Icons.volunteer_activism_outlined, size: 18),
|
||||
onPressed: () => showPlantareSheet(
|
||||
context,
|
||||
repository: context.read<VarietyRepository>(),
|
||||
varietyId: detail.id,
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
key: const Key('detail.addPlantare'),
|
||||
icon: const Icon(Icons.volunteer_activism_outlined, size: 18),
|
||||
onPressed: () => showPlantareSheet(
|
||||
context,
|
||||
repository: context.read<VarietyRepository>(),
|
||||
varietyId: detail.id,
|
||||
),
|
||||
label: Text(t.plantare.add),
|
||||
),
|
||||
label: Text(t.plantare.add),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
key: const Key('detail.addSale'),
|
||||
icon: const Icon(Icons.sell_outlined, size: 18),
|
||||
onPressed: () => showSaleSheet(
|
||||
context,
|
||||
repository: context.read<VarietyRepository>(),
|
||||
varietyId: detail.id,
|
||||
),
|
||||
label: Text(t.sale.add),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (detail.notes != null && detail.notes!.trim().isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue