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:
vjrj 2026-07-08 10:51:02 +02:00
parent 48e9d15772
commit 3942975dba
26 changed files with 4220 additions and 315 deletions

View file

@ -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,