import 'package:hl_lieferservice/domain/entity/delivery.dart'; import 'package:hl_lieferservice/domain/entity/delivery_credit.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'; import 'package:hl_lieferservice/domain/entity/tour.dart'; import 'package:hl_lieferservice/domain/entity/tour_details.dart'; /// Port für das Tour-Aggregat. /// /// Der Port deckt in dieser Migrations-Phase nur die Read-Seite + die /// beiden Operationen, die zum Loading-Flow zwingend gebraucht werden: /// Sortierung und Fahrzeug-Zuweisung. Hold/Resume/Cancel/Complete und /// Notizen werden in C+D-4 nachgezogen, damit das hier nicht überladen /// wird und der Bloc fokussiert bleibt. /// /// Account-Filter sitzt serverseitig im JWT — der Client schickt nie eine /// `personalnummer`/`accountId` mit. abstract interface class TourRepository { /// Die heutige Tour-Übersicht des angemeldeten Fahrers oder `null`, /// wenn keine Tour für heute angelegt ist (ERP-Sync noch nicht /// gelaufen, Treiber-Urlaub etc.). /// /// Liefert nur die schlanke `TourSummary`-Repräsentation; /// [getTourDetails] zieht dann den vollen Aggregat-Snapshot. Future getMyTourSummaryOfToday(); /// Lädt das volle Tour-Aggregat (Tour + Lieferungen + Items + /// Stammdaten + Notizen) für die gegebene Tour-Id. Future getTourDetails(String tourId); /// Convenience: kombiniert [getMyTourSummaryOfToday] + [getTourDetails] /// und gibt `null` zurück, wenn keine Tour existiert. Verwendet die App /// beim Initial-Load. Future getMyTourDetailsOfToday(); /// Schreibt die Sortier-Reihenfolge der Lieferungen einer Tour neu. /// /// `orderedDeliveryIds` muss **alle** Lieferungen der Tour enthalten, /// in der gewünschten Reihenfolge — das Backend lehnt unvollständige /// Listen mit `400 validation` ab. /// /// Rückgabe: deliveryId → neuer sortOrder (für den Bloc-Reducer). Future> setDeliveryOrder({ required String tourId, required List orderedDeliveryIds, }); /// Weist einer Lieferung ein Fahrzeug zu. `carId == null` löst die /// bestehende Zuweisung. Der Server gibt die aktualisierte Delivery /// zurück; weil dieser Endpoint nur Stamm-Felder mutiert, ist es Aufgabe /// des Aufrufers, die `items` aus dem lokalen Aggregat zu erhalten. /// /// Rückgabe: die Stamm-Delivery **ohne** Items — Aufrufer nutzt /// `copyWith(items: ...)` zum Mergen mit dem lokalen State. Future assignCarToDelivery({ required String deliveryId, required String? carId, }); /// Bricht eine Lieferung ab — endgültig (`canceled`). `reason` ist /// vom Backend Pflicht; leere Begründungen werden mit 400 abgelehnt. /// Rückgabe: Server-Snapshot der Delivery **ohne** Items. Future cancelDelivery({ required String deliveryId, required String reason, }); /// Pausiert eine Lieferung (`held`). Reversibel über [resumeDelivery]. /// `reason` ist Pflicht. Future holdDelivery({ required String deliveryId, required String reason, }); /// Setzt eine pausierte Lieferung auf `active` zurück. Kein Reason /// erforderlich. Future resumeDelivery({required String deliveryId}); /// Schließt eine Lieferung ab (`completed`). Lädt beide Unterschriften /// (Kunde + Fahrer, PNG) per multipart hoch und dokumentiert die /// Bestätigungen des Kunden. Atomar serverseitig — das Backend prüft /// vorher: Lieferung aktiv, alle scanbaren Positionen fertig, Notizen /// bestätigt (falls vorhanden). [paymentMethodId] persistiert die ggf. im /// Summary geänderte Zahlungsmethode (muss existieren + aktiv sein); `null` /// lässt die am Beleg hinterlegte Methode unangetastet. Rückgabe: /// Server-Snapshot der Delivery **ohne** Items (Aufrufer merged Items aus /// dem lokalen Aggregat). Future completeDelivery({ required String deliveryId, required List customerSignaturePng, required List driverSignaturePng, required bool receiptConfirmed, required bool notesAcknowledged, required List acknowledgedNoteIds, String? paymentMethodId, String? actorCarId, bool paymentCollected = false, }); /// Legt eine neue Notiz an einer Lieferung an. /// /// Mindestens eines von [text] und [imageAttachment] muss inhaltlich /// gefüllt sein — das Backend erzwingt das. Aktuell unterstützt die App /// nur den Text-Pfad; das `imageAttachment`-Feld bleibt der zukünftigen /// Foto-Upload-Phase vorbehalten. /// /// Rückgabe: die neu angelegte Notiz (mit Server-gesetzter `id` und /// `createdAt`) — der Aufrufer hängt sie an das lokale Tour-Aggregat. Future addDeliveryNote({ required String deliveryId, String? text, String? imageAttachment, String? creditDeliveryItemId, bool isAmountCreditNote, }); /// Ändert Text/Bild einer bestehenden Notiz. Mindestens eines von [text] /// und [imageAttachment] muss inhaltlich gefüllt sein. Rückgabe: die /// aktualisierte Notiz (Autor/`createdAt` bleiben). Future updateDeliveryNote({ required String deliveryId, required String noteId, String? text, String? imageAttachment, }); /// Löscht eine Notiz. Innerhalb des (geteilten) Accounts darf jeder Fahrer /// löschen — keine Autor-Prüfung serverseitig. Future deleteDeliveryNote({ required String deliveryId, required String noteId, }); /// Lädt ein Bild zu einer Lieferung hoch (multipart, Feld `file`). Das /// Backend reicht es an DOCUframe weiter und legt eine Bild-Notiz mit der /// zurückgelieferten Referenz (`~ObjectID`) als `imageAttachment` an. /// Rückgabe: die neue Notiz. Future uploadDeliveryNoteImage({ required String deliveryId, required String filename, required String mime, required List bytes, }); /// Setzt/ändert die Betrags-Gutschrift einer Lieferung. Append-only + /// idempotent über [clientEventId]. Rückgabe: aktueller Stand (`null`, wenn /// — theoretisch — nichts gesetzt ist). Future setDeliveryCredit({ required String deliveryId, required String clientEventId, required int amountCents, required String reason, String? actorCarId, }); /// Entfernt die Betrags-Gutschrift einer Lieferung (append-only `remove`). /// Rückgabe: aktueller Stand danach (`null`). Future removeDeliveryCredit({ required String deliveryId, required String clientEventId, 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 setDeliveryService({ required String deliveryId, required String serviceId, bool? boolValue, int? numericValue, String? actorCarId, }); /// Entfernt den Service-Wert einer Lieferung (Service „nicht gesetzt"). Future removeDeliveryService({ required String deliveryId, required String serviceId, }); /// Wendet eine Liste Scan-Ereignisse als Batch am Server an. /// /// Der Endpoint ist bewusst Bulk: damit kann der Client einen /// Scanner-Burst (z. B. 5 Barcodes in 2 Sekunden) in einem HTTP-Call /// abschicken, **muss** aber nicht — auch ein Aufruf mit nur einem /// `ScanIntent` ist erlaubt. /// /// Idempotenz: das Backend speichert pro `clientScanId` einmal. Wer /// retried, bekommt `duplicate` zurück; doppelte Anwendung kann es /// nicht geben. /// /// Rückgabe: pro Eingabe-Intent ein [ScanOutcome] (Key = /// `clientScanId`). Die Map enthält **jeden** Intent, auch /// `rejected`-Fälle; bei Netzwerk-/Server-Fehlern wirft das Repository /// stattdessen [TourRepositoryException], die Map ist dann nicht /// teilweise gefüllt. Future> applyScans(List intents); } /// Allgemeine Repository-Exception für Tour-Operationen. Konkrete Impls /// dürfen spezifischere Subtypen werfen. class TourRepositoryException implements Exception { const TourRepositoryException(this.message, [this.cause]); final String message; final Object? cause; @override String toString() => 'TourRepositoryException: $message'; }