fix: Stationsabschluss absichern und Kundenformulare anpassen

Sperrt nachträgliche Aufgabenabschlüsse in App und Backend und zeigt erledigte Aufträge trotz offener optionaler Aufgaben grün an. Ergänzt Regressionstests, die Anredeauswahl und optionale Steuerangaben für Kunden. Enthält App-Version 0.9.25, Backend-Version 0.9.26 und das aktualisierte Frontend-Bundle.
This commit is contained in:
Sven committed 2026-10-06 15:09:51 +02:00
1 parent 8758c53e5b
commit 5e6044e4b1
16 files changed
+638 -69

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:votianlt_app/app_theme.dart';
import 'package:votianlt_app/cargo_items_view.dart';
import 'package:votianlt_app/jobs_view.dart';
import 'package:votianlt_app/models/job.dart';
import 'package:votianlt_app/models/job_progress.dart';
import 'package:votianlt_app/task_view.dart';
Job jobWithOptionalTask({
bool closedStation = true,
String status = 'IN_PROGRESS',
}) => Job.fromJson({
'id': 'job-1',
'jobNumber': 'TEST-1',
'status': status,
'completedStationOrders': closedStation ? [0] : [],
'deliveryStations': [
{
'stationOrder': 0,
'tasks': [
{
'id': 'optional',
'jobId': 'job-1',
'optional': true,
'completed': false,
'description': 'Optionale Bestätigung',
'taskOrder': 0,
'taskSpecificData': {
'taskType': 'CONFIRMATION',
'buttonText': 'Jetzt bestätigen',
},
},
],
},
{'stationOrder': 1, 'tasks': []},
],
});
void main() {
for (final status in ['IN_PROGRESS', 'COMPLETED']) {
testWidgets(
'optional task cannot open a dialog after station closure ($status)',
(tester) async {
final job = jobWithOptionalTask(status: status);
await tester.pumpWidget(
MaterialApp(home: TaskView(job: job, stationOrder: 0)),
);
await tester.pump();
final task = find.text('Optionale Bestätigung');
expect(task, findsOneWidget);
final tile = tester.widget<InkWell>(
find.ancestor(of: task, matching: find.byType(InkWell)).first,
);
expect(tile.onTap, isNull);
await tester.tap(task);
await tester.pump();
expect(find.byType(AlertDialog), findsNothing);
expect(find.text('Station abgeschlossen'), findsOneWidget);
await tester.pumpWidget(const SizedBox.shrink());
},
);
}
testWidgets('optional task remains executable at an open station', (
tester,
) async {
await tester.pumpWidget(
MaterialApp(
home: TaskView(
job: jobWithOptionalTask(closedStation: false),
stationOrder: 0,
),
),
);
await tester.pump();
await tester.tap(find.text('Optionale Bestätigung'));
await tester.pump();
expect(find.byType(AlertDialog), findsOneWidget);
expect(find.text('Jetzt bestätigen'), findsOneWidget);
await tester.pumpWidget(const SizedBox.shrink());
});
testWidgets(
'an already open dialog cannot complete a task after the job closes',
(tester) async {
Widget view(Job job) => MaterialApp(
home: TaskView(
key: const ValueKey('task-view'),
job: job,
stationOrder: 0,
),
);
await tester.pumpWidget(view(jobWithOptionalTask(closedStation: false)));
await tester.pump();
await tester.tap(find.text('Optionale Bestätigung'));
await tester.pump();
expect(find.byType(AlertDialog), findsOneWidget);
await tester.pumpWidget(view(jobWithOptionalTask(status: 'COMPLETED')));
await tester.pump();
await tester.tap(find.text('Jetzt bestätigen'));
await tester.pumpAndSettle();
expect(find.byType(AlertDialog), findsNothing);
final taskCard = tester.widget<Card>(
find
.ancestor(
of: find.text('Optionale Bestätigung'),
matching: find.byType(Card),
)
.first,
);
expect(
taskCard.elevation,
0,
); // still open and locked, never marked completed
expect(find.text('Station abgeschlossen'), findsOneWidget);
await tester.pumpWidget(const SizedBox.shrink());
},
);
test('completed job card is green with optional tasks still open', () {
final job = jobWithOptionalTask(status: 'COMPLETED');
expect(jobCardBackgroundColor(JobProgress(job)), AppColors.successSoft);
expect(job.tasks.single.completed, isFalse);
});
test('offline final station completion makes the job card green', () {
final job = jobWithOptionalTask();
expect(
jobCardBackgroundColor(JobProgress(job, localCompletedStations: {1})),
AppColors.successSoft,
);
expect(jobCardBackgroundColor(JobProgress(job)), AppColors.warningSoft);
expect(
deliveryStationCardBackgroundColor(
job.deliveryStations.first,
{},
isClosed: true,
),
AppColors.successSoft,
);
});
}