Commit Graph

18 Commits

Author SHA1 Message Date
09cd45d67c revert(erp): Belegzeilen-Mengen wieder ins ERP zurückschreiben
Rücknahme der Entkernung: set_line_menge + die Schleife in run() sind
wieder aktiv, d. h. die tatsächlich ausgelieferte Menge wird pro Belegzeile
gesetzt. Das umgeht bewusst die ERPframe-Engine (keine Lagerbuchung) — das
Lager verbucht den Bestand jetzt MANUELL nach Rückgabe des Geräts.

Gutschrift-Toggle + Vier-Augen-Review bleiben unverändert bestehen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 21:55:52 +02:00
02aa216bc6 feat(admin): GET /admin/belege/{belegnummer}/html - HTML-Detailansicht
Rendert dieselben Daten wie der JSON-Detail-Endpunkt als schoen kategorisierte
HTML-Seite (askama, compile-time-Template). Der Handler destilliert das
DeliveryDetails-Aggregat in ein flaches View-Modell (Artikel-/Lagernamen
aufgeloest, Euro/Datum formatiert, deutsche Status-/Rollen-Labels); die
Template-Datei bleibt frei von Domain-Logik. Kategorien: Uebersicht, Kunde,
Lieferadresse, Positionen (Tabelle, veraenderte Zeilen hervorgehoben),
Geld-Gutschrift, Dienstleistungen, Notizen, Kontakte. Nutzt den bestehenden
get_delivery_details-Use-Case; admin-key-geschuetzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 00:11:05 +02:00
af034250e7 feat(admin): GET /admin/belege/{belegnummer} - volle Lieferdetails
Neuer Admin-Endpunkt, der zu einer ERP-Belegnummer das komplette Detail-Paket
einer Lieferung liefert (Kopf, Positionen mit Scan-Ständen, Kunde +
Ansprechpartner, referenzierte Artikel/Lager, Notizen, Geld-Gutschrift,
Dienstleistungswerte, Kontakte). Jede Lieferung, unabhaengig vom Status;
404 bei unbekannter Belegnummer.

Umsetzung: TourRepository::find_tour_id_by_belegnummer loest die Belegnummer
auf ihre tour_id auf; der Use Case laedt das bestehende Tour-Aggregat und
destilliert daraus in-memory die eine Lieferung samt referenzierter Stammdaten
(neues DTO DeliveryDetails). Kein neuer grosser Query.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:42:57 +02:00
7c6d883d44 feat(admin): completed-deliveries ohne Parameter = ALLE ausgelieferten Belege
Die "mindestens eine Grenze"-Sperre (400) entfernt. Ruft man
/admin/completed-deliveries ohne day/from/to, kommt jetzt der komplette
Bestand aller ausgelieferten (abgeschlossenen) Belege zurück, datumsunabhängig.
Der Repo-Query unterstützt from=NULL/to=NULL bereits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:33:07 +02:00
865fcfcd23 feat(admin): GET /admin/belege/{belegnummer}/positions-modified
Neuer Admin-Endpunkt, der zu EINER ERP-Belegnummer das positions_modified-Flag
liefert (Stück-Gutschrift auf einer Belegzeile ODER aktive Geld-Gutschrift) —
gleiche Definition wie in completed-deliveries, aber pro Beleg und ohne
Abschluss-Voraussetzung. 404 bei unbekannter Belegnummer; bei mehreren
Lieferungen gleicher Belegnummer true, sobald eine verändert ist (bool_or).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:32:04 +02:00
6378b1101b feat(admin): completed-deliveries um from/to-Range erweitern
/admin/completed-deliveries akzeptiert jetzt zusätzlich zu `day` (Kurzform
from=to) optionale `from`/`to` (DD-MM-YYYY, inklusive, je offen). Damit lassen
sich Halb-Grenzen abfragen: `?from=` bzw. `?to=` liefern je eine Belegnummern-
Menge, deren SQL-AND-Schnitt einen Zeitraum ergibt (Range-Filter über zwei
unabhängige ERPframe-Filter). Response gibt statt `day` nun `from`/`to` zurück.
Mindestens eine Grenze ist Pflicht (kein Full-Table-Dump).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 17:35:11 +02:00
748df577ef feat(admin): GET /admin/completed-deliveries — abgeschlossene Lieferungen eines Tages
Neuer Admin-Endpunkt, der ALLE an einem Tag (Berliner Kalendertag von
completed_at) abgeschlossenen Lieferungen liefert — unabhängig vom
Mail-Versand-Status. Pro Lieferung: Belegnummer und ein Flag
`positions_modified`, das true ist, wenn eine Belegzeile in der Menge
reduziert/entfernt wurde (delivery_items.credited_quantity > 0) oder eine
aktive Geld-Gutschrift vorliegt (jüngstes delivery_credit_audit-Event 'set').

Pflichtparameter `day` (DD-MM-YYYY). Geschützt per admin_api_key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 16:06:01 +02:00
819005eaa5 feat(dev): /dev/reset-delivery — einzelne Lieferung per Belegnummer zurücksetzen
Setzt EINE Lieferung zurück, ohne die Tour zu löschen (Alternative zum
destruktiven /dev/resync): Abschluss (delivery_completions), Scan-/
Gutschrift-Audit + offener Report-Job weg, Positionen zurück (scanned/
credited = 0, Status in_progress), Lieferung wieder active (state_reason/
assigned_car/review_* geleert). Notizen/Dienstleistungen/Anhänge bleiben.

Vervollständigt den bisher nur als Port-Stub vorhandenen
reset_delivery_by_belegnummer (Build war dadurch kaputt) + Use Case +
DEV-Route (nur bei dev.sync_enabled gemountet, unauthentifiziert).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:35:45 +02:00
fb5f43ed7a feat(erp): Gutschrift-Rückschreibung per Config togglen
Neues Flag [erp] gutschrift_writeback_enabled (Default true): steuert, ob
die Geld-Gutschrift (GUTSCHRIFT10-Belegzeile) beim Abschluss ins ERP
zurückgeschrieben wird. Gutschriften sind bestandsneutral (keine
Bestandsführung) → unkritisch, daher standardmäßig an, aber abschaltbar.

Die Belegzeilen-Menge wird weiterhin generell NICHT zurückgeschrieben
(Bestands-Inkonsistenz); geänderte Belege gehen in die Vier-Augen-Prüfung.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:26:05 +02:00
2d364f3fb7 feat(review): Vier-Augen-Prüfung für geänderte Lieferscheine
Geänderte Lieferscheine (Artikel entfernt/teil-gutgeschrieben ODER
Geld-Gutschrift) sollen von der Fakturierung gegengeprüft werden, statt
die Menge im ERP zu reduzieren (was den Lagerbestand inkonsistent machte,
weil der Raw-Writeback die ERPframe-Engine umgeht).

- ERP-Writeback: kein Setzen der Belegzeilen-Menge mehr → ERP-Beleg bleibt
  im Original (kein Bestandskonflikt). Geld-Gutschrift wird weiter
  geschrieben (unkritisch, nur Vier-Augen). delivered-at/State/Zahlbed
  bleiben.
- Migration 0031: review_resolved_at/by/note auf deliveries. Der Status
  wird ABGELEITET (Abweichung via credited_quantity / aktueller Gutschrift
  vs. resolved_at), daher: Originalzustand wiederhergestellt ⇒ Flag weg;
  nach Bestätigung erneut geändert ⇒ wieder offen.
- ReviewRepository (Port + Pg-Impl) + Use Cases ListPendingReviews/
  ResolveReview.
- Endpoints: GET /admin/reviews (offene Prüfungen inkl. Änderungsdetails
  aus scan_audit/credit_audit) + POST /admin/reviews/{delivery_id}/resolve
  (Admin-Key).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:02:27 +02:00
1e6dfb10b0 feat(signature): Dateinamen-Schema delivery_{Belegnummer}_signature_{role}.png
Unterschrifts-Dateien folgen jetzt dem Schema
delivery_{Belegnummer}_signature_{customer|driver}.png
(z. B. delivery_V-30690287_signature_customer.png) statt zuvor
{delivery_id}_{role}.png.

- SignatureStorage::save nimmt Belegnummer statt delivery_id; Adapter
  baut den Dateinamen + saubere Sanitisierung des Belegnummer-Anteils
  (Schutz gegen Pfad-Ausbruch, übliche Werte wie V-30690287 bleiben).
