/// Vom Treiber ausgelöstes Scan-Ereignis, bevor es serverseitig /// angewendet wurde. /// /// `clientScanId` ist ein vom Client generierter UUID-Schlüssel und dient /// als **Idempotenz-Anker**: der Server speichert ihn beim ersten Apply /// und antwortet auf jeden weiteren Request mit derselben Id mit /// `duplicate` statt einer zweiten Anwendung. So bleibt Network-Retry /// (z. B. nach Verbindungsabbruch beim ersten POST) bedeutungslos. /// /// `clientScannedAt` ist die Wall-Clock-Zeit am Gerät zum Zeitpunkt des /// Scans — der Server nutzt das nur als Audit-Spur, sortiert aber selbst /// nach Server-Empfangszeit, sodass eine schiefe Uhr am Phone die /// Reihenfolge nicht durcheinanderbringt. class ScanIntent { const ScanIntent({ required this.clientScanId, required this.clientScannedAt, required this.deliveryItemId, required this.action, this.actorCarId, this.reason, this.quantity, this.manual = false, }); final String clientScanId; final DateTime clientScannedAt; final String deliveryItemId; final ScanAction action; /// `true`, wenn der Fahrer die Position manuell als geladen bestätigt hat /// (Fallback ohne Barcode). Reine Audit-Information; Default `false`. final bool manual; /// Menge für `remove` / `unremove` (Mengen-Gutschrift): wie viele Stück /// der Belegzeile gutgeschrieben bzw. wiederhergestellt werden. `null` = /// ganze Restmenge. Bei `scan`/`unscan`/`hold`/`unhold` ignoriert. final int? quantity; /// Fahrzeug, mit dem gescannt wurde — Audit-Spur. Optional, aber die /// App schickt ihn in der Loading-Phase immer mit, weil das Auto zu /// dem Zeitpunkt definitiv gewählt ist. final String? actorCarId; /// Klartext-Begründung. Bei `unscan` / `hold` / `remove` vom Backend /// erwartet, bei `scan` / `unhold` ignoriert. final String? reason; } /// Auswirkung eines Scan-Ereignisses auf die Pipeline eines Items. /// Spiegel des Backend-Enums `AuditAction`. /// /// `unremove` ist die Umkehrung von `remove`: setzt ein `Removed`-Item /// zurück auf `InProgress` (oder `Done`, falls die Soll-Menge schon /// erreicht war). Der ursprüngliche `remove`-Audit-Eintrag bleibt /// erhalten — `unremove` erzeugt einen eigenen Eintrag, sodass die /// Historie der Korrektur vollständig nachvollziehbar bleibt. enum ScanAction { scan, unscan, hold, unhold, remove, unremove } /// Ergebnis eines Apply-Versuchs vom Server. class ScanOutcome { const ScanOutcome({ required this.clientScanId, required this.status, this.deliveryItemId, this.reason, }); final String clientScanId; final ScanOutcomeStatus status; /// Bei `applied` und `duplicate` immer gesetzt, bei `rejected` häufig /// `null` (z. B. wenn die Id beim Server gar nicht ankam). final String? deliveryItemId; /// Bei `rejected` die Server-Begründung — Standard-Text in der UI. final String? reason; } enum ScanOutcomeStatus { /// Server hat den Scan angewendet — `scannedQuantity` ist hochgezählt /// oder Status hat sich geändert. applied, /// Server hat denselben `clientScanId` schon einmal verarbeitet — /// kein Effekt, aber auch kein Fehler. duplicate, /// Server hat den Scan abgelehnt (z. B. Item gehört zu fremder /// Lieferung, Soll-Menge schon voll, Item ist auf `removed`). UI muss /// optimistische Mutation zurückrollen. rejected, }