Merge branch 'claude/zealous-nobel-7dabeb'
This commit is contained in:
commit
332f52f470
29 changed files with 5874 additions and 201 deletions
2171
apps/app_seeds/drift_schemas/drift_schema_v11.json
Normal file
2171
apps/app_seeds/drift_schemas/drift_schema_v11.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -79,6 +79,8 @@ class InventoryJsonCodec {
|
||||||
'originPlace': l.originPlace,
|
'originPlace': l.originPlace,
|
||||||
'abundance': l.abundance?.name,
|
'abundance': l.abundance?.name,
|
||||||
'preservationFormat': l.preservationFormat?.name,
|
'preservationFormat': l.preservationFormat?.name,
|
||||||
|
'priceAmount': l.priceAmount,
|
||||||
|
'priceCurrency': l.priceCurrency,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'vernacularNames': [
|
'vernacularNames': [
|
||||||
|
|
@ -338,6 +340,8 @@ class InventoryJsonCodec {
|
||||||
PreservationFormat.values,
|
PreservationFormat.values,
|
||||||
m['preservationFormat'],
|
m['preservationFormat'],
|
||||||
),
|
),
|
||||||
|
priceAmount: (m['priceAmount'] as num?)?.toDouble(),
|
||||||
|
priceCurrency: m['priceCurrency'] as String?,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
vernacularNames: _rows(root, 'vernacularNames', (m) {
|
vernacularNames: _rows(root, 'vernacularNames', (m) {
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,8 @@ class ShareableLot extends Equatable {
|
||||||
this.category,
|
this.category,
|
||||||
this.harvestYear,
|
this.harvestYear,
|
||||||
this.isOrganic = false,
|
this.isOrganic = false,
|
||||||
|
this.priceAmount,
|
||||||
|
this.priceCurrency,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String lotId;
|
final String lotId;
|
||||||
|
|
@ -246,9 +248,23 @@ class ShareableLot extends Equatable {
|
||||||
/// filter the market for it.
|
/// filter the market for it.
|
||||||
final bool isOrganic;
|
final bool isOrganic;
|
||||||
|
|
||||||
|
/// Asking price, only populated for [OfferStatus.sell] lots. Null amount on
|
||||||
|
/// a sale means "price to be agreed" — the offer publishes without a price.
|
||||||
|
final double? priceAmount;
|
||||||
|
final String? priceCurrency;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props =>
|
List<Object?> get props => [
|
||||||
[lotId, varietyId, summary, offerStatus, category, harvestYear, isOrganic];
|
lotId,
|
||||||
|
varietyId,
|
||||||
|
summary,
|
||||||
|
offerStatus,
|
||||||
|
category,
|
||||||
|
harvestYear,
|
||||||
|
isOrganic,
|
||||||
|
priceAmount,
|
||||||
|
priceCurrency,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One germination test on a lot; [rate] is derived (0..1), null when it can't
|
/// One germination test on a lot; [rate] is derived (0..1), null when it can't
|
||||||
|
|
@ -322,6 +338,8 @@ class VarietyLot extends Equatable {
|
||||||
this.abundance,
|
this.abundance,
|
||||||
this.preservationFormat,
|
this.preservationFormat,
|
||||||
this.offerStatus = OfferStatus.private,
|
this.offerStatus = OfferStatus.private,
|
||||||
|
this.priceAmount,
|
||||||
|
this.priceCurrency,
|
||||||
this.germinationTests = const [],
|
this.germinationTests = const [],
|
||||||
this.conditionChecks = const [],
|
this.conditionChecks = const [],
|
||||||
});
|
});
|
||||||
|
|
@ -354,6 +372,11 @@ class VarietyLot extends Equatable {
|
||||||
/// the network is Block 2.
|
/// the network is Block 2.
|
||||||
final OfferStatus offerStatus;
|
final OfferStatus offerStatus;
|
||||||
|
|
||||||
|
/// Asking price, meaningful when [offerStatus] is `sell`. Null amount means
|
||||||
|
/// "price to be agreed".
|
||||||
|
final double? priceAmount;
|
||||||
|
final String? priceCurrency;
|
||||||
|
|
||||||
final List<GerminationEntry> germinationTests;
|
final List<GerminationEntry> germinationTests;
|
||||||
|
|
||||||
/// Storage-condition checks, most-recent first (`conditionChecks.first` = last).
|
/// Storage-condition checks, most-recent first (`conditionChecks.first` = last).
|
||||||
|
|
@ -377,6 +400,8 @@ class VarietyLot extends Equatable {
|
||||||
abundance,
|
abundance,
|
||||||
preservationFormat,
|
preservationFormat,
|
||||||
offerStatus,
|
offerStatus,
|
||||||
|
priceAmount,
|
||||||
|
priceCurrency,
|
||||||
germinationTests,
|
germinationTests,
|
||||||
conditionChecks,
|
conditionChecks,
|
||||||
];
|
];
|
||||||
|
|
@ -1174,7 +1199,10 @@ class VarietyRepository {
|
||||||
Future<({String id, String? family})?> _autoClassifyFromLabel(
|
Future<({String id, String? family})?> _autoClassifyFromLabel(
|
||||||
String label,
|
String label,
|
||||||
) async {
|
) async {
|
||||||
final speciesId = matchSpeciesInLabel(label, await _speciesNamesForMatching());
|
final speciesId = matchSpeciesInLabel(
|
||||||
|
label,
|
||||||
|
await _speciesNamesForMatching(),
|
||||||
|
);
|
||||||
if (speciesId == null) return null;
|
if (speciesId == null) return null;
|
||||||
final species = await (_db.select(
|
final species = await (_db.select(
|
||||||
_db.species,
|
_db.species,
|
||||||
|
|
@ -1281,9 +1309,12 @@ class VarietyRepository {
|
||||||
Abundance? abundance,
|
Abundance? abundance,
|
||||||
PreservationFormat? preservationFormat,
|
PreservationFormat? preservationFormat,
|
||||||
OfferStatus offerStatus = OfferStatus.private,
|
OfferStatus offerStatus = OfferStatus.private,
|
||||||
|
double? priceAmount,
|
||||||
|
String? priceCurrency,
|
||||||
}) async {
|
}) async {
|
||||||
final (created, updated) = await _stamp();
|
final (created, updated) = await _stamp();
|
||||||
final id = idGen.newId();
|
final id = idGen.newId();
|
||||||
|
final forSale = offerStatus == OfferStatus.sell;
|
||||||
await _db
|
await _db
|
||||||
.into(_db.lots)
|
.into(_db.lots)
|
||||||
.insert(
|
.insert(
|
||||||
|
|
@ -1306,6 +1337,8 @@ class VarietyRepository {
|
||||||
abundance: Value(abundance),
|
abundance: Value(abundance),
|
||||||
preservationFormat: Value(preservationFormat),
|
preservationFormat: Value(preservationFormat),
|
||||||
offerStatus: Value(offerStatus),
|
offerStatus: Value(offerStatus),
|
||||||
|
priceAmount: Value(forSale ? priceAmount : null),
|
||||||
|
priceCurrency: Value(forSale ? priceCurrency : null),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return id;
|
return id;
|
||||||
|
|
@ -1326,8 +1359,13 @@ class VarietyRepository {
|
||||||
Abundance? abundance,
|
Abundance? abundance,
|
||||||
PreservationFormat? preservationFormat,
|
PreservationFormat? preservationFormat,
|
||||||
OfferStatus offerStatus = OfferStatus.private,
|
OfferStatus offerStatus = OfferStatus.private,
|
||||||
|
double? priceAmount,
|
||||||
|
String? priceCurrency,
|
||||||
}) async {
|
}) async {
|
||||||
final (_, updated) = await _stamp();
|
final (_, updated) = await _stamp();
|
||||||
|
// Price only makes sense on a lot offered for sale; clearing the sale
|
||||||
|
// status clears the price with it.
|
||||||
|
final forSale = offerStatus == OfferStatus.sell;
|
||||||
await (_db.update(_db.lots)..where((l) => l.id.equals(lotId))).write(
|
await (_db.update(_db.lots)..where((l) => l.id.equals(lotId))).write(
|
||||||
LotsCompanion(
|
LotsCompanion(
|
||||||
type: Value(type),
|
type: Value(type),
|
||||||
|
|
@ -1343,6 +1381,8 @@ class VarietyRepository {
|
||||||
abundance: Value(abundance),
|
abundance: Value(abundance),
|
||||||
preservationFormat: Value(preservationFormat),
|
preservationFormat: Value(preservationFormat),
|
||||||
offerStatus: Value(offerStatus),
|
offerStatus: Value(offerStatus),
|
||||||
|
priceAmount: Value(forSale ? priceAmount : null),
|
||||||
|
priceCurrency: Value(forSale ? priceCurrency : null),
|
||||||
updatedAt: Value(updated),
|
updatedAt: Value(updated),
|
||||||
lastAuthor: Value(nodeId),
|
lastAuthor: Value(nodeId),
|
||||||
),
|
),
|
||||||
|
|
@ -1532,16 +1572,13 @@ class VarietyRepository {
|
||||||
.get();
|
.get();
|
||||||
final bySpecies = <String, List<({String name, String? language})>>{};
|
final bySpecies = <String, List<({String name, String? language})>>{};
|
||||||
for (final n in rows) {
|
for (final n in rows) {
|
||||||
(bySpecies[n.speciesId] ??= []).add((
|
(bySpecies[n.speciesId] ??= []).add((name: n.name, language: n.language));
|
||||||
name: n.name,
|
|
||||||
language: n.language,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
final result = <String, String>{};
|
final result = <String, String>{};
|
||||||
for (final entry in bySpecies.entries) {
|
for (final entry in bySpecies.entries) {
|
||||||
final inLocale = entry.value.where((n) => n.language == languageCode);
|
final inLocale = entry.value.where((n) => n.language == languageCode);
|
||||||
result[entry.key] = (inLocale.isEmpty ? entry.value.first : inLocale.first)
|
result[entry.key] =
|
||||||
.name;
|
(inLocale.isEmpty ? entry.value.first : inLocale.first).name;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -1580,6 +1617,12 @@ class VarietyRepository {
|
||||||
category: v.category,
|
category: v.category,
|
||||||
harvestYear: l.harvestYear,
|
harvestYear: l.harvestYear,
|
||||||
isOrganic: v.isOrganic,
|
isOrganic: v.isOrganic,
|
||||||
|
priceAmount: l.offerStatus == OfferStatus.sell
|
||||||
|
? l.priceAmount
|
||||||
|
: null,
|
||||||
|
priceCurrency: l.offerStatus == OfferStatus.sell
|
||||||
|
? l.priceCurrency
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -1823,7 +1866,9 @@ class VarietyRepository {
|
||||||
}) async {
|
}) async {
|
||||||
final (created, updated) = await _stamp();
|
final (created, updated) = await _stamp();
|
||||||
final id = idGen.newId();
|
final id = idGen.newId();
|
||||||
await _db.into(_db.plantares).insert(
|
await _db
|
||||||
|
.into(_db.plantares)
|
||||||
|
.insert(
|
||||||
PlantaresCompanion.insert(
|
PlantaresCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
createdAt: created,
|
createdAt: created,
|
||||||
|
|
@ -1842,10 +1887,11 @@ class VarietyRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All live commitments, newest first (for the Plantares screen).
|
/// All live commitments, newest first (for the Plantares screen).
|
||||||
Stream<List<Plantare>> watchPlantares() => (_db.select(_db.plantares)
|
Stream<List<Plantare>> watchPlantares() =>
|
||||||
..where((p) => p.isDeleted.equals(false))
|
(_db.select(_db.plantares)
|
||||||
..orderBy([(p) => OrderingTerm.desc(p.madeOn)]))
|
..where((p) => p.isDeleted.equals(false))
|
||||||
.watch();
|
..orderBy([(p) => OrderingTerm.desc(p.madeOn)]))
|
||||||
|
.watch();
|
||||||
|
|
||||||
/// The commitments recorded against one variety.
|
/// The commitments recorded against one variety.
|
||||||
Stream<List<Plantare>> watchPlantaresForVariety(String varietyId) =>
|
Stream<List<Plantare>> watchPlantaresForVariety(String varietyId) =>
|
||||||
|
|
@ -1863,8 +1909,7 @@ class VarietyRepository {
|
||||||
await (_db.update(_db.plantares)..where((p) => p.id.equals(id))).write(
|
await (_db.update(_db.plantares)..where((p) => p.id.equals(id))).write(
|
||||||
PlantaresCompanion(
|
PlantaresCompanion(
|
||||||
status: Value(status),
|
status: Value(status),
|
||||||
settledOn:
|
settledOn: Value(status == PlantareStatus.open ? null : now),
|
||||||
Value(status == PlantareStatus.open ? null : now),
|
|
||||||
updatedAt: Value(updated),
|
updatedAt: Value(updated),
|
||||||
lastAuthor: Value(nodeId),
|
lastAuthor: Value(nodeId),
|
||||||
),
|
),
|
||||||
|
|
@ -1896,7 +1941,9 @@ class VarietyRepository {
|
||||||
}) async {
|
}) async {
|
||||||
final (created, updated) = await _stamp();
|
final (created, updated) = await _stamp();
|
||||||
final id = idGen.newId();
|
final id = idGen.newId();
|
||||||
await _db.into(_db.sales).insert(
|
await _db
|
||||||
|
.into(_db.sales)
|
||||||
|
.insert(
|
||||||
SalesCompanion.insert(
|
SalesCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
createdAt: created,
|
createdAt: created,
|
||||||
|
|
@ -1915,10 +1962,11 @@ class VarietyRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All live sales, newest first (for the Sales screen).
|
/// All live sales, newest first (for the Sales screen).
|
||||||
Stream<List<Sale>> watchSales() => (_db.select(_db.sales)
|
Stream<List<Sale>> watchSales() =>
|
||||||
..where((s) => s.isDeleted.equals(false))
|
(_db.select(_db.sales)
|
||||||
..orderBy([(s) => OrderingTerm.desc(s.soldOn)]))
|
..where((s) => s.isDeleted.equals(false))
|
||||||
.watch();
|
..orderBy([(s) => OrderingTerm.desc(s.soldOn)]))
|
||||||
|
.watch();
|
||||||
|
|
||||||
/// The sales recorded against one variety.
|
/// The sales recorded against one variety.
|
||||||
Stream<List<Sale>> watchSalesForVariety(String varietyId) =>
|
Stream<List<Sale>> watchSalesForVariety(String varietyId) =>
|
||||||
|
|
@ -1941,6 +1989,130 @@ class VarietyRepository {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Hand-over: the one moment seeds change hands --------------------------
|
||||||
|
|
||||||
|
/// Records a hand-over in a single transaction (data-model §2.8: a closed
|
||||||
|
/// deal produces a `Movement` and, optionally, a `Plantare`): the Movement
|
||||||
|
/// (given/received) plus — when they came with it — the Sale (money changed
|
||||||
|
/// hands, informational only, never processed in-app) and the Plantare (a
|
||||||
|
/// return promise), all sharing the same free-text [counterparty].
|
||||||
|
///
|
||||||
|
/// With nothing optional this is a plain gift: just the Movement.
|
||||||
|
///
|
||||||
|
/// [gaveAll] is the one-tap "all of it" (the natural path for a whole plant
|
||||||
|
/// or shrub): the movement carries the lot's current quantity, the lot's
|
||||||
|
/// precise quantity drops to 0 and it returns to [OfferStatus.private] —
|
||||||
|
/// withdrawing it from the market on the next publish cycle. Otherwise
|
||||||
|
/// [quantity] says how much of the batch moved (optional).
|
||||||
|
Future<({String movementId, String? saleId, String? plantareId})>
|
||||||
|
recordHandover({
|
||||||
|
required String lotId,
|
||||||
|
required HandoverDirection direction,
|
||||||
|
bool gaveAll = false,
|
||||||
|
Quantity? quantity,
|
||||||
|
String? counterparty,
|
||||||
|
bool withPayment = false,
|
||||||
|
double? paymentAmount,
|
||||||
|
String? paymentCurrency,
|
||||||
|
bool withPromise = false,
|
||||||
|
String? promiseOwedDescription,
|
||||||
|
int? promiseDueBy,
|
||||||
|
String? note,
|
||||||
|
}) async {
|
||||||
|
final gave = direction == HandoverDirection.iGave;
|
||||||
|
return _db.transaction(() async {
|
||||||
|
final lot = await (_db.select(
|
||||||
|
_db.lots,
|
||||||
|
)..where((l) => l.id.equals(lotId))).getSingle();
|
||||||
|
|
||||||
|
// The promise first, so the movement can carry its id — the link the
|
||||||
|
// model reserved for exactly this (data-model §2.4: "a `given` may
|
||||||
|
// carry a Plantare").
|
||||||
|
String? plantareId;
|
||||||
|
if (withPromise) {
|
||||||
|
plantareId = await createPlantare(
|
||||||
|
direction: gave
|
||||||
|
? PlantareDirection.owedToMe
|
||||||
|
: PlantareDirection.iReturn,
|
||||||
|
varietyId: lot.varietyId,
|
||||||
|
counterparty: counterparty,
|
||||||
|
owedDescription: promiseOwedDescription,
|
||||||
|
dueBy: promiseDueBy,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final movedAll = gaveAll && gave;
|
||||||
|
final lotHasQuantity =
|
||||||
|
lot.quantityKind != null ||
|
||||||
|
lot.quantityPrecise != null ||
|
||||||
|
lot.quantityLabel != null;
|
||||||
|
final movedQuantity = movedAll
|
||||||
|
? (lotHasQuantity
|
||||||
|
? Quantity(
|
||||||
|
kind: _parseKind(lot.quantityKind),
|
||||||
|
count: lot.quantityPrecise,
|
||||||
|
label: lot.quantityLabel,
|
||||||
|
)
|
||||||
|
: null)
|
||||||
|
: quantity;
|
||||||
|
final noteParts = [
|
||||||
|
if (counterparty != null && counterparty.trim().isNotEmpty)
|
||||||
|
counterparty.trim(),
|
||||||
|
if (note != null && note.trim().isNotEmpty) note.trim(),
|
||||||
|
];
|
||||||
|
final (created, _) = await _stamp();
|
||||||
|
final movementId = idGen.newId();
|
||||||
|
await _db
|
||||||
|
.into(_db.movements)
|
||||||
|
.insert(
|
||||||
|
MovementsCompanion.insert(
|
||||||
|
id: movementId,
|
||||||
|
createdAt: created,
|
||||||
|
lastAuthor: nodeId,
|
||||||
|
lotId: lotId,
|
||||||
|
type: gave ? MovementType.given : MovementType.received,
|
||||||
|
occurredOn: Value(created),
|
||||||
|
quantityKind: Value(movedQuantity?.kind.name),
|
||||||
|
quantityPrecise: Value(movedQuantity?.count?.toDouble()),
|
||||||
|
quantityLabel: Value(movedQuantity?.label),
|
||||||
|
plantareId: Value(plantareId),
|
||||||
|
notes: Value(noteParts.isEmpty ? null : noteParts.join(' — ')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (movedAll) {
|
||||||
|
// The batch is gone: an empty jar (0, same unit) with nothing left to
|
||||||
|
// declare or offer. Price goes with the sale status.
|
||||||
|
final (_, updated) = await _stamp();
|
||||||
|
await (_db.update(_db.lots)..where((l) => l.id.equals(lotId))).write(
|
||||||
|
LotsCompanion(
|
||||||
|
quantityPrecise: const Value(0),
|
||||||
|
abundance: const Value(null),
|
||||||
|
offerStatus: const Value(OfferStatus.private),
|
||||||
|
priceAmount: const Value(null),
|
||||||
|
priceCurrency: const Value(null),
|
||||||
|
updatedAt: Value(updated),
|
||||||
|
lastAuthor: Value(nodeId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? saleId;
|
||||||
|
if (withPayment) {
|
||||||
|
saleId = await createSale(
|
||||||
|
direction: gave ? SaleDirection.iSold : SaleDirection.iBought,
|
||||||
|
varietyId: lot.varietyId,
|
||||||
|
counterparty: counterparty,
|
||||||
|
amount: paymentAmount,
|
||||||
|
currency: paymentCurrency,
|
||||||
|
note: note,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (movementId: movementId, saleId: saleId, plantareId: plantareId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Snapshots the live inventory (tombstones excluded) for the interchange
|
/// Snapshots the live inventory (tombstones excluded) for the interchange
|
||||||
/// export — data-model §7. Includes photo bytes; the JSON codec embeds them
|
/// export — data-model §7. Includes photo bytes; the JSON codec embeds them
|
||||||
/// as base64 and the CSV codec ignores them.
|
/// as base64 and the CSV codec ignores them.
|
||||||
|
|
@ -2363,6 +2535,8 @@ class VarietyRepository {
|
||||||
abundance: l.abundance,
|
abundance: l.abundance,
|
||||||
preservationFormat: l.preservationFormat,
|
preservationFormat: l.preservationFormat,
|
||||||
offerStatus: l.offerStatus,
|
offerStatus: l.offerStatus,
|
||||||
|
priceAmount: l.priceAmount,
|
||||||
|
priceCurrency: l.priceCurrency,
|
||||||
germinationTests: germinationTests,
|
germinationTests: germinationTests,
|
||||||
conditionChecks: conditionChecks,
|
conditionChecks: conditionChecks,
|
||||||
quantity: hasQuantity
|
quantity: hasQuantity
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class AppDatabase extends _$AppDatabase {
|
||||||
|
|
||||||
/// Current schema version; also stamped into interchange exports so an
|
/// Current schema version; also stamped into interchange exports so an
|
||||||
/// importer knows which app generation wrote the file (data-model §7).
|
/// importer knows which app generation wrote the file (data-model §7).
|
||||||
static const int currentSchemaVersion = 10;
|
static const int currentSchemaVersion = 11;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => currentSchemaVersion;
|
int get schemaVersion => currentSchemaVersion;
|
||||||
|
|
@ -153,6 +153,16 @@ class AppDatabase extends _$AppDatabase {
|
||||||
await m.createTable(sales);
|
await m.createTable(sales);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// v11: asking price on Lots — published with "sell" market offers.
|
||||||
|
// Guarded (see the v7 note above).
|
||||||
|
if (from < 11) {
|
||||||
|
if (!await _hasColumn('lots', 'price_amount')) {
|
||||||
|
await m.addColumn(lots, lots.priceAmount);
|
||||||
|
}
|
||||||
|
if (!await _hasColumn('lots', 'price_currency')) {
|
||||||
|
await m.addColumn(lots, lots.priceCurrency);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3266,6 +3266,28 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
|
||||||
).withConverter<PreservationFormat?>(
|
).withConverter<PreservationFormat?>(
|
||||||
$LotsTable.$converterpreservationFormatn,
|
$LotsTable.$converterpreservationFormatn,
|
||||||
);
|
);
|
||||||
|
static const VerificationMeta _priceAmountMeta = const VerificationMeta(
|
||||||
|
'priceAmount',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<double> priceAmount = GeneratedColumn<double>(
|
||||||
|
'price_amount',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: DriftSqlType.double,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
static const VerificationMeta _priceCurrencyMeta = const VerificationMeta(
|
||||||
|
'priceCurrency',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> priceCurrency = GeneratedColumn<String>(
|
||||||
|
'price_currency',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [
|
List<GeneratedColumn> get $columns => [
|
||||||
id,
|
id,
|
||||||
|
|
@ -3289,6 +3311,8 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
|
||||||
originPlace,
|
originPlace,
|
||||||
abundance,
|
abundance,
|
||||||
preservationFormat,
|
preservationFormat,
|
||||||
|
priceAmount,
|
||||||
|
priceCurrency,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
|
@ -3429,6 +3453,24 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('price_amount')) {
|
||||||
|
context.handle(
|
||||||
|
_priceAmountMeta,
|
||||||
|
priceAmount.isAcceptableOrUnknown(
|
||||||
|
data['price_amount']!,
|
||||||
|
_priceAmountMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('price_currency')) {
|
||||||
|
context.handle(
|
||||||
|
_priceCurrencyMeta,
|
||||||
|
priceCurrency.isAcceptableOrUnknown(
|
||||||
|
data['price_currency']!,
|
||||||
|
_priceCurrencyMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3532,6 +3574,14 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
|
||||||
data['${effectivePrefix}preservation_format'],
|
data['${effectivePrefix}preservation_format'],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
priceAmount: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.double,
|
||||||
|
data['${effectivePrefix}price_amount'],
|
||||||
|
),
|
||||||
|
priceCurrency: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}price_currency'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3600,6 +3650,17 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
/// How the (seed) lot is physically conserved — see [PreservationFormat].
|
/// How the (seed) lot is physically conserved — see [PreservationFormat].
|
||||||
/// Distinct from [storageLocation]. Optional.
|
/// Distinct from [storageLocation]. Optional.
|
||||||
final PreservationFormat? preservationFormat;
|
final PreservationFormat? preservationFormat;
|
||||||
|
|
||||||
|
/// Asking price when [offerStatus] is `sell` (data-model §2.8 puts price on
|
||||||
|
/// the Offer; with offer state collapsed onto the Lot, it lives here and is
|
||||||
|
/// published with the market offer). Informational only — money changes
|
||||||
|
/// hands off-platform, never in-app. Null amount on a sell lot means
|
||||||
|
/// "price to be agreed" (the offer publishes without a price).
|
||||||
|
final double? priceAmount;
|
||||||
|
|
||||||
|
/// Free-text currency, like [Sales.currency]: "€", "Ğ1", a local/time
|
||||||
|
/// currency. Never assumed (sharing-model §6).
|
||||||
|
final String? priceCurrency;
|
||||||
const Lot({
|
const Lot({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
|
|
@ -3622,6 +3683,8 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
this.originPlace,
|
this.originPlace,
|
||||||
this.abundance,
|
this.abundance,
|
||||||
this.preservationFormat,
|
this.preservationFormat,
|
||||||
|
this.priceAmount,
|
||||||
|
this.priceCurrency,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
|
@ -3683,6 +3746,12 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
$LotsTable.$converterpreservationFormatn.toSql(preservationFormat),
|
$LotsTable.$converterpreservationFormatn.toSql(preservationFormat),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!nullToAbsent || priceAmount != null) {
|
||||||
|
map['price_amount'] = Variable<double>(priceAmount);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || priceCurrency != null) {
|
||||||
|
map['price_currency'] = Variable<String>(priceCurrency);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3733,6 +3802,12 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
preservationFormat: preservationFormat == null && nullToAbsent
|
preservationFormat: preservationFormat == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(preservationFormat),
|
: Value(preservationFormat),
|
||||||
|
priceAmount: priceAmount == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(priceAmount),
|
||||||
|
priceCurrency: priceCurrency == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(priceCurrency),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3773,6 +3848,8 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
preservationFormat: $LotsTable.$converterpreservationFormatn.fromJson(
|
preservationFormat: $LotsTable.$converterpreservationFormatn.fromJson(
|
||||||
serializer.fromJson<String?>(json['preservationFormat']),
|
serializer.fromJson<String?>(json['preservationFormat']),
|
||||||
),
|
),
|
||||||
|
priceAmount: serializer.fromJson<double?>(json['priceAmount']),
|
||||||
|
priceCurrency: serializer.fromJson<String?>(json['priceCurrency']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
|
|
@ -3808,6 +3885,8 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
'preservationFormat': serializer.toJson<String?>(
|
'preservationFormat': serializer.toJson<String?>(
|
||||||
$LotsTable.$converterpreservationFormatn.toJson(preservationFormat),
|
$LotsTable.$converterpreservationFormatn.toJson(preservationFormat),
|
||||||
),
|
),
|
||||||
|
'priceAmount': serializer.toJson<double?>(priceAmount),
|
||||||
|
'priceCurrency': serializer.toJson<String?>(priceCurrency),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3833,6 +3912,8 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
Value<String?> originPlace = const Value.absent(),
|
Value<String?> originPlace = const Value.absent(),
|
||||||
Value<Abundance?> abundance = const Value.absent(),
|
Value<Abundance?> abundance = const Value.absent(),
|
||||||
Value<PreservationFormat?> preservationFormat = const Value.absent(),
|
Value<PreservationFormat?> preservationFormat = const Value.absent(),
|
||||||
|
Value<double?> priceAmount = const Value.absent(),
|
||||||
|
Value<String?> priceCurrency = const Value.absent(),
|
||||||
}) => Lot(
|
}) => Lot(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
|
@ -3863,6 +3944,10 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
preservationFormat: preservationFormat.present
|
preservationFormat: preservationFormat.present
|
||||||
? preservationFormat.value
|
? preservationFormat.value
|
||||||
: this.preservationFormat,
|
: this.preservationFormat,
|
||||||
|
priceAmount: priceAmount.present ? priceAmount.value : this.priceAmount,
|
||||||
|
priceCurrency: priceCurrency.present
|
||||||
|
? priceCurrency.value
|
||||||
|
: this.priceCurrency,
|
||||||
);
|
);
|
||||||
Lot copyWithCompanion(LotsCompanion data) {
|
Lot copyWithCompanion(LotsCompanion data) {
|
||||||
return Lot(
|
return Lot(
|
||||||
|
|
@ -3915,6 +4000,12 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
preservationFormat: data.preservationFormat.present
|
preservationFormat: data.preservationFormat.present
|
||||||
? data.preservationFormat.value
|
? data.preservationFormat.value
|
||||||
: this.preservationFormat,
|
: this.preservationFormat,
|
||||||
|
priceAmount: data.priceAmount.present
|
||||||
|
? data.priceAmount.value
|
||||||
|
: this.priceAmount,
|
||||||
|
priceCurrency: data.priceCurrency.present
|
||||||
|
? data.priceCurrency.value
|
||||||
|
: this.priceCurrency,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3941,7 +4032,9 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
..write('originName: $originName, ')
|
..write('originName: $originName, ')
|
||||||
..write('originPlace: $originPlace, ')
|
..write('originPlace: $originPlace, ')
|
||||||
..write('abundance: $abundance, ')
|
..write('abundance: $abundance, ')
|
||||||
..write('preservationFormat: $preservationFormat')
|
..write('preservationFormat: $preservationFormat, ')
|
||||||
|
..write('priceAmount: $priceAmount, ')
|
||||||
|
..write('priceCurrency: $priceCurrency')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
@ -3969,6 +4062,8 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
originPlace,
|
originPlace,
|
||||||
abundance,
|
abundance,
|
||||||
preservationFormat,
|
preservationFormat,
|
||||||
|
priceAmount,
|
||||||
|
priceCurrency,
|
||||||
]);
|
]);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
|
|
@ -3994,7 +4089,9 @@ class Lot extends DataClass implements Insertable<Lot> {
|
||||||
other.originName == this.originName &&
|
other.originName == this.originName &&
|
||||||
other.originPlace == this.originPlace &&
|
other.originPlace == this.originPlace &&
|
||||||
other.abundance == this.abundance &&
|
other.abundance == this.abundance &&
|
||||||
other.preservationFormat == this.preservationFormat);
|
other.preservationFormat == this.preservationFormat &&
|
||||||
|
other.priceAmount == this.priceAmount &&
|
||||||
|
other.priceCurrency == this.priceCurrency);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LotsCompanion extends UpdateCompanion<Lot> {
|
class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
|
|
@ -4019,6 +4116,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
final Value<String?> originPlace;
|
final Value<String?> originPlace;
|
||||||
final Value<Abundance?> abundance;
|
final Value<Abundance?> abundance;
|
||||||
final Value<PreservationFormat?> preservationFormat;
|
final Value<PreservationFormat?> preservationFormat;
|
||||||
|
final Value<double?> priceAmount;
|
||||||
|
final Value<String?> priceCurrency;
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
const LotsCompanion({
|
const LotsCompanion({
|
||||||
this.id = const Value.absent(),
|
this.id = const Value.absent(),
|
||||||
|
|
@ -4042,6 +4141,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
this.originPlace = const Value.absent(),
|
this.originPlace = const Value.absent(),
|
||||||
this.abundance = const Value.absent(),
|
this.abundance = const Value.absent(),
|
||||||
this.preservationFormat = const Value.absent(),
|
this.preservationFormat = const Value.absent(),
|
||||||
|
this.priceAmount = const Value.absent(),
|
||||||
|
this.priceCurrency = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
});
|
});
|
||||||
LotsCompanion.insert({
|
LotsCompanion.insert({
|
||||||
|
|
@ -4066,6 +4167,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
this.originPlace = const Value.absent(),
|
this.originPlace = const Value.absent(),
|
||||||
this.abundance = const Value.absent(),
|
this.abundance = const Value.absent(),
|
||||||
this.preservationFormat = const Value.absent(),
|
this.preservationFormat = const Value.absent(),
|
||||||
|
this.priceAmount = const Value.absent(),
|
||||||
|
this.priceCurrency = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : id = Value(id),
|
}) : id = Value(id),
|
||||||
createdAt = Value(createdAt),
|
createdAt = Value(createdAt),
|
||||||
|
|
@ -4094,6 +4197,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
Expression<String>? originPlace,
|
Expression<String>? originPlace,
|
||||||
Expression<String>? abundance,
|
Expression<String>? abundance,
|
||||||
Expression<String>? preservationFormat,
|
Expression<String>? preservationFormat,
|
||||||
|
Expression<double>? priceAmount,
|
||||||
|
Expression<String>? priceCurrency,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
|
|
@ -4118,6 +4223,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
if (originPlace != null) 'origin_place': originPlace,
|
if (originPlace != null) 'origin_place': originPlace,
|
||||||
if (abundance != null) 'abundance': abundance,
|
if (abundance != null) 'abundance': abundance,
|
||||||
if (preservationFormat != null) 'preservation_format': preservationFormat,
|
if (preservationFormat != null) 'preservation_format': preservationFormat,
|
||||||
|
if (priceAmount != null) 'price_amount': priceAmount,
|
||||||
|
if (priceCurrency != null) 'price_currency': priceCurrency,
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -4144,6 +4251,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
Value<String?>? originPlace,
|
Value<String?>? originPlace,
|
||||||
Value<Abundance?>? abundance,
|
Value<Abundance?>? abundance,
|
||||||
Value<PreservationFormat?>? preservationFormat,
|
Value<PreservationFormat?>? preservationFormat,
|
||||||
|
Value<double?>? priceAmount,
|
||||||
|
Value<String?>? priceCurrency,
|
||||||
Value<int>? rowid,
|
Value<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return LotsCompanion(
|
return LotsCompanion(
|
||||||
|
|
@ -4168,6 +4277,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
originPlace: originPlace ?? this.originPlace,
|
originPlace: originPlace ?? this.originPlace,
|
||||||
abundance: abundance ?? this.abundance,
|
abundance: abundance ?? this.abundance,
|
||||||
preservationFormat: preservationFormat ?? this.preservationFormat,
|
preservationFormat: preservationFormat ?? this.preservationFormat,
|
||||||
|
priceAmount: priceAmount ?? this.priceAmount,
|
||||||
|
priceCurrency: priceCurrency ?? this.priceCurrency,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -4250,6 +4361,12 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (priceAmount.present) {
|
||||||
|
map['price_amount'] = Variable<double>(priceAmount.value);
|
||||||
|
}
|
||||||
|
if (priceCurrency.present) {
|
||||||
|
map['price_currency'] = Variable<String>(priceCurrency.value);
|
||||||
|
}
|
||||||
if (rowid.present) {
|
if (rowid.present) {
|
||||||
map['rowid'] = Variable<int>(rowid.value);
|
map['rowid'] = Variable<int>(rowid.value);
|
||||||
}
|
}
|
||||||
|
|
@ -4280,6 +4397,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
||||||
..write('originPlace: $originPlace, ')
|
..write('originPlace: $originPlace, ')
|
||||||
..write('abundance: $abundance, ')
|
..write('abundance: $abundance, ')
|
||||||
..write('preservationFormat: $preservationFormat, ')
|
..write('preservationFormat: $preservationFormat, ')
|
||||||
|
..write('priceAmount: $priceAmount, ')
|
||||||
|
..write('priceCurrency: $priceCurrency, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
|
|
@ -11663,6 +11782,8 @@ typedef $$LotsTableCreateCompanionBuilder =
|
||||||
Value<String?> originPlace,
|
Value<String?> originPlace,
|
||||||
Value<Abundance?> abundance,
|
Value<Abundance?> abundance,
|
||||||
Value<PreservationFormat?> preservationFormat,
|
Value<PreservationFormat?> preservationFormat,
|
||||||
|
Value<double?> priceAmount,
|
||||||
|
Value<String?> priceCurrency,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
typedef $$LotsTableUpdateCompanionBuilder =
|
typedef $$LotsTableUpdateCompanionBuilder =
|
||||||
|
|
@ -11688,6 +11809,8 @@ typedef $$LotsTableUpdateCompanionBuilder =
|
||||||
Value<String?> originPlace,
|
Value<String?> originPlace,
|
||||||
Value<Abundance?> abundance,
|
Value<Abundance?> abundance,
|
||||||
Value<PreservationFormat?> preservationFormat,
|
Value<PreservationFormat?> preservationFormat,
|
||||||
|
Value<double?> priceAmount,
|
||||||
|
Value<String?> priceCurrency,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -11812,6 +11935,16 @@ class $$LotsTableFilterComposer extends Composer<_$AppDatabase, $LotsTable> {
|
||||||
column: $table.preservationFormat,
|
column: $table.preservationFormat,
|
||||||
builder: (column) => ColumnWithTypeConverterFilters(column),
|
builder: (column) => ColumnWithTypeConverterFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnFilters<double> get priceAmount => $composableBuilder(
|
||||||
|
column: $table.priceAmount,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnFilters<String> get priceCurrency => $composableBuilder(
|
||||||
|
column: $table.priceCurrency,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LotsTableOrderingComposer extends Composer<_$AppDatabase, $LotsTable> {
|
class $$LotsTableOrderingComposer extends Composer<_$AppDatabase, $LotsTable> {
|
||||||
|
|
@ -11926,6 +12059,16 @@ class $$LotsTableOrderingComposer extends Composer<_$AppDatabase, $LotsTable> {
|
||||||
column: $table.preservationFormat,
|
column: $table.preservationFormat,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<double> get priceAmount => $composableBuilder(
|
||||||
|
column: $table.priceAmount,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get priceCurrency => $composableBuilder(
|
||||||
|
column: $table.priceCurrency,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LotsTableAnnotationComposer
|
class $$LotsTableAnnotationComposer
|
||||||
|
|
@ -12030,6 +12173,16 @@ class $$LotsTableAnnotationComposer
|
||||||
column: $table.preservationFormat,
|
column: $table.preservationFormat,
|
||||||
builder: (column) => column,
|
builder: (column) => column,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
GeneratedColumn<double> get priceAmount => $composableBuilder(
|
||||||
|
column: $table.priceAmount,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get priceCurrency => $composableBuilder(
|
||||||
|
column: $table.priceCurrency,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LotsTableTableManager
|
class $$LotsTableTableManager
|
||||||
|
|
@ -12082,6 +12235,8 @@ class $$LotsTableTableManager
|
||||||
Value<Abundance?> abundance = const Value.absent(),
|
Value<Abundance?> abundance = const Value.absent(),
|
||||||
Value<PreservationFormat?> preservationFormat =
|
Value<PreservationFormat?> preservationFormat =
|
||||||
const Value.absent(),
|
const Value.absent(),
|
||||||
|
Value<double?> priceAmount = const Value.absent(),
|
||||||
|
Value<String?> priceCurrency = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => LotsCompanion(
|
}) => LotsCompanion(
|
||||||
id: id,
|
id: id,
|
||||||
|
|
@ -12105,6 +12260,8 @@ class $$LotsTableTableManager
|
||||||
originPlace: originPlace,
|
originPlace: originPlace,
|
||||||
abundance: abundance,
|
abundance: abundance,
|
||||||
preservationFormat: preservationFormat,
|
preservationFormat: preservationFormat,
|
||||||
|
priceAmount: priceAmount,
|
||||||
|
priceCurrency: priceCurrency,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
|
|
@ -12131,6 +12288,8 @@ class $$LotsTableTableManager
|
||||||
Value<Abundance?> abundance = const Value.absent(),
|
Value<Abundance?> abundance = const Value.absent(),
|
||||||
Value<PreservationFormat?> preservationFormat =
|
Value<PreservationFormat?> preservationFormat =
|
||||||
const Value.absent(),
|
const Value.absent(),
|
||||||
|
Value<double?> priceAmount = const Value.absent(),
|
||||||
|
Value<String?> priceCurrency = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => LotsCompanion.insert(
|
}) => LotsCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
|
|
@ -12154,6 +12313,8 @@ class $$LotsTableTableManager
|
||||||
originPlace: originPlace,
|
originPlace: originPlace,
|
||||||
abundance: abundance,
|
abundance: abundance,
|
||||||
preservationFormat: preservationFormat,
|
preservationFormat: preservationFormat,
|
||||||
|
priceAmount: priceAmount,
|
||||||
|
priceCurrency: priceCurrency,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
|
|
|
||||||
|
|
@ -106,3 +106,10 @@ enum PlantareStatus { open, returned, forgiven }
|
||||||
/// SEPARATE model from a gift or a Plantare — seed for money (any currency:
|
/// SEPARATE model from a gift or a Plantare — seed for money (any currency:
|
||||||
/// €, Ğ1, a local/time currency…). No commission is ever taken on seeds.
|
/// €, Ğ1, a local/time currency…). No commission is ever taken on seeds.
|
||||||
enum SaleDirection { iSold, iBought }
|
enum SaleDirection { iSold, iBought }
|
||||||
|
|
||||||
|
/// Direction of a hand-over — the one moment seeds actually change hands
|
||||||
|
/// (data-model §2.8: a closed deal produces a Movement and, optionally, a
|
||||||
|
/// Plantare and/or a Sale record). Not stored in a column of its own: it maps
|
||||||
|
/// to [MovementType.given]/[MovementType.received] plus the derived directions
|
||||||
|
/// of the optional Sale and Plantare rows.
|
||||||
|
enum HandoverDirection { iGave, iReceived }
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,17 @@ class Lots extends Table with SyncColumns {
|
||||||
/// Distinct from [storageLocation]. Optional.
|
/// Distinct from [storageLocation]. Optional.
|
||||||
TextColumn get preservationFormat =>
|
TextColumn get preservationFormat =>
|
||||||
textEnum<PreservationFormat>().nullable()();
|
textEnum<PreservationFormat>().nullable()();
|
||||||
|
|
||||||
|
/// Asking price when [offerStatus] is `sell` (data-model §2.8 puts price on
|
||||||
|
/// the Offer; with offer state collapsed onto the Lot, it lives here and is
|
||||||
|
/// published with the market offer). Informational only — money changes
|
||||||
|
/// hands off-platform, never in-app. Null amount on a sell lot means
|
||||||
|
/// "price to be agreed" (the offer publishes without a price).
|
||||||
|
RealColumn get priceAmount => real().nullable()();
|
||||||
|
|
||||||
|
/// Free-text currency, like [Sales.currency]: "€", "Ğ1", a local/time
|
||||||
|
/// currency. Never assumed (sharing-model §6).
|
||||||
|
TextColumn get priceCurrency => text().nullable()();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optional germination history for a Lot; percent is derived in code.
|
/// Optional germination history for a Lot; percent is derived in code.
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,8 @@
|
||||||
"add": "¿Compártesla?",
|
"add": "¿Compártesla?",
|
||||||
"title": "¿Compártesla?",
|
"title": "¿Compártesla?",
|
||||||
"nudge": "Tienes a esgaya: podríes compartir un poco.",
|
"nudge": "Tienes a esgaya: podríes compartir un poco.",
|
||||||
|
"price": "Preciu",
|
||||||
|
"priceHint": "Déxalu baleru p'alcordalu depués",
|
||||||
"private": "Namás pa min",
|
"private": "Namás pa min",
|
||||||
"gift": "Pa regalar",
|
"gift": "Pa regalar",
|
||||||
"exchange": "Pa trocar",
|
"exchange": "Pa trocar",
|
||||||
|
|
@ -617,6 +619,19 @@
|
||||||
"settledSection": "Fechos",
|
"settledSection": "Fechos",
|
||||||
"removeConfirm": "¿Quitar esti compromisu?"
|
"removeConfirm": "¿Quitar esti compromisu?"
|
||||||
},
|
},
|
||||||
|
"handover": {
|
||||||
|
"title": "Di o recibí semientes",
|
||||||
|
"help": "Un regalu, un truecu o una venta: apúntalo, con promesa de devolver semiente o ensin ella.",
|
||||||
|
"iGave": "Di semientes",
|
||||||
|
"iReceived": "Recibí semientes",
|
||||||
|
"whichLot": "¿De qué llote?",
|
||||||
|
"howMuch": "¿Cuánto?",
|
||||||
|
"allOfIt": "Too",
|
||||||
|
"partOfIt": "Una parte",
|
||||||
|
"paymentChip": "Hubo perres pel mediu",
|
||||||
|
"promiseGave": "Van devolveme semiente",
|
||||||
|
"promiseReceived": "Voi devolver semiente"
|
||||||
|
},
|
||||||
"sale": {
|
"sale": {
|
||||||
"title": "Ventes",
|
"title": "Ventes",
|
||||||
"help": "Rexistra semilla vendida o mercada — dineru, Ğ1 o cualquier moneda. Un modelu aparte del regalu y del Plantare. Enxamás se cobra comisión poles semilles.",
|
"help": "Rexistra semilla vendida o mercada — dineru, Ğ1 o cualquier moneda. Un modelu aparte del regalu y del Plantare. Enxamás se cobra comisión poles semilles.",
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,8 @@
|
||||||
"add": "Do you share it?",
|
"add": "Do you share it?",
|
||||||
"title": "Do you share it?",
|
"title": "Do you share it?",
|
||||||
"nudge": "You have plenty — you could share some.",
|
"nudge": "You have plenty — you could share some.",
|
||||||
|
"price": "Price",
|
||||||
|
"priceHint": "Leave it empty to agree it later",
|
||||||
"private": "Just for me",
|
"private": "Just for me",
|
||||||
"gift": "To give away",
|
"gift": "To give away",
|
||||||
"exchange": "To swap",
|
"exchange": "To swap",
|
||||||
|
|
@ -620,6 +622,19 @@
|
||||||
"settledSection": "Done",
|
"settledSection": "Done",
|
||||||
"removeConfirm": "Remove this commitment?"
|
"removeConfirm": "Remove this commitment?"
|
||||||
},
|
},
|
||||||
|
"handover": {
|
||||||
|
"title": "Seeds changed hands",
|
||||||
|
"help": "A gift, a swap or a sale — note it down, with a return promise or without.",
|
||||||
|
"iGave": "I gave seeds",
|
||||||
|
"iReceived": "I received seeds",
|
||||||
|
"whichLot": "Which batch?",
|
||||||
|
"howMuch": "How much?",
|
||||||
|
"allOfIt": "All of it",
|
||||||
|
"partOfIt": "A part",
|
||||||
|
"paymentChip": "Money changed hands",
|
||||||
|
"promiseGave": "They'll return me seed",
|
||||||
|
"promiseReceived": "I'll return seed"
|
||||||
|
},
|
||||||
"sale": {
|
"sale": {
|
||||||
"title": "Sales",
|
"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.",
|
"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.",
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,8 @@
|
||||||
"add": "¿La compartes?",
|
"add": "¿La compartes?",
|
||||||
"title": "¿La compartes?",
|
"title": "¿La compartes?",
|
||||||
"nudge": "Tienes de sobra: podrías compartir un poco.",
|
"nudge": "Tienes de sobra: podrías compartir un poco.",
|
||||||
|
"price": "Precio",
|
||||||
|
"priceHint": "Déjalo vacío para acordarlo luego",
|
||||||
"private": "Solo para mí",
|
"private": "Solo para mí",
|
||||||
"gift": "Para regalar",
|
"gift": "Para regalar",
|
||||||
"exchange": "Para intercambiar",
|
"exchange": "Para intercambiar",
|
||||||
|
|
@ -619,6 +621,19 @@
|
||||||
"settledSection": "Hechos",
|
"settledSection": "Hechos",
|
||||||
"removeConfirm": "¿Quitar este compromiso?"
|
"removeConfirm": "¿Quitar este compromiso?"
|
||||||
},
|
},
|
||||||
|
"handover": {
|
||||||
|
"title": "Di o recibí semillas",
|
||||||
|
"help": "Un regalo, un trueque o una venta: apúntalo, con promesa de devolver semilla o sin ella.",
|
||||||
|
"iGave": "Di semillas",
|
||||||
|
"iReceived": "Recibí semillas",
|
||||||
|
"whichLot": "¿De qué lote?",
|
||||||
|
"howMuch": "¿Cuánto?",
|
||||||
|
"allOfIt": "Todo",
|
||||||
|
"partOfIt": "Una parte",
|
||||||
|
"paymentChip": "Hubo dinero por medio",
|
||||||
|
"promiseGave": "Me devolverán semilla",
|
||||||
|
"promiseReceived": "Devolveré semilla"
|
||||||
|
},
|
||||||
"sale": {
|
"sale": {
|
||||||
"title": "Ventas",
|
"title": "Ventas",
|
||||||
"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.",
|
"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.",
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,8 @@
|
||||||
"add": "Partilhas esta?",
|
"add": "Partilhas esta?",
|
||||||
"title": "Partilhas esta?",
|
"title": "Partilhas esta?",
|
||||||
"nudge": "Tens de sobra: podias partilhar um pouco.",
|
"nudge": "Tens de sobra: podias partilhar um pouco.",
|
||||||
|
"price": "Preço",
|
||||||
|
"priceHint": "Deixa vazio para combinar depois",
|
||||||
"private": "Só para mim",
|
"private": "Só para mim",
|
||||||
"gift": "Para dar",
|
"gift": "Para dar",
|
||||||
"exchange": "Para trocar",
|
"exchange": "Para trocar",
|
||||||
|
|
@ -616,6 +618,19 @@
|
||||||
"settledSection": "Feitos",
|
"settledSection": "Feitos",
|
||||||
"removeConfirm": "Remover este compromisso?"
|
"removeConfirm": "Remover este compromisso?"
|
||||||
},
|
},
|
||||||
|
"handover": {
|
||||||
|
"title": "Dei ou recebi sementes",
|
||||||
|
"help": "Uma oferta, uma troca ou uma venda: anota-o, com promessa de devolver semente ou sem ela.",
|
||||||
|
"iGave": "Dei sementes",
|
||||||
|
"iReceived": "Recebi sementes",
|
||||||
|
"whichLot": "De que lote?",
|
||||||
|
"howMuch": "Quanto?",
|
||||||
|
"allOfIt": "Tudo",
|
||||||
|
"partOfIt": "Uma parte",
|
||||||
|
"paymentChip": "Houve dinheiro pelo meio",
|
||||||
|
"promiseGave": "Vão devolver-me semente",
|
||||||
|
"promiseReceived": "Vou devolver semente"
|
||||||
|
},
|
||||||
"sale": {
|
"sale": {
|
||||||
"title": "Vendas",
|
"title": "Vendas",
|
||||||
"help": "Regista semente vendida ou comprada — dinheiro, Ğ1 ou qualquer moeda. Um modelo separado do presente e do Plantare. Nunca se cobra comissão pelas sementes.",
|
"help": "Regista semente vendida ou comprada — dinheiro, Ğ1 ou qualquer moeda. Um modelo separado do presente e do Plantare. Nunca se cobra comissão pelas sementes.",
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
/// To regenerate, run: `dart run slang`
|
/// To regenerate, run: `dart run slang`
|
||||||
///
|
///
|
||||||
/// Locales: 4
|
/// Locales: 4
|
||||||
/// Strings: 1968 (492 per locale)
|
/// Strings: 2020 (505 per locale)
|
||||||
///
|
///
|
||||||
/// Built on 2026-07-11 at 05:50 UTC
|
/// Built on 2026-07-11 at 06:00 UTC
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint, unused_import
|
// ignore_for_file: type=lint, unused_import
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ class TranslationsAst extends Translations with BaseTranslations<AppLocale, Tran
|
||||||
@override late final _Translations$wot$ast wot = _Translations$wot$ast._(_root);
|
@override late final _Translations$wot$ast wot = _Translations$wot$ast._(_root);
|
||||||
@override late final _Translations$notifications$ast notifications = _Translations$notifications$ast._(_root);
|
@override late final _Translations$notifications$ast notifications = _Translations$notifications$ast._(_root);
|
||||||
@override late final _Translations$plantare$ast plantare = _Translations$plantare$ast._(_root);
|
@override late final _Translations$plantare$ast plantare = _Translations$plantare$ast._(_root);
|
||||||
|
@override late final _Translations$handover$ast handover = _Translations$handover$ast._(_root);
|
||||||
@override late final _Translations$sale$ast sale = _Translations$sale$ast._(_root);
|
@override late final _Translations$sale$ast sale = _Translations$sale$ast._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -547,6 +548,8 @@ class _Translations$share$ast extends Translations$share$en {
|
||||||
@override String get add => '¿Compártesla?';
|
@override String get add => '¿Compártesla?';
|
||||||
@override String get title => '¿Compártesla?';
|
@override String get title => '¿Compártesla?';
|
||||||
@override String get nudge => 'Tienes a esgaya: podríes compartir un poco.';
|
@override String get nudge => 'Tienes a esgaya: podríes compartir un poco.';
|
||||||
|
@override String get price => 'Preciu';
|
||||||
|
@override String get priceHint => 'Déxalu baleru p\'alcordalu depués';
|
||||||
@override String get private => 'Namás pa min';
|
@override String get private => 'Namás pa min';
|
||||||
@override String get gift => 'Pa regalar';
|
@override String get gift => 'Pa regalar';
|
||||||
@override String get exchange => 'Pa trocar';
|
@override String get exchange => 'Pa trocar';
|
||||||
|
|
@ -895,6 +898,26 @@ class _Translations$plantare$ast extends Translations$plantare$en {
|
||||||
@override String get removeConfirm => '¿Quitar esti compromisu?';
|
@override String get removeConfirm => '¿Quitar esti compromisu?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: handover
|
||||||
|
class _Translations$handover$ast extends Translations$handover$en {
|
||||||
|
_Translations$handover$ast._(TranslationsAst root) : this._root = root, super.internal(root);
|
||||||
|
|
||||||
|
final TranslationsAst _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get title => 'Di o recibí semientes';
|
||||||
|
@override String get help => 'Un regalu, un truecu o una venta: apúntalo, con promesa de devolver semiente o ensin ella.';
|
||||||
|
@override String get iGave => 'Di semientes';
|
||||||
|
@override String get iReceived => 'Recibí semientes';
|
||||||
|
@override String get whichLot => '¿De qué llote?';
|
||||||
|
@override String get howMuch => '¿Cuánto?';
|
||||||
|
@override String get allOfIt => 'Too';
|
||||||
|
@override String get partOfIt => 'Una parte';
|
||||||
|
@override String get paymentChip => 'Hubo perres pel mediu';
|
||||||
|
@override String get promiseGave => 'Van devolveme semiente';
|
||||||
|
@override String get promiseReceived => 'Voi devolver semiente';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: sale
|
// Path: sale
|
||||||
class _Translations$sale$ast extends Translations$sale$en {
|
class _Translations$sale$ast extends Translations$sale$en {
|
||||||
_Translations$sale$ast._(TranslationsAst root) : this._root = root, super.internal(root);
|
_Translations$sale$ast._(TranslationsAst root) : this._root = root, super.internal(root);
|
||||||
|
|
@ -1501,6 +1524,8 @@ extension on TranslationsAst {
|
||||||
'share.add' => '¿Compártesla?',
|
'share.add' => '¿Compártesla?',
|
||||||
'share.title' => '¿Compártesla?',
|
'share.title' => '¿Compártesla?',
|
||||||
'share.nudge' => 'Tienes a esgaya: podríes compartir un poco.',
|
'share.nudge' => 'Tienes a esgaya: podríes compartir un poco.',
|
||||||
|
'share.price' => 'Preciu',
|
||||||
|
'share.priceHint' => 'Déxalu baleru p\'alcordalu depués',
|
||||||
'share.private' => 'Namás pa min',
|
'share.private' => 'Namás pa min',
|
||||||
'share.gift' => 'Pa regalar',
|
'share.gift' => 'Pa regalar',
|
||||||
'share.exchange' => 'Pa trocar',
|
'share.exchange' => 'Pa trocar',
|
||||||
|
|
@ -1736,6 +1761,17 @@ extension on TranslationsAst {
|
||||||
'plantare.openSection' => 'Pendientes',
|
'plantare.openSection' => 'Pendientes',
|
||||||
'plantare.settledSection' => 'Fechos',
|
'plantare.settledSection' => 'Fechos',
|
||||||
'plantare.removeConfirm' => '¿Quitar esti compromisu?',
|
'plantare.removeConfirm' => '¿Quitar esti compromisu?',
|
||||||
|
'handover.title' => 'Di o recibí semientes',
|
||||||
|
'handover.help' => 'Un regalu, un truecu o una venta: apúntalo, con promesa de devolver semiente o ensin ella.',
|
||||||
|
'handover.iGave' => 'Di semientes',
|
||||||
|
'handover.iReceived' => 'Recibí semientes',
|
||||||
|
'handover.whichLot' => '¿De qué llote?',
|
||||||
|
'handover.howMuch' => '¿Cuánto?',
|
||||||
|
'handover.allOfIt' => 'Too',
|
||||||
|
'handover.partOfIt' => 'Una parte',
|
||||||
|
'handover.paymentChip' => 'Hubo perres pel mediu',
|
||||||
|
'handover.promiseGave' => 'Van devolveme semiente',
|
||||||
|
'handover.promiseReceived' => 'Voi devolver semiente',
|
||||||
'sale.title' => 'Ventes',
|
'sale.title' => 'Ventes',
|
||||||
'sale.help' => 'Rexistra semilla vendida o mercada — dineru, Ğ1 o cualquier moneda. Un modelu aparte del regalu y del Plantare. Enxamás se cobra comisión poles semilles.',
|
'sale.help' => 'Rexistra semilla vendida o mercada — dineru, Ğ1 o cualquier moneda. Un modelu aparte del regalu y del Plantare. Enxamás se cobra comisión poles semilles.',
|
||||||
'sale.add' => 'Rexistrar venta',
|
'sale.add' => 'Rexistrar venta',
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ class Translations with BaseTranslations<AppLocale, Translations> {
|
||||||
late final Translations$wot$en wot = Translations$wot$en.internal(_root);
|
late final Translations$wot$en wot = Translations$wot$en.internal(_root);
|
||||||
late final Translations$notifications$en notifications = Translations$notifications$en.internal(_root);
|
late final Translations$notifications$en notifications = Translations$notifications$en.internal(_root);
|
||||||
late final Translations$plantare$en plantare = Translations$plantare$en.internal(_root);
|
late final Translations$plantare$en plantare = Translations$plantare$en.internal(_root);
|
||||||
|
late final Translations$handover$en handover = Translations$handover$en.internal(_root);
|
||||||
late final Translations$sale$en sale = Translations$sale$en.internal(_root);
|
late final Translations$sale$en sale = Translations$sale$en.internal(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -988,6 +989,12 @@ class Translations$share$en {
|
||||||
/// en: 'You have plenty — you could share some.'
|
/// en: 'You have plenty — you could share some.'
|
||||||
String get nudge => 'You have plenty — you could share some.';
|
String get nudge => 'You have plenty — you could share some.';
|
||||||
|
|
||||||
|
/// en: 'Price'
|
||||||
|
String get price => 'Price';
|
||||||
|
|
||||||
|
/// en: 'Leave it empty to agree it later'
|
||||||
|
String get priceHint => 'Leave it empty to agree it later';
|
||||||
|
|
||||||
/// en: 'Just for me'
|
/// en: 'Just for me'
|
||||||
String get private => 'Just for me';
|
String get private => 'Just for me';
|
||||||
|
|
||||||
|
|
@ -1716,6 +1723,48 @@ class Translations$plantare$en {
|
||||||
String get removeConfirm => 'Remove this commitment?';
|
String get removeConfirm => 'Remove this commitment?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: handover
|
||||||
|
class Translations$handover$en {
|
||||||
|
Translations$handover$en.internal(this._root);
|
||||||
|
|
||||||
|
final Translations _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
|
||||||
|
/// en: 'Seeds changed hands'
|
||||||
|
String get title => 'Seeds changed hands';
|
||||||
|
|
||||||
|
/// en: 'A gift, a swap or a sale — note it down, with a return promise or without.'
|
||||||
|
String get help => 'A gift, a swap or a sale — note it down, with a return promise or without.';
|
||||||
|
|
||||||
|
/// en: 'I gave seeds'
|
||||||
|
String get iGave => 'I gave seeds';
|
||||||
|
|
||||||
|
/// en: 'I received seeds'
|
||||||
|
String get iReceived => 'I received seeds';
|
||||||
|
|
||||||
|
/// en: 'Which batch?'
|
||||||
|
String get whichLot => 'Which batch?';
|
||||||
|
|
||||||
|
/// en: 'How much?'
|
||||||
|
String get howMuch => 'How much?';
|
||||||
|
|
||||||
|
/// en: 'All of it'
|
||||||
|
String get allOfIt => 'All of it';
|
||||||
|
|
||||||
|
/// en: 'A part'
|
||||||
|
String get partOfIt => 'A part';
|
||||||
|
|
||||||
|
/// en: 'Money changed hands'
|
||||||
|
String get paymentChip => 'Money changed hands';
|
||||||
|
|
||||||
|
/// en: 'They'll return me seed'
|
||||||
|
String get promiseGave => 'They\'ll return me seed';
|
||||||
|
|
||||||
|
/// en: 'I'll return seed'
|
||||||
|
String get promiseReceived => 'I\'ll return seed';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: sale
|
// Path: sale
|
||||||
class Translations$sale$en {
|
class Translations$sale$en {
|
||||||
Translations$sale$en.internal(this._root);
|
Translations$sale$en.internal(this._root);
|
||||||
|
|
@ -2473,6 +2522,8 @@ extension on Translations {
|
||||||
'share.add' => 'Do you share it?',
|
'share.add' => 'Do you share it?',
|
||||||
'share.title' => 'Do you share it?',
|
'share.title' => 'Do you share it?',
|
||||||
'share.nudge' => 'You have plenty — you could share some.',
|
'share.nudge' => 'You have plenty — you could share some.',
|
||||||
|
'share.price' => 'Price',
|
||||||
|
'share.priceHint' => 'Leave it empty to agree it later',
|
||||||
'share.private' => 'Just for me',
|
'share.private' => 'Just for me',
|
||||||
'share.gift' => 'To give away',
|
'share.gift' => 'To give away',
|
||||||
'share.exchange' => 'To swap',
|
'share.exchange' => 'To swap',
|
||||||
|
|
@ -2710,6 +2761,17 @@ extension on Translations {
|
||||||
'plantare.openSection' => 'Open',
|
'plantare.openSection' => 'Open',
|
||||||
'plantare.settledSection' => 'Done',
|
'plantare.settledSection' => 'Done',
|
||||||
'plantare.removeConfirm' => 'Remove this commitment?',
|
'plantare.removeConfirm' => 'Remove this commitment?',
|
||||||
|
'handover.title' => 'Seeds changed hands',
|
||||||
|
'handover.help' => 'A gift, a swap or a sale — note it down, with a return promise or without.',
|
||||||
|
'handover.iGave' => 'I gave seeds',
|
||||||
|
'handover.iReceived' => 'I received seeds',
|
||||||
|
'handover.whichLot' => 'Which batch?',
|
||||||
|
'handover.howMuch' => 'How much?',
|
||||||
|
'handover.allOfIt' => 'All of it',
|
||||||
|
'handover.partOfIt' => 'A part',
|
||||||
|
'handover.paymentChip' => 'Money changed hands',
|
||||||
|
'handover.promiseGave' => 'They\'ll return me seed',
|
||||||
|
'handover.promiseReceived' => 'I\'ll return seed',
|
||||||
'sale.title' => 'Sales',
|
'sale.title' => 'Sales',
|
||||||
'sale.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.',
|
'sale.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.',
|
||||||
'sale.add' => 'Record a sale',
|
'sale.add' => 'Record a sale',
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ class TranslationsEs extends Translations with BaseTranslations<AppLocale, Trans
|
||||||
@override late final _Translations$wot$es wot = _Translations$wot$es._(_root);
|
@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$notifications$es notifications = _Translations$notifications$es._(_root);
|
||||||
@override late final _Translations$plantare$es plantare = _Translations$plantare$es._(_root);
|
@override late final _Translations$plantare$es plantare = _Translations$plantare$es._(_root);
|
||||||
|
@override late final _Translations$handover$es handover = _Translations$handover$es._(_root);
|
||||||
@override late final _Translations$sale$es sale = _Translations$sale$es._(_root);
|
@override late final _Translations$sale$es sale = _Translations$sale$es._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -547,6 +548,8 @@ class _Translations$share$es extends Translations$share$en {
|
||||||
@override String get add => '¿La compartes?';
|
@override String get add => '¿La compartes?';
|
||||||
@override String get title => '¿La compartes?';
|
@override String get title => '¿La compartes?';
|
||||||
@override String get nudge => 'Tienes de sobra: podrías compartir un poco.';
|
@override String get nudge => 'Tienes de sobra: podrías compartir un poco.';
|
||||||
|
@override String get price => 'Precio';
|
||||||
|
@override String get priceHint => 'Déjalo vacío para acordarlo luego';
|
||||||
@override String get private => 'Solo para mí';
|
@override String get private => 'Solo para mí';
|
||||||
@override String get gift => 'Para regalar';
|
@override String get gift => 'Para regalar';
|
||||||
@override String get exchange => 'Para intercambiar';
|
@override String get exchange => 'Para intercambiar';
|
||||||
|
|
@ -897,6 +900,26 @@ class _Translations$plantare$es extends Translations$plantare$en {
|
||||||
@override String get removeConfirm => '¿Quitar este compromiso?';
|
@override String get removeConfirm => '¿Quitar este compromiso?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: handover
|
||||||
|
class _Translations$handover$es extends Translations$handover$en {
|
||||||
|
_Translations$handover$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||||
|
|
||||||
|
final TranslationsEs _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get title => 'Di o recibí semillas';
|
||||||
|
@override String get help => 'Un regalo, un trueque o una venta: apúntalo, con promesa de devolver semilla o sin ella.';
|
||||||
|
@override String get iGave => 'Di semillas';
|
||||||
|
@override String get iReceived => 'Recibí semillas';
|
||||||
|
@override String get whichLot => '¿De qué lote?';
|
||||||
|
@override String get howMuch => '¿Cuánto?';
|
||||||
|
@override String get allOfIt => 'Todo';
|
||||||
|
@override String get partOfIt => 'Una parte';
|
||||||
|
@override String get paymentChip => 'Hubo dinero por medio';
|
||||||
|
@override String get promiseGave => 'Me devolverán semilla';
|
||||||
|
@override String get promiseReceived => 'Devolveré semilla';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: sale
|
// Path: sale
|
||||||
class _Translations$sale$es extends Translations$sale$en {
|
class _Translations$sale$es extends Translations$sale$en {
|
||||||
_Translations$sale$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
_Translations$sale$es._(TranslationsEs root) : this._root = root, super.internal(root);
|
||||||
|
|
@ -1503,6 +1526,8 @@ extension on TranslationsEs {
|
||||||
'share.add' => '¿La compartes?',
|
'share.add' => '¿La compartes?',
|
||||||
'share.title' => '¿La compartes?',
|
'share.title' => '¿La compartes?',
|
||||||
'share.nudge' => 'Tienes de sobra: podrías compartir un poco.',
|
'share.nudge' => 'Tienes de sobra: podrías compartir un poco.',
|
||||||
|
'share.price' => 'Precio',
|
||||||
|
'share.priceHint' => 'Déjalo vacío para acordarlo luego',
|
||||||
'share.private' => 'Solo para mí',
|
'share.private' => 'Solo para mí',
|
||||||
'share.gift' => 'Para regalar',
|
'share.gift' => 'Para regalar',
|
||||||
'share.exchange' => 'Para intercambiar',
|
'share.exchange' => 'Para intercambiar',
|
||||||
|
|
@ -1740,6 +1765,17 @@ extension on TranslationsEs {
|
||||||
'plantare.openSection' => 'Pendientes',
|
'plantare.openSection' => 'Pendientes',
|
||||||
'plantare.settledSection' => 'Hechos',
|
'plantare.settledSection' => 'Hechos',
|
||||||
'plantare.removeConfirm' => '¿Quitar este compromiso?',
|
'plantare.removeConfirm' => '¿Quitar este compromiso?',
|
||||||
|
'handover.title' => 'Di o recibí semillas',
|
||||||
|
'handover.help' => 'Un regalo, un trueque o una venta: apúntalo, con promesa de devolver semilla o sin ella.',
|
||||||
|
'handover.iGave' => 'Di semillas',
|
||||||
|
'handover.iReceived' => 'Recibí semillas',
|
||||||
|
'handover.whichLot' => '¿De qué lote?',
|
||||||
|
'handover.howMuch' => '¿Cuánto?',
|
||||||
|
'handover.allOfIt' => 'Todo',
|
||||||
|
'handover.partOfIt' => 'Una parte',
|
||||||
|
'handover.paymentChip' => 'Hubo dinero por medio',
|
||||||
|
'handover.promiseGave' => 'Me devolverán semilla',
|
||||||
|
'handover.promiseReceived' => 'Devolveré semilla',
|
||||||
'sale.title' => 'Ventas',
|
'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.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.add' => 'Registrar venta',
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ class TranslationsPt extends Translations with BaseTranslations<AppLocale, Trans
|
||||||
@override late final _Translations$wot$pt wot = _Translations$wot$pt._(_root);
|
@override late final _Translations$wot$pt wot = _Translations$wot$pt._(_root);
|
||||||
@override late final _Translations$notifications$pt notifications = _Translations$notifications$pt._(_root);
|
@override late final _Translations$notifications$pt notifications = _Translations$notifications$pt._(_root);
|
||||||
@override late final _Translations$plantare$pt plantare = _Translations$plantare$pt._(_root);
|
@override late final _Translations$plantare$pt plantare = _Translations$plantare$pt._(_root);
|
||||||
|
@override late final _Translations$handover$pt handover = _Translations$handover$pt._(_root);
|
||||||
@override late final _Translations$sale$pt sale = _Translations$sale$pt._(_root);
|
@override late final _Translations$sale$pt sale = _Translations$sale$pt._(_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -544,6 +545,8 @@ class _Translations$share$pt extends Translations$share$en {
|
||||||
@override String get add => 'Partilhas esta?';
|
@override String get add => 'Partilhas esta?';
|
||||||
@override String get title => 'Partilhas esta?';
|
@override String get title => 'Partilhas esta?';
|
||||||
@override String get nudge => 'Tens de sobra: podias partilhar um pouco.';
|
@override String get nudge => 'Tens de sobra: podias partilhar um pouco.';
|
||||||
|
@override String get price => 'Preço';
|
||||||
|
@override String get priceHint => 'Deixa vazio para combinar depois';
|
||||||
@override String get private => 'Só para mim';
|
@override String get private => 'Só para mim';
|
||||||
@override String get gift => 'Para dar';
|
@override String get gift => 'Para dar';
|
||||||
@override String get exchange => 'Para trocar';
|
@override String get exchange => 'Para trocar';
|
||||||
|
|
@ -894,6 +897,26 @@ class _Translations$plantare$pt extends Translations$plantare$en {
|
||||||
@override String get removeConfirm => 'Remover este compromisso?';
|
@override String get removeConfirm => 'Remover este compromisso?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: handover
|
||||||
|
class _Translations$handover$pt extends Translations$handover$en {
|
||||||
|
_Translations$handover$pt._(TranslationsPt root) : this._root = root, super.internal(root);
|
||||||
|
|
||||||
|
final TranslationsPt _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get title => 'Dei ou recebi sementes';
|
||||||
|
@override String get help => 'Uma oferta, uma troca ou uma venda: anota-o, com promessa de devolver semente ou sem ela.';
|
||||||
|
@override String get iGave => 'Dei sementes';
|
||||||
|
@override String get iReceived => 'Recebi sementes';
|
||||||
|
@override String get whichLot => 'De que lote?';
|
||||||
|
@override String get howMuch => 'Quanto?';
|
||||||
|
@override String get allOfIt => 'Tudo';
|
||||||
|
@override String get partOfIt => 'Uma parte';
|
||||||
|
@override String get paymentChip => 'Houve dinheiro pelo meio';
|
||||||
|
@override String get promiseGave => 'Vão devolver-me semente';
|
||||||
|
@override String get promiseReceived => 'Vou devolver semente';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: sale
|
// Path: sale
|
||||||
class _Translations$sale$pt extends Translations$sale$en {
|
class _Translations$sale$pt extends Translations$sale$en {
|
||||||
_Translations$sale$pt._(TranslationsPt root) : this._root = root, super.internal(root);
|
_Translations$sale$pt._(TranslationsPt root) : this._root = root, super.internal(root);
|
||||||
|
|
@ -1497,6 +1520,8 @@ extension on TranslationsPt {
|
||||||
'share.add' => 'Partilhas esta?',
|
'share.add' => 'Partilhas esta?',
|
||||||
'share.title' => 'Partilhas esta?',
|
'share.title' => 'Partilhas esta?',
|
||||||
'share.nudge' => 'Tens de sobra: podias partilhar um pouco.',
|
'share.nudge' => 'Tens de sobra: podias partilhar um pouco.',
|
||||||
|
'share.price' => 'Preço',
|
||||||
|
'share.priceHint' => 'Deixa vazio para combinar depois',
|
||||||
'share.private' => 'Só para mim',
|
'share.private' => 'Só para mim',
|
||||||
'share.gift' => 'Para dar',
|
'share.gift' => 'Para dar',
|
||||||
'share.exchange' => 'Para trocar',
|
'share.exchange' => 'Para trocar',
|
||||||
|
|
@ -1734,6 +1759,17 @@ extension on TranslationsPt {
|
||||||
'plantare.openSection' => 'Pendentes',
|
'plantare.openSection' => 'Pendentes',
|
||||||
'plantare.settledSection' => 'Feitos',
|
'plantare.settledSection' => 'Feitos',
|
||||||
'plantare.removeConfirm' => 'Remover este compromisso?',
|
'plantare.removeConfirm' => 'Remover este compromisso?',
|
||||||
|
'handover.title' => 'Dei ou recebi sementes',
|
||||||
|
'handover.help' => 'Uma oferta, uma troca ou uma venda: anota-o, com promessa de devolver semente ou sem ela.',
|
||||||
|
'handover.iGave' => 'Dei sementes',
|
||||||
|
'handover.iReceived' => 'Recebi sementes',
|
||||||
|
'handover.whichLot' => 'De que lote?',
|
||||||
|
'handover.howMuch' => 'Quanto?',
|
||||||
|
'handover.allOfIt' => 'Tudo',
|
||||||
|
'handover.partOfIt' => 'Uma parte',
|
||||||
|
'handover.paymentChip' => 'Houve dinheiro pelo meio',
|
||||||
|
'handover.promiseGave' => 'Vão devolver-me semente',
|
||||||
|
'handover.promiseReceived' => 'Vou devolver semente',
|
||||||
'sale.title' => 'Vendas',
|
'sale.title' => 'Vendas',
|
||||||
'sale.help' => 'Regista semente vendida ou comprada — dinheiro, Ğ1 ou qualquer moeda. Um modelo separado do presente e do Plantare. Nunca se cobra comissão pelas sementes.',
|
'sale.help' => 'Regista semente vendida ou comprada — dinheiro, Ğ1 ou qualquer moeda. Um modelo separado do presente e do Plantare. Nunca se cobra comissão pelas sementes.',
|
||||||
'sale.add' => 'Registar venda',
|
'sale.add' => 'Registar venda',
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,8 @@ class OffersCubit extends Cubit<OffersState> {
|
||||||
areaGeohash: areaGeohash,
|
areaGeohash: areaGeohash,
|
||||||
category: lot.category,
|
category: lot.category,
|
||||||
isOrganic: lot.isOrganic,
|
isOrganic: lot.isOrganic,
|
||||||
|
priceAmount: lot.priceAmount,
|
||||||
|
priceCurrency: lot.priceCurrency,
|
||||||
imageUrl: await _coverThumbnail(lot.varietyId),
|
imageUrl: await _coverThumbnail(lot.varietyId),
|
||||||
);
|
);
|
||||||
final result = await transport.publish(offer);
|
final result = await transport.publish(offer);
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,8 @@ class VarietyDetailCubit extends Cubit<VarietyDetailState> {
|
||||||
Abundance? abundance,
|
Abundance? abundance,
|
||||||
PreservationFormat? preservationFormat,
|
PreservationFormat? preservationFormat,
|
||||||
OfferStatus offerStatus = OfferStatus.private,
|
OfferStatus offerStatus = OfferStatus.private,
|
||||||
|
double? priceAmount,
|
||||||
|
String? priceCurrency,
|
||||||
}) => _repo.addLot(
|
}) => _repo.addLot(
|
||||||
varietyId: varietyId,
|
varietyId: varietyId,
|
||||||
type: type,
|
type: type,
|
||||||
|
|
@ -93,6 +95,8 @@ class VarietyDetailCubit extends Cubit<VarietyDetailState> {
|
||||||
abundance: abundance,
|
abundance: abundance,
|
||||||
preservationFormat: preservationFormat,
|
preservationFormat: preservationFormat,
|
||||||
offerStatus: offerStatus,
|
offerStatus: offerStatus,
|
||||||
|
priceAmount: priceAmount,
|
||||||
|
priceCurrency: priceCurrency,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> updateLot({
|
Future<void> updateLot({
|
||||||
|
|
@ -107,6 +111,8 @@ class VarietyDetailCubit extends Cubit<VarietyDetailState> {
|
||||||
Abundance? abundance,
|
Abundance? abundance,
|
||||||
PreservationFormat? preservationFormat,
|
PreservationFormat? preservationFormat,
|
||||||
OfferStatus offerStatus = OfferStatus.private,
|
OfferStatus offerStatus = OfferStatus.private,
|
||||||
|
double? priceAmount,
|
||||||
|
String? priceCurrency,
|
||||||
}) => _repo.updateLot(
|
}) => _repo.updateLot(
|
||||||
lotId: lotId,
|
lotId: lotId,
|
||||||
type: type,
|
type: type,
|
||||||
|
|
@ -119,6 +125,8 @@ class VarietyDetailCubit extends Cubit<VarietyDetailState> {
|
||||||
abundance: abundance,
|
abundance: abundance,
|
||||||
preservationFormat: preservationFormat,
|
preservationFormat: preservationFormat,
|
||||||
offerStatus: offerStatus,
|
offerStatus: offerStatus,
|
||||||
|
priceAmount: priceAmount,
|
||||||
|
priceCurrency: priceCurrency,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> deleteLot(String lotId) => _repo.softDeleteLot(lotId);
|
Future<void> deleteLot(String lotId) => _repo.softDeleteLot(lotId);
|
||||||
|
|
|
||||||
44
apps/app_seeds/lib/ui/currency_quick_picks.dart
Normal file
44
apps/app_seeds/lib/ui/currency_quick_picks.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../i18n/strings.g.dart';
|
||||||
|
|
||||||
|
/// One-tap currency chips bound to a free-text [controller]. Ğ1 sits quietly
|
||||||
|
/// among the familiar ones — offered, never imposed — and free text still
|
||||||
|
/// takes anything else (sharing-model §6). Shared by the sale sheet, the lot
|
||||||
|
/// price field and the hand-over sheet so every money-ish input feels the same.
|
||||||
|
class CurrencyQuickPicks extends StatelessWidget {
|
||||||
|
const CurrencyQuickPicks({
|
||||||
|
required this.controller,
|
||||||
|
required this.keyPrefix,
|
||||||
|
required this.onChanged,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
|
||||||
|
/// Namespaces the chip [Key]s (e.g. `sale` → `sale.currencyChip.€`).
|
||||||
|
final String keyPrefix;
|
||||||
|
|
||||||
|
/// Fires after a chip toggles the controller text (for `setState`).
|
||||||
|
final VoidCallback onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final t = context.t;
|
||||||
|
return Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
for (final c in ['€', 'Ğ1', t.sale.hours])
|
||||||
|
ChoiceChip(
|
||||||
|
key: Key('$keyPrefix.currencyChip.$c'),
|
||||||
|
label: Text(c),
|
||||||
|
selected: controller.text.trim() == c,
|
||||||
|
onSelected: (sel) {
|
||||||
|
controller.text = sel ? c : '';
|
||||||
|
onChanged();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
346
apps/app_seeds/lib/ui/handover_sheet.dart
Normal file
346
apps/app_seeds/lib/ui/handover_sheet.dart
Normal file
|
|
@ -0,0 +1,346 @@
|
||||||
|
import 'package:commons_core/commons_core.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../data/variety_repository.dart';
|
||||||
|
import '../db/enums.dart';
|
||||||
|
import '../i18n/strings.g.dart';
|
||||||
|
import 'currency_quick_picks.dart';
|
||||||
|
import 'harvest_date_picker.dart';
|
||||||
|
import 'quantity_kind_l10n.dart';
|
||||||
|
import 'quantity_picker.dart';
|
||||||
|
import 'theme.dart';
|
||||||
|
|
||||||
|
/// Opens the hand-over sheet — the ONE place to note that seeds changed hands
|
||||||
|
/// (a gift, a swap, a sale…), optionally with money and/or a return promise.
|
||||||
|
/// It records the Movement and spawns the Sale/Plantare rows in one save
|
||||||
|
/// (data-model §2.8), so the person never juggles three separate forms.
|
||||||
|
///
|
||||||
|
/// [lots] are the variety's live batches: one → preselected; several → a
|
||||||
|
/// chip picker; none → the quantity section hides and only the optional
|
||||||
|
/// records are saved. [initialLot] preselects a batch (entry from a lot row).
|
||||||
|
Future<void> showHandoverSheet(
|
||||||
|
BuildContext context, {
|
||||||
|
required VarietyRepository repository,
|
||||||
|
required String varietyId,
|
||||||
|
List<VarietyLot> lots = const [],
|
||||||
|
VarietyLot? initialLot,
|
||||||
|
}) {
|
||||||
|
return showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (_) => _HandoverSheet(
|
||||||
|
repository: repository,
|
||||||
|
varietyId: varietyId,
|
||||||
|
lots: lots,
|
||||||
|
initialLot: initialLot ?? (lots.length == 1 ? lots.single : null),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HandoverSheet extends StatefulWidget {
|
||||||
|
const _HandoverSheet({
|
||||||
|
required this.repository,
|
||||||
|
required this.varietyId,
|
||||||
|
required this.lots,
|
||||||
|
this.initialLot,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VarietyRepository repository;
|
||||||
|
final String varietyId;
|
||||||
|
final List<VarietyLot> lots;
|
||||||
|
final VarietyLot? initialLot;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_HandoverSheet> createState() => _HandoverSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HandoverSheetState extends State<_HandoverSheet> {
|
||||||
|
HandoverDirection _direction = HandoverDirection.iGave;
|
||||||
|
VarietyLot? _lot;
|
||||||
|
bool _gaveAll = true;
|
||||||
|
Quantity? _partialQuantity;
|
||||||
|
final _counterparty = TextEditingController();
|
||||||
|
bool _withPayment = false;
|
||||||
|
final _amount = TextEditingController();
|
||||||
|
final _currency = TextEditingController();
|
||||||
|
bool _withPromise = false;
|
||||||
|
final _owed = TextEditingController();
|
||||||
|
bool _saving = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_lot = widget.initialLot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_counterparty.dispose();
|
||||||
|
_amount.dispose();
|
||||||
|
_currency.dispose();
|
||||||
|
_owed.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _nullIfBlank(String s) => s.trim().isEmpty ? null : s.trim();
|
||||||
|
|
||||||
|
String _lotLabel(Translations t, VarietyLot lot) {
|
||||||
|
final parts = <String>[
|
||||||
|
if (lot.harvestYear != null)
|
||||||
|
harvestDateLabel(t, year: lot.harvestYear!, month: lot.harvestMonth)
|
||||||
|
else
|
||||||
|
t.detail.noYear,
|
||||||
|
if (lot.quantity != null) quantityDisplay(t, lot.quantity!),
|
||||||
|
];
|
||||||
|
return parts.join(' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _save() async {
|
||||||
|
setState(() => _saving = true);
|
||||||
|
final counterparty = _nullIfBlank(_counterparty.text);
|
||||||
|
final amount = double.tryParse(_amount.text.trim().replaceAll(',', '.'));
|
||||||
|
final currency = _nullIfBlank(_currency.text);
|
||||||
|
final owed = _nullIfBlank(_owed.text);
|
||||||
|
final lot = _lot;
|
||||||
|
if (lot != null) {
|
||||||
|
await widget.repository.recordHandover(
|
||||||
|
lotId: lot.id,
|
||||||
|
direction: _direction,
|
||||||
|
gaveAll: _gaveAll,
|
||||||
|
quantity: _gaveAll ? null : _partialQuantity,
|
||||||
|
counterparty: counterparty,
|
||||||
|
withPayment: _withPayment,
|
||||||
|
paymentAmount: amount,
|
||||||
|
paymentCurrency: currency,
|
||||||
|
withPromise: _withPromise,
|
||||||
|
promiseOwedDescription: owed,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// No batch to move — record just the ledgers the person asked for.
|
||||||
|
if (_withPayment) {
|
||||||
|
await widget.repository.createSale(
|
||||||
|
direction: _direction == HandoverDirection.iGave
|
||||||
|
? SaleDirection.iSold
|
||||||
|
: SaleDirection.iBought,
|
||||||
|
varietyId: widget.varietyId,
|
||||||
|
counterparty: counterparty,
|
||||||
|
amount: amount,
|
||||||
|
currency: currency,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_withPromise) {
|
||||||
|
await widget.repository.createPlantare(
|
||||||
|
direction: _direction == HandoverDirection.iGave
|
||||||
|
? PlantareDirection.owedToMe
|
||||||
|
: PlantareDirection.iReturn,
|
||||||
|
varietyId: widget.varietyId,
|
||||||
|
counterparty: counterparty,
|
||||||
|
owedDescription: owed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mounted) Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final t = context.t;
|
||||||
|
final gave = _direction == HandoverDirection.iGave;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
top: 16,
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom + 16,
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
t.handover.title,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
t.handover.help,
|
||||||
|
style: const TextStyle(color: seedMuted, fontSize: 13),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// Two big, human choices — same idiom as the plantare sheet.
|
||||||
|
for (final d in HandoverDirection.values)
|
||||||
|
ListTile(
|
||||||
|
key: Key('handover.direction.${d.name}'),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
leading: Icon(
|
||||||
|
_direction == d
|
||||||
|
? Icons.radio_button_checked
|
||||||
|
: Icons.radio_button_unchecked,
|
||||||
|
color: _direction == d ? seedGreen : seedMuted,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
d == HandoverDirection.iGave
|
||||||
|
? t.handover.iGave
|
||||||
|
: t.handover.iReceived,
|
||||||
|
),
|
||||||
|
onTap: () => setState(() => _direction = d),
|
||||||
|
),
|
||||||
|
if (widget.lots.length > 1) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
t.handover.whichLot,
|
||||||
|
style: Theme.of(context).textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 4,
|
||||||
|
children: [
|
||||||
|
for (final lot in widget.lots)
|
||||||
|
ChoiceChip(
|
||||||
|
key: Key('handover.lot.${lot.id}'),
|
||||||
|
label: Text(_lotLabel(t, lot)),
|
||||||
|
selected: _lot?.id == lot.id,
|
||||||
|
onSelected: (sel) =>
|
||||||
|
setState(() => _lot = sel ? lot : null),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (_lot != null && gave) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
t.handover.howMuch,
|
||||||
|
style: Theme.of(context).textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
ChoiceChip(
|
||||||
|
key: const Key('handover.allOfIt'),
|
||||||
|
label: Text(t.handover.allOfIt),
|
||||||
|
selected: _gaveAll,
|
||||||
|
onSelected: (sel) => setState(() => _gaveAll = true),
|
||||||
|
),
|
||||||
|
ChoiceChip(
|
||||||
|
key: const Key('handover.partOfIt'),
|
||||||
|
label: Text(t.handover.partOfIt),
|
||||||
|
selected: !_gaveAll,
|
||||||
|
onSelected: (sel) => setState(() => _gaveAll = false),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!_gaveAll) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
QuantityPicker(
|
||||||
|
type: _lot!.type,
|
||||||
|
value: _partialQuantity,
|
||||||
|
onChanged: (q) => _partialQuantity = q,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextField(
|
||||||
|
key: const Key('handover.counterparty'),
|
||||||
|
controller: _counterparty,
|
||||||
|
textCapitalization: TextCapitalization.words,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: t.sale.counterparty,
|
||||||
|
helperText: t.sale.counterpartyHint,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// Money and promise stay one tap away — a plain gift needs neither.
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
if (!_withPayment)
|
||||||
|
ActionChip(
|
||||||
|
key: const Key('handover.addPayment'),
|
||||||
|
avatar: const Icon(Icons.sell_outlined, size: 18),
|
||||||
|
label: Text(t.handover.paymentChip),
|
||||||
|
onPressed: () => setState(() => _withPayment = true),
|
||||||
|
),
|
||||||
|
if (!_withPromise)
|
||||||
|
ActionChip(
|
||||||
|
key: const Key('handover.addPromise'),
|
||||||
|
avatar: const Icon(
|
||||||
|
Icons.volunteer_activism_outlined,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
gave
|
||||||
|
? t.handover.promiseGave
|
||||||
|
: t.handover.promiseReceived,
|
||||||
|
),
|
||||||
|
onPressed: () => setState(() => _withPromise = true),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_withPayment) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
key: const Key('handover.amount'),
|
||||||
|
controller: _amount,
|
||||||
|
keyboardType: const TextInputType.numberWithOptions(
|
||||||
|
decimal: true,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: t.sale.amount,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
key: const Key('handover.currency'),
|
||||||
|
controller: _currency,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: t.sale.currency,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: (_) => setState(() {}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
CurrencyQuickPicks(
|
||||||
|
controller: _currency,
|
||||||
|
keyPrefix: 'handover',
|
||||||
|
onChanged: () => setState(() {}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (_withPromise) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextField(
|
||||||
|
key: const Key('handover.owed'),
|
||||||
|
controller: _owed,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: t.plantare.owed,
|
||||||
|
helperText: t.plantare.owedHint,
|
||||||
|
helperMaxLines: 2,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
FilledButton(
|
||||||
|
key: const Key('handover.save'),
|
||||||
|
onPressed: _saving ? null : _save,
|
||||||
|
child: Text(t.common.save),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import '../data/variety_repository.dart';
|
import '../data/variety_repository.dart';
|
||||||
import '../db/enums.dart';
|
import '../db/enums.dart';
|
||||||
import '../i18n/strings.g.dart';
|
import '../i18n/strings.g.dart';
|
||||||
|
import 'currency_quick_picks.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
||||||
/// Opens the "record a sale" sheet. Optionally pre-attached to [varietyId]
|
/// Opens the "record a sale" sheet. Optionally pre-attached to [varietyId]
|
||||||
|
|
@ -140,22 +141,10 @@ class _SaleSheetState extends State<_SaleSheet> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// Quick picks so a currency is one tap. Ğ1 sits quietly among the
|
CurrencyQuickPicks(
|
||||||
// familiar ones — offered, never imposed — and free text still takes
|
controller: _currency,
|
||||||
// anything else.
|
keyPrefix: 'sale',
|
||||||
Wrap(
|
onChanged: () => setState(() {}),
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
for (final c in ['€', 'Ğ1', t.sale.hours])
|
|
||||||
ChoiceChip(
|
|
||||||
key: Key('sale.currencyChip.$c'),
|
|
||||||
label: Text(c),
|
|
||||||
selected: _currency.text.trim() == c,
|
|
||||||
onSelected: (sel) => setState(() {
|
|
||||||
_currency.text = sel ? c : '';
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
TextField(
|
TextField(
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@ import '../domain/seed_viability.dart';
|
||||||
import '../domain/species_reference_links.dart';
|
import '../domain/species_reference_links.dart';
|
||||||
import '../i18n/strings.g.dart';
|
import '../i18n/strings.g.dart';
|
||||||
import '../state/variety_detail_cubit.dart';
|
import '../state/variety_detail_cubit.dart';
|
||||||
|
import 'currency_quick_picks.dart';
|
||||||
|
import 'handover_sheet.dart';
|
||||||
import 'harvest_date_picker.dart';
|
import 'harvest_date_picker.dart';
|
||||||
import 'photo_pick.dart';
|
import 'photo_pick.dart';
|
||||||
import 'plantare_sheet.dart';
|
|
||||||
import 'quantity_kind_l10n.dart';
|
import 'quantity_kind_l10n.dart';
|
||||||
import 'sale_sheet.dart';
|
|
||||||
import 'quantity_picker.dart';
|
import 'quantity_picker.dart';
|
||||||
import 'seed_glyph.dart';
|
import 'seed_glyph.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
@ -117,25 +117,19 @@ class _DetailView extends StatelessWidget {
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: [
|
children: [
|
||||||
|
// ONE door for "seeds changed hands": gift, swap or sale, with
|
||||||
|
// or without money or a return promise. The sales/plantares
|
||||||
|
// screens keep their own add buttons for off-app records.
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
key: const Key('detail.addPlantare'),
|
key: const Key('detail.handover'),
|
||||||
icon: const Icon(Icons.volunteer_activism_outlined, size: 18),
|
icon: const Icon(Icons.handshake_outlined, size: 18),
|
||||||
onPressed: () => showPlantareSheet(
|
onPressed: () => showHandoverSheet(
|
||||||
context,
|
context,
|
||||||
repository: context.read<VarietyRepository>(),
|
repository: context.read<VarietyRepository>(),
|
||||||
varietyId: detail.id,
|
varietyId: detail.id,
|
||||||
|
lots: detail.lots,
|
||||||
),
|
),
|
||||||
label: Text(t.plantare.add),
|
label: Text(t.handover.title),
|
||||||
),
|
|
||||||
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),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -322,22 +316,32 @@ class _LotTile extends StatelessWidget {
|
||||||
?warning,
|
?warning,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Germination only applies to seed lots, not to plantel.
|
trailing: Row(
|
||||||
trailing: lot.type != LotType.seed
|
mainAxisSize: MainAxisSize.min,
|
||||||
? null
|
children: [
|
||||||
: Row(
|
if (lot.type == LotType.seed && lot.latestGerminationRate != null)
|
||||||
mainAxisSize: MainAxisSize.min,
|
_GerminationBadge(rate: lot.latestGerminationRate!),
|
||||||
children: [
|
IconButton(
|
||||||
if (lot.latestGerminationRate != null)
|
key: Key('lot.handover.${lot.id}'),
|
||||||
_GerminationBadge(rate: lot.latestGerminationRate!),
|
icon: const Icon(Icons.handshake_outlined),
|
||||||
IconButton(
|
tooltip: t.handover.title,
|
||||||
key: Key('lot.germination.${lot.id}'),
|
onPressed: () => showHandoverSheet(
|
||||||
icon: const Icon(Icons.grass_outlined),
|
context,
|
||||||
tooltip: t.germination.title,
|
repository: context.read<VarietyRepository>(),
|
||||||
onPressed: () => _showGerminationSheet(context, cubit, lot),
|
varietyId: cubit.varietyId,
|
||||||
),
|
lots: [lot],
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
// Germination only applies to seed lots, not to plantel.
|
||||||
|
if (lot.type == LotType.seed)
|
||||||
|
IconButton(
|
||||||
|
key: Key('lot.germination.${lot.id}'),
|
||||||
|
icon: const Icon(Icons.grass_outlined),
|
||||||
|
tooltip: t.germination.title,
|
||||||
|
onPressed: () => _showGerminationSheet(context, cubit, lot),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -496,80 +500,82 @@ Future<void> _showGerminationSheet(
|
||||||
t.germination.title,
|
t.germination.title,
|
||||||
style: Theme.of(sheetContext).textTheme.titleLarge,
|
style: Theme.of(sheetContext).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (lot.germinationTests.isEmpty)
|
if (lot.germinationTests.isEmpty)
|
||||||
Text(t.germination.none)
|
Text(t.germination.none)
|
||||||
else
|
else
|
||||||
for (final test in lot.germinationTests)
|
for (final test in lot.germinationTests)
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
leading: const Icon(Icons.grass_outlined),
|
leading: const Icon(Icons.grass_outlined),
|
||||||
title: Text(
|
title: Text(
|
||||||
test.rate == null ? '—' : _germinationPercent(t, test.rate!),
|
test.rate == null
|
||||||
|
? '—'
|
||||||
|
: _germinationPercent(t, test.rate!),
|
||||||
|
),
|
||||||
|
subtitle:
|
||||||
|
(test.sampleSize != null && test.germinatedCount != null)
|
||||||
|
? Text('${test.germinatedCount}/${test.sampleSize}')
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
subtitle:
|
const Divider(),
|
||||||
(test.sampleSize != null && test.germinatedCount != null)
|
Row(
|
||||||
? Text('${test.germinatedCount}/${test.sampleSize}')
|
children: [
|
||||||
: null,
|
Expanded(
|
||||||
),
|
child: TextField(
|
||||||
const Divider(),
|
key: const Key('germination.germinated'),
|
||||||
Row(
|
controller: germinated,
|
||||||
children: [
|
keyboardType: TextInputType.number,
|
||||||
Expanded(
|
decoration: InputDecoration(
|
||||||
child: TextField(
|
labelText: t.germination.germinated,
|
||||||
key: const Key('germination.germinated'),
|
border: const OutlineInputBorder(
|
||||||
controller: germinated,
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
keyboardType: TextInputType.number,
|
),
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: t.germination.germinated,
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 12),
|
||||||
const SizedBox(width: 12),
|
Expanded(
|
||||||
Expanded(
|
child: TextField(
|
||||||
child: TextField(
|
key: const Key('germination.sampleSize'),
|
||||||
key: const Key('germination.sampleSize'),
|
controller: sample,
|
||||||
controller: sample,
|
keyboardType: TextInputType.number,
|
||||||
keyboardType: TextInputType.number,
|
decoration: InputDecoration(
|
||||||
decoration: InputDecoration(
|
labelText: t.germination.sampleSize,
|
||||||
labelText: t.germination.sampleSize,
|
border: const OutlineInputBorder(
|
||||||
border: const OutlineInputBorder(
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
Row(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
TextButton(
|
||||||
TextButton(
|
onPressed: () => Navigator.of(sheetContext).pop(),
|
||||||
onPressed: () => Navigator.of(sheetContext).pop(),
|
child: Text(t.common.cancel),
|
||||||
child: Text(t.common.cancel),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
const Spacer(),
|
FilledButton(
|
||||||
FilledButton(
|
key: const Key('germination.save'),
|
||||||
key: const Key('germination.save'),
|
onPressed: () {
|
||||||
onPressed: () {
|
cubit.addGerminationTest(
|
||||||
cubit.addGerminationTest(
|
lotId: lot.id,
|
||||||
lotId: lot.id,
|
testedOn: DateTime.now().millisecondsSinceEpoch,
|
||||||
testedOn: DateTime.now().millisecondsSinceEpoch,
|
sampleSize: int.tryParse(sample.text.trim()),
|
||||||
sampleSize: int.tryParse(sample.text.trim()),
|
germinatedCount: int.tryParse(germinated.text.trim()),
|
||||||
germinatedCount: int.tryParse(germinated.text.trim()),
|
);
|
||||||
);
|
Navigator.of(sheetContext).pop();
|
||||||
Navigator.of(sheetContext).pop();
|
},
|
||||||
},
|
child: Text(t.germination.add),
|
||||||
child: Text(t.germination.add),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -1375,62 +1381,63 @@ Future<void> _showConditionSheet(
|
||||||
t.conditionCheck.title,
|
t.conditionCheck.title,
|
||||||
style: Theme.of(sheetContext).textTheme.titleLarge,
|
style: Theme.of(sheetContext).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
TextField(
|
TextField(
|
||||||
key: const Key('conditionCheck.containers'),
|
key: const Key('conditionCheck.containers'),
|
||||||
controller: containers,
|
controller: containers,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: t.conditionCheck.containers,
|
labelText: t.conditionCheck.containers,
|
||||||
border: const OutlineInputBorder(
|
border: const OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
Text(
|
||||||
Text(
|
t.conditionCheck.desiccant,
|
||||||
t.conditionCheck.desiccant,
|
style: Theme.of(sheetContext).textTheme.labelLarge,
|
||||||
style: Theme.of(sheetContext).textTheme.labelLarge,
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 8),
|
Wrap(
|
||||||
Wrap(
|
spacing: 8,
|
||||||
spacing: 8,
|
runSpacing: 4,
|
||||||
runSpacing: 4,
|
children: [
|
||||||
children: [
|
for (final d in DesiccantState.values)
|
||||||
for (final d in DesiccantState.values)
|
ChoiceChip(
|
||||||
ChoiceChip(
|
key: Key('desiccant.${d.name}'),
|
||||||
key: Key('desiccant.${d.name}'),
|
label: Text(desiccantLabel(t, d)),
|
||||||
label: Text(desiccantLabel(t, d)),
|
selected: state == d,
|
||||||
selected: state == d,
|
onSelected: (sel) =>
|
||||||
onSelected: (sel) => setState(() => state = sel ? d : null),
|
setState(() => state = sel ? d : null),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(sheetContext).pop(),
|
||||||
|
child: Text(t.common.cancel),
|
||||||
),
|
),
|
||||||
],
|
const Spacer(),
|
||||||
),
|
FilledButton(
|
||||||
const SizedBox(height: 16),
|
key: const Key('conditionCheck.save'),
|
||||||
Row(
|
onPressed: () {
|
||||||
children: [
|
cubit.addConditionCheck(
|
||||||
TextButton(
|
lotId: lot.id,
|
||||||
onPressed: () => Navigator.of(sheetContext).pop(),
|
checkedOn: DateTime.now().millisecondsSinceEpoch,
|
||||||
child: Text(t.common.cancel),
|
containerCount: int.tryParse(containers.text.trim()),
|
||||||
),
|
desiccantState: state,
|
||||||
const Spacer(),
|
);
|
||||||
FilledButton(
|
Navigator.of(sheetContext).pop();
|
||||||
key: const Key('conditionCheck.save'),
|
},
|
||||||
onPressed: () {
|
child: Text(t.conditionCheck.add),
|
||||||
cubit.addConditionCheck(
|
),
|
||||||
lotId: lot.id,
|
],
|
||||||
checkedOn: DateTime.now().millisecondsSinceEpoch,
|
),
|
||||||
containerCount: int.tryParse(containers.text.trim()),
|
],
|
||||||
desiccantState: state,
|
),
|
||||||
);
|
|
||||||
Navigator.of(sheetContext).pop();
|
|
||||||
},
|
|
||||||
child: Text(t.conditionCheck.add),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1464,6 +1471,18 @@ Future<void> _showLotSheet(
|
||||||
existing?.originName != null || existing?.originPlace != null;
|
existing?.originName != null || existing?.originPlace != null;
|
||||||
var showAbundance = existing?.abundance != null;
|
var showAbundance = existing?.abundance != null;
|
||||||
var showShare = selectedShare != OfferStatus.private;
|
var showShare = selectedShare != OfferStatus.private;
|
||||||
|
final existingPrice = existing?.priceAmount;
|
||||||
|
final priceAmountCtrl = TextEditingController(
|
||||||
|
// Drop a trailing .0 so the field shows "5", not "5.0".
|
||||||
|
text: existingPrice == null
|
||||||
|
? ''
|
||||||
|
: (existingPrice == existingPrice.roundToDouble()
|
||||||
|
? existingPrice.toInt().toString()
|
||||||
|
: existingPrice.toString()),
|
||||||
|
);
|
||||||
|
final priceCurrencyCtrl = TextEditingController(
|
||||||
|
text: existing?.priceCurrency ?? '',
|
||||||
|
);
|
||||||
return showModalBottomSheet<void>(
|
return showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
|
@ -1660,6 +1679,49 @@ Future<void> _showLotSheet(
|
||||||
style: Theme.of(sheetContext).textTheme.bodySmall,
|
style: Theme.of(sheetContext).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Price only when selling — informational, agreed and
|
||||||
|
// paid off-app. Empty amount = "to be agreed".
|
||||||
|
if (selectedShare == OfferStatus.sell) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
key: const Key('lot.priceAmount'),
|
||||||
|
controller: priceAmountCtrl,
|
||||||
|
keyboardType:
|
||||||
|
const TextInputType.numberWithOptions(
|
||||||
|
decimal: true,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: t.share.price,
|
||||||
|
helperText: t.share.priceHint,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
key: const Key('lot.priceCurrency'),
|
||||||
|
controller: priceCurrencyCtrl,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: t.sale.currency,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: (_) => setState(() {}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
CurrencyQuickPicks(
|
||||||
|
controller: priceCurrencyCtrl,
|
||||||
|
keyPrefix: 'lot',
|
||||||
|
onChanged: () => setState(() {}),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
// Layer 2: advanced seed-bank details, collapsed by default.
|
// Layer 2: advanced seed-bank details, collapsed by default.
|
||||||
// Only for seed lots, and (condition checks) an existing lot.
|
// Only for seed lots, and (condition checks) an existing lot.
|
||||||
|
|
@ -1710,6 +1772,10 @@ Future<void> _showLotSheet(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final originName = _nullIfBlank(originNameCtrl.text);
|
final originName = _nullIfBlank(originNameCtrl.text);
|
||||||
final originPlace = _nullIfBlank(originPlaceCtrl.text);
|
final originPlace = _nullIfBlank(originPlaceCtrl.text);
|
||||||
|
final priceAmount = double.tryParse(
|
||||||
|
priceAmountCtrl.text.replaceAll(',', '.').trim(),
|
||||||
|
);
|
||||||
|
final priceCurrency = _nullIfBlank(priceCurrencyCtrl.text);
|
||||||
if (editing) {
|
if (editing) {
|
||||||
cubit.updateLot(
|
cubit.updateLot(
|
||||||
lotId: existing.id,
|
lotId: existing.id,
|
||||||
|
|
@ -1723,6 +1789,8 @@ Future<void> _showLotSheet(
|
||||||
abundance: selectedAbundance,
|
abundance: selectedAbundance,
|
||||||
preservationFormat: selectedPreservation,
|
preservationFormat: selectedPreservation,
|
||||||
offerStatus: selectedShare,
|
offerStatus: selectedShare,
|
||||||
|
priceAmount: priceAmount,
|
||||||
|
priceCurrency: priceCurrency,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
cubit.addLot(
|
cubit.addLot(
|
||||||
|
|
@ -1736,6 +1804,8 @@ Future<void> _showLotSheet(
|
||||||
abundance: selectedAbundance,
|
abundance: selectedAbundance,
|
||||||
preservationFormat: selectedPreservation,
|
preservationFormat: selectedPreservation,
|
||||||
offerStatus: selectedShare,
|
offerStatus: selectedShare,
|
||||||
|
priceAmount: priceAmount,
|
||||||
|
priceCurrency: priceCurrency,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Navigator.of(sheetContext).pop();
|
Navigator.of(sheetContext).pop();
|
||||||
|
|
|
||||||
181
apps/app_seeds/test/data/handover_test.dart
Normal file
181
apps/app_seeds/test/data/handover_test.dart
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
import 'package:commons_core/commons_core.dart';
|
||||||
|
import 'package:drift/drift.dart' hide isNull;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:tane/data/variety_repository.dart';
|
||||||
|
import 'package:tane/db/database.dart';
|
||||||
|
import 'package:tane/db/enums.dart';
|
||||||
|
|
||||||
|
import '../support/test_support.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late AppDatabase db;
|
||||||
|
late VarietyRepository repo;
|
||||||
|
late String varietyId;
|
||||||
|
setUp(() async {
|
||||||
|
db = newTestDatabase();
|
||||||
|
repo = newTestRepository(db);
|
||||||
|
varietyId = await repo.addQuickVariety(label: 'Tomate rosa');
|
||||||
|
});
|
||||||
|
tearDown(() => db.close());
|
||||||
|
|
||||||
|
Future<Movement> movementById(String id) =>
|
||||||
|
(db.select(db.movements)..where((m) => m.id.equals(id))).getSingle();
|
||||||
|
|
||||||
|
Future<Lot> lotById(String id) =>
|
||||||
|
(db.select(db.lots)..where((l) => l.id.equals(id))).getSingle();
|
||||||
|
|
||||||
|
test('a plain gift records just the given movement', () async {
|
||||||
|
final lotId = await repo.addLot(varietyId: varietyId);
|
||||||
|
|
||||||
|
final result = await repo.recordHandover(
|
||||||
|
lotId: lotId,
|
||||||
|
direction: HandoverDirection.iGave,
|
||||||
|
counterparty: 'María',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.saleId, isNull);
|
||||||
|
expect(result.plantareId, isNull);
|
||||||
|
final movement = await movementById(result.movementId);
|
||||||
|
expect(movement.type, MovementType.given);
|
||||||
|
expect(movement.lotId, lotId);
|
||||||
|
expect(movement.notes, 'María');
|
||||||
|
expect(movement.plantareId, isNull);
|
||||||
|
expect(await repo.watchSales().first, isEmpty);
|
||||||
|
expect(await repo.watchPlantares().first, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'giving all depletes the lot, privatizes it and drops its price',
|
||||||
|
() async {
|
||||||
|
final lotId = await repo.addLot(
|
||||||
|
varietyId: varietyId,
|
||||||
|
quantity: const Quantity(kind: QuantityKind.grams, count: 40),
|
||||||
|
abundance: Abundance.plentyToShare,
|
||||||
|
offerStatus: OfferStatus.sell,
|
||||||
|
priceAmount: 3,
|
||||||
|
priceCurrency: '€',
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await repo.recordHandover(
|
||||||
|
lotId: lotId,
|
||||||
|
direction: HandoverDirection.iGave,
|
||||||
|
gaveAll: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// The movement carries what actually moved: the lot's whole quantity.
|
||||||
|
final movement = await movementById(result.movementId);
|
||||||
|
expect(movement.quantityKind, QuantityKind.grams.name);
|
||||||
|
expect(movement.quantityPrecise, 40);
|
||||||
|
|
||||||
|
// The lot stays as an empty jar, withdrawn from the market.
|
||||||
|
final lot = await lotById(lotId);
|
||||||
|
expect(lot.quantityPrecise, 0);
|
||||||
|
expect(lot.quantityKind, QuantityKind.grams.name);
|
||||||
|
expect(lot.abundance, isNull);
|
||||||
|
expect(lot.offerStatus, OfferStatus.private);
|
||||||
|
expect(lot.priceAmount, isNull);
|
||||||
|
expect(lot.priceCurrency, isNull);
|
||||||
|
expect(
|
||||||
|
(await repo.shareableLots()).where((l) => l.lotId == lotId),
|
||||||
|
isEmpty,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('giving part keeps the lot open and offered', () async {
|
||||||
|
final lotId = await repo.addLot(
|
||||||
|
varietyId: varietyId,
|
||||||
|
quantity: const Quantity(kind: QuantityKind.grams, count: 40),
|
||||||
|
offerStatus: OfferStatus.shared,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await repo.recordHandover(
|
||||||
|
lotId: lotId,
|
||||||
|
direction: HandoverDirection.iGave,
|
||||||
|
quantity: const Quantity(kind: QuantityKind.grams, count: 10),
|
||||||
|
);
|
||||||
|
|
||||||
|
final movement = await movementById(result.movementId);
|
||||||
|
expect(movement.quantityPrecise, 10);
|
||||||
|
final lot = await lotById(lotId);
|
||||||
|
expect(lot.quantityPrecise, 40); // untouched: the log holds the history
|
||||||
|
expect(lot.offerStatus, OfferStatus.shared);
|
||||||
|
expect(
|
||||||
|
(await repo.shareableLots()).where((l) => l.lotId == lotId),
|
||||||
|
hasLength(1),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a payment on a give creates the matching sale record', () async {
|
||||||
|
final lotId = await repo.addLot(varietyId: varietyId);
|
||||||
|
|
||||||
|
final result = await repo.recordHandover(
|
||||||
|
lotId: lotId,
|
||||||
|
direction: HandoverDirection.iGave,
|
||||||
|
counterparty: 'María',
|
||||||
|
withPayment: true,
|
||||||
|
paymentAmount: 2.5,
|
||||||
|
paymentCurrency: 'Ğ1',
|
||||||
|
);
|
||||||
|
|
||||||
|
final sales = await repo.watchSales().first;
|
||||||
|
expect(sales, hasLength(1));
|
||||||
|
expect(sales.single.id, result.saleId);
|
||||||
|
expect(sales.single.direction, SaleDirection.iSold);
|
||||||
|
expect(sales.single.varietyId, varietyId);
|
||||||
|
expect(sales.single.counterparty, 'María');
|
||||||
|
expect(sales.single.amount, 2.5);
|
||||||
|
expect(sales.single.currency, 'Ğ1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a promise on a give creates a plantare owed to me, linked from the '
|
||||||
|
'movement', () async {
|
||||||
|
final lotId = await repo.addLot(varietyId: varietyId);
|
||||||
|
|
||||||
|
final result = await repo.recordHandover(
|
||||||
|
lotId: lotId,
|
||||||
|
direction: HandoverDirection.iGave,
|
||||||
|
counterparty: 'María',
|
||||||
|
withPromise: true,
|
||||||
|
promiseOwedDescription: 'un puñado la próxima temporada',
|
||||||
|
);
|
||||||
|
|
||||||
|
final plantares = await repo.watchPlantares().first;
|
||||||
|
expect(plantares, hasLength(1));
|
||||||
|
expect(plantares.single.id, result.plantareId);
|
||||||
|
expect(plantares.single.direction, PlantareDirection.owedToMe);
|
||||||
|
expect(plantares.single.varietyId, varietyId);
|
||||||
|
expect(plantares.single.counterparty, 'María');
|
||||||
|
expect(plantares.single.owedDescription, 'un puñado la próxima temporada');
|
||||||
|
// The link the model reserved: the movement carries the plantare id.
|
||||||
|
final movement = await movementById(result.movementId);
|
||||||
|
expect(movement.plantareId, result.plantareId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('receiving with payment and promise mirrors the directions', () async {
|
||||||
|
final lotId = await repo.addLot(
|
||||||
|
varietyId: varietyId,
|
||||||
|
quantity: const Quantity(kind: QuantityKind.grams, count: 40),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await repo.recordHandover(
|
||||||
|
lotId: lotId,
|
||||||
|
direction: HandoverDirection.iReceived,
|
||||||
|
// Meaningless on a receive; must not deplete anything.
|
||||||
|
gaveAll: true,
|
||||||
|
withPayment: true,
|
||||||
|
paymentAmount: 5,
|
||||||
|
paymentCurrency: '€',
|
||||||
|
withPromise: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final movement = await movementById(result.movementId);
|
||||||
|
expect(movement.type, MovementType.received);
|
||||||
|
final lot = await lotById(lotId);
|
||||||
|
expect(lot.quantityPrecise, 40);
|
||||||
|
final sales = await repo.watchSales().first;
|
||||||
|
expect(sales.single.direction, SaleDirection.iBought);
|
||||||
|
final plantares = await repo.watchPlantares().first;
|
||||||
|
expect(plantares.single.direction, PlantareDirection.iReturn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -40,4 +40,71 @@ void main() {
|
||||||
final lots = await repo.shareableLots();
|
final lots = await repo.shareableLots();
|
||||||
expect(lots.where((l) => l.lotId == privateId), isEmpty);
|
expect(lots.where((l) => l.lotId == privateId), isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sell lots expose their asking price; other statuses never do',
|
||||||
|
() async {
|
||||||
|
final repo = newTestRepository(db);
|
||||||
|
final varietyId = await repo.addQuickVariety(label: 'Calabaza');
|
||||||
|
final sellId = await repo.addLot(
|
||||||
|
varietyId: varietyId,
|
||||||
|
offerStatus: OfferStatus.sell,
|
||||||
|
priceAmount: 3.5,
|
||||||
|
priceCurrency: 'Ğ1',
|
||||||
|
);
|
||||||
|
// Price passed on a gift lot is dropped, not stored.
|
||||||
|
final giftId = await repo.addLot(
|
||||||
|
varietyId: varietyId,
|
||||||
|
offerStatus: OfferStatus.shared,
|
||||||
|
priceAmount: 99,
|
||||||
|
priceCurrency: '€',
|
||||||
|
);
|
||||||
|
|
||||||
|
final lots = await repo.shareableLots();
|
||||||
|
final sell = lots.singleWhere((l) => l.lotId == sellId);
|
||||||
|
expect(sell.priceAmount, 3.5);
|
||||||
|
expect(sell.priceCurrency, 'Ğ1');
|
||||||
|
final gift = lots.singleWhere((l) => l.lotId == giftId);
|
||||||
|
expect(gift.priceAmount, isNull);
|
||||||
|
expect(gift.priceCurrency, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a sell lot without amount is "to be agreed" — shareable, no price',
|
||||||
|
() async {
|
||||||
|
final repo = newTestRepository(db);
|
||||||
|
final varietyId = await repo.addQuickVariety(label: 'Lechuga');
|
||||||
|
final sellId = await repo.addLot(
|
||||||
|
varietyId: varietyId,
|
||||||
|
offerStatus: OfferStatus.sell,
|
||||||
|
);
|
||||||
|
|
||||||
|
final lots = await repo.shareableLots();
|
||||||
|
final sell = lots.singleWhere((l) => l.lotId == sellId);
|
||||||
|
expect(sell.priceAmount, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('moving a lot out of sale clears its stored price', () async {
|
||||||
|
final repo = newTestRepository(db);
|
||||||
|
final varietyId = await repo.addQuickVariety(label: 'Haba');
|
||||||
|
final lotId = await repo.addLot(
|
||||||
|
varietyId: varietyId,
|
||||||
|
offerStatus: OfferStatus.sell,
|
||||||
|
priceAmount: 2,
|
||||||
|
priceCurrency: '€',
|
||||||
|
);
|
||||||
|
|
||||||
|
await repo.updateLot(
|
||||||
|
lotId: lotId,
|
||||||
|
type: LotType.seed,
|
||||||
|
offerStatus: OfferStatus.shared,
|
||||||
|
// Price args still sent by the form; must be ignored off-sale.
|
||||||
|
priceAmount: 2,
|
||||||
|
priceCurrency: '€',
|
||||||
|
);
|
||||||
|
|
||||||
|
final detail = await repo.watchVariety(varietyId).first;
|
||||||
|
final lot = detail!.lots.singleWhere((l) => l.id == lotId);
|
||||||
|
expect(lot.priceAmount, isNull);
|
||||||
|
expect(lot.priceCurrency, isNull);
|
||||||
|
expect(lot.offerStatus, OfferStatus.shared);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,19 @@ void main() {
|
||||||
verifier = SchemaVerifier(GeneratedHelper());
|
verifier = SchemaVerifier(GeneratedHelper());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('freshly created database matches the exported schema v10', () async {
|
test('freshly created database matches the exported schema v11', () async {
|
||||||
final schema = await verifier.schemaAt(10);
|
final schema = await verifier.schemaAt(11);
|
||||||
final db = AppDatabase(schema.newConnection());
|
final db = AppDatabase(schema.newConnection());
|
||||||
await verifier.migrateAndValidate(db, 10);
|
await verifier.migrateAndValidate(db, 11);
|
||||||
await db.close();
|
await db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Every historical version upgrades cleanly to the current schema (v10).
|
// Every historical version upgrades cleanly to the current schema (v11).
|
||||||
for (var from = 1; from <= 9; from++) {
|
for (var from = 1; from <= 10; from++) {
|
||||||
test('upgrades v$from → v10 and matches the fresh schema', () async {
|
test('upgrades v$from → v11 and matches the fresh schema', () async {
|
||||||
final connection = await verifier.startAt(from);
|
final connection = await verifier.startAt(from);
|
||||||
final db = AppDatabase(connection);
|
final db = AppDatabase(connection);
|
||||||
await verifier.migrateAndValidate(db, 10);
|
await verifier.migrateAndValidate(db, 11);
|
||||||
await db.close();
|
await db.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import 'schema_v7.dart' as v7;
|
||||||
import 'schema_v8.dart' as v8;
|
import 'schema_v8.dart' as v8;
|
||||||
import 'schema_v9.dart' as v9;
|
import 'schema_v9.dart' as v9;
|
||||||
import 'schema_v10.dart' as v10;
|
import 'schema_v10.dart' as v10;
|
||||||
|
import 'schema_v11.dart' as v11;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
|
|
@ -39,10 +40,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
return v9.DatabaseAtV9(db);
|
return v9.DatabaseAtV9(db);
|
||||||
case 10:
|
case 10:
|
||||||
return v10.DatabaseAtV10(db);
|
return v10.DatabaseAtV10(db);
|
||||||
|
case 11:
|
||||||
|
return v11.DatabaseAtV11(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2015
apps/app_seeds/test/db/schema/schema_v11.dart
Normal file
2015
apps/app_seeds/test/db/schema/schema_v11.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -294,6 +294,43 @@ void main() {
|
||||||
await cubit.close();
|
await cubit.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('publishLots carries the lot asking price on sale offers', () async {
|
||||||
|
final transport = FakeOfferTransport();
|
||||||
|
final cubit = OffersCubit(transport);
|
||||||
|
await cubit.publishLots(
|
||||||
|
const [
|
||||||
|
ShareableLot(
|
||||||
|
lotId: 'lot-sale',
|
||||||
|
varietyId: 'var-1',
|
||||||
|
summary: 'Tomate',
|
||||||
|
offerStatus: OfferStatus.sell,
|
||||||
|
priceAmount: 3.5,
|
||||||
|
priceCurrency: 'Ğ1',
|
||||||
|
),
|
||||||
|
// "To be agreed": sale without a figure publishes without a price.
|
||||||
|
ShareableLot(
|
||||||
|
lotId: 'lot-negotiable',
|
||||||
|
varietyId: 'var-2',
|
||||||
|
summary: 'Judía',
|
||||||
|
offerStatus: OfferStatus.sell,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
authorPubkeyHex: 'ab' * 32,
|
||||||
|
areaGeohash: 'sp3e9',
|
||||||
|
);
|
||||||
|
|
||||||
|
await cubit.discover('sp3');
|
||||||
|
await pumpEventQueue();
|
||||||
|
final sale = cubit.state.offers.singleWhere((o) => o.id == 'lot-sale');
|
||||||
|
expect(sale.type, OfferType.sale);
|
||||||
|
expect(sale.priceAmount, 3.5);
|
||||||
|
expect(sale.priceCurrency, 'Ğ1');
|
||||||
|
final negotiable =
|
||||||
|
cubit.state.offers.singleWhere((o) => o.id == 'lot-negotiable');
|
||||||
|
expect(negotiable.priceAmount, isNull);
|
||||||
|
await cubit.close();
|
||||||
|
});
|
||||||
|
|
||||||
test('publishLots embeds an inline thumbnail data-URI on the offer',
|
test('publishLots embeds an inline thumbnail data-URI on the offer',
|
||||||
() async {
|
() async {
|
||||||
final transport = FakeOfferTransport();
|
final transport = FakeOfferTransport();
|
||||||
|
|
|
||||||
143
apps/app_seeds/test/ui/handover_sheet_test.dart
Normal file
143
apps/app_seeds/test/ui/handover_sheet_test.dart
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
import 'package:commons_core/commons_core.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:tane/db/database.dart';
|
||||||
|
import 'package:tane/db/enums.dart';
|
||||||
|
|
||||||
|
import '../support/test_support.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late AppDatabase db;
|
||||||
|
|
||||||
|
setUp(() => db = newTestDatabase());
|
||||||
|
tearDown(() => db.close());
|
||||||
|
|
||||||
|
testWidgets('the detail screen offers ONE hand-over door, no separate '
|
||||||
|
'sale/plantare buttons', (tester) async {
|
||||||
|
final repo = newTestRepository(db);
|
||||||
|
final id = await repo.addQuickVariety(label: 'Maize');
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
wrapDetail(
|
||||||
|
repository: repo,
|
||||||
|
varietyId: id,
|
||||||
|
species: newTestSpeciesRepository(db),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('detail.handover')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('detail.addSale')), findsNothing);
|
||||||
|
expect(find.byKey(const Key('detail.addPlantare')), findsNothing);
|
||||||
|
await disposeTree(tester);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('giving all with payment and promise records the three pieces '
|
||||||
|
'in one save', (tester) async {
|
||||||
|
final repo = newTestRepository(db);
|
||||||
|
final id = await repo.addQuickVariety(label: 'Maize');
|
||||||
|
final lotId = await repo.addLot(
|
||||||
|
varietyId: id,
|
||||||
|
quantity: const Quantity(kind: QuantityKind.grams, count: 40),
|
||||||
|
offerStatus: OfferStatus.sell,
|
||||||
|
priceAmount: 3,
|
||||||
|
priceCurrency: '€',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
wrapDetail(
|
||||||
|
repository: repo,
|
||||||
|
varietyId: id,
|
||||||
|
species: newTestSpeciesRepository(db),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('detail.handover')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// "All of it" is the default — one tap covers the whole-plant case.
|
||||||
|
final allChip = tester.widget<ChoiceChip>(
|
||||||
|
find.byKey(const Key('handover.allOfIt')),
|
||||||
|
);
|
||||||
|
expect(allChip.selected, isTrue);
|
||||||
|
|
||||||
|
await tester.enterText(
|
||||||
|
find.byKey(const Key('handover.counterparty')),
|
||||||
|
'María',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('handover.addPayment')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.enterText(find.byKey(const Key('handover.amount')), '2,5');
|
||||||
|
await tester.tap(find.byKey(const Key('handover.currencyChip.Ğ1')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('handover.addPromise')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.enterText(
|
||||||
|
find.byKey(const Key('handover.owed')),
|
||||||
|
'un puñado',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.ensureVisible(find.byKey(const Key('handover.save')));
|
||||||
|
await tester.tap(find.byKey(const Key('handover.save')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final sales = await repo.watchSales().first;
|
||||||
|
expect(sales.single.direction, SaleDirection.iSold);
|
||||||
|
expect(sales.single.amount, 2.5);
|
||||||
|
expect(sales.single.currency, 'Ğ1');
|
||||||
|
expect(sales.single.counterparty, 'María');
|
||||||
|
|
||||||
|
final plantares = await repo.watchPlantares().first;
|
||||||
|
expect(plantares.single.direction, PlantareDirection.owedToMe);
|
||||||
|
expect(plantares.single.owedDescription, 'un puñado');
|
||||||
|
|
||||||
|
final lot = await (db.select(
|
||||||
|
db.lots,
|
||||||
|
)..where((l) => l.id.equals(lotId))).getSingle();
|
||||||
|
expect(lot.quantityPrecise, 0);
|
||||||
|
expect(lot.offerStatus, OfferStatus.private);
|
||||||
|
expect(lot.priceAmount, isNull);
|
||||||
|
|
||||||
|
final movements = await db.select(db.movements).get();
|
||||||
|
expect(movements.single.type, MovementType.given);
|
||||||
|
expect(movements.single.plantareId, plantares.single.id);
|
||||||
|
await disposeTree(tester);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('receiving hides the how-much choice and mirrors directions', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final repo = newTestRepository(db);
|
||||||
|
final id = await repo.addQuickVariety(label: 'Maize');
|
||||||
|
await repo.addLot(varietyId: id);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
wrapDetail(
|
||||||
|
repository: repo,
|
||||||
|
varietyId: id,
|
||||||
|
species: newTestSpeciesRepository(db),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('detail.handover')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(
|
||||||
|
find.byKey(const Key('handover.direction.iReceived')),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const Key('handover.allOfIt')), findsNothing);
|
||||||
|
|
||||||
|
await tester.ensureVisible(find.byKey(const Key('handover.save')));
|
||||||
|
await tester.tap(find.byKey(const Key('handover.save')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final movements = await db.select(db.movements).get();
|
||||||
|
expect(movements.single.type, MovementType.received);
|
||||||
|
await disposeTree(tester);
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue