From 6d2f4967005599c69431ef24ddb187d21764976e Mon Sep 17 00:00:00 2001 From: Dennis Nemec Date: Thu, 18 Jun 2026 17:10:17 +0200 Subject: [PATCH] feat(summary): Zahlungsweise-Auswahl bei offen==0 deaktivieren MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steht kein offener Betrag mehr aus (vollständig vorab bezahlt oder per Gutschrift ausgeglichen), wird die Zahlungsmethoden-Auswahl gesperrt und ein erklärender Hinweis angezeigt — analog zur Sperre bei bereits abgeschlossener Lieferung. - Offener-Betrag-Formel in Helper _openAmount(delivery, credit) extrahiert (Single Source; vorher nur in _PaymentSummary). - _PaymentMethodPicker bekommt die Gutschrift und sperrt das Dropdown bei state != active ODER offen == 0 (editable = active && offen > 0). - Sperr-/Info-Hinweis in wiederverwendbares _PickerHint-Widget gezogen. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../presentation/steps/step_summary.dart | 84 +++++++++++++------ 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/lib/feature/delivery/detail/presentation/steps/step_summary.dart b/lib/feature/delivery/detail/presentation/steps/step_summary.dart index cc218bb..cf60e12 100644 --- a/lib/feature/delivery/detail/presentation/steps/step_summary.dart +++ b/lib/feature/delivery/detail/presentation/steps/step_summary.dart @@ -56,6 +56,7 @@ class StepSummary extends StatelessWidget { _PaymentMethodPicker( delivery: delivery, overrideId: wfState.paymentMethodOverrideId, + credit: details.creditOf(delivery.id), ), const SizedBox(height: 16), const _SignHint(), @@ -213,6 +214,17 @@ class _DeliveredRow extends StatelessWidget { } } +/// Offener Betrag der Lieferung in Euro: Warenwert (Σ Stückpreis × gelieferte +/// Menge) − Anzahlung − Gutschrift, nie negativ. Einzige Quelle dieser Formel — +/// genutzt von der Zahlungs-Übersicht UND der Zahlungsmethoden-Auswahl. +double _openAmount(Delivery delivery, DeliveryCredit? credit) { + final creditEuros = (credit?.amountCents ?? 0) / 100.0; + final warenwert = + delivery.items.fold(0, (acc, item) => acc + item.lineTotal); + return (warenwert - delivery.prepaidAmount - creditEuros) + .clamp(0.0, double.infinity); +} + class _PaymentSummary extends StatelessWidget { const _PaymentSummary({required this.delivery, required this.credit}); @@ -222,15 +234,13 @@ class _PaymentSummary extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - // Exakt aus Cent (nicht gerundet) — Gutschrift kann Cent-Beträge haben. - final creditEuros = (credit?.amountCents ?? 0) / 100.0; // Warenwert = Σ Stückpreis × ausgelieferte Menge (entfernte/teil-entfernte // Positionen fallen automatisch raus). final warenwert = delivery.items .fold(0, (acc, item) => acc + item.lineTotal); - // Offener Betrag = Warenwert − Anzahlung − Gutschrift, nie negativ. - final open = (warenwert - delivery.prepaidAmount - creditEuros) - .clamp(0.0, double.infinity); + // Offener Betrag über den gemeinsamen Helper (gleiche Formel wie die + // Zahlungsmethoden-Auswahl). + final open = _openAmount(delivery, credit); return Card( margin: EdgeInsets.zero, child: Padding( @@ -349,10 +359,12 @@ class _PaymentMethodPicker extends StatelessWidget { const _PaymentMethodPicker({ required this.delivery, required this.overrideId, + required this.credit, }); final Delivery delivery; final String? overrideId; + final DeliveryCredit? credit; @override Widget build(BuildContext context) { @@ -407,6 +419,11 @@ class _PaymentMethodPicker extends StatelessWidget { // abgeschlossener/abgebrochener/pausierter Lieferung zeigt das // Dropdown den gewählten Stand, ist aber gesperrt. final active = delivery.state == DeliveryState.active; + // Steht kein offener Betrag mehr aus (vollständig vorab bezahlt + // oder per Gutschrift ausgeglichen), ist keine Zahlungsweise zu + // wählen → Auswahl deaktivieren. + final hasOpenAmount = _openAmount(delivery, credit) > 0; + final editable = active && hasOpenAmount; return Card( margin: EdgeInsets.zero, child: Padding( @@ -428,7 +445,7 @@ class _PaymentMethodPicker extends StatelessWidget { ), ], // `null` deaktiviert das Dropdown (Flutter-Konvention). - onChanged: active + onChanged: editable ? (newId) { if (newId == null) return; context.read().add( @@ -447,27 +464,15 @@ class _PaymentMethodPicker extends StatelessWidget { ), if (!active) ...[ const SizedBox(height: 8), - Row( - children: [ - Icon(Icons.lock_outline, - size: 16, - color: Theme.of(context).colorScheme.onSurfaceVariant), - const SizedBox(width: 8), - Expanded( - child: Text( - 'Lieferung abgeschlossen — Zahlungsmethode nicht ' - 'mehr änderbar.', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - color: Theme.of(context) - .colorScheme - .onSurfaceVariant, - ), - ), - ), - ], + const _PickerHint( + text: 'Lieferung abgeschlossen — Zahlungsmethode nicht ' + 'mehr änderbar.', + ), + ] else if (!hasOpenAmount) ...[ + const SizedBox(height: 8), + const _PickerHint( + text: 'Kein offener Betrag — Auswahl der Zahlungsweise ' + 'nicht erforderlich.', ), ], ], @@ -479,6 +484,31 @@ class _PaymentMethodPicker extends StatelessWidget { } } +/// Dezenter Sperr-/Info-Hinweis unter dem Zahlungsmethoden-Dropdown +/// (Schloss-Icon + Text in gedämpfter Farbe). +class _PickerHint extends StatelessWidget { + const _PickerHint({required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + final muted = Theme.of(context).colorScheme.onSurfaceVariant; + return Row( + children: [ + Icon(Icons.lock_outline, size: 16, color: muted), + const SizedBox(width: 8), + Expanded( + child: Text( + text, + style: Theme.of(context).textTheme.bodySmall?.copyWith(color: muted), + ), + ), + ], + ); + } +} + class _SignHint extends StatelessWidget { const _SignHint();