Files
Holzleitner---Backend--aktu…/crates/domain/src/audit.rs
Dennis Nemec 6a9b5872e1 Backend-Arbeitsstand: ERP-Sync, Lieferlebenszyklus, Reports + config.toml
Bringt das Backend vom initialen Skeleton auf den aktuellen Arbeitsstand
(Clean Architecture: domain → application → infrastructure → api).

Wesentliche Bereiche:
- ERP-Anbindung (MSSQL-Pull der Touren, Import-Scheduler, Rückschreiben)
- Lieferlebenszyklus: Scan/Hold/Cancel/Complete, Gutschriften, Notizen,
  Bild-Anhänge, Unterschriften, PDF-Lieferreport → DOCUframe
- Stammdaten: Kunden, Artikel, Lager, Zahlungsarten, Services
- Keycloak-JWT-Gate + Fahrer-Provisionierung via Admin-API
- Admin-API-Key-Gate (X-Admin-Api-Key) für Maschinen-Endpunkte

Jüngste Änderungen dieser Session:
- Belegspezifische Kontaktdaten: alle ERP-Adressen (Beleg-/Liefer-/
  Rechnungsadresse, Ansprechpartner, Kundenstamm) mit Telefon/Mobil/
  E-Mail werden gesynct (Migration 0029, MSSQL-Query, TourDetails)
- Konfiguration von .env (envy/dotenvy) auf config.toml (toml/serde)
  umgestellt; Vorlage config.example.toml, Pfad via HOLZLEITNER_CONFIG

Nicht im Repo (per .gitignore): config.toml (Secrets), data/ (Laufzeit-/
Kundendaten), demo.mp4, .claude/, variocontrol-ai/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 17:52:58 +02:00

74 lines
2.6 KiB
Rust

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<String>,
/// Akteur — Personalnummer aus dem JWT.
pub actor_personalnummer: i64,
/// Akteur-Fahrzeug, sofern bekannt (cars werden später verwaltet).
pub actor_car_id: Option<Uuid>,
pub client_scanned_at: DateTime<Utc>,
pub server_recorded_at: DateTime<Utc>,
// ── 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<String>,
}