- Neuer Workflow-Step "Zahlung" zeigt ausgelieferte Artikel mit Menge und Preis, Betragsaufstellung, Zahlungsstatus und einen grossen Button "Zahlung abwickeln" (fest unten) - Zahlungs-Modal: grosser offener Betrag, Methoden als Cards aus den Backend-Stammdaten, bei EC-Karte gross die Kundennummer; schliesst nur ueber Abbrechen, "Zahlung erhalten" oder "Lieferung abbrechen" (mit Grund) - Bestaetigung wird am Server protokolliert (RecordDeliveryPayment); bis dahin sind "Weiter" und die Uebersicht gesperrt, ausser es ist nichts offen (bereits bezahlt). Aendert sich der Betrag danach, wird die Zahlung als veraltet markiert und muss erneut abgewickelt werden - Uebersicht zeigt die abgewickelte Zahlung statt eigener Methodenauswahl; Unterschrift-Flow ohne doppelte Inkasso-Stufe; Abschluss sendet Methode und Inkasso-Flag der gueltigen Zahlung - DeliveryPaymentStatus als einzige Quelle fuer offenen Betrag (gleiche Formel wie Backend); Artikelliste/Betragskarte als geteilte Widgets; Begruendungs-Dialog geteilt mit Info-Step - API-Client aus aktueller Backend-Spec neu generiert - Tests fuer Zahlungsstatus und Modal Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
2.8 KiB
Dart
81 lines
2.8 KiB
Dart
import 'package:intl/intl.dart';
|
||
|
||
import 'package:hl_lieferservice/domain/entity/delivery.dart';
|
||
import 'package:hl_lieferservice/domain/entity/delivery_payment.dart';
|
||
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
|
||
|
||
/// Zahlungsstand einer Lieferung — einzige Quelle für offenen Betrag und
|
||
/// „ist die Zahlung erledigt?". Genutzt von Zahlungs-Step, Übersicht und
|
||
/// Abschluss, damit alle exakt dieselben Zahlen sehen.
|
||
///
|
||
/// Offener Betrag = Warenwert (Σ Stückpreis × ausgelieferte Menge) −
|
||
/// Anzahlung − Gutschrift, nie negativ. Identische Formel und Rundung wie
|
||
/// im Backend (`open_amount_cents`), das den Betrag beim Protokollieren
|
||
/// gegenprüft.
|
||
class DeliveryPaymentStatus {
|
||
const DeliveryPaymentStatus._({
|
||
required this.goodsValue,
|
||
required this.prepaidAmount,
|
||
required this.creditAmount,
|
||
required this.openCents,
|
||
required this.latestPayment,
|
||
});
|
||
|
||
factory DeliveryPaymentStatus.of(Delivery delivery, TourDetails details) {
|
||
final goods = delivery.items.fold<double>(
|
||
0,
|
||
(acc, item) => acc + item.lineTotal,
|
||
);
|
||
final credit = (details.creditOf(delivery.id)?.amountCents ?? 0) / 100.0;
|
||
final open = goods - delivery.prepaidAmount - credit;
|
||
return DeliveryPaymentStatus._(
|
||
goodsValue: goods,
|
||
prepaidAmount: delivery.prepaidAmount,
|
||
creditAmount: credit,
|
||
openCents: open <= 0 ? 0 : (open * 100).round(),
|
||
latestPayment: details.paymentOf(delivery.id),
|
||
);
|
||
}
|
||
|
||
/// Warenwert in Euro.
|
||
final double goodsValue;
|
||
|
||
/// Bei Bestellung bezahlt (Anzahlung) in Euro.
|
||
final double prepaidAmount;
|
||
|
||
/// Aktuelle Geld-Gutschrift in Euro (0 = keine).
|
||
final double creditAmount;
|
||
|
||
/// Offener Betrag in Cent.
|
||
final int openCents;
|
||
|
||
/// Jüngste protokollierte Zahlung, egal ob noch gültig.
|
||
final DeliveryPayment? latestPayment;
|
||
|
||
double get openAmount => openCents / 100.0;
|
||
|
||
bool get hasOpenAmount => openCents > 0;
|
||
|
||
/// Die jüngste Zahlung, sofern sie zum aktuellen offenen Betrag passt.
|
||
DeliveryPayment? get validPayment {
|
||
final p = latestPayment;
|
||
if (p == null || !hasOpenAmount || p.amountCents != openCents) return null;
|
||
return p;
|
||
}
|
||
|
||
/// Es wurde abgewickelt, aber der offene Betrag hat sich seitdem geändert.
|
||
bool get isStale =>
|
||
latestPayment != null && hasOpenAmount && validPayment == null;
|
||
|
||
/// Zahlungs-Step erledigt: nichts offen oder gültig abgewickelt.
|
||
bool get isSettled => !hasOpenAmount || validPayment != null;
|
||
}
|
||
|
||
final NumberFormat _euro = NumberFormat.currency(locale: 'de_DE', symbol: '€');
|
||
|
||
/// Betrag in Euro im deutschen Format, z. B. `1.234,50 €`.
|
||
String formatEuro(double amount) => _euro.format(amount);
|
||
|
||
/// Cent-Betrag im deutschen Format, z. B. `1.234,50 €`.
|
||
String formatEuroCents(int cents) => _euro.format(cents / 100.0);
|