Beleg-, Liefer-, Rechnungsadresse, Ansprechpartner und Kundenstamm tragen
jetzt neben Namensblock und Kanälen auch ihre Anschrift (Straße, Nr., PLZ,
Ort, Land aus dem Länderstamm, Adresszusatz), sofern im ERP gepflegt.
Migration 0035 ergänzt die Spalten an delivery_contact_sources;
ContactSource.address in der API.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Sync nimmt die Lieferadresse als Ganzes: mit eigener LieferAdressId
alle Felder aus ihr, sonst alle aus der Belegadresse. Das bisherige
COALESCE je Feld hat leere Felder mit Werten des Bestellers gefüllt
(z. B. dessen Ortsteil an eine fremde Anschrift gehängt).
- ERP-Freitext `Adressen.Land` (praktisch Ortsteil/Etage/Hinweis) wird
zum Adresszusatz `Address.addition`; `country` kommt jetzt aus dem
Länderstamm (`LandID` → `Laender.Land`). Deutschland-Kürzel und reine
Landnamen sind kein Zusatz.
- Migration 0034: `customers.address_addition`, `deliveries.snap_addition`;
Bestandstext aus `country` wandert in den Zusatz.
- Lieferbericht und Beleg-Ansicht: Land nur außerhalb Deutschlands,
Zusatz als eigene Zeile „Zusatz: …".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Weicht Lieferadresse oder Empfänger (ERP-LieferAdressId) vom Besteller ab,
führt der Abschnitt „Kunde & Lieferadresse" Besteller und Empfänger
getrennt auf — jeweils mit Anschrift und Telefonnummern — plus Hinweis
„Abweichende Lieferadresse". Sonst bleibt der Abschnitt unverändert.
Erkennung wie in der App: andere Anschrift ODER anderer Name an der
Lieferadresse (gegen die Belegadresse), Schreibweise egal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ohne offenen Betrag gab es im Abschnitt "Zahlung" keine Aussage zur
Abwicklung, nur die Zahlungsmethode vom Beleg (z. B. "Bar"). Jetzt steht
dort "Zahlungsabwicklung: Nicht erforderlich, ..." mit Begruendung:
bereits bei Bestellung bezahlt, durch Anzahlung und Gutschrift oder nur
durch Gutschrift ausgeglichen, bzw. kein Warenwert. Unit-Tests dazu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CompleteDeliveryAcknowledgements.internalNote (optional, getrimmt,
max. 2000 Zeichen) wird atomar mit dem Abschluss gespeichert
(delivery_completions.internal_note)
- Bewusst keine delivery_notes-Zeile: loest weder die Kunden-Bestaetigung
der Notizen aus noch erscheint sie im Notizen-Step der App
- Lieferbericht: eigener Abschnitt "Interne Notiz" mit Zeitpunkt und Fahrer
- Bericht: Betrag in "Betrag erhalten" ans Zeilenende (Helvetica setzt nach
dem Euro-Zeichen zu eng)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
accessTokenLifespan und ssoSessionIdleTimeout standen beide auf 1800 s. Die
App refresht erst bei Ablauf des Access-Tokens (30 min) - genau dann war die
SSO-Session wegen '30 min idle' bereits beendet (invalid_grant). Die Session
ueberlebte dadurch praktisch nie den ersten Refresh: 'Sitzung abgelaufen'
etwa alle 30 Minuten.
ssoSessionIdleTimeout 1800 -> 43200, ssoSessionMaxLifespan 36000 -> 43200
(12 h). Achtung: --import-realm wirkt nur beim ERSTEN Start; ein bestehendes
Realm (persistentes Volume) muss in der Admin-Konsole angepasst werden.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Job::new_async interpretiert den Cron-Ausdruck in UTC - '0 0 17 * * *'
feuerte dadurch erst um 19:00 deutscher Zeit (CEST), der 17-Uhr-Import
fuer 'morgen' blieb aus. Alle drei Scheduler (ERP-Import, Report-Retry,
Signatur-Cleanup) nutzen jetzt Job::new_async_tz mit der konfigurierten
Fach-Zeitzone, sodass die Uhrzeit in der Config als Ortszeit gilt.
Hinweis: tokio-cron-scheduler ermittelt den UTC-Offset einmalig beim
Anlegen des Jobs (Serverstart); ein DST-Wechsel greift erst nach Neustart.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Lieferbericht zeigte UTC (08.09. 22:54 statt 09.09. 00:54): renderer.dt()
formatierte DateTime<Utc> ohne Umrechnung. Weitere Stellen hingen an
chrono::Local (OS-Zone des Hosts) bzw. hardcodiertem 'Europe/Berlin'.
Neu: server.timezone (IANA, Default Europe/Berlin, chrono-tz) und
durchgaengig UTC-Instant -> konfigurierte Zone:
- Lieferbericht: alle Zeitstempel (Abgeschlossen, Notizen, Scans, erzeugt am)
- Belegansicht (fmt_datetime)
- ERP-Rueckschreibung delivered_at (Wanduhrzeit)
- 'heute'-Ableitung: /me/tours, /dev/resync, /admin/import-erp,
Import-Cron + Startup-Catch-up
- Completion-Repo: AT TIME ZONE als Bind-Param statt Literal
Speicherung bleibt UTC (TIMESTAMPTZ / DateTime<Utc>) - nur Anzeige und
Kalendertag-Ableitung nutzen die Zone.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Das Makro ordnet den Lieferbericht dem Vorgang des Kunden zu und braucht
dafuer jetzt zusaetzlich die im ERP hinterlegte Kundennummer:
{ objectId, belegnummer, kundennummer }
- AssignReportRequest + Gateway-Port um kundennummer (String) erweitert
- AttachmentRepository.delivery_customer_number(): liest
customers.erp_customer_id (= ERP Kunden.Kundennummer) ueber
deliveries.customer_id
- ProcessDeliveryReportUseCase liest die Nummer aus der DB (nicht aus dem
Render-Ergebnis), damit sie auch im Retry-Pfad ohne erneutes Rendern vorliegt
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fahrer können jetzt die Tour eines beliebigen Datums abrufen (z. B. morgen
vorab). Ohne date-Param bleibt serverseitig 'heute' (Local::now bzw.
Dev-today_override); Praezedenz: requested_date > today_override > Uhr.
Endpoint umbenannt /me/tours/today -> /me/tours (semantisch sauberer).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
setzt CARGO_TARGET_DIR auf lokale Platte und baut Release fuer
x86_64-pc-windows-msvc. Auszufuehren in der x64 Native Tools Prompt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'Heute' wurde per Utc::now().date_naive() bestimmt. Bei CEST (UTC+2)
wechselte der Tag dadurch erst um 02:00 Ortszeit — der Fahrer sah nach
Mitternacht noch die Touren von gestern. Umgestellt auf Local::now() in
list_my_tours_today sowie den Import-/Resync-Defaults und dem
Scheduler-Catch-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Konsistent zur App-Umbenennung (Step "Services" → "Checkliste"): die
Report-Sektion für delivery_services heißt im PDF jetzt "Checkliste".
Nur Anzeige/Wording (+ Kommentare), Datenmodell unverändert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beim Mengen-Rückschreiben werden jetzt auch die von der ERPframe-Engine
mitgeführten abgeleiteten Felder gesetzt, damit der Beleg konsistent bleibt
(Lagerbestand bucht das Lager weiterhin manuell — keine Lagerbewegung):
- Belegzeile: StatUmsatz, StatKosten (linear aus Alt-Stand skaliert),
Lagerverteilung (XML neu), row_update_time. VPEMenge NICHT (berechnete
Spalte → Fehler 271).
- Belegkopf: zusätzlich StatKosten, kalk (Kalkulations-XML), Druckkennzeichen,
row_update_time.
- Entfernt: mark_delivered (_SV_DELIVERY_DELIVERED_AT/_SV_DELIVERY_STATE) —
wird nicht mehr benötigt.
Alle geschriebenen Spalten gegen sys.computed_columns verifiziert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
/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>
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>
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>
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>
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>
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>
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>
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>
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>
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>