feat(labels): choose copies per label, prefilled from container count

Each label in the print sheet gets an editable copy count, suggested from
the lot's latest condition-check container count (3 jars → 3 labels), and
falling back to 1. Type or step the count; the total updates live and Save
is disabled at zero. On save each seed expands into that many identical
labels.

- SeedLabelEntry.suggestedCopies; labelRows reads the latest container count
- label print sheet: per-row −/number/+ stepper, live total, expand on save
- tests: repository suggestedCopies + print-sheet copy chooser
This commit is contained in:
vjrj 2026-07-10 20:12:36 +02:00
parent 63f48db5c2
commit d36fd05741
4 changed files with 298 additions and 4 deletions

View file

@ -132,5 +132,26 @@ void main() {
final rows = await repo.labelRows({selected}, languageCode: 'en');
expect(rows.map((r) => r.varietyLabel), ['Selected']);
});
test('suggests copies from the latest container count', () async {
final v = await repo.addQuickVariety(label: 'Tomate');
final withJars = await repo.addLot(varietyId: v, harvestYear: 2024);
final noCheck = await repo.addLot(varietyId: v, harvestYear: 2025);
// Two checks on the same lot: the most recent (3 jars) wins.
await repo.addConditionCheck(lotId: withJars, checkedOn: 1, containerCount: 2);
await repo.addConditionCheck(lotId: withJars, checkedOn: 2, containerCount: 3);
final rows = await repo.labelRows({v}, languageCode: 'en');
final byYear = {for (final r in rows) r.harvestYear: r};
expect(byYear[2024]!.suggestedCopies, 3); // latest check
expect(byYear[2025]!.suggestedCopies, 1); // no check default 1
expect(noCheck, isNotEmpty);
});
test('a variety with no lots suggests a single copy', () async {
final v = await repo.addQuickVariety(label: 'Alubia');
final rows = await repo.labelRows({v}, languageCode: 'en');
expect(rows.single.suggestedCopies, 1);
});
});
}

View file

@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tane/data/variety_repository.dart';
import 'package:tane/i18n/strings.g.dart';
import 'package:tane/ui/label_print_sheet.dart';
/// Hosts a button that opens the print-labels sheet for [entries].
Widget _host(List<SeedLabelEntry> entries) {
LocaleSettings.setLocaleSync(AppLocale.en);
return TranslationProvider(
child: MaterialApp(
supportedLocales: AppLocaleUtils.supportedLocales,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: Scaffold(
body: Builder(
builder: (context) => ElevatedButton(
onPressed: () => showLabelPrintSheet(context, entries),
child: const Text('Open'),
),
),
),
),
);
}
void main() {
Future<void> open(WidgetTester tester) async {
await tester.tap(find.text('Open'));
await tester.pumpAndSettle();
}
testWidgets('prefills copies from the suggested count and totals them', (
tester,
) async {
await tester.pumpWidget(
_host(const [
SeedLabelEntry(varietyLabel: 'Tomate', suggestedCopies: 3),
SeedLabelEntry(varietyLabel: 'Alubia', suggestedCopies: 1),
]),
);
await open(tester);
// 3 + 1 suggested = 4 labels.
expect(find.text('4 labels'), findsOneWidget);
});
testWidgets('the stepper and the field both edit a copy count', (
tester,
) async {
await tester.pumpWidget(
_host(const [
SeedLabelEntry(varietyLabel: 'Tomate', suggestedCopies: 3),
SeedLabelEntry(varietyLabel: 'Alubia', suggestedCopies: 1),
]),
);
await open(tester);
await tester.tap(
find.byKey(const Key('printLabels.copies.minus.Tomate')),
);
await tester.pumpAndSettle();
expect(find.text('3 labels'), findsOneWidget); // 2 + 1
await tester.enterText(
find.byKey(const Key('printLabels.copies.field.Tomate')),
'5',
);
await tester.pumpAndSettle();
expect(find.text('6 labels'), findsOneWidget); // 5 + 1
});
testWidgets('saving is disabled when the total is zero', (tester) async {
await tester.pumpWidget(
_host(const [
SeedLabelEntry(varietyLabel: 'Tomate', suggestedCopies: 1),
]),
);
await open(tester);
await tester.enterText(
find.byKey(const Key('printLabels.copies.field.Tomate')),
'0',
);
await tester.pumpAndSettle();
expect(find.text('0 labels'), findsOneWidget);
final save = tester.widget<FilledButton>(
find.byKey(const Key('printLabels.save')),
);
expect(save.onPressed, isNull);
});
}