feat(app): scan a printed seed label back into its record
Closes the physical↔digital loop the labels opened: the QR on a printed envelope (tane://seed, already encoded by seed_label_codec) can now be scanned from the inventory bar. A known label opens its variety; an unknown one asks before adding anything, then creates the variety (species linked by exact scientific name when bundled) with a lot carrying the label's year and origin. Scanner is pure ZXing (zxing_barcode_scanner, MIT) — no ML Kit, no Play Services, F-Droid-safe, the stack Ğ1nkgo ships. Mobile-only; other platforms don't show the button (OCR precedent). The post-scan flow is a plain function (handleScannedPayload) so it's widget-tested without a camera.
This commit is contained in:
parent
bb5b3d4a43
commit
f1aa8f8e66
18 changed files with 1130 additions and 42 deletions
109
apps/app_seeds/test/services/seed_label_scan_test.dart
Normal file
109
apps/app_seeds/test/services/seed_label_scan_test.dart
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tane/data/export_import/seed_label_codec.dart';
|
||||
import 'package:tane/data/species_repository.dart';
|
||||
import 'package:tane/db/database.dart';
|
||||
import 'package:tane/services/seed_label_scan.dart';
|
||||
|
||||
import '../support/test_support.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase db;
|
||||
late var repo = newTestRepository(db);
|
||||
late SpeciesRepository species;
|
||||
|
||||
setUp(() {
|
||||
db = newTestDatabase();
|
||||
repo = newTestRepository(db);
|
||||
species = newTestSpeciesRepository(db);
|
||||
});
|
||||
|
||||
tearDown(() async => db.close());
|
||||
|
||||
group('resolveScannedLabel', () {
|
||||
test('rejects anything that is not a seed label', () async {
|
||||
expect(
|
||||
await resolveScannedLabel(repo, 'https://example.org'),
|
||||
const SeedScanResult.notALabel(),
|
||||
);
|
||||
expect(
|
||||
await resolveScannedLabel(repo, 'gibberish'),
|
||||
const SeedScanResult.notALabel(),
|
||||
);
|
||||
});
|
||||
|
||||
test('finds the existing variety by its label, case-insensitively',
|
||||
() async {
|
||||
final id = await repo.addQuickVariety(label: 'Tomate Rosa');
|
||||
final raw = SeedLabelCodec.encode(
|
||||
const SeedLabelData(varietyLabel: 'tomate rosa'),
|
||||
);
|
||||
expect(
|
||||
await resolveScannedLabel(repo, raw),
|
||||
SeedScanResult.match(id),
|
||||
);
|
||||
});
|
||||
|
||||
test('an unknown label comes back with its data for the add prompt',
|
||||
() async {
|
||||
final raw = SeedLabelCodec.encode(
|
||||
const SeedLabelData(
|
||||
varietyLabel: 'Acelga de Perales',
|
||||
scientificName: 'Beta vulgaris',
|
||||
year: 2024,
|
||||
origin: 'María · Aiako',
|
||||
),
|
||||
);
|
||||
final result = await resolveScannedLabel(repo, raw);
|
||||
expect(result.data?.varietyLabel, 'Acelga de Perales');
|
||||
expect(result.data?.year, 2024);
|
||||
});
|
||||
});
|
||||
|
||||
group('importScannedLabel', () {
|
||||
test('creates the variety with a lot carrying year and origin', () async {
|
||||
const data = SeedLabelData(
|
||||
varietyLabel: 'Acelga de Perales',
|
||||
year: 2024,
|
||||
origin: 'María · Aiako',
|
||||
);
|
||||
final varietyId = await importScannedLabel(
|
||||
repository: repo,
|
||||
species: species,
|
||||
data: data,
|
||||
);
|
||||
final varieties = await db.select(db.varieties).get();
|
||||
expect(varieties.single.id, varietyId);
|
||||
expect(varieties.single.label, 'Acelga de Perales');
|
||||
final lots = await db.select(db.lots).get();
|
||||
expect(lots.single.varietyId, varietyId);
|
||||
expect(lots.single.harvestYear, 2024);
|
||||
expect(lots.single.originName, 'María · Aiako');
|
||||
});
|
||||
|
||||
test('links the species by exact scientific name when bundled', () async {
|
||||
await species.seedBundled(const [
|
||||
SpeciesSeed(
|
||||
scientificName: 'Beta vulgaris',
|
||||
family: 'Amaranthaceae',
|
||||
commonNames: {
|
||||
'es': ['Acelga'],
|
||||
},
|
||||
),
|
||||
]);
|
||||
const data = SeedLabelData(
|
||||
varietyLabel: 'Una acelga cualquiera',
|
||||
scientificName: 'beta vulgaris',
|
||||
);
|
||||
final varietyId = await importScannedLabel(
|
||||
repository: repo,
|
||||
species: species,
|
||||
data: data,
|
||||
);
|
||||
final variety = await (db.select(
|
||||
db.varieties,
|
||||
)..where((v) => v.id.equals(varietyId))).getSingle();
|
||||
final bundled = await db.select(db.species).get();
|
||||
expect(variety.speciesId, bundled.single.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue