feat(offers): asking price on lots, published with sale offers (schema v11)

The data model always meant sale offers to carry an informational price
(data-model §2.8, sharing-model §4.4); with offer state collapsed onto
the Lot, the price lives there. Null amount on a sell lot = 'to be
agreed' — the NIP-99 event simply omits the price tag.

- schema v11: Lots.price_amount/price_currency, guarded migration,
  schema dump + generated schema_v11 for the round-trip test
- ShareableLot/VarietyLot carry the price (sell-only in shareableLots);
  addLot/updateLot persist it and clear it when the lot leaves sale
- OffersCubit.publishLots forwards it (outbox flush picks it up free)
- lot sheet: price + currency inputs revealed only on 'For sale', with
  the €/Ğ1/hours quick-picks extracted to a shared CurrencyQuickPicks
  widget (reused by the sale sheet)
- backup JSON round-trips the new columns
- i18n share.price/priceHint in en/es/pt/ast
This commit is contained in:
vjrj 2026-07-11 07:24:23 +02:00
parent 89addb1ed7
commit cfa1053842
25 changed files with 4690 additions and 31 deletions

View file

@ -3266,6 +3266,28 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
).withConverter<PreservationFormat?>(
$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
List<GeneratedColumn> get $columns => [
id,
@ -3289,6 +3311,8 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
originPlace,
abundance,
preservationFormat,
priceAmount,
priceCurrency,
];
@override
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;
}
@ -3532,6 +3574,14 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
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].
/// Distinct from [storageLocation]. Optional.
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({
required this.id,
required this.createdAt,
@ -3622,6 +3683,8 @@ class Lot extends DataClass implements Insertable<Lot> {
this.originPlace,
this.abundance,
this.preservationFormat,
this.priceAmount,
this.priceCurrency,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@ -3683,6 +3746,12 @@ class Lot extends DataClass implements Insertable<Lot> {
$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;
}
@ -3733,6 +3802,12 @@ class Lot extends DataClass implements Insertable<Lot> {
preservationFormat: preservationFormat == null && nullToAbsent
? const Value.absent()
: 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(
serializer.fromJson<String?>(json['preservationFormat']),
),
priceAmount: serializer.fromJson<double?>(json['priceAmount']),
priceCurrency: serializer.fromJson<String?>(json['priceCurrency']),
);
}
@override
@ -3808,6 +3885,8 @@ class Lot extends DataClass implements Insertable<Lot> {
'preservationFormat': serializer.toJson<String?>(
$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<Abundance?> abundance = const Value.absent(),
Value<PreservationFormat?> preservationFormat = const Value.absent(),
Value<double?> priceAmount = const Value.absent(),
Value<String?> priceCurrency = const Value.absent(),
}) => Lot(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
@ -3863,6 +3944,10 @@ class Lot extends DataClass implements Insertable<Lot> {
preservationFormat: preservationFormat.present
? preservationFormat.value
: this.preservationFormat,
priceAmount: priceAmount.present ? priceAmount.value : this.priceAmount,
priceCurrency: priceCurrency.present
? priceCurrency.value
: this.priceCurrency,
);
Lot copyWithCompanion(LotsCompanion data) {
return Lot(
@ -3915,6 +4000,12 @@ class Lot extends DataClass implements Insertable<Lot> {
preservationFormat: data.preservationFormat.present
? data.preservationFormat.value
: 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('originPlace: $originPlace, ')
..write('abundance: $abundance, ')
..write('preservationFormat: $preservationFormat')
..write('preservationFormat: $preservationFormat, ')
..write('priceAmount: $priceAmount, ')
..write('priceCurrency: $priceCurrency')
..write(')'))
.toString();
}
@ -3969,6 +4062,8 @@ class Lot extends DataClass implements Insertable<Lot> {
originPlace,
abundance,
preservationFormat,
priceAmount,
priceCurrency,
]);
@override
bool operator ==(Object other) =>
@ -3994,7 +4089,9 @@ class Lot extends DataClass implements Insertable<Lot> {
other.originName == this.originName &&
other.originPlace == this.originPlace &&
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> {
@ -4019,6 +4116,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
final Value<String?> originPlace;
final Value<Abundance?> abundance;
final Value<PreservationFormat?> preservationFormat;
final Value<double?> priceAmount;
final Value<String?> priceCurrency;
final Value<int> rowid;
const LotsCompanion({
this.id = const Value.absent(),
@ -4042,6 +4141,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
this.originPlace = const Value.absent(),
this.abundance = const Value.absent(),
this.preservationFormat = const Value.absent(),
this.priceAmount = const Value.absent(),
this.priceCurrency = const Value.absent(),
this.rowid = const Value.absent(),
});
LotsCompanion.insert({
@ -4066,6 +4167,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
this.originPlace = const Value.absent(),
this.abundance = const Value.absent(),
this.preservationFormat = const Value.absent(),
this.priceAmount = const Value.absent(),
this.priceCurrency = const Value.absent(),
this.rowid = const Value.absent(),
}) : id = Value(id),
createdAt = Value(createdAt),
@ -4094,6 +4197,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
Expression<String>? originPlace,
Expression<String>? abundance,
Expression<String>? preservationFormat,
Expression<double>? priceAmount,
Expression<String>? priceCurrency,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
@ -4118,6 +4223,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
if (originPlace != null) 'origin_place': originPlace,
if (abundance != null) 'abundance': abundance,
if (preservationFormat != null) 'preservation_format': preservationFormat,
if (priceAmount != null) 'price_amount': priceAmount,
if (priceCurrency != null) 'price_currency': priceCurrency,
if (rowid != null) 'rowid': rowid,
});
}
@ -4144,6 +4251,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
Value<String?>? originPlace,
Value<Abundance?>? abundance,
Value<PreservationFormat?>? preservationFormat,
Value<double?>? priceAmount,
Value<String?>? priceCurrency,
Value<int>? rowid,
}) {
return LotsCompanion(
@ -4168,6 +4277,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
originPlace: originPlace ?? this.originPlace,
abundance: abundance ?? this.abundance,
preservationFormat: preservationFormat ?? this.preservationFormat,
priceAmount: priceAmount ?? this.priceAmount,
priceCurrency: priceCurrency ?? this.priceCurrency,
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) {
map['rowid'] = Variable<int>(rowid.value);
}
@ -4280,6 +4397,8 @@ class LotsCompanion extends UpdateCompanion<Lot> {
..write('originPlace: $originPlace, ')
..write('abundance: $abundance, ')
..write('preservationFormat: $preservationFormat, ')
..write('priceAmount: $priceAmount, ')
..write('priceCurrency: $priceCurrency, ')
..write('rowid: $rowid')
..write(')'))
.toString();
@ -11663,6 +11782,8 @@ typedef $$LotsTableCreateCompanionBuilder =
Value<String?> originPlace,
Value<Abundance?> abundance,
Value<PreservationFormat?> preservationFormat,
Value<double?> priceAmount,
Value<String?> priceCurrency,
Value<int> rowid,
});
typedef $$LotsTableUpdateCompanionBuilder =
@ -11688,6 +11809,8 @@ typedef $$LotsTableUpdateCompanionBuilder =
Value<String?> originPlace,
Value<Abundance?> abundance,
Value<PreservationFormat?> preservationFormat,
Value<double?> priceAmount,
Value<String?> priceCurrency,
Value<int> rowid,
});
@ -11812,6 +11935,16 @@ class $$LotsTableFilterComposer extends Composer<_$AppDatabase, $LotsTable> {
column: $table.preservationFormat,
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> {
@ -11926,6 +12059,16 @@ class $$LotsTableOrderingComposer extends Composer<_$AppDatabase, $LotsTable> {
column: $table.preservationFormat,
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
@ -12030,6 +12173,16 @@ class $$LotsTableAnnotationComposer
column: $table.preservationFormat,
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
@ -12082,6 +12235,8 @@ class $$LotsTableTableManager
Value<Abundance?> abundance = const Value.absent(),
Value<PreservationFormat?> preservationFormat =
const Value.absent(),
Value<double?> priceAmount = const Value.absent(),
Value<String?> priceCurrency = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => LotsCompanion(
id: id,
@ -12105,6 +12260,8 @@ class $$LotsTableTableManager
originPlace: originPlace,
abundance: abundance,
preservationFormat: preservationFormat,
priceAmount: priceAmount,
priceCurrency: priceCurrency,
rowid: rowid,
),
createCompanionCallback:
@ -12131,6 +12288,8 @@ class $$LotsTableTableManager
Value<Abundance?> abundance = const Value.absent(),
Value<PreservationFormat?> preservationFormat =
const Value.absent(),
Value<double?> priceAmount = const Value.absent(),
Value<String?> priceCurrency = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => LotsCompanion.insert(
id: id,
@ -12154,6 +12313,8 @@ class $$LotsTableTableManager
originPlace: originPlace,
abundance: abundance,
preservationFormat: preservationFormat,
priceAmount: priceAmount,
priceCurrency: priceCurrency,
rowid: rowid,
),
withReferenceMapper: (p0) => p0