README.md 26 KB

GlobalCube Enterprise Planning, Information & Controlling (gcepic)

Das ultimative Tool für:

  • Forderungsmanagement
  • Offene Aufträge
  • Planung
  • Fahrzeug-Auftragseingang

Installation

Zunächst muss uv, der Python-Paketmanager, installiert werden:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Im Anschluss git-Repository klonen (am Beispiel von Ordner C:\Projekte - im Grunde beliebig):

cd C:\Projekte
git clone https://git.global-cube.com/GlobalCube/Reisacher-Forderung.git

Git erstellt einen Unterordner C:\Projekte\Reisacher-Forderung, in dem das gesamte Projekt abgelegt ist. Jetzt müssen alle Requirements erfüllt werden: passende Python-Version, benötigte Module mit entsprechenden Abhängigkeiten. Das übernimmt uv

Konfiguration

cd C:\Projekte\Reisacher-Forderung
uv sync
copy .env.example .env

Danach müssen in .env die Datenbank-, LDAP-, Proxy- und JWT-Platzhalter ersetzt werden.

Danach kann der Webserver gestartet werden:

.venv\Scripts\activate.bat
uvicorn app.main:app --reload --host 0.0.0.0 

Die Konsole meldet dann Folgendes:

(Reisacher-Forderung) PS C:\Projekte\Reisacher-Forderung> uvicorn app.main:app --reload --host 0.0.0.0                                
INFO:     Will watch for changes in these directories: ['C:\\Projekte\\Reisacher-Forderung']
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [41364] using WatchFiles
INFO:     Started server process [23216]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Das Repository ist nun bereit für die Nutzung.

Nutzung

Öffne http://127.0.0.1:8000 im Browser.

Authentifizierung und Deployment

Die Anwendung läuft im Produktivbetrieb ausschließlich hinter dem IIS-Reverse-Proxy auf 127.0.0.1:8094. IIS entfernt einen eventuell vom Client gelieferten Header X-Remote-User und setzt ihn nur für erfolgreich per Windows Authentication bestätigte Benutzer neu. Fehlt der Header, bietet die Anwendung die Anmeldung gegen Active Directory über LDAPS an.

  1. migrations/001_authentication.sql auf der Anwendungsdatenbank ausführen.
  2. .env.example nach .env kopieren und alle Platzhalter ersetzen.
  3. Ein eigenes zufälliges JWT_SECRET mit mindestens 32 Byte konfigurieren.
  4. Das LDAPS-Zertifikat des Domain Controllers auf dem Server als vertrauenswürdig installieren.
  5. Die IIS-Proxyadresse in TRUSTED_PROXY_IPS eintragen und den FastAPI-Port nicht extern freigeben.
  6. Den Windows-Dienst mit webservice/install_service.bat installieren.

Die Rollenprioritäten sind: Buchhaltung (400), Standortleitung (300), Abteilungsleitung (200) und Mitarbeiter (100). Berechtigungen werden bei jedem Request aus dbo.Benutzer_Rechte gelesen; leere Benutzerkennungen erteilen keine Rechte.

Tests werden ohne zusätzliche Testabhängigkeiten ausgeführt:

.venv\Scripts\python.exe -m unittest discover -v

Filter auf der Forderungsdetailseite

Kommentare und Buchungsbelege starten jeweils mit „Diese Rechnung“ und lassen sich unabhängig auf offene Rechnungen, das Fahrzeug oder die gesamte Debitorhistorie erweitern. Die Auswahl gilt für die geöffnete Seite und wird beim Neuladen zurückgesetzt. Die Fahrzeugansicht bleibt auf denselben Debitor begrenzt und ist ohne Fahrzeugnummer deaktiviert. Offen bedeutet Beleg_Offen <> 0; Zahlungen werden über Bezug_Beleg_Nr zugeordnet.

Alle Filter bleiben im Mandanten der Ausgangsrechnung. Nach erfolgreicher Rechteprüfung dieser Rechnung umfasst die erweiterte Ansicht ausdrücklich auch andere Standorte und Abteilungen des Debitors. Der direkte Zugriff auf andere Rechnungen wird separat geprüft. Neue Kommentare gehören immer zur geöffneten Rechnung, auch in erweiterten Ansichten. Filterwechsel erhalten ungespeicherte Eingaben. Buchungsbelege sind auf 1.000 Zeilen begrenzt; bei Erreichen dieses Limits erscheint ein Hinweis.

Die lesenden Endpunkte /app/forderungen/details/{primary_key}/kommentare und /app/forderungen/details/{primary_key}/buchungen liefern HTML-Fragmente für HTMX. scope akzeptiert invoice (Standard), open, vehicle und debtor; andere Werte werden mit HTTP 422 abgelehnt. Kommentar-POSTs übergeben die aktuelle Auswahl als comment_scope. Datenbankschemaänderungen sind nicht erforderlich.

Debug-Anmeldung ohne LDAP

Für Entwicklung und manuelle Tests diese Werte in .env setzen und die Anwendung neu starten:

ENVIRONMENT=development
DEBUG_AUTH=true
COOKIE_SECURE=false

Unter /login dieselbe Benutzerkennung in beide Felder eingeben, z. B. Benutzername winter und Passwort winter. Beide Eingaben müssen exakt übereinstimmen. Die Kennung wird anschließend wie beim AD-Login normalisiert. Eine gültige Zuordnung in dbo.Benutzer_Rechte zu einer aktiven Rolle in dbo.Rollen ist weiterhin erforderlich; mehrere Berechtigungszeilen werden wie bisher vereinigt. Ein LDAP-Server ist dafür nicht nötig, die konfigurierte SQL-Datenbank und die Authentifizierungsmigration hingegen schon.

Im Debugmodus werden IIS-Identitätsheader ignoriert, damit verschiedene Benutzer über das Formular getestet werden können. JWT, CSRF, Logout und die Rechteprüfung pro Request bleiben aktiv; der Token enthält auth_source=debug. Benutzername wird als Anzeigename verwendet, E-Mail bleibt leer. Nach dem Deaktivieren von DEBUG_AUTH werden diese Tokens nicht mehr akzeptiert. Standardmäßig ist DEBUG_AUTH=false; außerhalb der Umgebungen development, debug und test verweigert die Anwendung den Start mit aktivem Debug-Login. COOKIE_SECURE=false ist nur für lokale HTTP-Tests gedacht; bei HTTPS auf true setzen.

Forderungsdashboard

Das Dashboard verwendet dieselbe berechtigte Belegabfrage wie die Liste. Anzahl und Beleg_Offen enthalten auch Gutschriften, negative Beträge und Nullbeträge; jeder Beleg wird anhand von Mandant und Belegnummer einmal gezählt. Die Berechtigungsprüfung erfolgt vor der Aggregation über Benutzer, Hauptbetrieb, Standort und Abteilung. Beträge werden mit Dezimalarithmetik summiert. An den Browser gehen nur berechtigte Aggregate.

Die Rubriken Altersstaffel, Standort und Verursacher zeigen horizontale Balken mit Umschalter zwischen Betrag und Anzahl. Die Stapelung lässt sich zwischen Abteilung und Wiedervorlage wählen (stack=abteilung|wiedervorlage). Negative Beträge liegen links der Null. Verursacherüberschreibungen und die effektive Wiedervorlage werden wie in der Liste berücksichtigt. Für die Staffelung gilt zuerst Forderungen_Details.Wiedervorlage, ersatzweise Beleg_Faellig_Datum. Maßgeblich ist der Berliner Kalendertag: „Überfällig“ liegt vor heute, „In 3 Tagen fällig“ umfasst heute bis heute + 3 Tage, „Noch nicht fällig“ heute + 4 bis einschließlich heute + 14 Tage und „zurückgestellt“ alles danach. Fehlen beide Datumsfelder, bleibt der Beleg separat als „Ohne Datum“ sichtbar. Die Wiedervorlagestaffelung beeinflusst derzeit nur die Darstellung im Dashboard; beim Öffnen der Liste wird diese Stapelung nicht als Datumsfilter übernommen. Alte Dashboardadressen mit page=Wiedervorlage wechseln zum Standort mit Wiedervorlagestapelung; bestehende Listenlinks bleiben gültig.

Mouseover zeigt beide Kennzahlen für Segment und Gruppe; Antippen fixiert die Anzeige, Escape schließt sie. „Liste öffnen“ startet einen neuen Tab. Die aufklappbare Datentabelle bietet dieselben Werte und Links per Tastatur, auch für Beträge von null Euro. Suche und Diagrammseiten mit jeweils 20 Gruppen ändern nicht die Gesamtkennzahlen. page=Altersstaffel|Standort|Verursacher, metric=betrag|anzahl und stack bestimmen die initiale Ansicht; Standard ist „Nach Standort“. Chart.js 4.6.1 wird erst beim Dashboardaufruf geladen; bei einem Ladefehler bleibt die Datentabelle verfügbar. Es gibt keine neuen Schreibaktionen.

Die standardmäßig ausgeschaltete Checkbox „Nur Rechnungen“ (only_invoices=true) begrenzt Kennzahlen, Diagramme und Datentabelle exakt auf Rechnung_Gutschrift = 'Rechnung'. Rechnungen mit negativem oder null offenem Betrag bleiben enthalten. Die Auswahl bleibt bei Rubrik- und Kennzahlwechseln sowie beim Neuladen erhalten und gilt auch für sämtliche verlinkten Listen; dort wird dafür RechnungGutschrift=Rechnung gesetzt.

Dashboardlinks verwenden die normalen Listenfilter: Standort setzt Mandant und Filiale, Altersstaffel setzt Staffel, Verursacher setzt den Namen in Verursacher. Gleichnamige Verursacher werden deshalb gemeinsam dargestellt. Ein Abteilungssegment ergänzt Abteilung. Alle übernommenen Werte sind in den vorhandenen Filterfeldern sichtbar, änderbar und entfernbar. Es wird kein zusätzlicher Dashboardfilter erzeugt. Gruppen ohne Wert verwenden die reguläre Auswahl „Ohne Wert“ (__empty__), die NULL und leere Zeichenketten umfasst. Textfelder behalten ihre Teiltextsuche, Auswahlfelder vergleichen die Schlüssel exakt. Alte Listenlinks mit dashboard_segment werden in normale Listenparameter umgewandelt und auf diese URL weitergeleitet. Es gibt keinen versteckten Dashboardfilter und keine separate Dashboardfilter-Anzeige mehr. Bereits gesetzte normale Filter haben Vorrang.

Darstellung der Forderungsliste

Die Filter bleiben vollständig sichtbar und sind in Organisation, Kunde und Beleg sowie Bearbeitung und Wiedervorlage gegliedert. Beim Scrollen der Seite dockt der Tabellenkopf unter der Navigation an; bei schmalen Fenstern lässt sich die Tabelle seitlich verschieben. Kommentare und Antragsinformationen werden je Beleg über „Anzeigen“ eingeblendet.

Auswahl- und Textfelder unterstützen mehrere Werte in suchbaren Dropdowns mit Checkboxen. Textfelder erlauben zusätzlich freie Suchbegriffe, die mit Enter übernommen werden. Änderungen werden beim Schließen des Dropdowns angewendet; einzelne Werte können direkt entfernt werden. Während die Liste aktualisiert wird, erscheinen ein Wartesymbol und „Filter werden angewendet …“; sämtliche Filtereingaben sind bis zum Abschluss gesperrt. Auch bei einem abgebrochenen oder fehlgeschlagenen Aufruf wird die Sperre aufgehoben. Innerhalb eines Feldes gilt ODER, zwischen Feldern UND. Datumsgrenzen bleiben einzelne Werte. Vorschläge beachten alle anderen Filter, aber nicht die eigene Feldauswahl. Sie stammen ausschließlich aus berechtigten Forderungen.

Die GET-Parameternamen bleiben unverändert, beispielsweise Filiale=10,20. Jeder Einzelwert wird URL-kodiert, bevor die Werte mit Kommas verbunden und als GET-Parameter kodiert werden. Die Trennkommas bleiben dabei sichtbar (Mandant=1,2), auch beim Filtern und Nachladen. Ein Komma im Wert erscheint dadurch als %252C in der fertigen URL, beispielsweise Kunde=M%25C3%25BCller%252C%2520GmbH,Meier. Nach dem normalen GET-Dekodieren zuerst an Kommas trennen und danach jeden Einzelwert genau einmal dekodieren. Einfache bestehende Einzelwert-Links bleiben gültig. Leere Einträge und doppelte Werte werden entfernt.

GET /app/forderungen/liste/vorschlaege nimmt die Listenfilter sowie field und search entgegen. Die authentifizierte Antwort enthält höchstens 50 eindeutige, sortierte items mit value und label sowie has_more. Bei weiteren Treffern muss die Suche eingegrenzt werden. Ungültige Felder oder Dashboardfilter ergeben HTTP 400.

Die Fußleiste zeigt Anzahl und offenen Gesamtbetrag aller gefilterten Treffer, unabhängig von den bereits nachgeladenen Zeilen. Aktive Filter lassen sich dort aufklappen. Auch ohne Treffer bleiben die Filter bedienbar. Die gemeinsamen Gestaltungsregeln liegen in app.css, die Ansichten ergänzen list.css beziehungsweise details.css.

Rückkehr aus der Detailseite und erneute Anmeldung

„Zurück“, „Speichern & Zurück“ und Browser-Zurück laden die Liste frisch, behalten die Filter und führen zur zuvor geöffneten Rechnung. Dazu wird auf der Detailseite eine validierte Listenadresse in return_to und auf der Liste die Belegkennung in focus mitgeführt. Liegt der Beleg hinter dem ersten Nachladeblock, liefert der erste Aufruf die Zeilen bis einschließlich seines Blocks; danach läuft das Nachladen normal weiter. Ist der Beleg nicht mehr enthalten, bleiben die Filter bestehen und ein Hinweis erscheint. Der Browser hält Rückkehrkontexte getrennt je Tab und Benutzer; es werden keine Kommentare oder Formulareingaben im Browserspeicher abgelegt.

Bei abgelaufener Anmeldung führen HTMX-Anfragen nach dem Login auf die vollständige gefilterte Liste beziehungsweise Detailseite. Kommentar- und Buchungsfragmente sowie technische Nachladeseiten sind keine Login-Rücksprungziele. Unterbrochene Schreibaktionen werden nicht automatisch wiederholt; ungespeicherte Eingaben werden nicht gesichert.

Verursacher ändern

Der Stift neben dem Verursacher öffnet in Liste und Detailseite dasselbe Auswahlmodal. Die Benutzer stammen aus dbo.Benutzer_Rolle (Benutzer_ID, Name, Rolle_Name) und lassen sich nach Rolle filtern. Mehrere Rollen erzeugen keine doppelten Benutzer oder Rechnungszeilen. Jeder Benutzer mit Belegzugriff darf die Zuordnung ändern.

Gespeichert wird ausschließlich dbo.Forderungen_Details.Verursacher_Benutzer_ID. „Auf ursprünglichen Verursacher zurücksetzen“ entfernt diese Überschreibung. Auf der Detailseite steht die aktuelle Zuordnung im Bereich „Bearbeitung“ oberhalb der Wiedervorlage; in den Grunddaten bleibt der ursprüngliche Verursacher sichtbar. Das Modal speichert unabhängig vom übrigen Formular und erhält dessen ungespeicherte Eingaben sowie Kommentarentwürfe. Die normalen Speicherbuttons ändern diese Zuordnung nicht. In der Liste führt das Speichern zu einem frischen Aufruf mit denselben Filtern und Fokus auf die Rechnung; entfällt sie durch die Filter, erscheint ein Hinweis.

Bei abgelaufener Anmeldung wird nach dem Login die vollständige Liste beziehungsweise Detailseite geöffnet. Eine unterbrochene Zuordnungsänderung wird nicht automatisch wiederholt. Es sind keine neuen Datenbanktabellen oder Migrationen erforderlich.

Wiedervorlage in der Liste ändern

Das Wiedervorlagedatum ist ein Button mit einem Datumsmodal. „Heute“ und „Heute + 7 Tage“ setzen das Eingabefeld anhand des aktuellen Datums in Europe/Berlin; erst „Speichern“ übernimmt die Änderung. Ein fehlendes Datum lässt sich über „Festlegen“ ergänzen. Die Änderung schreibt ausschließlich Forderungen_Details.Wiedervorlage und den Kommentar WV auf dd.mm.yyyy angepasst mit dem angemeldeten Benutzer und Berliner Zeitstempel. Eine optionale mehrzeilige „Begründung“ wird nach einem Doppelpunkt an diesen Kommentar angehängt; Leerzeichen am Anfang und Ende werden entfernt. Bei Validierungs- oder Speicherfehlern bleiben Datum und Begründung im Dialog erhalten. Datum und Kommentar werden gemeinsam in einer Transaktion gespeichert. Ein unverändertes Datum erzeugt keinen weiteren Kommentar. Danach wird die Liste mit den bestehenden Filtern frisch geladen und die Rechnung fokussiert; passt sie nicht mehr zum Filter, erscheint der entsprechende Hinweis. Nach einem Login wird ein unterbrochener Speichervorgang nicht automatisch wiederholt.

Chat und Wiedervorlage speichern Kommentare über dieselbe Funktion. Hat der Autor in dbo.Benutzer_Rolle mindestens eine Zuordnung mit Rolle_ID = 'FIBU', erhält der neue Kommentar Aktuell_Fibu = 1, andernfalls Aktuell_Abteilung = 1; das jeweils andere Flag des neuen Kommentars ist 0. Bei bisherigen Kommentaren derselben Kombination aus Client_DB und Beleg_Nr wird nur das entsprechende Flag auf 0 gesetzt. Flags, Kommentar und gegebenenfalls Datum werden gemeinsam gespeichert oder zurückgerollt. Eine SQL-Server-Sperre auf dem Forderungskopf serialisiert beide Speicherwege. Reine Datei-Uploads verändern die Flags nicht. Die beiden Flagspalten und Benutzer_Rolle.Rolle_ID müssen bereits im Datenbankschema vorhanden sein.

Dokumente im Kommentarbereich

Über die Büroklammer oder durch Ablegen auf dem Kommentarfeld können mehrere Dateien beliebigen Typs ausgewählt werden. Die Auswahl mit Name, Größe und Entfernen-Schaltfläche bleibt bis „Abschicken“ ausschließlich im Browser. Kommentartext ist optional; reine Datei-Uploads erzeugen keinen Chat-Eintrag. Beim Verlassen oder Neuladen geht eine noch nicht abgeschickte Auswahl verloren.

LOCAL_STORAGE="C:\\Projekte\\Reisacher-Fileserver"
UPLOAD_MAX_FILE_SIZE_MB=50

Die Größenbegrenzung gilt je Datei, einschließlich leerer Dateien: 50 MB entsprechen 50 × 1024² = 52.428.800 Bytes. Auswahl und Server prüfen die Grenze. Neue Dateien liegen unter LOCAL_STORAGE/{Client_DB}_{Beleg_Nr}/{Originalname}. Altdateien unter LOCAL_STORAGE/{Client_DB}/{Beleg_Nr} bleiben sichtbar und herunterladbar; es gibt keine Migration. Unicode-Namen bleiben erhalten. Unter Windows unzulässige Dateinamen werden mit einer Fehlermeldung abgelehnt.

Bei Namenskonflikten in beiden Belegordnern wird die ältere Datei in ihrem jeweiligen Ordner als Name_YYYYMMDD_HHMMSS_ffffff.ext archiviert (Europe/Berlin, bei Bedarf mit Zähler). Eine prozessübergreifende Belegsperre unter .upload-locks schützt die Ablage auch bei mehreren Workern. Dateien werden zuerst unter .upload-staging vollständig geschrieben und geprüft; danach erfolgt die Übernahme mit Rücknahme der Archivierung, wenn diese Datei nicht abgelegt werden kann. Diese internen Ordner sind über die Downloadroute nicht erreichbar. Der Ablagepfad darf nicht als öffentliches IIS-Verzeichnis oder als statischer Webpfad freigegeben werden.

Das Anwendungskonto benötigt Lesen, Auflisten, Anlegen, Schreiben, Umbenennen und Löschen im Ablagestamm, beiden Belegstrukturen sowie den internen Arbeitsordnern. Für diese Ordner nur den erforderlichen Betriebskonten Zugriff geben. Alle Worker müssen denselben Stamm mit funktionierenden Dateisperren verwenden. Downloads prüfen weiterhin die Belegrechte.

Erfolgreiche Dateien bleiben bei Teilerfolgen oder fehlgeschlagener Kommentarspeicherung bestehen. Nur fehlgeschlagene Dateien und nicht gespeicherter Text bleiben zur erneuten Übermittlung in der Auswahl. Nach einer erneuten Anmeldung werden Uploads nicht automatisch wiederholt. Kommentarhistorie und Dateiliste aktualisieren sich ohne Seitenneuladen.

Bei IIS system.webServer/security/requestFiltering/requestLimits/maxAllowedContentLength (in Bytes) sowie gegebenenfalls vorgeschaltete Proxy- und Hosting-Limits auf die gesamte Multipart-Anfrage abstimmen: Anzahl gleichzeitig versandter Dateien × Dateilimit plus Kommentar und Multipart-Overhead. Das Dateilimit ist kein Gesamtlimit. Beispielsweise brauchen vier Dateien à 50 MB mehr als 209.715.200 Bytes; 220.200.960 Bytes (210 MiB) lassen hierfür zusätzlichen Overhead zu. Das übliche IIS-Standardlimit von 30.000.000 Bytes reicht bereits für eine 50-MB-Datei nicht aus. Eine serverseitig mit 413 abgewiesene Anfrage bleibt in der Auswahl; dann weniger Dateien gleichzeitig senden oder das Betriebslimit anpassen. Genügend temporären Speicher auch für den Multipart-Parser des Servers einplanen.

Uploadtests verwenden ausschließlich temporäre Verzeichnisse: python -m unittest discover -s tests -q.

Zentrales Logging

Webanwendung und Hilfsprogramme verwenden dieselbe idempotente Logging-Konfiguration. Entwicklung: python server.py; Dienstbetrieb: python server.py --no-reload. webservice/webservice.bat verwendet diesen Einstieg ohne zusätzliche Dateiumleitung. Nach Änderungen an Konfiguration oder Dienstskript den Dienst neu starten.

LOG_LEVEL=INFO
LOG_DIR=logs
LOG_TO_FILE=true
LOG_TO_CONSOLE=true
LOG_RETENTION_DAYS=30
LOG_SQL_STATEMENT=true

Relative Logverzeichnisse beziehen sich auf den Projektstamm. application.log enthält je Ereignis eine JSON-Zeile; die Konsole zeigt dieselben Ereignisse lesbar an. LOG_LEVEL akzeptiert DEBUG, INFO, WARNING, ERROR und CRITICAL. Beide Ausgaben lassen sich unabhängig abschalten. Die Zeitstempel enthalten Millisekunden und den Europe/Berlin-Offset. Die tägliche Rotation erfolgt dagegen um Mitternacht UTC. Eigene Archive heißen application.log.YYYY-MM-DD; Archive älter als die konfigurierte Aufbewahrungsdauer werden beim Start und bei Rotation entfernt. Frühere Dienstlogs bleiben erhalten.

Alle Prozesse müssen dasselbe Verzeichnis und dieselben Rotationseinstellungen verwenden. concurrent-log-handler koordiniert Schreiben und Rotation über Dateisperren, auch unter Windows. Das Dienstkonto benötigt Auflisten, Anlegen, Lesen, Schreiben, Umbenennen und Löschen für Logdateien, Archive und Sperrdateien im Logverzeichnis. Fehlende Schreibrechte, Rotations- oder Schreibfehler erzeugen eine datenarme Konsolenmeldung; bei Dateiausfall bleibt die Konsole als Fallback verfügbar. Deshalb auch die Dienstkonsole erfassbar halten. Logs nur den erforderlichen Betriebskonten zugänglich machen.

HTTP-Abschlüsse enthalten event=http_completed, eine eigene event_id, request_id, Benutzerkennung (soweit angemeldet), Methode, Pfad, Status und Laufzeit in Millisekunden. Die Antwort liefert dieselbe Request-ID als X-Request-ID. Zur Fehlersuche beispielsweise:

Select-String -LiteralPath .\logs\application.log -Pattern 'REQUEST-ID-AUS-DER-ANTWORT'

4xx werden als WARNING, 5xx als ERROR und andere Anfragen als INFO erfasst. Erfolgreiche Abrufe unter /static/ und /health erscheinen nur mit DEBUG. Loginanforderungen haben zusätzlich event=auth_login_required, auch bei HTMX-Antworten mit HTTP 200. Uvicorns separates Zugriffslog ist abgeschaltet. Unerwartete Exceptions werden einmal technisch erfasst; der HTTP-Abschluss bleibt ein eigenes, über die Request-ID zugeordnetes Ereignis. Fehler nach Beginn einer Streamingantwort behalten den tatsächlich gesendeten Status. Fehler, die IIS oder ein vorgeschalteter Proxy vor Python erzeugt, stehen weiterhin in den jeweiligen Serverlogs.

Datenbankfehler haben event=database_error mit Operation, Abfrageherkunft, query_fingerprint, Exceptiontyp, verfügbaren technischen Fehlercodes und Stackframes (Dateiname, Funktion, Zeile). Templateabfragen tragen ihren SQL-Dateinamen als Herkunft. Für SQL Server werden zusätzlich db_code (nativer Fehlercode, etwa 207), sqlstate und db_message (bereinigte Serverbeschreibung) ausgegeben. db_diagnostics enthält auch weitere ODBC-Meldungen desselben Fehlers mit jeweils Code, SQLSTATE und Beschreibung. Diese Angaben bleiben bei LOG_SQL_STATEMENT=false verfügbar. Zitierte Inhalte, zurückgemeldete Parameterwerte und Wertetupel werden in den Beschreibungen maskiert; es wird nicht die um SQL und Parameter ergänzte SQLAlchemy-Exception ausgegeben. Ausnahme: Bei SQL-Server-Fehler 207 („Ungültiger Spaltenname“ / „Invalid column name“) bleibt der Spaltenname in der Standardmeldung sichtbar, auch bei abgeschaltetem SQL-Text. SQL-Parameter, lokale Variablen und ungeprüfte Exceptiontexte werden nicht ausgegeben. SQL-Kommentare werden entfernt und Zeichenketten- sowie Zahlenliterale maskiert; der SQL-Text wird auf 8192 Zeichen begrenzt. Mit LOG_SQL_STATEMENT=false entfällt auch diese bereinigte Anweisung; Fingerabdruck und Herkunft bleiben erhalten. Abgefangene Datenbankfehler werden ebenfalls erfasst, weitere Logger erzeugen für dieselbe Exception keinen zweiten technischen Datensatz. Ein zusätzlicher Rollbackfehler bleibt sichtbar. Neue Logaufrufe dürfen keine Passwörter, Tokens, Header, Queryparameter, Formularinhalte, Mailbetreffzeilen oder Datei-/PDF-Inhalte enthalten. Binärdaten nur über ihre Größe erfassen.

