- Nach "Abschliessen" oeffnet sich ein Blatt "Interne Notiz" (optional, max. 2000 Zeichen). Ohne Eingabe genuegt ein Tipp auf "Ohne Notiz abschliessen"; "Zurueck zur Unterschrift" bricht nur das Abschliessen ab - Die Notiz reist im Abschluss-Aufruf mit (internalNote) und landet im Lieferbericht, nicht in den Kunden-Notizen - API-Client aus aktueller Backend-Spec neu generiert - Widget-Tests fuer das Blatt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
229 lines
9.2 KiB
Dart
229 lines
9.2 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_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';
|
|
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 Tour-Übersicht des angemeldeten Fahrers für [date] (oder „heute",
|
|
/// wenn `null`) — bzw. `null`, wenn für den Tag keine Tour angelegt ist
|
|
/// (ERP-Sync noch nicht gelaufen, Fahrer-Urlaub etc.).
|
|
///
|
|
/// Ohne [date] entscheidet der Server über „heute" (autoritativ). Ist
|
|
/// [date] gesetzt (Fahrer wählt es im Kopf-Kalender), wird dieses Datum
|
|
/// angefragt.
|
|
///
|
|
/// Liefert nur die schlanke `TourSummary`-Repräsentation;
|
|
/// [getTourDetails] zieht dann den vollen Aggregat-Snapshot.
|
|
Future<TourSummary?> getMyTourSummary({DateTime? date});
|
|
|
|
/// Lädt das volle Tour-Aggregat (Tour + Lieferungen + Items +
|
|
/// Stammdaten + Notizen) für die gegebene Tour-Id.
|
|
Future<TourDetails> getTourDetails(String tourId);
|
|
|
|
/// Convenience: kombiniert [getMyTourSummary] + [getTourDetails] und gibt
|
|
/// `null` zurück, wenn für [date] (Default „heute") keine Tour existiert.
|
|
/// Verwendet die App beim Initial-Load und beim Datumswechsel.
|
|
Future<TourDetails?> getMyTourDetails({DateTime? date});
|
|
|
|
/// 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,
|
|
String? internalNote,
|
|
});
|
|
|
|
/// 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,
|
|
});
|
|
|
|
/// 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({
|
|
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';
|
|
}
|