Files
Holzleitner-Lieferservice-App/lib/domain/repository/tour_repository.dart
Dennis Nemec a9bf8ecdd1 Final commit.
2026-06-01 17:12:28 +02:00

211 lines
8.4 KiB
Dart

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<TourSummary?> getMyTourSummaryOfToday();
/// Lädt das volle Tour-Aggregat (Tour + Lieferungen + Items +
/// Stammdaten + Notizen) für die gegebene Tour-Id.
Future<TourDetails> getTourDetails(String tourId);
/// Convenience: kombiniert [getMyTourSummaryOfToday] + [getTourDetails]
/// und gibt `null` zurück, wenn keine Tour existiert. Verwendet die App
/// beim Initial-Load.
Future<TourDetails?> 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<Map<String, int>> setDeliveryOrder({
required String tourId,
required List<String> 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<Delivery> 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<Delivery> cancelDelivery({
required String deliveryId,
required String reason,
});
/// Pausiert eine Lieferung (`held`). Reversibel über [resumeDelivery].
/// `reason` ist Pflicht.
Future<Delivery> holdDelivery({
required String deliveryId,
required String reason,
});
/// Setzt eine pausierte Lieferung auf `active` zurück. Kein Reason
/// erforderlich.
Future<Delivery> 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<Delivery> completeDelivery({
required String deliveryId,
required List<int> customerSignaturePng,
required List<int> driverSignaturePng,
required bool receiptConfirmed,
required bool notesAcknowledged,
required List<String> 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<DeliveryNote> 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<DeliveryNote> 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<void> 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<DeliveryNote> uploadDeliveryNoteImage({
required String deliveryId,
required String filename,
required String mime,
required List<int> bytes,
});
/// Setzt/ändert die Betrags-Gutschrift einer Lieferung. Append-only +
/// idempotent über [clientEventId]. Rückgabe: aktueller Stand (`null`, wenn
/// — theoretisch — nichts gesetzt ist).
Future<DeliveryCredit?> 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<DeliveryCredit?> 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<DeliveryServiceValue> setDeliveryService({
required String deliveryId,
required String serviceId,
bool? boolValue,
int? numericValue,
String? actorCarId,
});
/// Entfernt den Service-Wert einer Lieferung (Service „nicht gesetzt").
Future<void> 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<Map<String, ScanOutcome>> applyScans(List<ScanIntent> 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';
}