-- Append-only Audit-Log für jede Scan-Aktion an einer Lieferposition. -- -- Doppelter Wahrheits-Ansatz: -- * delivery_items.scan_* = "schnelle Wahrheit" über WIEVIEL (Embedded) -- * scan_audit = "lange Wahrheit" über WIE und WANN -- -- Idempotenz: client_scan_id ist UNIQUE. Schickt die App einen Scan -- nach Netzfehler erneut, kollidiert sie auf diesem Index — der Server -- liefert "duplicate" zurück, ohne den embedded scan_state zu verändern. -- -- Beleg-Bezug wird denormalisiert mitgespeichert: bleibt nachvollziehbar, -- auch wenn delivery_items irgendwann archiviert oder bereinigt wird. CREATE TABLE scan_audit ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Idempotenz-Schlüssel vom Client. client_scan_id UUID NOT NULL UNIQUE, delivery_item_id UUID NOT NULL REFERENCES delivery_items(id) ON DELETE CASCADE, action TEXT NOT NULL CHECK (action IN ('scan','unscan','hold','unhold','remove')), -- Signed Delta auf scanned_quantity. +1 / -1 / 0 abhängig vom action. delta INT NOT NULL, -- Snapshot scanned_quantity nach dieser Aktion (Reports brauchen keinen Replay). resulting_quantity INT NOT NULL CHECK (resulting_quantity >= 0), resulting_status TEXT NOT NULL CHECK (resulting_status IN ('in_progress','done','held','removed')), reason TEXT, -- Akteur: Personalnummer aus dem JWT (Pflicht). -- car_id NULL bis Cars im Backend gemanagt werden. actor_personalnummer BIGINT NOT NULL, actor_car_id UUID, -- Zeitpunkt am Client (offline-fähig) + Eingang am Server. client_scanned_at TIMESTAMPTZ NOT NULL, server_recorded_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- Denormalisierter ERP-Beleg-Bezug. erp_belegart_id BIGINT NOT NULL, erp_belegnummer TEXT NOT NULL, erp_belegzeilen_nr INT NOT NULL, erp_komponenten_artikel_nr TEXT ); CREATE INDEX scan_audit_item ON scan_audit(delivery_item_id); CREATE INDEX scan_audit_recorded ON scan_audit(server_recorded_at); CREATE INDEX scan_audit_actor ON scan_audit(actor_personalnummer, server_recorded_at);