211 lines
8.4 KiB
Dart
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';
|
|
}
|