feat(inventory): informal quantity scale (number + unit) + seed/plant lots
Rework quantities the way seeds are actually described — a number of an informal
unit — and let a lot hold seeds or plants (plantel).
Quantity model (commons_core):
- Quantity is now { kind, count?, label } — "3 cobs", "2 packets", "a few".
Count is optional; uncountable vibes (a few, some, pinch) carry no number.
- QuantityKind reorganised into an ascending scale: vibes → containers
(teaspoon/spoon/cup/jar/sack) → seed & fruit forms → plant units, each tagged
countable/not. Stored by name; count reuses the existing numeric column.
Lot type (schema v2):
- Lots gain a `type` (seed | plant); germination stays meaningful for seeds.
schemaVersion 2 with a from1To2 migration (adds the column), drift_schema_v2
exported, migration test covers v1→v2.
UI:
- New QuantityPicker: a horizontal scale of large seed-glyph icons + a number
stepper (shown only for countable units), plus a seed/plant SegmentedButton.
Used in quick-add and add-lot. Lot lines render "3 cobs" / "a few".
- i18n: unit singular/plural (ES/EN, Weblate-friendly), lot-type labels.
Build: slang moved to its CLI (disabled in build_runner to avoid a multi-file
collision); CI runs `dart run slang` before build_runner. 38 tests green,
Linux migrates the existing v1 DB and runs.
This commit is contained in:
parent
48e9d15772
commit
3942975dba
26 changed files with 4220 additions and 315 deletions
|
|
@ -27,12 +27,16 @@ class AppDatabase extends _$AppDatabase {
|
|||
AppDatabase(super.e);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
int get schemaVersion => 2;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
onCreate: (m) async => m.createAll(),
|
||||
// Step-by-step upgrades (from1To2, …) are generated into
|
||||
// schema_versions.dart when schemaVersion is bumped. See data-model §5.
|
||||
onUpgrade: (m, from, to) async {
|
||||
// v2: lots can hold seeds or plants/seedlings (plantel).
|
||||
if (from < 2) {
|
||||
await m.addColumn(lots, lots.type);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2610,6 +2610,16 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
|
|||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<LotType, String> type =
|
||||
GeneratedColumn<String>(
|
||||
'type',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const Constant('seed'),
|
||||
).withConverter<LotType>($LotsTable.$convertertype);
|
||||
static const VerificationMeta _harvestYearMeta = const VerificationMeta(
|
||||
'harvestYear',
|
||||
);
|
||||
|
|
@ -2695,6 +2705,7 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
|
|||
isDeleted,
|
||||
schemaRowVersion,
|
||||
varietyId,
|
||||
type,
|
||||
harvestYear,
|
||||
quantityKind,
|
||||
quantityPrecise,
|
||||
|
|
@ -2855,6 +2866,12 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
|
|||
DriftSqlType.string,
|
||||
data['${effectivePrefix}variety_id'],
|
||||
)!,
|
||||
type: $LotsTable.$convertertype.fromSql(
|
||||
attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}type'],
|
||||
)!,
|
||||
),
|
||||
harvestYear: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}harvest_year'],
|
||||
|
|
@ -2893,6 +2910,8 @@ class $LotsTable extends Lots with TableInfo<$LotsTable, Lot> {
|
|||
return $LotsTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static JsonTypeConverter2<LotType, String, String> $convertertype =
|
||||
const EnumNameConverter<LotType>(LotType.values);
|
||||
static JsonTypeConverter2<OfferStatus, String, String> $converterofferStatus =
|
||||
const EnumNameConverter<OfferStatus>(OfferStatus.values);
|
||||
}
|
||||
|
|
@ -2905,6 +2924,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
final bool isDeleted;
|
||||
final int schemaRowVersion;
|
||||
final String varietyId;
|
||||
final LotType type;
|
||||
final int? harvestYear;
|
||||
final String? quantityKind;
|
||||
final double? quantityPrecise;
|
||||
|
|
@ -2920,6 +2940,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
required this.isDeleted,
|
||||
required this.schemaRowVersion,
|
||||
required this.varietyId,
|
||||
required this.type,
|
||||
this.harvestYear,
|
||||
this.quantityKind,
|
||||
this.quantityPrecise,
|
||||
|
|
@ -2938,6 +2959,9 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
map['is_deleted'] = Variable<bool>(isDeleted);
|
||||
map['schema_row_version'] = Variable<int>(schemaRowVersion);
|
||||
map['variety_id'] = Variable<String>(varietyId);
|
||||
{
|
||||
map['type'] = Variable<String>($LotsTable.$convertertype.toSql(type));
|
||||
}
|
||||
if (!nullToAbsent || harvestYear != null) {
|
||||
map['harvest_year'] = Variable<int>(harvestYear);
|
||||
}
|
||||
|
|
@ -2973,6 +2997,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
isDeleted: Value(isDeleted),
|
||||
schemaRowVersion: Value(schemaRowVersion),
|
||||
varietyId: Value(varietyId),
|
||||
type: Value(type),
|
||||
harvestYear: harvestYear == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(harvestYear),
|
||||
|
|
@ -3008,6 +3033,9 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
isDeleted: serializer.fromJson<bool>(json['isDeleted']),
|
||||
schemaRowVersion: serializer.fromJson<int>(json['schemaRowVersion']),
|
||||
varietyId: serializer.fromJson<String>(json['varietyId']),
|
||||
type: $LotsTable.$convertertype.fromJson(
|
||||
serializer.fromJson<String>(json['type']),
|
||||
),
|
||||
harvestYear: serializer.fromJson<int?>(json['harvestYear']),
|
||||
quantityKind: serializer.fromJson<String?>(json['quantityKind']),
|
||||
quantityPrecise: serializer.fromJson<double?>(json['quantityPrecise']),
|
||||
|
|
@ -3030,6 +3058,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
'isDeleted': serializer.toJson<bool>(isDeleted),
|
||||
'schemaRowVersion': serializer.toJson<int>(schemaRowVersion),
|
||||
'varietyId': serializer.toJson<String>(varietyId),
|
||||
'type': serializer.toJson<String>($LotsTable.$convertertype.toJson(type)),
|
||||
'harvestYear': serializer.toJson<int?>(harvestYear),
|
||||
'quantityKind': serializer.toJson<String?>(quantityKind),
|
||||
'quantityPrecise': serializer.toJson<double?>(quantityPrecise),
|
||||
|
|
@ -3050,6 +3079,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
bool? isDeleted,
|
||||
int? schemaRowVersion,
|
||||
String? varietyId,
|
||||
LotType? type,
|
||||
Value<int?> harvestYear = const Value.absent(),
|
||||
Value<String?> quantityKind = const Value.absent(),
|
||||
Value<double?> quantityPrecise = const Value.absent(),
|
||||
|
|
@ -3065,6 +3095,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
isDeleted: isDeleted ?? this.isDeleted,
|
||||
schemaRowVersion: schemaRowVersion ?? this.schemaRowVersion,
|
||||
varietyId: varietyId ?? this.varietyId,
|
||||
type: type ?? this.type,
|
||||
harvestYear: harvestYear.present ? harvestYear.value : this.harvestYear,
|
||||
quantityKind: quantityKind.present ? quantityKind.value : this.quantityKind,
|
||||
quantityPrecise: quantityPrecise.present
|
||||
|
|
@ -3092,6 +3123,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
? data.schemaRowVersion.value
|
||||
: this.schemaRowVersion,
|
||||
varietyId: data.varietyId.present ? data.varietyId.value : this.varietyId,
|
||||
type: data.type.present ? data.type.value : this.type,
|
||||
harvestYear: data.harvestYear.present
|
||||
? data.harvestYear.value
|
||||
: this.harvestYear,
|
||||
|
|
@ -3126,6 +3158,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
..write('isDeleted: $isDeleted, ')
|
||||
..write('schemaRowVersion: $schemaRowVersion, ')
|
||||
..write('varietyId: $varietyId, ')
|
||||
..write('type: $type, ')
|
||||
..write('harvestYear: $harvestYear, ')
|
||||
..write('quantityKind: $quantityKind, ')
|
||||
..write('quantityPrecise: $quantityPrecise, ')
|
||||
|
|
@ -3146,6 +3179,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
isDeleted,
|
||||
schemaRowVersion,
|
||||
varietyId,
|
||||
type,
|
||||
harvestYear,
|
||||
quantityKind,
|
||||
quantityPrecise,
|
||||
|
|
@ -3165,6 +3199,7 @@ class Lot extends DataClass implements Insertable<Lot> {
|
|||
other.isDeleted == this.isDeleted &&
|
||||
other.schemaRowVersion == this.schemaRowVersion &&
|
||||
other.varietyId == this.varietyId &&
|
||||
other.type == this.type &&
|
||||
other.harvestYear == this.harvestYear &&
|
||||
other.quantityKind == this.quantityKind &&
|
||||
other.quantityPrecise == this.quantityPrecise &&
|
||||
|
|
@ -3182,6 +3217,7 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
|||
final Value<bool> isDeleted;
|
||||
final Value<int> schemaRowVersion;
|
||||
final Value<String> varietyId;
|
||||
final Value<LotType> type;
|
||||
final Value<int?> harvestYear;
|
||||
final Value<String?> quantityKind;
|
||||
final Value<double?> quantityPrecise;
|
||||
|
|
@ -3198,6 +3234,7 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
|||
this.isDeleted = const Value.absent(),
|
||||
this.schemaRowVersion = const Value.absent(),
|
||||
this.varietyId = const Value.absent(),
|
||||
this.type = const Value.absent(),
|
||||
this.harvestYear = const Value.absent(),
|
||||
this.quantityKind = const Value.absent(),
|
||||
this.quantityPrecise = const Value.absent(),
|
||||
|
|
@ -3215,6 +3252,7 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
|||
this.isDeleted = const Value.absent(),
|
||||
this.schemaRowVersion = const Value.absent(),
|
||||
required String varietyId,
|
||||
this.type = const Value.absent(),
|
||||
this.harvestYear = const Value.absent(),
|
||||
this.quantityKind = const Value.absent(),
|
||||
this.quantityPrecise = const Value.absent(),
|
||||
|
|
@ -3236,6 +3274,7 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
|||
Expression<bool>? isDeleted,
|
||||
Expression<int>? schemaRowVersion,
|
||||
Expression<String>? varietyId,
|
||||
Expression<String>? type,
|
||||
Expression<int>? harvestYear,
|
||||
Expression<String>? quantityKind,
|
||||
Expression<double>? quantityPrecise,
|
||||
|
|
@ -3253,6 +3292,7 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
|||
if (isDeleted != null) 'is_deleted': isDeleted,
|
||||
if (schemaRowVersion != null) 'schema_row_version': schemaRowVersion,
|
||||
if (varietyId != null) 'variety_id': varietyId,
|
||||
if (type != null) 'type': type,
|
||||
if (harvestYear != null) 'harvest_year': harvestYear,
|
||||
if (quantityKind != null) 'quantity_kind': quantityKind,
|
||||
if (quantityPrecise != null) 'quantity_precise': quantityPrecise,
|
||||
|
|
@ -3272,6 +3312,7 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
|||
Value<bool>? isDeleted,
|
||||
Value<int>? schemaRowVersion,
|
||||
Value<String>? varietyId,
|
||||
Value<LotType>? type,
|
||||
Value<int?>? harvestYear,
|
||||
Value<String?>? quantityKind,
|
||||
Value<double?>? quantityPrecise,
|
||||
|
|
@ -3289,6 +3330,7 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
|||
isDeleted: isDeleted ?? this.isDeleted,
|
||||
schemaRowVersion: schemaRowVersion ?? this.schemaRowVersion,
|
||||
varietyId: varietyId ?? this.varietyId,
|
||||
type: type ?? this.type,
|
||||
harvestYear: harvestYear ?? this.harvestYear,
|
||||
quantityKind: quantityKind ?? this.quantityKind,
|
||||
quantityPrecise: quantityPrecise ?? this.quantityPrecise,
|
||||
|
|
@ -3324,6 +3366,11 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
|||
if (varietyId.present) {
|
||||
map['variety_id'] = Variable<String>(varietyId.value);
|
||||
}
|
||||
if (type.present) {
|
||||
map['type'] = Variable<String>(
|
||||
$LotsTable.$convertertype.toSql(type.value),
|
||||
);
|
||||
}
|
||||
if (harvestYear.present) {
|
||||
map['harvest_year'] = Variable<int>(harvestYear.value);
|
||||
}
|
||||
|
|
@ -3363,6 +3410,7 @@ class LotsCompanion extends UpdateCompanion<Lot> {
|
|||
..write('isDeleted: $isDeleted, ')
|
||||
..write('schemaRowVersion: $schemaRowVersion, ')
|
||||
..write('varietyId: $varietyId, ')
|
||||
..write('type: $type, ')
|
||||
..write('harvestYear: $harvestYear, ')
|
||||
..write('quantityKind: $quantityKind, ')
|
||||
..write('quantityPrecise: $quantityPrecise, ')
|
||||
|
|
@ -8138,6 +8186,7 @@ typedef $$LotsTableCreateCompanionBuilder =
|
|||
Value<bool> isDeleted,
|
||||
Value<int> schemaRowVersion,
|
||||
required String varietyId,
|
||||
Value<LotType> type,
|
||||
Value<int?> harvestYear,
|
||||
Value<String?> quantityKind,
|
||||
Value<double?> quantityPrecise,
|
||||
|
|
@ -8156,6 +8205,7 @@ typedef $$LotsTableUpdateCompanionBuilder =
|
|||
Value<bool> isDeleted,
|
||||
Value<int> schemaRowVersion,
|
||||
Value<String> varietyId,
|
||||
Value<LotType> type,
|
||||
Value<int?> harvestYear,
|
||||
Value<String?> quantityKind,
|
||||
Value<double?> quantityPrecise,
|
||||
|
|
@ -8209,6 +8259,12 @@ class $$LotsTableFilterComposer extends Composer<_$AppDatabase, $LotsTable> {
|
|||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnWithTypeConverterFilters<LotType, LotType, String> get type =>
|
||||
$composableBuilder(
|
||||
column: $table.type,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<int> get harvestYear => $composableBuilder(
|
||||
column: $table.harvestYear,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
|
|
@ -8289,6 +8345,11 @@ class $$LotsTableOrderingComposer extends Composer<_$AppDatabase, $LotsTable> {
|
|||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get type => $composableBuilder(
|
||||
column: $table.type,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<int> get harvestYear => $composableBuilder(
|
||||
column: $table.harvestYear,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
|
|
@ -8359,6 +8420,9 @@ class $$LotsTableAnnotationComposer
|
|||
GeneratedColumn<String> get varietyId =>
|
||||
$composableBuilder(column: $table.varietyId, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<LotType, String> get type =>
|
||||
$composableBuilder(column: $table.type, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get harvestYear => $composableBuilder(
|
||||
column: $table.harvestYear,
|
||||
builder: (column) => column,
|
||||
|
|
@ -8431,6 +8495,7 @@ class $$LotsTableTableManager
|
|||
Value<bool> isDeleted = const Value.absent(),
|
||||
Value<int> schemaRowVersion = const Value.absent(),
|
||||
Value<String> varietyId = const Value.absent(),
|
||||
Value<LotType> type = const Value.absent(),
|
||||
Value<int?> harvestYear = const Value.absent(),
|
||||
Value<String?> quantityKind = const Value.absent(),
|
||||
Value<double?> quantityPrecise = const Value.absent(),
|
||||
|
|
@ -8447,6 +8512,7 @@ class $$LotsTableTableManager
|
|||
isDeleted: isDeleted,
|
||||
schemaRowVersion: schemaRowVersion,
|
||||
varietyId: varietyId,
|
||||
type: type,
|
||||
harvestYear: harvestYear,
|
||||
quantityKind: quantityKind,
|
||||
quantityPrecise: quantityPrecise,
|
||||
|
|
@ -8465,6 +8531,7 @@ class $$LotsTableTableManager
|
|||
Value<bool> isDeleted = const Value.absent(),
|
||||
Value<int> schemaRowVersion = const Value.absent(),
|
||||
required String varietyId,
|
||||
Value<LotType> type = const Value.absent(),
|
||||
Value<int?> harvestYear = const Value.absent(),
|
||||
Value<String?> quantityKind = const Value.absent(),
|
||||
Value<double?> quantityPrecise = const Value.absent(),
|
||||
|
|
@ -8481,6 +8548,7 @@ class $$LotsTableTableManager
|
|||
isDeleted: isDeleted,
|
||||
schemaRowVersion: schemaRowVersion,
|
||||
varietyId: varietyId,
|
||||
type: type,
|
||||
harvestYear: harvestYear,
|
||||
quantityKind: quantityKind,
|
||||
quantityPrecise: quantityPrecise,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
// may only be appended, never renumbered or reused; when sync arrives, readers
|
||||
// must tolerate unknown values (map to a safe default). No sync yet in Block 1.
|
||||
|
||||
/// Whether a lot holds seeds or living plants/seedlings (plantel). Germination
|
||||
/// tests apply only to seed lots.
|
||||
enum LotType { seed, plant }
|
||||
|
||||
/// Per-lot visibility (data-model §2.3). Used by the future sharing layer.
|
||||
enum OfferStatus { private, shared, exchange, sell }
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ class SpeciesCommonNames extends Table with SyncColumns {
|
|||
/// Quantity (commons_core value type) is flattened into columns here.
|
||||
class Lots extends Table with SyncColumns {
|
||||
TextColumn get varietyId => text()();
|
||||
TextColumn get type =>
|
||||
textEnum<LotType>().withDefault(const Constant('seed'))();
|
||||
IntColumn get harvestYear => integer().nullable()();
|
||||
TextColumn get quantityKind => text().nullable()(); // QuantityKind.name
|
||||
RealColumn get quantityPrecise => real().nullable()();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue