import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hl_lieferservice/domain/entity/address.dart'; import 'package:hl_lieferservice/domain/entity/customer.dart'; import 'package:hl_lieferservice/domain/entity/delivery.dart'; import 'package:hl_lieferservice/domain/entity/delivery_credit.dart'; import 'package:hl_lieferservice/domain/entity/delivery_item.dart'; import 'package:hl_lieferservice/domain/entity/delivery_payment.dart'; import 'package:hl_lieferservice/domain/entity/payment_method.dart'; import 'package:hl_lieferservice/domain/entity/scan_progress.dart'; import 'package:hl_lieferservice/domain/entity/tour.dart'; import 'package:hl_lieferservice/domain/entity/tour_details.dart'; import 'package:hl_lieferservice/domain/repository/payment_methods_repository.dart'; import 'package:hl_lieferservice/feature/delivery/detail/model/delivery_payment_status.dart'; import 'package:hl_lieferservice/feature/delivery/detail/presentation/widget/payment_sheet.dart'; import 'package:hl_lieferservice/feature/payment_methods/bloc/payment_methods_cubit.dart'; const _address = Address( street: 'Konstantinplatz', houseNumber: '3', postalCode: '41849', city: 'Wassenberg', country: 'DE', ); final _methods = [ PaymentMethod( id: 'm-cash', code: 'cash', name: 'Bar', active: true, createdAt: DateTime(2026), ), PaymentMethod( id: 'm-ec', code: 'ec_card', name: 'EC-Karte', active: true, createdAt: DateTime(2026), ), PaymentMethod( id: 'm-inv', code: 'invoice', name: 'Rechnung', active: true, createdAt: DateTime(2026), ), ]; /// Lieferung: 2 × 50 € Warenwert, 20 € angezahlt ⇒ 80 € offen. Delivery _delivery({double prepaid = 20}) => Delivery( id: 'd1', tourId: 't1', customerId: 'c1', contactPersonIds: const [], deliveryAddressSnapshot: _address, erpBelegartId: 24, erpBelegnummer: 'V-1', state: DeliveryState.active, sortOrder: 1, prepaidAmount: prepaid, paymentMethodId: 'm-cash', items: [ DeliveryItem( id: 'i1', deliveryId: 'd1', articleId: 'a1', warehouseId: 'w1', belegzeilenNr: 1, requiredQuantity: 2, unitPrice: 50, scanProgress: ScanProgress( status: ScanStatus.done, scannedQuantity: 2, lastUpdatedAt: DateTime(2026), ), ), ], ); TourDetails _details( Delivery delivery, { DeliveryPayment? payment, DeliveryCredit? credit, }) => TourDetails( tour: Tour( id: 't1', accountId: 414, date: DateTime(2026, 9, 25), syncedAt: DateTime(2026), ), deliveries: [delivery], customers: const { 'c1': Customer( id: 'c1', name: 'Cremer', erpCustomerId: 7843068, address: _address, ), }, contacts: const {}, articles: const {}, warehouses: const {}, notesByDeliveryId: const {}, creditsByDeliveryId: {if (credit != null) 'd1': credit}, paymentsByDeliveryId: {if (payment != null) 'd1': payment}, services: const [], serviceValuesByDeliveryId: const {}, contactSourcesByDeliveryId: const {}, contactChannelsBySourceId: const {}, ); DeliveryPayment _payment(int cents, {String code = 'ec_card'}) => DeliveryPayment( id: 'p1', deliveryId: 'd1', paymentMethodId: code == 'ec_card' ? 'm-ec' : 'm-cash', paymentMethodCode: code, amountCents: cents, recordedAt: DateTime(2026, 9, 25, 13, 5), ); class _FakePaymentMethods implements PaymentMethodsRepository { @override Future> list({bool includeInactive = false}) async => _methods; @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } void main() { group('DeliveryPaymentStatus', () { test('offener Betrag ohne Zahlung ist nicht erledigt', () { final d = _delivery(); final s = DeliveryPaymentStatus.of(d, _details(d)); expect(s.openCents, 8000); expect(s.isSettled, isFalse); expect(s.isStale, isFalse); }); test('passende Zahlung erledigt den Step', () { final d = _delivery(); final s = DeliveryPaymentStatus.of( d, _details(d, payment: _payment(8000)), ); expect(s.validPayment?.id, 'p1'); expect(s.isSettled, isTrue); }); test('Gutschrift nach Zahlung macht sie veraltet', () { final d = _delivery(); final s = DeliveryPaymentStatus.of( d, _details( d, payment: _payment(8000), credit: const DeliveryCredit( deliveryId: 'd1', amountCents: 1000, reason: 'Kratzer', ), ), ); expect(s.openCents, 7000); expect(s.isStale, isTrue); expect(s.isSettled, isFalse); }); test('bereits bezahlt: nichts offen, Step überspringbar', () { final d = _delivery(prepaid: 100); final s = DeliveryPaymentStatus.of(d, _details(d)); expect(s.hasOpenAmount, isFalse); expect(s.isSettled, isTrue); }); }); group('Zahlungs-Modal', () { late PaymentSheetResult? result; Future openSheet(WidgetTester tester) async { result = null; final cubit = PaymentMethodsCubit(repository: _FakePaymentMethods()); await cubit.load(); await tester.pumpWidget( BlocProvider.value( value: cubit, child: MaterialApp( home: Builder( builder: (context) => Scaffold( body: TextButton( onPressed: () async { result = await showPaymentSheet( context, openCents: 8000, customerNumber: 7843068, preselectedMethodId: 'm-cash', ); }, child: const Text('open'), ), ), ), ), ), ); await tester.tap(find.text('open')); await tester.pumpAndSettle(); } testWidgets('zeigt Betrag, bei EC die Kundennummer, bestätigt Methode', ( tester, ) async { tester.view.physicalSize = const Size(1080, 2400); tester.view.devicePixelRatio = 2.75; addTearDown(tester.view.reset); await openSheet(tester); expect(find.textContaining('80,00'), findsOneWidget); expect(find.text('Bar'), findsOneWidget); expect(find.text('EC-Karte'), findsOneWidget); expect(find.text('Rechnung'), findsOneWidget); expect(find.text('7843068'), findsNothing); await tester.tap(find.text('EC-Karte')); await tester.pumpAndSettle(); expect(find.text('7843068'), findsOneWidget); await tester.tap(find.text('Zahlung erhalten')); await tester.pumpAndSettle(); expect(result, isA()); expect((result as PaymentConfirmed).method.code, 'ec_card'); }); testWidgets('Rechnung ändert den Bestätigungs-Text', (tester) async { tester.view.physicalSize = const Size(1080, 2400); tester.view.devicePixelRatio = 2.75; addTearDown(tester.view.reset); await openSheet(tester); await tester.tap(find.text('Rechnung')); await tester.pumpAndSettle(); expect(find.text('Auf Rechnung bestätigen'), findsOneWidget); }); testWidgets('Abbrechen schließt ohne Ergebnis', (tester) async { tester.view.physicalSize = const Size(1080, 2400); tester.view.devicePixelRatio = 2.75; addTearDown(tester.view.reset); await openSheet(tester); await tester.tap(find.text('Abbrechen')); await tester.pumpAndSettle(); expect(result, isNull); expect(find.text('Zahlung abwickeln'), findsNothing); }); testWidgets('Lieferung abbrechen fragt Grund ab', (tester) async { tester.view.physicalSize = const Size(1080, 2400); tester.view.devicePixelRatio = 2.75; addTearDown(tester.view.reset); await openSheet(tester); await tester.tap(find.text('Lieferung abbrechen')); await tester.pumpAndSettle(); await tester.enterText(find.byType(TextField), 'Kunde zahlt nicht'); await tester.tap(find.text('Bestätigen')); await tester.pumpAndSettle(); expect(result, isA()); expect((result as DeliveryCancelRequested).reason, 'Kunde zahlt nicht'); }); }); }