feat(zahlung): eigener Step "Zahlung" vor der Uebersicht
- 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>
This commit is contained in:
32
lib/domain/entity/delivery_payment.dart
Normal file
32
lib/domain/entity/delivery_payment.dart
Normal file
@ -0,0 +1,32 @@
|
||||
/// Protokollierte Zahlungsabwicklung („Abkassieren") einer Lieferung —
|
||||
/// spiegelt das Backend-Aggregat `DeliveryPayment`.
|
||||
///
|
||||
/// Das Backend liefert pro Lieferung nur den jüngsten Eintrag. Der Betrag ist
|
||||
/// der offene Betrag zum Zeitpunkt der Bestätigung. Ändert sich der offene
|
||||
/// Betrag danach (z. B. neue Gutschrift), ist der Eintrag veraltet — siehe
|
||||
/// `DeliveryPaymentStatus`.
|
||||
class DeliveryPayment {
|
||||
const DeliveryPayment({
|
||||
required this.id,
|
||||
required this.deliveryId,
|
||||
required this.paymentMethodId,
|
||||
required this.paymentMethodCode,
|
||||
required this.amountCents,
|
||||
required this.recordedAt,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String deliveryId;
|
||||
final String paymentMethodId;
|
||||
|
||||
/// Snapshot des Methoden-Codes (`cash`, `ec_card`, `invoice`, …).
|
||||
final String paymentMethodCode;
|
||||
|
||||
/// Abgewickelter Betrag in Cent.
|
||||
final int amountCents;
|
||||
final DateTime recordedAt;
|
||||
|
||||
/// Bar/EC = vor Ort kassiert. „Auf Rechnung" bleibt offen.
|
||||
bool get isCollectedOnSite =>
|
||||
paymentMethodCode == 'cash' || paymentMethodCode == 'ec_card';
|
||||
}
|
||||
@ -3,6 +3,7 @@ import 'contact_source.dart';
|
||||
import 'customer.dart';
|
||||
import 'delivery.dart';
|
||||
import 'delivery_credit.dart';
|
||||
import 'delivery_payment.dart';
|
||||
import 'delivery_item.dart';
|
||||
import 'delivery_note.dart';
|
||||
import 'delivery_service_value.dart';
|
||||
@ -28,6 +29,7 @@ class TourDetails {
|
||||
required this.warehouses,
|
||||
required this.notesByDeliveryId,
|
||||
required this.creditsByDeliveryId,
|
||||
required this.paymentsByDeliveryId,
|
||||
required this.services,
|
||||
required this.serviceValuesByDeliveryId,
|
||||
required this.contactSourcesByDeliveryId,
|
||||
@ -56,6 +58,10 @@ class TourDetails {
|
||||
/// der Eintrag, gibt es aktuell keine Gutschrift.
|
||||
final Map<String, DeliveryCredit> creditsByDeliveryId;
|
||||
|
||||
/// Pro Lieferung die jüngste protokollierte Zahlungsabwicklung. Fehlt der
|
||||
/// Eintrag, wurde für diese Lieferung noch keine Zahlung abgewickelt.
|
||||
final Map<String, DeliveryPayment> paymentsByDeliveryId;
|
||||
|
||||
/// Aktive Service-Definitionen (Stammdaten), nach `sortOrder`. Daraus
|
||||
/// rendert Phase 4 die Auswahl.
|
||||
final List<Service> services;
|
||||
@ -200,6 +206,10 @@ class TourDetails {
|
||||
DeliveryCredit? creditOf(String deliveryId) =>
|
||||
creditsByDeliveryId[deliveryId];
|
||||
|
||||
/// Jüngste protokollierte Zahlung dieser Lieferung, oder `null`.
|
||||
DeliveryPayment? paymentOf(String deliveryId) =>
|
||||
paymentsByDeliveryId[deliveryId];
|
||||
|
||||
/// Gesetzter Service-Wert dieser Lieferung für einen Service, oder `null`.
|
||||
DeliveryServiceValue? serviceValueOf(String deliveryId, String serviceId) =>
|
||||
serviceValuesByDeliveryId[deliveryId]?[serviceId];
|
||||
@ -398,6 +408,7 @@ class TourDetails {
|
||||
List<Delivery>? deliveries,
|
||||
Map<String, List<DeliveryNote>>? notesByDeliveryId,
|
||||
Map<String, DeliveryCredit>? creditsByDeliveryId,
|
||||
Map<String, DeliveryPayment>? paymentsByDeliveryId,
|
||||
Map<String, Map<String, DeliveryServiceValue>>? serviceValuesByDeliveryId,
|
||||
}) {
|
||||
return TourDetails(
|
||||
@ -409,6 +420,7 @@ class TourDetails {
|
||||
warehouses: warehouses,
|
||||
notesByDeliveryId: notesByDeliveryId ?? this.notesByDeliveryId,
|
||||
creditsByDeliveryId: creditsByDeliveryId ?? this.creditsByDeliveryId,
|
||||
paymentsByDeliveryId: paymentsByDeliveryId ?? this.paymentsByDeliveryId,
|
||||
services: services,
|
||||
serviceValuesByDeliveryId:
|
||||
serviceValuesByDeliveryId ?? this.serviceValuesByDeliveryId,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:hl_lieferservice/domain/entity/delivery.dart';
|
||||
import 'package:hl_lieferservice/domain/entity/delivery_credit.dart';
|
||||
import 'package:hl_lieferservice/domain/entity/delivery_payment.dart';
|
||||
import 'package:hl_lieferservice/domain/entity/delivery_note.dart';
|
||||
import 'package:hl_lieferservice/domain/entity/delivery_service_value.dart';
|
||||
import 'package:hl_lieferservice/domain/entity/scan_intent.dart';
|
||||
@ -166,6 +167,18 @@ abstract interface class TourRepository {
|
||||
String? actorCarId,
|
||||
});
|
||||
|
||||
/// Protokolliert die Zahlungsabwicklung („Abkassieren") einer Lieferung.
|
||||
/// Append-only + idempotent über [clientEventId]. [expectedAmountCents] ist
|
||||
/// der Betrag, den der Fahrer gesehen hat; weicht der Server-Betrag ab,
|
||||
/// lehnt das Backend ab. Rückgabe: der protokollierte Eintrag.
|
||||
Future<DeliveryPayment> recordDeliveryPayment({
|
||||
required String deliveryId,
|
||||
required String clientEventId,
|
||||
required String paymentMethodId,
|
||||
required int expectedAmountCents,
|
||||
String? actorCarId,
|
||||
});
|
||||
|
||||
/// Setzt (Upsert) den Wert eines Service für eine Lieferung. Genau das zum
|
||||
/// Service-Typ passende Feld angeben. Rückgabe: der gespeicherte Wert.
|
||||
Future<DeliveryServiceValue> setDeliveryService({
|
||||
|
||||
Reference in New Issue
Block a user