feat(app): ask 'how did it do?' when a harvest is noted (schema v13)
The note growers most wish they had two seasons later — did it do well, was it worth keeping — never had a home. New append-only GardenOutcomes table (lotId, season year, three-face rating, free note); the question appears ONCE, inline, right after recording a harvest in the batch story, and is skippable without a trace. The answer shows as one more story line. Rides backups/sync like every mutable table (JSON codec, LWW import, HLC clock absorb). Migration v12→v13 guarded + verified from every historical version.
This commit is contained in:
parent
92fd84590b
commit
bb5b3d4a43
20 changed files with 6030 additions and 15 deletions
2403
apps/app_seeds/drift_schemas/drift_schema_v13.json
Normal file
2403
apps/app_seeds/drift_schemas/drift_schema_v13.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -153,6 +153,23 @@ class InventoryJsonCodec {
|
|||
'notes': c.notes,
|
||||
},
|
||||
],
|
||||
'gardenOutcomes': [
|
||||
for (final o in snapshot.gardenOutcomes)
|
||||
{
|
||||
..._syncMeta(
|
||||
id: o.id,
|
||||
createdAt: o.createdAt,
|
||||
updatedAt: o.updatedAt,
|
||||
lastAuthor: o.lastAuthor,
|
||||
schemaRowVersion: o.schemaRowVersion,
|
||||
isDeleted: o.isDeleted,
|
||||
),
|
||||
'lotId': o.lotId,
|
||||
'year': o.year,
|
||||
'rating': o.rating?.name,
|
||||
'notes': o.notes,
|
||||
},
|
||||
],
|
||||
'movements': [
|
||||
for (final m in snapshot.movements)
|
||||
{
|
||||
|
|
@ -417,6 +434,20 @@ class InventoryJsonCodec {
|
|||
notes: m['notes'] as String?,
|
||||
);
|
||||
}),
|
||||
gardenOutcomes: _rows(root, 'gardenOutcomes', (m) {
|
||||
return GardenOutcome(
|
||||
id: _string(m, 'id'),
|
||||
createdAt: _int(m, 'createdAt'),
|
||||
updatedAt: _string(m, 'updatedAt'),
|
||||
lastAuthor: _string(m, 'lastAuthor'),
|
||||
isDeleted: _bool(m, 'isDeleted'),
|
||||
schemaRowVersion: _int(m, 'schemaRowVersion', fallback: 1),
|
||||
lotId: _string(m, 'lotId'),
|
||||
year: m['year'] as int?,
|
||||
rating: _enumOrNull(GardenOutcomeRating.values, m['rating']),
|
||||
notes: m['notes'] as String?,
|
||||
);
|
||||
}),
|
||||
movements: _rows(root, 'movements', (m) {
|
||||
final type = _enumOrNull(MovementType.values, m['type']);
|
||||
if (type == null) return null; // no safe default → drop row
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class InventorySnapshot {
|
|||
this.externalLinks = const [],
|
||||
this.germinationTests = const [],
|
||||
this.conditionChecks = const [],
|
||||
this.gardenOutcomes = const [],
|
||||
this.movements = const [],
|
||||
this.parties = const [],
|
||||
this.attachments = const [],
|
||||
|
|
@ -41,6 +42,7 @@ class InventorySnapshot {
|
|||
final List<ExternalLink> externalLinks;
|
||||
final List<GerminationTest> germinationTests;
|
||||
final List<ConditionCheck> conditionChecks;
|
||||
final List<GardenOutcome> gardenOutcomes;
|
||||
final List<Movement> movements;
|
||||
final List<Party> parties;
|
||||
final List<Attachment> attachments;
|
||||
|
|
|
|||
|
|
@ -327,7 +327,13 @@ class ConditionEntry extends Equatable {
|
|||
}
|
||||
|
||||
/// What kind of event a [LotHistoryEntry] narrates.
|
||||
enum LotHistoryKind { created, movement, germinationTest, conditionCheck }
|
||||
enum LotHistoryKind {
|
||||
created,
|
||||
movement,
|
||||
germinationTest,
|
||||
conditionCheck,
|
||||
gardenOutcome,
|
||||
}
|
||||
|
||||
/// One line of a lot's composite history — the batch's own story, merged from
|
||||
/// what is already recorded (the lot's creation with its origin, movements,
|
||||
|
|
@ -346,6 +352,8 @@ class LotHistoryEntry extends Equatable {
|
|||
this.germinationRate,
|
||||
this.containerCount,
|
||||
this.desiccantState,
|
||||
this.rating,
|
||||
this.year,
|
||||
this.hasParentLink = false,
|
||||
});
|
||||
|
||||
|
|
@ -374,6 +382,10 @@ class LotHistoryEntry extends Equatable {
|
|||
final int? containerCount;
|
||||
final DesiccantState? desiccantState;
|
||||
|
||||
/// Set for [LotHistoryKind.gardenOutcome] entries: how that season went.
|
||||
final GardenOutcomeRating? rating;
|
||||
final int? year;
|
||||
|
||||
/// True when the movement links back to a source batch (provenance DAG).
|
||||
final bool hasParentLink;
|
||||
|
||||
|
|
@ -390,6 +402,8 @@ class LotHistoryEntry extends Equatable {
|
|||
germinationRate,
|
||||
containerCount,
|
||||
desiccantState,
|
||||
rating,
|
||||
year,
|
||||
hasParentLink,
|
||||
];
|
||||
}
|
||||
|
|
@ -1973,6 +1987,33 @@ class VarietyRepository {
|
|||
return id;
|
||||
}
|
||||
|
||||
/// Records how a season went for a lot — the optional, skippable answer to
|
||||
/// "how did it do?" asked when a harvest is noted. Returns the new row id.
|
||||
Future<String> addGardenOutcome({
|
||||
required String lotId,
|
||||
int? year,
|
||||
GardenOutcomeRating? rating,
|
||||
String? notes,
|
||||
}) async {
|
||||
final (created, updated) = await _stamp();
|
||||
final id = idGen.newId();
|
||||
await _db
|
||||
.into(_db.gardenOutcomes)
|
||||
.insert(
|
||||
GardenOutcomesCompanion.insert(
|
||||
id: id,
|
||||
lotId: lotId,
|
||||
createdAt: created,
|
||||
updatedAt: updated,
|
||||
lastAuthor: nodeId,
|
||||
year: Value(year),
|
||||
rating: Value(rating),
|
||||
notes: Value(_hasText(notes) ? notes!.trim() : null),
|
||||
),
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
/// The lot's composite history, most-recent first: every movement,
|
||||
/// germination test and condition check already recorded, closed by the
|
||||
/// lot's own creation entry (carrying its origin, so "received from…" shows
|
||||
|
|
@ -1993,6 +2034,9 @@ class VarietyRepository {
|
|||
final checks = await (_db.select(
|
||||
_db.conditionChecks,
|
||||
)..where((c) => c.lotId.equals(lotId) & c.isDeleted.equals(false))).get();
|
||||
final outcomes = await (_db.select(
|
||||
_db.gardenOutcomes,
|
||||
)..where((o) => o.lotId.equals(lotId) & o.isDeleted.equals(false))).get();
|
||||
|
||||
// Resolve counterparty display names in one go (usually zero or one).
|
||||
final partyIds = movements
|
||||
|
|
@ -2047,6 +2091,14 @@ class VarietyRepository {
|
|||
containerCount: c.containerCount,
|
||||
desiccantState: c.desiccantState,
|
||||
),
|
||||
for (final o in outcomes)
|
||||
LotHistoryEntry(
|
||||
kind: LotHistoryKind.gardenOutcome,
|
||||
when: o.createdAt,
|
||||
notes: o.notes,
|
||||
rating: o.rating,
|
||||
year: o.year,
|
||||
),
|
||||
]..sort((a, b) => (b.when ?? 0).compareTo(a.when ?? 0));
|
||||
|
||||
return [
|
||||
|
|
@ -2451,6 +2503,9 @@ class VarietyRepository {
|
|||
conditionChecks: await (_db.select(
|
||||
_db.conditionChecks,
|
||||
)..where((c) => c.isDeleted.equals(false))).get(),
|
||||
gardenOutcomes: await (_db.select(
|
||||
_db.gardenOutcomes,
|
||||
)..where((o) => o.isDeleted.equals(false))).get(),
|
||||
movements: await _db.select(_db.movements).get(),
|
||||
parties: await (_db.select(
|
||||
_db.parties,
|
||||
|
|
@ -2484,6 +2539,7 @@ class VarietyRepository {
|
|||
externalLinks: await _db.select(_db.externalLinks).get(),
|
||||
germinationTests: await _db.select(_db.germinationTests).get(),
|
||||
conditionChecks: await _db.select(_db.conditionChecks).get(),
|
||||
gardenOutcomes: await _db.select(_db.gardenOutcomes).get(),
|
||||
movements: await _db.select(_db.movements).get(),
|
||||
parties: await _db.select(_db.parties).get(),
|
||||
plantares: await _db.select(_db.plantares).get(),
|
||||
|
|
@ -2564,6 +2620,14 @@ class VarietyRepository {
|
|||
updatedAtOf: (r) => r.updatedAt,
|
||||
reconciler: reconciler,
|
||||
);
|
||||
summary += await _importMutableRows(
|
||||
table: _db.gardenOutcomes,
|
||||
idColumn: _db.gardenOutcomes.id,
|
||||
rows: snapshot.gardenOutcomes,
|
||||
idOf: (r) => r.id,
|
||||
updatedAtOf: (r) => r.updatedAt,
|
||||
reconciler: reconciler,
|
||||
);
|
||||
summary += await _importMutableRows(
|
||||
table: _db.parties,
|
||||
idColumn: _db.parties.id,
|
||||
|
|
@ -2606,6 +2670,7 @@ class VarietyRepository {
|
|||
for (final e in snapshot.externalLinks) e.updatedAt,
|
||||
for (final g in snapshot.germinationTests) g.updatedAt,
|
||||
for (final c in snapshot.conditionChecks) c.updatedAt,
|
||||
for (final o in snapshot.gardenOutcomes) o.updatedAt,
|
||||
for (final p in snapshot.parties) p.updatedAt,
|
||||
for (final a in snapshot.attachments) a.updatedAt,
|
||||
for (final pl in snapshot.plantares) pl.updatedAt,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ part 'database.g.dart';
|
|||
Lots,
|
||||
GerminationTests,
|
||||
ConditionChecks,
|
||||
GardenOutcomes,
|
||||
Movements,
|
||||
Parties,
|
||||
Attachments,
|
||||
|
|
@ -31,7 +32,7 @@ class AppDatabase extends _$AppDatabase {
|
|||
|
||||
/// Current schema version; also stamped into interchange exports so an
|
||||
/// importer knows which app generation wrote the file (data-model §7).
|
||||
static const int currentSchemaVersion = 12;
|
||||
static const int currentSchemaVersion = 13;
|
||||
|
||||
@override
|
||||
int get schemaVersion => currentSchemaVersion;
|
||||
|
|
@ -187,6 +188,14 @@ class AppDatabase extends _$AppDatabase {
|
|||
await addIfMissing('return_kind', plantares.returnKind);
|
||||
await addIfMissing('work_hours', plantares.workHours);
|
||||
}
|
||||
// v13: GardenOutcomes — the per-season "how did it do in my garden"
|
||||
// answer (rating + note) captured when a harvest is recorded. Guarded
|
||||
// (see the v7 note above).
|
||||
if (from < 13) {
|
||||
if (!await _hasTable('garden_outcomes')) {
|
||||
await m.createTable(gardenOutcomes);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -5765,6 +5765,624 @@ class ConditionChecksCompanion extends UpdateCompanion<ConditionCheck> {
|
|||
}
|
||||
}
|
||||
|
||||
class $GardenOutcomesTable extends GardenOutcomes
|
||||
with TableInfo<$GardenOutcomesTable, GardenOutcome> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$GardenOutcomesTable(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||
'id',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const VerificationMeta _createdAtMeta = const VerificationMeta(
|
||||
'createdAt',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<int> createdAt = GeneratedColumn<int>(
|
||||
'created_at',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const VerificationMeta _updatedAtMeta = const VerificationMeta(
|
||||
'updatedAt',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<String> updatedAt = GeneratedColumn<String>(
|
||||
'updated_at',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const VerificationMeta _lastAuthorMeta = const VerificationMeta(
|
||||
'lastAuthor',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<String> lastAuthor = GeneratedColumn<String>(
|
||||
'last_author',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const VerificationMeta _isDeletedMeta = const VerificationMeta(
|
||||
'isDeleted',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<bool> isDeleted = GeneratedColumn<bool>(
|
||||
'is_deleted',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("is_deleted" IN (0, 1))',
|
||||
),
|
||||
defaultValue: const Constant(false),
|
||||
);
|
||||
static const VerificationMeta _schemaRowVersionMeta = const VerificationMeta(
|
||||
'schemaRowVersion',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<int> schemaRowVersion = GeneratedColumn<int>(
|
||||
'schema_row_version',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const Constant(1),
|
||||
);
|
||||
static const VerificationMeta _lotIdMeta = const VerificationMeta('lotId');
|
||||
@override
|
||||
late final GeneratedColumn<String> lotId = GeneratedColumn<String>(
|
||||
'lot_id',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
);
|
||||
static const VerificationMeta _yearMeta = const VerificationMeta('year');
|
||||
@override
|
||||
late final GeneratedColumn<int> year = GeneratedColumn<int>(
|
||||
'year',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<GardenOutcomeRating?, String>
|
||||
rating = GeneratedColumn<String>(
|
||||
'rating',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
).withConverter<GardenOutcomeRating?>($GardenOutcomesTable.$converterratingn);
|
||||
static const VerificationMeta _notesMeta = const VerificationMeta('notes');
|
||||
@override
|
||||
late final GeneratedColumn<String> notes = GeneratedColumn<String>(
|
||||
'notes',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [
|
||||
id,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
lastAuthor,
|
||||
isDeleted,
|
||||
schemaRowVersion,
|
||||
lotId,
|
||||
year,
|
||||
rating,
|
||||
notes,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'garden_outcomes';
|
||||
@override
|
||||
VerificationContext validateIntegrity(
|
||||
Insertable<GardenOutcome> instance, {
|
||||
bool isInserting = false,
|
||||
}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_idMeta);
|
||||
}
|
||||
if (data.containsKey('created_at')) {
|
||||
context.handle(
|
||||
_createdAtMeta,
|
||||
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_createdAtMeta);
|
||||
}
|
||||
if (data.containsKey('updated_at')) {
|
||||
context.handle(
|
||||
_updatedAtMeta,
|
||||
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_updatedAtMeta);
|
||||
}
|
||||
if (data.containsKey('last_author')) {
|
||||
context.handle(
|
||||
_lastAuthorMeta,
|
||||
lastAuthor.isAcceptableOrUnknown(data['last_author']!, _lastAuthorMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_lastAuthorMeta);
|
||||
}
|
||||
if (data.containsKey('is_deleted')) {
|
||||
context.handle(
|
||||
_isDeletedMeta,
|
||||
isDeleted.isAcceptableOrUnknown(data['is_deleted']!, _isDeletedMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('schema_row_version')) {
|
||||
context.handle(
|
||||
_schemaRowVersionMeta,
|
||||
schemaRowVersion.isAcceptableOrUnknown(
|
||||
data['schema_row_version']!,
|
||||
_schemaRowVersionMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('lot_id')) {
|
||||
context.handle(
|
||||
_lotIdMeta,
|
||||
lotId.isAcceptableOrUnknown(data['lot_id']!, _lotIdMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_lotIdMeta);
|
||||
}
|
||||
if (data.containsKey('year')) {
|
||||
context.handle(
|
||||
_yearMeta,
|
||||
year.isAcceptableOrUnknown(data['year']!, _yearMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('notes')) {
|
||||
context.handle(
|
||||
_notesMeta,
|
||||
notes.isAcceptableOrUnknown(data['notes']!, _notesMeta),
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
GardenOutcome map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return GardenOutcome(
|
||||
id: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}id'],
|
||||
)!,
|
||||
createdAt: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}created_at'],
|
||||
)!,
|
||||
updatedAt: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}updated_at'],
|
||||
)!,
|
||||
lastAuthor: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}last_author'],
|
||||
)!,
|
||||
isDeleted: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.bool,
|
||||
data['${effectivePrefix}is_deleted'],
|
||||
)!,
|
||||
schemaRowVersion: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}schema_row_version'],
|
||||
)!,
|
||||
lotId: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}lot_id'],
|
||||
)!,
|
||||
year: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}year'],
|
||||
),
|
||||
rating: $GardenOutcomesTable.$converterratingn.fromSql(
|
||||
attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}rating'],
|
||||
),
|
||||
),
|
||||
notes: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}notes'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$GardenOutcomesTable createAlias(String alias) {
|
||||
return $GardenOutcomesTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static JsonTypeConverter2<GardenOutcomeRating, String, String>
|
||||
$converterrating = const EnumNameConverter<GardenOutcomeRating>(
|
||||
GardenOutcomeRating.values,
|
||||
);
|
||||
static JsonTypeConverter2<GardenOutcomeRating?, String?, String?>
|
||||
$converterratingn = JsonTypeConverter2.asNullable($converterrating);
|
||||
}
|
||||
|
||||
class GardenOutcome extends DataClass implements Insertable<GardenOutcome> {
|
||||
final String id;
|
||||
final int createdAt;
|
||||
final String updatedAt;
|
||||
final String lastAuthor;
|
||||
final bool isDeleted;
|
||||
final int schemaRowVersion;
|
||||
final String lotId;
|
||||
final int? year;
|
||||
final GardenOutcomeRating? rating;
|
||||
final String? notes;
|
||||
const GardenOutcome({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.lastAuthor,
|
||||
required this.isDeleted,
|
||||
required this.schemaRowVersion,
|
||||
required this.lotId,
|
||||
this.year,
|
||||
this.rating,
|
||||
this.notes,
|
||||
});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<String>(id);
|
||||
map['created_at'] = Variable<int>(createdAt);
|
||||
map['updated_at'] = Variable<String>(updatedAt);
|
||||
map['last_author'] = Variable<String>(lastAuthor);
|
||||
map['is_deleted'] = Variable<bool>(isDeleted);
|
||||
map['schema_row_version'] = Variable<int>(schemaRowVersion);
|
||||
map['lot_id'] = Variable<String>(lotId);
|
||||
if (!nullToAbsent || year != null) {
|
||||
map['year'] = Variable<int>(year);
|
||||
}
|
||||
if (!nullToAbsent || rating != null) {
|
||||
map['rating'] = Variable<String>(
|
||||
$GardenOutcomesTable.$converterratingn.toSql(rating),
|
||||
);
|
||||
}
|
||||
if (!nullToAbsent || notes != null) {
|
||||
map['notes'] = Variable<String>(notes);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
GardenOutcomesCompanion toCompanion(bool nullToAbsent) {
|
||||
return GardenOutcomesCompanion(
|
||||
id: Value(id),
|
||||
createdAt: Value(createdAt),
|
||||
updatedAt: Value(updatedAt),
|
||||
lastAuthor: Value(lastAuthor),
|
||||
isDeleted: Value(isDeleted),
|
||||
schemaRowVersion: Value(schemaRowVersion),
|
||||
lotId: Value(lotId),
|
||||
year: year == null && nullToAbsent ? const Value.absent() : Value(year),
|
||||
rating: rating == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(rating),
|
||||
notes: notes == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(notes),
|
||||
);
|
||||
}
|
||||
|
||||
factory GardenOutcome.fromJson(
|
||||
Map<String, dynamic> json, {
|
||||
ValueSerializer? serializer,
|
||||
}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return GardenOutcome(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
createdAt: serializer.fromJson<int>(json['createdAt']),
|
||||
updatedAt: serializer.fromJson<String>(json['updatedAt']),
|
||||
lastAuthor: serializer.fromJson<String>(json['lastAuthor']),
|
||||
isDeleted: serializer.fromJson<bool>(json['isDeleted']),
|
||||
schemaRowVersion: serializer.fromJson<int>(json['schemaRowVersion']),
|
||||
lotId: serializer.fromJson<String>(json['lotId']),
|
||||
year: serializer.fromJson<int?>(json['year']),
|
||||
rating: $GardenOutcomesTable.$converterratingn.fromJson(
|
||||
serializer.fromJson<String?>(json['rating']),
|
||||
),
|
||||
notes: serializer.fromJson<String?>(json['notes']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<String>(id),
|
||||
'createdAt': serializer.toJson<int>(createdAt),
|
||||
'updatedAt': serializer.toJson<String>(updatedAt),
|
||||
'lastAuthor': serializer.toJson<String>(lastAuthor),
|
||||
'isDeleted': serializer.toJson<bool>(isDeleted),
|
||||
'schemaRowVersion': serializer.toJson<int>(schemaRowVersion),
|
||||
'lotId': serializer.toJson<String>(lotId),
|
||||
'year': serializer.toJson<int?>(year),
|
||||
'rating': serializer.toJson<String?>(
|
||||
$GardenOutcomesTable.$converterratingn.toJson(rating),
|
||||
),
|
||||
'notes': serializer.toJson<String?>(notes),
|
||||
};
|
||||
}
|
||||
|
||||
GardenOutcome copyWith({
|
||||
String? id,
|
||||
int? createdAt,
|
||||
String? updatedAt,
|
||||
String? lastAuthor,
|
||||
bool? isDeleted,
|
||||
int? schemaRowVersion,
|
||||
String? lotId,
|
||||
Value<int?> year = const Value.absent(),
|
||||
Value<GardenOutcomeRating?> rating = const Value.absent(),
|
||||
Value<String?> notes = const Value.absent(),
|
||||
}) => GardenOutcome(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
lastAuthor: lastAuthor ?? this.lastAuthor,
|
||||
isDeleted: isDeleted ?? this.isDeleted,
|
||||
schemaRowVersion: schemaRowVersion ?? this.schemaRowVersion,
|
||||
lotId: lotId ?? this.lotId,
|
||||
year: year.present ? year.value : this.year,
|
||||
rating: rating.present ? rating.value : this.rating,
|
||||
notes: notes.present ? notes.value : this.notes,
|
||||
);
|
||||
GardenOutcome copyWithCompanion(GardenOutcomesCompanion data) {
|
||||
return GardenOutcome(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||
lastAuthor: data.lastAuthor.present
|
||||
? data.lastAuthor.value
|
||||
: this.lastAuthor,
|
||||
isDeleted: data.isDeleted.present ? data.isDeleted.value : this.isDeleted,
|
||||
schemaRowVersion: data.schemaRowVersion.present
|
||||
? data.schemaRowVersion.value
|
||||
: this.schemaRowVersion,
|
||||
lotId: data.lotId.present ? data.lotId.value : this.lotId,
|
||||
year: data.year.present ? data.year.value : this.year,
|
||||
rating: data.rating.present ? data.rating.value : this.rating,
|
||||
notes: data.notes.present ? data.notes.value : this.notes,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('GardenOutcome(')
|
||||
..write('id: $id, ')
|
||||
..write('createdAt: $createdAt, ')
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('lastAuthor: $lastAuthor, ')
|
||||
..write('isDeleted: $isDeleted, ')
|
||||
..write('schemaRowVersion: $schemaRowVersion, ')
|
||||
..write('lotId: $lotId, ')
|
||||
..write('year: $year, ')
|
||||
..write('rating: $rating, ')
|
||||
..write('notes: $notes')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
id,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
lastAuthor,
|
||||
isDeleted,
|
||||
schemaRowVersion,
|
||||
lotId,
|
||||
year,
|
||||
rating,
|
||||
notes,
|
||||
);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is GardenOutcome &&
|
||||
other.id == this.id &&
|
||||
other.createdAt == this.createdAt &&
|
||||
other.updatedAt == this.updatedAt &&
|
||||
other.lastAuthor == this.lastAuthor &&
|
||||
other.isDeleted == this.isDeleted &&
|
||||
other.schemaRowVersion == this.schemaRowVersion &&
|
||||
other.lotId == this.lotId &&
|
||||
other.year == this.year &&
|
||||
other.rating == this.rating &&
|
||||
other.notes == this.notes);
|
||||
}
|
||||
|
||||
class GardenOutcomesCompanion extends UpdateCompanion<GardenOutcome> {
|
||||
final Value<String> id;
|
||||
final Value<int> createdAt;
|
||||
final Value<String> updatedAt;
|
||||
final Value<String> lastAuthor;
|
||||
final Value<bool> isDeleted;
|
||||
final Value<int> schemaRowVersion;
|
||||
final Value<String> lotId;
|
||||
final Value<int?> year;
|
||||
final Value<GardenOutcomeRating?> rating;
|
||||
final Value<String?> notes;
|
||||
final Value<int> rowid;
|
||||
const GardenOutcomesCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.createdAt = const Value.absent(),
|
||||
this.updatedAt = const Value.absent(),
|
||||
this.lastAuthor = const Value.absent(),
|
||||
this.isDeleted = const Value.absent(),
|
||||
this.schemaRowVersion = const Value.absent(),
|
||||
this.lotId = const Value.absent(),
|
||||
this.year = const Value.absent(),
|
||||
this.rating = const Value.absent(),
|
||||
this.notes = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
GardenOutcomesCompanion.insert({
|
||||
required String id,
|
||||
required int createdAt,
|
||||
required String updatedAt,
|
||||
required String lastAuthor,
|
||||
this.isDeleted = const Value.absent(),
|
||||
this.schemaRowVersion = const Value.absent(),
|
||||
required String lotId,
|
||||
this.year = const Value.absent(),
|
||||
this.rating = const Value.absent(),
|
||||
this.notes = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
createdAt = Value(createdAt),
|
||||
updatedAt = Value(updatedAt),
|
||||
lastAuthor = Value(lastAuthor),
|
||||
lotId = Value(lotId);
|
||||
static Insertable<GardenOutcome> custom({
|
||||
Expression<String>? id,
|
||||
Expression<int>? createdAt,
|
||||
Expression<String>? updatedAt,
|
||||
Expression<String>? lastAuthor,
|
||||
Expression<bool>? isDeleted,
|
||||
Expression<int>? schemaRowVersion,
|
||||
Expression<String>? lotId,
|
||||
Expression<int>? year,
|
||||
Expression<String>? rating,
|
||||
Expression<String>? notes,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (createdAt != null) 'created_at': createdAt,
|
||||
if (updatedAt != null) 'updated_at': updatedAt,
|
||||
if (lastAuthor != null) 'last_author': lastAuthor,
|
||||
if (isDeleted != null) 'is_deleted': isDeleted,
|
||||
if (schemaRowVersion != null) 'schema_row_version': schemaRowVersion,
|
||||
if (lotId != null) 'lot_id': lotId,
|
||||
if (year != null) 'year': year,
|
||||
if (rating != null) 'rating': rating,
|
||||
if (notes != null) 'notes': notes,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
GardenOutcomesCompanion copyWith({
|
||||
Value<String>? id,
|
||||
Value<int>? createdAt,
|
||||
Value<String>? updatedAt,
|
||||
Value<String>? lastAuthor,
|
||||
Value<bool>? isDeleted,
|
||||
Value<int>? schemaRowVersion,
|
||||
Value<String>? lotId,
|
||||
Value<int?>? year,
|
||||
Value<GardenOutcomeRating?>? rating,
|
||||
Value<String?>? notes,
|
||||
Value<int>? rowid,
|
||||
}) {
|
||||
return GardenOutcomesCompanion(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
lastAuthor: lastAuthor ?? this.lastAuthor,
|
||||
isDeleted: isDeleted ?? this.isDeleted,
|
||||
schemaRowVersion: schemaRowVersion ?? this.schemaRowVersion,
|
||||
lotId: lotId ?? this.lotId,
|
||||
year: year ?? this.year,
|
||||
rating: rating ?? this.rating,
|
||||
notes: notes ?? this.notes,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<String>(id.value);
|
||||
}
|
||||
if (createdAt.present) {
|
||||
map['created_at'] = Variable<int>(createdAt.value);
|
||||
}
|
||||
if (updatedAt.present) {
|
||||
map['updated_at'] = Variable<String>(updatedAt.value);
|
||||
}
|
||||
if (lastAuthor.present) {
|
||||
map['last_author'] = Variable<String>(lastAuthor.value);
|
||||
}
|
||||
if (isDeleted.present) {
|
||||
map['is_deleted'] = Variable<bool>(isDeleted.value);
|
||||
}
|
||||
if (schemaRowVersion.present) {
|
||||
map['schema_row_version'] = Variable<int>(schemaRowVersion.value);
|
||||
}
|
||||
if (lotId.present) {
|
||||
map['lot_id'] = Variable<String>(lotId.value);
|
||||
}
|
||||
if (year.present) {
|
||||
map['year'] = Variable<int>(year.value);
|
||||
}
|
||||
if (rating.present) {
|
||||
map['rating'] = Variable<String>(
|
||||
$GardenOutcomesTable.$converterratingn.toSql(rating.value),
|
||||
);
|
||||
}
|
||||
if (notes.present) {
|
||||
map['notes'] = Variable<String>(notes.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('GardenOutcomesCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('createdAt: $createdAt, ')
|
||||
..write('updatedAt: $updatedAt, ')
|
||||
..write('lastAuthor: $lastAuthor, ')
|
||||
..write('isDeleted: $isDeleted, ')
|
||||
..write('schemaRowVersion: $schemaRowVersion, ')
|
||||
..write('lotId: $lotId, ')
|
||||
..write('year: $year, ')
|
||||
..write('rating: $rating, ')
|
||||
..write('notes: $notes, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class $MovementsTable extends Movements
|
||||
with TableInfo<$MovementsTable, Movement> {
|
||||
@override
|
||||
|
|
@ -10810,6 +11428,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||
late final $ConditionChecksTable conditionChecks = $ConditionChecksTable(
|
||||
this,
|
||||
);
|
||||
late final $GardenOutcomesTable gardenOutcomes = $GardenOutcomesTable(this);
|
||||
late final $MovementsTable movements = $MovementsTable(this);
|
||||
late final $PartiesTable parties = $PartiesTable(this);
|
||||
late final $AttachmentsTable attachments = $AttachmentsTable(this);
|
||||
|
|
@ -10828,6 +11447,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||
lots,
|
||||
germinationTests,
|
||||
conditionChecks,
|
||||
gardenOutcomes,
|
||||
movements,
|
||||
parties,
|
||||
attachments,
|
||||
|
|
@ -13499,6 +14119,312 @@ typedef $$ConditionChecksTableProcessedTableManager =
|
|||
ConditionCheck,
|
||||
PrefetchHooks Function()
|
||||
>;
|
||||
typedef $$GardenOutcomesTableCreateCompanionBuilder =
|
||||
GardenOutcomesCompanion Function({
|
||||
required String id,
|
||||
required int createdAt,
|
||||
required String updatedAt,
|
||||
required String lastAuthor,
|
||||
Value<bool> isDeleted,
|
||||
Value<int> schemaRowVersion,
|
||||
required String lotId,
|
||||
Value<int?> year,
|
||||
Value<GardenOutcomeRating?> rating,
|
||||
Value<String?> notes,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $$GardenOutcomesTableUpdateCompanionBuilder =
|
||||
GardenOutcomesCompanion Function({
|
||||
Value<String> id,
|
||||
Value<int> createdAt,
|
||||
Value<String> updatedAt,
|
||||
Value<String> lastAuthor,
|
||||
Value<bool> isDeleted,
|
||||
Value<int> schemaRowVersion,
|
||||
Value<String> lotId,
|
||||
Value<int?> year,
|
||||
Value<GardenOutcomeRating?> rating,
|
||||
Value<String?> notes,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
class $$GardenOutcomesTableFilterComposer
|
||||
extends Composer<_$AppDatabase, $GardenOutcomesTable> {
|
||||
$$GardenOutcomesTableFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<String> get id => $composableBuilder(
|
||||
column: $table.id,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<int> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get updatedAt => $composableBuilder(
|
||||
column: $table.updatedAt,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get lastAuthor => $composableBuilder(
|
||||
column: $table.lastAuthor,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<bool> get isDeleted => $composableBuilder(
|
||||
column: $table.isDeleted,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<int> get schemaRowVersion => $composableBuilder(
|
||||
column: $table.schemaRowVersion,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get lotId => $composableBuilder(
|
||||
column: $table.lotId,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<int> get year => $composableBuilder(
|
||||
column: $table.year,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnWithTypeConverterFilters<
|
||||
GardenOutcomeRating?,
|
||||
GardenOutcomeRating,
|
||||
String
|
||||
>
|
||||
get rating => $composableBuilder(
|
||||
column: $table.rating,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get notes => $composableBuilder(
|
||||
column: $table.notes,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$GardenOutcomesTableOrderingComposer
|
||||
extends Composer<_$AppDatabase, $GardenOutcomesTable> {
|
||||
$$GardenOutcomesTableOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<String> get id => $composableBuilder(
|
||||
column: $table.id,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<int> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get updatedAt => $composableBuilder(
|
||||
column: $table.updatedAt,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get lastAuthor => $composableBuilder(
|
||||
column: $table.lastAuthor,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<bool> get isDeleted => $composableBuilder(
|
||||
column: $table.isDeleted,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<int> get schemaRowVersion => $composableBuilder(
|
||||
column: $table.schemaRowVersion,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get lotId => $composableBuilder(
|
||||
column: $table.lotId,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<int> get year => $composableBuilder(
|
||||
column: $table.year,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get rating => $composableBuilder(
|
||||
column: $table.rating,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get notes => $composableBuilder(
|
||||
column: $table.notes,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$GardenOutcomesTableAnnotationComposer
|
||||
extends Composer<_$AppDatabase, $GardenOutcomesTable> {
|
||||
$$GardenOutcomesTableAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<String> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get createdAt =>
|
||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get updatedAt =>
|
||||
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get lastAuthor => $composableBuilder(
|
||||
column: $table.lastAuthor,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
GeneratedColumn<bool> get isDeleted =>
|
||||
$composableBuilder(column: $table.isDeleted, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get schemaRowVersion => $composableBuilder(
|
||||
column: $table.schemaRowVersion,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
GeneratedColumn<String> get lotId =>
|
||||
$composableBuilder(column: $table.lotId, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get year =>
|
||||
$composableBuilder(column: $table.year, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<GardenOutcomeRating?, String> get rating =>
|
||||
$composableBuilder(column: $table.rating, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get notes =>
|
||||
$composableBuilder(column: $table.notes, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $$GardenOutcomesTableTableManager
|
||||
extends
|
||||
RootTableManager<
|
||||
_$AppDatabase,
|
||||
$GardenOutcomesTable,
|
||||
GardenOutcome,
|
||||
$$GardenOutcomesTableFilterComposer,
|
||||
$$GardenOutcomesTableOrderingComposer,
|
||||
$$GardenOutcomesTableAnnotationComposer,
|
||||
$$GardenOutcomesTableCreateCompanionBuilder,
|
||||
$$GardenOutcomesTableUpdateCompanionBuilder,
|
||||
(
|
||||
GardenOutcome,
|
||||
BaseReferences<_$AppDatabase, $GardenOutcomesTable, GardenOutcome>,
|
||||
),
|
||||
GardenOutcome,
|
||||
PrefetchHooks Function()
|
||||
> {
|
||||
$$GardenOutcomesTableTableManager(
|
||||
_$AppDatabase db,
|
||||
$GardenOutcomesTable table,
|
||||
) : super(
|
||||
TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$$GardenOutcomesTableFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$$GardenOutcomesTableOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$$GardenOutcomesTableAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback:
|
||||
({
|
||||
Value<String> id = const Value.absent(),
|
||||
Value<int> createdAt = const Value.absent(),
|
||||
Value<String> updatedAt = const Value.absent(),
|
||||
Value<String> lastAuthor = const Value.absent(),
|
||||
Value<bool> isDeleted = const Value.absent(),
|
||||
Value<int> schemaRowVersion = const Value.absent(),
|
||||
Value<String> lotId = const Value.absent(),
|
||||
Value<int?> year = const Value.absent(),
|
||||
Value<GardenOutcomeRating?> rating = const Value.absent(),
|
||||
Value<String?> notes = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) => GardenOutcomesCompanion(
|
||||
id: id,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
lastAuthor: lastAuthor,
|
||||
isDeleted: isDeleted,
|
||||
schemaRowVersion: schemaRowVersion,
|
||||
lotId: lotId,
|
||||
year: year,
|
||||
rating: rating,
|
||||
notes: notes,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
required String id,
|
||||
required int createdAt,
|
||||
required String updatedAt,
|
||||
required String lastAuthor,
|
||||
Value<bool> isDeleted = const Value.absent(),
|
||||
Value<int> schemaRowVersion = const Value.absent(),
|
||||
required String lotId,
|
||||
Value<int?> year = const Value.absent(),
|
||||
Value<GardenOutcomeRating?> rating = const Value.absent(),
|
||||
Value<String?> notes = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) => GardenOutcomesCompanion.insert(
|
||||
id: id,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
lastAuthor: lastAuthor,
|
||||
isDeleted: isDeleted,
|
||||
schemaRowVersion: schemaRowVersion,
|
||||
lotId: lotId,
|
||||
year: year,
|
||||
rating: rating,
|
||||
notes: notes,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
typedef $$GardenOutcomesTableProcessedTableManager =
|
||||
ProcessedTableManager<
|
||||
_$AppDatabase,
|
||||
$GardenOutcomesTable,
|
||||
GardenOutcome,
|
||||
$$GardenOutcomesTableFilterComposer,
|
||||
$$GardenOutcomesTableOrderingComposer,
|
||||
$$GardenOutcomesTableAnnotationComposer,
|
||||
$$GardenOutcomesTableCreateCompanionBuilder,
|
||||
$$GardenOutcomesTableUpdateCompanionBuilder,
|
||||
(
|
||||
GardenOutcome,
|
||||
BaseReferences<_$AppDatabase, $GardenOutcomesTable, GardenOutcome>,
|
||||
),
|
||||
GardenOutcome,
|
||||
PrefetchHooks Function()
|
||||
>;
|
||||
typedef $$MovementsTableCreateCompanionBuilder =
|
||||
MovementsCompanion Function({
|
||||
required String id,
|
||||
|
|
@ -15800,6 +16726,8 @@ class $AppDatabaseManager {
|
|||
$$GerminationTestsTableTableManager(_db, _db.germinationTests);
|
||||
$$ConditionChecksTableTableManager get conditionChecks =>
|
||||
$$ConditionChecksTableTableManager(_db, _db.conditionChecks);
|
||||
$$GardenOutcomesTableTableManager get gardenOutcomes =>
|
||||
$$GardenOutcomesTableTableManager(_db, _db.gardenOutcomes);
|
||||
$$MovementsTableTableManager get movements =>
|
||||
$$MovementsTableTableManager(_db, _db.movements);
|
||||
$$PartiesTableTableManager get parties =>
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ enum PreservationFormat {
|
|||
/// - `fresh` — just renewed (lila/violet).
|
||||
enum DesiccantState { none, add, replace, dry, fresh }
|
||||
|
||||
/// How a sowing from this batch turned out in the grower's own garden — the
|
||||
/// note people most wish they had two seasons later. Deliberately coarse
|
||||
/// (three faces, not a breeder's scorecard): the free-text note carries any
|
||||
/// nuance.
|
||||
enum GardenOutcomeRating { good, mixed, poor }
|
||||
|
||||
/// Append-only event kinds on a Lot (data-model §2.4).
|
||||
enum MovementType {
|
||||
received,
|
||||
|
|
|
|||
|
|
@ -142,6 +142,18 @@ class ConditionChecks extends Table with SyncColumns {
|
|||
TextColumn get notes => text().nullable()();
|
||||
}
|
||||
|
||||
/// "How did it do in MY garden" — one optional, skippable answer per season
|
||||
/// (v13). Captured at the natural moment (recording a harvest), shown as one
|
||||
/// more line of the lot's story. Deliberately minimal — a coarse rating plus a
|
||||
/// free note; the note carries any nuance. Mirrors [GerminationTests]: a dated
|
||||
/// log under a Lot.
|
||||
class GardenOutcomes extends Table with SyncColumns {
|
||||
TextColumn get lotId => text()();
|
||||
IntColumn get year => integer().nullable()(); // season, e.g. 2026
|
||||
TextColumn get rating => textEnum<GardenOutcomeRating>().nullable()();
|
||||
TextColumn get notes => text().nullable()();
|
||||
}
|
||||
|
||||
/// The append-only event log on a Lot — history + provenance DAG.
|
||||
class Movements extends Table with AppendOnlyColumns {
|
||||
TextColumn get lotId => text()();
|
||||
|
|
|
|||
|
|
@ -717,7 +717,18 @@
|
|||
"created": "Added to your collection",
|
||||
"from": "From {origin}",
|
||||
"germinationResult": "Germination test — {percent}%",
|
||||
"linkedEarlier": "Comes from an earlier batch"
|
||||
"linkedEarlier": "Comes from an earlier batch",
|
||||
"outcomeQuestion": "How did it do?",
|
||||
"outcomeGood": "Well",
|
||||
"outcomeMixed": "So-so",
|
||||
"outcomePoor": "Poorly",
|
||||
"outcomeNoteHint": "A note for your future self (optional)",
|
||||
"outcomeSaved": "Noted",
|
||||
"ratedGood": "It did well",
|
||||
"ratedMixed": "It did so-so",
|
||||
"ratedPoor": "It did poorly",
|
||||
"outcomeTitle": "Season note",
|
||||
"outcomeYear": "Season {year}"
|
||||
},
|
||||
"sale": {
|
||||
"title": "Sales",
|
||||
|
|
|
|||
|
|
@ -716,7 +716,18 @@
|
|||
"created": "Entró en tu colección",
|
||||
"from": "De {origin}",
|
||||
"germinationResult": "Prueba de germinación — {percent}%",
|
||||
"linkedEarlier": "Viene de un lote anterior"
|
||||
"linkedEarlier": "Viene de un lote anterior",
|
||||
"outcomeQuestion": "¿Qué tal se dio?",
|
||||
"outcomeGood": "Bien",
|
||||
"outcomeMixed": "Regular",
|
||||
"outcomePoor": "Mal",
|
||||
"outcomeNoteHint": "Una nota para tu yo del futuro (opcional)",
|
||||
"outcomeSaved": "Anotado",
|
||||
"ratedGood": "Se dio bien",
|
||||
"ratedMixed": "Se dio regular",
|
||||
"ratedPoor": "Se dio mal",
|
||||
"outcomeTitle": "Nota de temporada",
|
||||
"outcomeYear": "Temporada {year}"
|
||||
},
|
||||
"sale": {
|
||||
"title": "Ventas",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 7
|
||||
/// Strings: 3528 (504 per locale)
|
||||
/// Strings: 3550 (507 per locale)
|
||||
///
|
||||
/// Built on 2026-07-18 at 04:22 UTC
|
||||
/// Built on 2026-07-18 at 10:13 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
|
|
|||
|
|
@ -2039,6 +2039,39 @@ class Translations$history$en {
|
|||
|
||||
/// en: 'Comes from an earlier batch'
|
||||
String get linkedEarlier => 'Comes from an earlier batch';
|
||||
|
||||
/// en: 'How did it do?'
|
||||
String get outcomeQuestion => 'How did it do?';
|
||||
|
||||
/// en: 'Well'
|
||||
String get outcomeGood => 'Well';
|
||||
|
||||
/// en: 'So-so'
|
||||
String get outcomeMixed => 'So-so';
|
||||
|
||||
/// en: 'Poorly'
|
||||
String get outcomePoor => 'Poorly';
|
||||
|
||||
/// en: 'A note for your future self (optional)'
|
||||
String get outcomeNoteHint => 'A note for your future self (optional)';
|
||||
|
||||
/// en: 'Noted'
|
||||
String get outcomeSaved => 'Noted';
|
||||
|
||||
/// en: 'It did well'
|
||||
String get ratedGood => 'It did well';
|
||||
|
||||
/// en: 'It did so-so'
|
||||
String get ratedMixed => 'It did so-so';
|
||||
|
||||
/// en: 'It did poorly'
|
||||
String get ratedPoor => 'It did poorly';
|
||||
|
||||
/// en: 'Season note'
|
||||
String get outcomeTitle => 'Season note';
|
||||
|
||||
/// en: 'Season {year}'
|
||||
String outcomeYear({required Object year}) => 'Season ${year}';
|
||||
}
|
||||
|
||||
// Path: sale
|
||||
|
|
@ -3277,6 +3310,17 @@ extension on Translations {
|
|||
'history.from' => ({required Object origin}) => 'From ${origin}',
|
||||
'history.germinationResult' => ({required Object percent}) => 'Germination test — ${percent}%',
|
||||
'history.linkedEarlier' => 'Comes from an earlier batch',
|
||||
'history.outcomeQuestion' => 'How did it do?',
|
||||
'history.outcomeGood' => 'Well',
|
||||
'history.outcomeMixed' => 'So-so',
|
||||
'history.outcomePoor' => 'Poorly',
|
||||
'history.outcomeNoteHint' => 'A note for your future self (optional)',
|
||||
'history.outcomeSaved' => 'Noted',
|
||||
'history.ratedGood' => 'It did well',
|
||||
'history.ratedMixed' => 'It did so-so',
|
||||
'history.ratedPoor' => 'It did poorly',
|
||||
'history.outcomeTitle' => 'Season note',
|
||||
'history.outcomeYear' => ({required Object year}) => 'Season ${year}',
|
||||
'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.add' => 'Record a sale',
|
||||
|
|
|
|||
|
|
@ -1046,6 +1046,17 @@ class _Translations$history$es extends Translations$history$en {
|
|||
@override String from({required Object origin}) => 'De ${origin}';
|
||||
@override String germinationResult({required Object percent}) => 'Prueba de germinación — ${percent}%';
|
||||
@override String get linkedEarlier => 'Viene de un lote anterior';
|
||||
@override String get outcomeQuestion => '¿Qué tal se dio?';
|
||||
@override String get outcomeGood => 'Bien';
|
||||
@override String get outcomeMixed => 'Regular';
|
||||
@override String get outcomePoor => 'Mal';
|
||||
@override String get outcomeNoteHint => 'Una nota para tu yo del futuro (opcional)';
|
||||
@override String get outcomeSaved => 'Anotado';
|
||||
@override String get ratedGood => 'Se dio bien';
|
||||
@override String get ratedMixed => 'Se dio regular';
|
||||
@override String get ratedPoor => 'Se dio mal';
|
||||
@override String get outcomeTitle => 'Nota de temporada';
|
||||
@override String outcomeYear({required Object year}) => 'Temporada ${year}';
|
||||
}
|
||||
|
||||
// Path: sale
|
||||
|
|
@ -2055,6 +2066,17 @@ extension on TranslationsEs {
|
|||
'history.from' => ({required Object origin}) => 'De ${origin}',
|
||||
'history.germinationResult' => ({required Object percent}) => 'Prueba de germinación — ${percent}%',
|
||||
'history.linkedEarlier' => 'Viene de un lote anterior',
|
||||
'history.outcomeQuestion' => '¿Qué tal se dio?',
|
||||
'history.outcomeGood' => 'Bien',
|
||||
'history.outcomeMixed' => 'Regular',
|
||||
'history.outcomePoor' => 'Mal',
|
||||
'history.outcomeNoteHint' => 'Una nota para tu yo del futuro (opcional)',
|
||||
'history.outcomeSaved' => 'Anotado',
|
||||
'history.ratedGood' => 'Se dio bien',
|
||||
'history.ratedMixed' => 'Se dio regular',
|
||||
'history.ratedPoor' => 'Se dio mal',
|
||||
'history.outcomeTitle' => 'Nota de temporada',
|
||||
'history.outcomeYear' => ({required Object year}) => 'Temporada ${year}',
|
||||
'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.add' => 'Registrar venta',
|
||||
|
|
|
|||
|
|
@ -38,12 +38,23 @@ class _LotHistorySheetState extends State<_LotHistorySheet> {
|
|||
late Future<List<LotHistoryEntry>> _history;
|
||||
bool _saving = false;
|
||||
|
||||
// The optional, skippable "how did it do?" asked right after a harvest is
|
||||
// noted — the only moment it appears. Dismissed, it never nags again.
|
||||
bool _askOutcome = false;
|
||||
final _outcomeNote = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_history = widget.repository.lotHistory(widget.lot.id);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_outcomeNote.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _record(MovementType type) async {
|
||||
if (_saving) return;
|
||||
setState(() => _saving = true);
|
||||
|
|
@ -65,6 +76,29 @@ class _LotHistorySheetState extends State<_LotHistorySheet> {
|
|||
);
|
||||
setState(() {
|
||||
_saving = false;
|
||||
_askOutcome = type == MovementType.harvested;
|
||||
_history = widget.repository.lotHistory(widget.lot.id);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveOutcome(GardenOutcomeRating rating) async {
|
||||
if (_saving) return;
|
||||
setState(() => _saving = true);
|
||||
final t = context.t;
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final note = _outcomeNote.text.trim();
|
||||
await widget.repository.addGardenOutcome(
|
||||
lotId: widget.lot.id,
|
||||
year: DateTime.now().year,
|
||||
rating: rating,
|
||||
notes: note.isEmpty ? null : note,
|
||||
);
|
||||
if (!mounted) return;
|
||||
messenger.showSnackBar(SnackBar(content: Text(t.history.outcomeSaved)));
|
||||
_outcomeNote.clear();
|
||||
setState(() {
|
||||
_saving = false;
|
||||
_askOutcome = false;
|
||||
_history = widget.repository.lotHistory(widget.lot.id);
|
||||
});
|
||||
}
|
||||
|
|
@ -74,7 +108,13 @@ class _LotHistorySheetState extends State<_LotHistorySheet> {
|
|||
final t = context.t;
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
// Bottom inset keeps the optional note field above the keyboard.
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
24 + MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
@ -106,6 +146,15 @@ class _LotHistorySheetState extends State<_LotHistorySheet> {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (_askOutcome) ...[
|
||||
const SizedBox(height: 8),
|
||||
_OutcomeQuestion(
|
||||
note: _outcomeNote,
|
||||
saving: _saving,
|
||||
onRate: _saveOutcome,
|
||||
onDismiss: () => setState(() => _askOutcome = false),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
Flexible(
|
||||
child: FutureBuilder<List<LotHistoryEntry>>(
|
||||
|
|
@ -134,6 +183,91 @@ class _LotHistorySheetState extends State<_LotHistorySheet> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The one skippable question of the whole flow: three faces and an optional
|
||||
/// note. Tapping a face saves; the X dismisses without a trace.
|
||||
class _OutcomeQuestion extends StatelessWidget {
|
||||
const _OutcomeQuestion({
|
||||
required this.note,
|
||||
required this.saving,
|
||||
required this.onRate,
|
||||
required this.onDismiss,
|
||||
});
|
||||
|
||||
final TextEditingController note;
|
||||
final bool saving;
|
||||
final ValueChanged<GardenOutcomeRating> onRate;
|
||||
final VoidCallback onDismiss;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
return Card(
|
||||
key: const Key('history.outcomeQuestion'),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 4, 4, 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
t.history.outcomeQuestion,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
key: const Key('history.outcomeDismiss'),
|
||||
icon: const Icon(Icons.close, size: 18),
|
||||
onPressed: onDismiss,
|
||||
),
|
||||
],
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
ActionChip(
|
||||
key: const Key('history.outcomeGood'),
|
||||
avatar: const Icon(Icons.sentiment_satisfied_alt, size: 18),
|
||||
label: Text(t.history.outcomeGood),
|
||||
onPressed: saving
|
||||
? null
|
||||
: () => onRate(GardenOutcomeRating.good),
|
||||
),
|
||||
ActionChip(
|
||||
key: const Key('history.outcomeMixed'),
|
||||
avatar: const Icon(Icons.sentiment_neutral, size: 18),
|
||||
label: Text(t.history.outcomeMixed),
|
||||
onPressed: saving
|
||||
? null
|
||||
: () => onRate(GardenOutcomeRating.mixed),
|
||||
),
|
||||
ActionChip(
|
||||
key: const Key('history.outcomePoor'),
|
||||
avatar: const Icon(Icons.sentiment_dissatisfied, size: 18),
|
||||
label: Text(t.history.outcomePoor),
|
||||
onPressed: saving
|
||||
? null
|
||||
: () => onRate(GardenOutcomeRating.poor),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextField(
|
||||
key: const Key('history.outcomeNote'),
|
||||
controller: note,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.history.outcomeNoteHint,
|
||||
isDense: true,
|
||||
border: InputBorder.none,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HistoryTile extends StatelessWidget {
|
||||
const _HistoryTile({required this.entry});
|
||||
|
||||
|
|
@ -143,6 +277,12 @@ class _HistoryTile extends StatelessWidget {
|
|||
LotHistoryKind.created => Icons.spa_outlined,
|
||||
LotHistoryKind.germinationTest => Icons.science_outlined,
|
||||
LotHistoryKind.conditionCheck => Icons.inventory_2_outlined,
|
||||
LotHistoryKind.gardenOutcome => switch (entry.rating) {
|
||||
GardenOutcomeRating.good => Icons.sentiment_satisfied_alt,
|
||||
GardenOutcomeRating.mixed => Icons.sentiment_neutral,
|
||||
GardenOutcomeRating.poor => Icons.sentiment_dissatisfied,
|
||||
null => Icons.edit_note,
|
||||
},
|
||||
LotHistoryKind.movement => switch (entry.movementType!) {
|
||||
MovementType.received => Icons.call_received,
|
||||
MovementType.given => Icons.redeem_outlined,
|
||||
|
|
@ -157,6 +297,12 @@ class _HistoryTile extends StatelessWidget {
|
|||
String _title(Translations t) => switch (entry.kind) {
|
||||
LotHistoryKind.created => t.history.created,
|
||||
LotHistoryKind.conditionCheck => t.conditionCheck.title,
|
||||
LotHistoryKind.gardenOutcome => switch (entry.rating) {
|
||||
GardenOutcomeRating.good => t.history.ratedGood,
|
||||
GardenOutcomeRating.mixed => t.history.ratedMixed,
|
||||
GardenOutcomeRating.poor => t.history.ratedPoor,
|
||||
null => t.history.outcomeTitle,
|
||||
},
|
||||
LotHistoryKind.germinationTest => entry.germinationRate == null
|
||||
? t.germination.title
|
||||
: t.history.germinationResult(
|
||||
|
|
@ -194,6 +340,7 @@ class _HistoryTile extends StatelessWidget {
|
|||
final details = [
|
||||
if (entry.kind == LotHistoryKind.created && origin.isNotEmpty)
|
||||
t.history.from(origin: origin),
|
||||
if (entry.year case final year?) t.history.outcomeYear(year: year),
|
||||
if (entry.quantity != null) quantityDisplay(t, entry.quantity!),
|
||||
?entry.counterpartyName,
|
||||
if (entry.hasParentLink) t.history.linkedEarlier,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,12 @@ void main() {
|
|||
containerCount: 2,
|
||||
desiccantState: DesiccantState.dry,
|
||||
);
|
||||
await source.addGardenOutcome(
|
||||
lotId: lotId,
|
||||
year: 2024,
|
||||
rating: GardenOutcomeRating.good,
|
||||
notes: 'kept it',
|
||||
);
|
||||
await source.addVernacularName(
|
||||
varietyId,
|
||||
'tomate de la abuela',
|
||||
|
|
@ -126,6 +132,7 @@ void main() {
|
|||
expect(targetRows.externalLinks, sourceRows.externalLinks);
|
||||
expect(targetRows.germinationTests, sourceRows.germinationTests);
|
||||
expect(targetRows.conditionChecks, sourceRows.conditionChecks);
|
||||
expect(targetRows.gardenOutcomes, sourceRows.gardenOutcomes);
|
||||
expect(targetRows.movements, sourceRows.movements);
|
||||
expect(targetRows.attachments, sourceRows.attachments);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,19 +11,19 @@ void main() {
|
|||
verifier = SchemaVerifier(GeneratedHelper());
|
||||
});
|
||||
|
||||
test('freshly created database matches the exported schema v12', () async {
|
||||
final schema = await verifier.schemaAt(12);
|
||||
test('freshly created database matches the exported schema v13', () async {
|
||||
final schema = await verifier.schemaAt(13);
|
||||
final db = AppDatabase(schema.newConnection());
|
||||
await verifier.migrateAndValidate(db, 12);
|
||||
await verifier.migrateAndValidate(db, 13);
|
||||
await db.close();
|
||||
});
|
||||
|
||||
// Every historical version upgrades cleanly to the current schema (v12).
|
||||
for (var from = 1; from <= 11; from++) {
|
||||
test('upgrades v$from → v12 and matches the fresh schema', () async {
|
||||
// Every historical version upgrades cleanly to the current schema (v13).
|
||||
for (var from = 1; from <= 12; from++) {
|
||||
test('upgrades v$from → v13 and matches the fresh schema', () async {
|
||||
final connection = await verifier.startAt(from);
|
||||
final db = AppDatabase(connection);
|
||||
await verifier.migrateAndValidate(db, 12);
|
||||
await verifier.migrateAndValidate(db, 13);
|
||||
await db.close();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'schema_v9.dart' as v9;
|
|||
import 'schema_v10.dart' as v10;
|
||||
import 'schema_v11.dart' as v11;
|
||||
import 'schema_v12.dart' as v12;
|
||||
import 'schema_v13.dart' as v13;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
|
|
@ -45,10 +46,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||
return v11.DatabaseAtV11(db);
|
||||
case 12:
|
||||
return v12.DatabaseAtV12(db);
|
||||
case 13:
|
||||
return v13.DatabaseAtV13(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
}
|
||||
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
||||
}
|
||||
|
|
|
|||
2223
apps/app_seeds/test/db/schema/schema_v13.dart
Normal file
2223
apps/app_seeds/test/db/schema/schema_v13.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -139,6 +139,23 @@ void main() {
|
|||
}
|
||||
});
|
||||
|
||||
test('includes garden outcomes as story lines', () async {
|
||||
final lotId = await newLot();
|
||||
await repo.addGardenOutcome(
|
||||
lotId: lotId,
|
||||
year: 2026,
|
||||
rating: GardenOutcomeRating.good,
|
||||
notes: 'true to type, keeping it',
|
||||
);
|
||||
final history = await repo.lotHistory(lotId);
|
||||
final outcome = history.singleWhere(
|
||||
(e) => e.kind == LotHistoryKind.gardenOutcome,
|
||||
);
|
||||
expect(outcome.rating, GardenOutcomeRating.good);
|
||||
expect(outcome.year, 2026);
|
||||
expect(outcome.notes, 'true to type, keeping it');
|
||||
});
|
||||
|
||||
test('ignores other lots', () async {
|
||||
final lotId = await newLot();
|
||||
final otherLot = await newLot();
|
||||
|
|
|
|||
|
|
@ -79,6 +79,80 @@ void main() {
|
|||
await disposeTree(tester);
|
||||
});
|
||||
|
||||
testWidgets('harvesting asks "how did it do?" once, a face tap saves it', (
|
||||
tester,
|
||||
) async {
|
||||
final repo = newTestRepository(db);
|
||||
final id = await repo.addQuickVariety(label: 'Maize');
|
||||
final lotId = await repo.addLot(varietyId: id);
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrapDetail(
|
||||
repository: repo,
|
||||
varietyId: id,
|
||||
species: newTestSpeciesRepository(db),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byKey(Key('lot.history.$lotId')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Sowing does NOT ask; harvesting does.
|
||||
await tester.tap(find.byKey(const Key('history.sowToday')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('history.outcomeQuestion')), findsNothing);
|
||||
|
||||
await tester.tap(find.byKey(const Key('history.harvestToday')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('history.outcomeQuestion')), findsOneWidget);
|
||||
|
||||
await tester.enterText(
|
||||
find.byKey(const Key('history.outcomeNote')),
|
||||
'true to type',
|
||||
);
|
||||
await tester.tap(find.byKey(const Key('history.outcomeGood')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Question gone, answer saved, story shows it.
|
||||
expect(find.byKey(const Key('history.outcomeQuestion')), findsNothing);
|
||||
final outcomes = await db.select(db.gardenOutcomes).get();
|
||||
expect(outcomes.single.rating, GardenOutcomeRating.good);
|
||||
expect(outcomes.single.notes, 'true to type');
|
||||
expect(outcomes.single.year, DateTime.now().year);
|
||||
expect(find.text('It did well'), findsOneWidget);
|
||||
await disposeTree(tester);
|
||||
});
|
||||
|
||||
testWidgets('the harvest question can be dismissed without a trace', (
|
||||
tester,
|
||||
) async {
|
||||
final repo = newTestRepository(db);
|
||||
final id = await repo.addQuickVariety(label: 'Maize');
|
||||
final lotId = await repo.addLot(varietyId: id);
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrapDetail(
|
||||
repository: repo,
|
||||
varietyId: id,
|
||||
species: newTestSpeciesRepository(db),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byKey(Key('lot.history.$lotId')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('history.harvestToday')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byKey(const Key('history.outcomeDismiss')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('history.outcomeQuestion')), findsNothing);
|
||||
expect(await db.select(db.gardenOutcomes).get(), isEmpty);
|
||||
await disposeTree(tester);
|
||||
});
|
||||
|
||||
testWidgets('a plant lot offers harvest but not sowing', (tester) async {
|
||||
final repo = newTestRepository(db);
|
||||
final id = await repo.addQuickVariety(label: 'Rosemary');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue