feat(block1): germination tests per lot with derived rate

Record germination tests on a lot and surface the result.

- GerminationEntry model with a derived rate (germinated / sample). VarietyLot
  carries its tests (most-recent first) and exposes latestGerminationRate.
- VarietyRepository.addGerminationTest; watchVariety now also re-emits on
  germination-test changes, so the detail refreshes live.
- Detail UI: each lot shows a germination % badge and a grass action that opens
  a sheet listing past tests and adding a new one (germinated + sample size).
  i18n strings (ES/EN).

Tests: derived-rate + reactivity at the repository, and a widget test for the
record-test → badge flow. Full suite: 35 passing, 0 skipped.
This commit is contained in:
vjrj 2026-07-07 21:59:34 +02:00
parent 4e8b8293e0
commit 37427fa738
10 changed files with 405 additions and 6 deletions

View file

@ -157,4 +157,42 @@ void main() {
await disposeTree(tester);
},
);
testWidgets('recording a germination test shows the rate badge on the lot', (
tester,
) async {
final repo = newTestRepository(db);
final id = await repo.addQuickVariety(label: 'Maize');
await repo.addLot(
varietyId: id,
harvestYear: 2024,
quantity: const Quantity(kind: QuantityKind.cob),
);
await tester.pumpWidget(
wrapDetail(
repository: repo,
varietyId: id,
species: newTestSpeciesRepository(db),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.grass_outlined).first);
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const Key('germination.germinated')),
'18',
);
await tester.enterText(
find.byKey(const Key('germination.sampleSize')),
'20',
);
await tester.tap(find.byKey(const Key('germination.save')));
await tester.pumpAndSettle();
expect(find.text('90%'), findsOneWidget); // germination badge on the lot
await disposeTree(tester);
});
}