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>
This commit is contained in:
Dennis Nemec
2026-06-01 17:52:58 +02:00
parent 438040acce
commit 6a9b5872e1
137 changed files with 13700 additions and 218 deletions

View File

@ -0,0 +1,54 @@
//! Port für den Unterschriften-Speicher.
//!
//! Im Gegensatz zu Notiz-Bildern (die nach DOCUframe gehen) liegen
//! Unterschriften bewusst **lokal im Backend-Server** — ein einfacher
//! Datei-Speicher reicht, und die Daten verlassen die Maschine nicht.
//!
//! Die konkrete Impl (lokales Dateisystem) lebt in
//! `holzleitner-infrastructure`. Der Use Case erhält eine relative
//! Referenz zurück, die in `delivery_completions` persistiert wird.
use async_trait::async_trait;
use uuid::Uuid;
use crate::error::ApplicationError;
/// Wer hat unterschrieben — bestimmt den Dateinamen.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureRole {
Customer,
Driver,
}
impl SignatureRole {
pub fn as_str(self) -> &'static str {
match self {
SignatureRole::Customer => "customer",
SignatureRole::Driver => "driver",
}
}
}
#[async_trait]
pub trait SignatureStorage: Send + Sync {
/// Speichert eine Unterschrift (PNG-Bytes) für eine Lieferung+Rolle und
/// liefert die persistente, relative Referenz (Dateiname) zurück.
/// Deterministisch über `delivery_id`+`role` — ein Retry überschreibt
/// dieselbe Datei statt Müll anzuhäufen.
async fn save(
&self,
delivery_id: Uuid,
role: SignatureRole,
bytes: Vec<u8>,
) -> Result<String, ApplicationError>;
/// Lädt die gespeicherten PNG-Bytes einer Unterschrift über ihre relative
/// Referenz (Dateiname, wie von [`save`](Self::save) geliefert) — fürs
/// Einbetten in den PDF-Report. `None`, wenn die Datei fehlt.
async fn load(&self, reference: &str) -> Result<Option<Vec<u8>>, ApplicationError>;
/// Löscht beide Unterschriften (Kunde + Fahrer) einer Lieferung — Aufräumen
/// nach erfolgreichem Report-Upload (die Unterschriften stecken dann
/// eingebettet im PDF in DOCUframe). Idempotent (fehlende Datei = ok).
async fn delete_for_delivery(&self, delivery_id: Uuid) -> Result<(), ApplicationError>;
}