- CompleteDeliveryUseCase löst die Belegnummer vor dem Speichern auf.
- Neue Lookup-Methode DeliveryCompletionRepository::belegnummer_for.
- Totes delete_for_delivery (Reconstruktion via delivery_id, keine
  Aufrufer mehr seit Cron-Cleanup) entfernt.

Abwärtskompatibel: bestehende Signaturen werden über die in
delivery_completions gespeicherte Referenz geladen, alte Dateinamen
bleiben lesbar. Nur neue Abschlüsse verwenden das neue Schema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:56:58 +02:00
47eb8ec57d feat(signature): Signaturen beim Report-Upload behalten + Cron-Cleanup nach Frist
Bisher loeschte die Report-Pipeline die Unterschriften nach erfolgreichem
DOCUframe-Upload. Wir brauchen die Signatur-Dateien aber weiterhin, daher:

- ProcessDeliveryReportUseCase: Signatur-Loeschung (delete_for_delivery) aus dem
  Cleanup entfernt + SignatureStorage-Dependency raus (Report-PDF/Bild-Notiz-
  Cleanup bleibt).
- SignatureStorage: neue Methode delete_older_than(max_age) -> Anzahl; lokaler
  Adapter loescht PNGs aelter als die Frist (per mtime).
- Config [signature]: retention_days (Default 90, 0 = aus) + cleanup_cron
  (Default taeglich 04:00).
- main.rs: Signatur-Cleanup-Scheduler (gated retention_days > 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:16:12 +02:00
2f4368ec52 feat(import): ERP-Sync in DB dokumentieren + Startup-Catch-up
Jeder ERPframe-Import (Scheduler, Startup, manuell) wird in der neuen Tabelle
erp_sync_runs protokolliert (target_date, trigger, success, Zaehler, Fehler).
Beim Serverstart synct das Backend das Zieldatum (heute + offset, i.d.R. morgen)
nach, falls dafuer noch kein erfolgreicher Lauf dokumentiert ist — deckt
Erststart UND laengere Unterbrechungen ab, bei denen der Cron-Zeitpunkt verpasst
wurde. Gated ueber [import] enabled.

- Migration 0030_erp_sync_runs
- Port SyncRunRepository (+ SyncRunRecord, SyncTrigger)
- Adapter PgSyncRunRepository
- ImportErpToursUseCase dokumentiert jeden Lauf; neues execute_with(date, trigger)
- main.rs: Repo verdrahtet, Scheduler-Trigger, Startup-Catch-up (tokio::spawn)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:23:12 +02:00
c65e13485d keycloak_bootstrap: parametrisierbar für baremetal-Prod
Das Bootstrap-Skript funktioniert jetzt gegen jede Keycloak-Instanz (Dev-Docker
wie baremetal-Prod) und nimmt Realm + Server-Adresse als Parameter:
- --url <basis-inkl-port>, --realm <name> (überschreibt den Namen in der
  JSON-Payload), --admin/--admin-password.
- --provisioner-secret <s>: ersetzt das Dev-Secret in der Payload (Prod).
- --no-test-user: entfernt "testfahrer" aus der Payload (Prod).
- Docker wird per Default NICHT mehr angefasst (Prod=baremetal); nur via --up.
- Alle Optionen auch via Env; CLI hat Vorrang.

README: parametrisierte Nutzung (Dev + Prod-Beispiel) + Abschnitt "Wie die
Authentifizierung funktioniert" (Skript→Keycloak Admin-Grant; Laufzeit
App↔Backend↔Keycloak: PKCE-Login, JWKS-JWT-Validierung mit aud/iss, Provisioner
client-credentials, X-Admin-Api-Key für /admin).

Verifiziert gegen Dev-Keycloak: custom Realm + überschriebenes Secret +
--no-test-user erzeugt korrekten Realm; Test-Realm danach wieder entfernt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 18:24:55 +02:00
f3e4c008ce Keycloak-Bootstrap-Skript (Realm-Import via Admin-REST-API)
tool/keycloak_bootstrap.sh importiert den Realm `holzleitner` idempotent über
die Keycloak-Admin-REST-API und konfiguriert ihn passend zum Backend (Client
holzleitner-app mit PKCE + Audience-Mapper holzleitner-api + personalnummer-
Claim, Rolle driver, Service-Account-Client holzleitner-provisioner, Test-User).

