use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::delivery::ScanStatus; /// Aktion-Typen im Scan-Audit-Log. /// /// * `Scan` / `Unscan` verändern die `scanned_quantity` (+1 / -1). /// * `Hold` / `Unhold` ändern nur den Status, keine Menge. /// * `Remove` markiert die Position als entfernt (Status `Removed`, /// z. B. weil der Kunde sie nicht annimmt). /// * `Unremove` hebt ein `Remove` wieder auf — die Position landet /// zurück in `InProgress` (oder `Done`, falls die `scanned_quantity` /// schon `required_quantity` erreicht hatte). Der ursprüngliche /// `Remove`-Audit-Eintrag bleibt unangetastet; das `Unremove` erzeugt /// einen eigenen Audit-Eintrag — die Historie bleibt vollständig. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] #[serde(rename_all = "snake_case")] pub enum AuditAction { Scan, Unscan, Hold, Unhold, Remove, Unremove, } /// Append-only Audit-Log-Eintrag: jedes Ereignis am Scan-Zustand einer /// Position bekommt eine eigene Zeile. Nie geupdated, nie gelöscht. /// /// Beleg-Bezug wird denormalisiert mitgeführt, damit der Audit-Trail /// auch dann auflösbar bleibt, wenn das zugehörige `DeliveryItem` /// irgendwann archiviert oder bereinigt wird. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] #[serde(rename_all = "camelCase")] pub struct ScanAuditEntry { pub id: Uuid, /// Vom Client vergebene UUID — Idempotenz-Schlüssel beim Retry. pub client_scan_id: Uuid, pub delivery_item_id: Uuid, pub action: AuditAction, /// Signed Δ in `scanned_quantity`: +1 bei SCAN, -1 bei UNSCAN, 0 sonst. pub delta: i32, /// Snapshot der `scanned_quantity` direkt NACH dieser Aktion. /// Vermeidet teure Aggregat-Queries bei Reports. pub resulting_quantity: i32, /// Status der Position NACH dieser Aktion. pub resulting_status: ScanStatus, /// Grund bei HOLD / REMOVE (jeweils Pflicht). pub reason: Option, /// Akteur — Personalnummer aus dem JWT. pub actor_personalnummer: i64, /// Akteur-Fahrzeug, sofern bekannt (cars werden später verwaltet). pub actor_car_id: Option, pub client_scanned_at: DateTime, pub server_recorded_at: DateTime, // ── Denormalisierter ERP-Beleg-Bezug (Archiv-stabil) ─────────────── pub erp_belegart_id: i64, pub erp_belegnummer: String, pub erp_belegzeilen_nr: i32, pub erp_komponenten_artikel_nr: Option, }