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( 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);