Löst den compose-`--import-realm`-Gotcha (greift nur beim ersten Start mit
leerem Volume): das Skript wendet die Realm-JSON jederzeit an.
- Startet Keycloak bei Bedarf (docker compose), wartet auf Erreichbarkeit,
  holt Admin-Token, legt den Realm via POST /admin/realms an.
- Default non-destruktiv (überspringt vorhandenen Realm); --reset löscht +
  importiert neu (Warnung: provisionierte Fahrer-Konten gehen verloren).
- Verifiziert die Clients und gibt die passenden [keycloak]-config.toml-Werte
  aus. Deps: bash + curl + python3 (kein jq).

Verifiziert: Import gegen Test-Realm → Token für testfahrer trägt
aud=holzleitner-api, personalnummer=1001 (int), Rolle driver.

README: Keycloak-Abschnitt aktualisiert (Import-Gotcha + Skript).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 18:20:18 +02:00
d30d43df3a Backend als Windows-Dienst registrierbar (SCM, wie Mail-Client)
Das Backend kann jetzt — analog zum Mail-Client — als Windows-Dienst laufen.

- main() refaktoriert: App-Logik in run_app(shutdown, service_mode); eigene
  tokio-Runtime statt #[tokio::main]. Windows startet zuerst den SCM-Dispatcher,
  fällt bei interaktivem Start auf Konsolenmodus zurück (--console erzwingt ihn).
- src/service.rs (windows-only): SCM-Integration via windows-service-Crate,
  Stop/Shutdown-Handler, Running/Stopped-Status. Setzt das Arbeitsverzeichnis
  aufs EXE-Verzeichnis (Dienst startet sonst in System32), damit config.toml/
  data/logs daneben liegen. Fallback-Log bei Boot-Fehler.
- Graceful Shutdown: GSD-Lizenz-Freigabe in den Serve-Wrapper gezogen (greift
  in beiden Modi); Stop-Trigger ist das übergebene shutdown-Future.
- Logging: Konsolenmodus → stderr (wie bisher); Dienst-Modus → rollende
  Tagesdatei (tracing-appender) unter [logging] dir (Default logs/).
- install-service.ps1 / uninstall-service.ps1 (Dienst "Holzleitner Backend").
- README: Windows-Dienst-Abschnitt; .gitignore: /logs + Fatal-Log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 18:12:12 +02:00
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
438040acce Initial: Rust-Backend mit Clean Architecture (domain/application/infrastructure/api)
Vier-Crate-Workspace mit:
- Domain: Account, Car, Tour, Delivery, DeliveryItem, DeliveryNote, Customer,
  Article, Warehouse, ScanState, AuditAction — alle mit serde + feature-gated
  utoipa::ToSchema.
- Application: Ports (TourRepository, DeliveryRepository, ScanRepository,
  DeliveryNoteRepository, CarRepository, AuthService) und Use Cases.
- Infrastructure: Postgres-Adapter via sqlx (PgTourRepository etc.) +
  Keycloak-AuthService mit JWKS-Cache + OIDC-Discovery.
- API: Axum 0.8, utoipa-OpenAPI + Swagger-UI, JWT-Bearer-Middleware,
  AuthenticatedUser-Extractor.

Endpoints:
- GET /me/tours/today, /tours/{id}, /accounts/{pn}, /me/cars, /health
- POST /sync/tour, /scans (bulk + idempotent via clientScanId),
  /deliveries/{id}/{hold,resume,cancel,complete,notes}, /me/cars
- PUT /tours/{id}/delivery-order, /deliveries/{id}/assigned-car, /me/cars/{id}
- PATCH /me/cars/{id}

Datenmodell:
- 6 Migrationen (accounts, tours/deliveries/items + Stammdaten,
  scan_audit mit clientScanId-UNIQUE, state_reason refactor,
  delivery_notes, cars + FKs nachziehen).
- Business-stabile Beleg-Keys (belegart_id, belegnummer) für ERP-Sync.
- Append-only scan_audit + embedded scan_state als doppelte Wahrheit.

Dev-Setup:
- docker-compose mit Postgres 17 + Keycloak 26
- Keycloak-Realm 'holzleitner' mit Public-Client (PKCE), Testfahrer
  (PN 1001) + Audience-/Personalnummer-Mapper
2026-05-14 22:28:31 +02:00