Hilfsskripte mit Paketimporten starten: python -m app.imap, python -m app.cleanup_comments, python -m app.docuware_search beziehungsweise python filter_export.py. Diese Programme behalten ihre fachlichen Aktionen bei; ein Import allein führt sie nicht aus. Nicht zu Logging-Testzwecken produktiv starten. Loggingtests nutzen ausschließlich temporäre Verzeichnisse und isolierte SQLite-Engines: python -m unittest tests.test_logging -q. Die vollständige Prüfung erfolgt mit python -m unittest discover -s tests -q.

Gemeinsame Dokumentliste / DocuWare offline testen

Der Belegnummer-Button öffnet eine Liste aus DocuWare und lokaler Ablage. Die Detailseite bietet zusätzlich „Offene Belege“, „Kunde“ und „Fahrzeug“. Kunde und Fahrzeug durchsuchen DocuWare direkt; beim Fahrzeug wird die VIN aus dbo.Fahrzeuge verwendet. Lokale Dateien werden aus den passenden aktuellen und historischen Belegen ergänzt, beim Fahrzeug auch über Kundenwechsel hinweg. Die Zuordnung bleibt im Mandanten der geöffneten Forderung. Fehlende Metadaten bleiben leer. Lokale PDFs öffnen als Vorschau, andere Dateien werden heruntergeladen. DocuWare-Ausfälle blenden lokale Dateien nicht aus.

In .env explizit einschalten:

DOCUWARE_MODE=mock
DOCUWARE_MOCK_PATH=data/docuware-sample

Die mitgelieferten Testmetadaten verwenden Mandant 1, Belege R1/R2, Kunde D1 und VIN VIN1/VIN2. Sie verwenden die bisherige Beispiel-PDF ausschließlich als bewusst aktivierte Mockdatei. Für andere Entwicklungsbelege können die Metadaten in einer Kopie angepasst werden. Eine nicht vorhandene Belegnummer simuliert eine leere Suche; "unavailable": true auf Manifestebene simuliert einen Ausfall. Fehlende file-Einträge simulieren einen Fehler beim Dokumentabruf. Der Livemodus greift niemals automatisch auf diese Beispiele zurück.

Auf einem System mit DocuWare-Zugang und bestehender DW_*-Konfiguration:

.venv/Scripts/python.exe -m app.docuware_export --client-db 1 --document-no R1 --document-no R2 --output data/docuware-exports/testlauf
# Optional: --customer-no D1 oder --vehicle-no VIN1 (zusätzliche Suchläufe)

Den gesamten Ausgabeordner in die Entwicklungsumgebung kopieren und DOCUWARE_MOCK_PATH darauf setzen. Ein bestehendes Manifest wird nicht überschrieben. manifest.json enthält Formatversion 1, Metadaten, relative PDF-Pfade und einen Fehlerbericht. Der Exitcode ist bei Teilfehlern 1. Dateinamen werden aus Archiv und Dokument-ID abgeleitet. Zugangsdaten und Sitzungstokens werden nicht exportiert. Produktivexporte unter data/docuware-exports/ sind von Git ausgeschlossen.

„Weiterleiten“ erzeugt eine .eml-Datei mit genau einem Anhang und X-Unsent: 1. Empfänger stammen aus der geöffneten Forderung; fehlende Adressen deaktivieren das jeweilige Ziel. Der Verursacher berücksichtigt die manuelle Zuordnung und wird über AD aufgelöst. Im Debuglogin ist die eigene Adresse leer. Die Anwendung versendet keine E-Mails. Die Bearbeitbarkeit des Entwurfs muss im eingesetzten Mailprogramm geprüft werden; sie hängt vom Client und dessen Dateizuordnung ab. Vor Produktivfreigabe sind außerdem Suchfelder und PDF-Ausgabe am echten Archiv zu prüfen. DW_TIMEOUT begrenzt die einzelnen DocuWare-Anfragen.