Beim Testen des neuen Inline-Nachfüll-Dialogs (Karte 07) gefunden: Sofort-
Nachfüllung während laufender Kontrolle + anschließender Abbruch führte zu
IntegrityError (FK-Verweis aus nachfuellung), da _verwirf_kontrolldaten
jeden Fehlbestand der Kontrolle unbedingt löschte.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Nutzer-Vorgabe: "Ablauf/Charge/SN gehört mit zu den Kontrollen wie auch die
Menge". PUT .../positionen/{material_id} akzeptiert jetzt optional
ablaufdatum/chargennummer/seriennummer und schreibt sie auf die
Objektposition (Vier-Kernbegriffe bleiben unberührt: nur diese drei Felder,
niemals istmenge - das bleibt exklusiv Nachfüllung vorbehalten). Offline-
Queue und PositionCard erweitert: Felder erscheinen nur passend zum
Materialtyp (ablauf_charge -> Datum+Charge, geraet_sn -> SN). Admin-Panel
(ObjektPositionenPanel) bleibt zusätzlich für nachträgliche Korrekturen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Nutzer-Fund: bei keinem Objekt war Chargennummer/Ablaufdatum sichtbar.
Backend-Endpoint (PATCH /objekte/{id}/positionen/{position_id}) existierte
bereits (Prompt 10), war im Frontend nirgends angebunden. Kontroll-Screen
selbst bleibt unverändert (Vier-Kernbegriffe: Kontrolle erfasst nur Menge,
Ablauf/Charge/SN gehören zur Objektposition) - neue Pflege-Ansicht im
Admin-Portal pro Objekt, nur für materialtyp ablauf_charge/geraet_sn.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Nutzer-Fund: "Kompresse 10x10 / 5 / Apr 29" - Wert 47209 in Spalte D ist
Excel-Datum 2029-04-01, kein Artikelnummer. Vollständige Prüfung aller drei
Listen bestätigt: D ist überwiegend ein echtes Ablaufdatum, keine
Artikelnummer-Spalte existiert überhaupt. Material-Abgleich läuft jetzt über
den Namen; ein plausibles Datum in Spalte D klassifiziert das Material als
'ablauf_charge' statt 'standard' (Wert selbst wird nicht gespeichert - gehört
auf objektposition, nicht auf die Vorlage). Bekannte Ausreißer (Platzhalter-
wert 12754, Freitext "Jahr 2023"/"MTK 23") werden per Plausibilitätsfenster
ignoriert statt fehlzuklassifizieren.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Vervollständigt die Stammdaten-Anlage im Frontend (bisher nur Standort/
Material/Objekt), damit ein Pilotbetrieb nicht zwingend Excel-Import
braucht. Jede Sektion eigene Datei unter pages/admin/ (Projekt-Vorgabe:
keine großen Monolith-Dateien).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Explizite Vorgabe: keine wachsenden Monolith-Dateien. AdminPage.tsx (349
Zeilen) in pages/admin/{Standort,Material,Objekt}Section.tsx aufgeteilt,
KontrollPage.tsx (277 Zeilen) in pages/kontrolle/{useKontrolle,
PositionCard,StatusBadge,types} aufgeteilt, services/kontrolle.py (332
Zeilen) in ein Package (lifecycle/erfassung/exceptions) aufgeteilt -
öffentliche API bleibt über __init__.py unverändert re-exportiert, kein
bestehender Import musste angepasst werden. Reines Refactoring, keine
Verhaltensänderung.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
PATCH /materialien/{id} existierte im Backend bereits (Prompt 07), im
Frontend bisher nicht angebunden. Formular dient für Anlage UND Bearbeitung
(Klick auf "Bearbeiten" befüllt dasselbe Formular).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Gemeinsames Stylesheet (Tokens, Karten, Buttons, Badges, Formulare) plus
AppShell (Kopfzeile mit Navigation/Logout) statt Inline-Styles je Screen.
Kein UI-Framework als neue Abhängigkeit - schlankes eigenes CSS, damit
Build/Deploy-Weg unverändert bleibt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Frontend deckte bisher nur den Mitarbeiter-Kontrollflow ab, Stammdaten
mussten per API/Skript angelegt werden (Test-Deployment-Lücke). /admin
zeigt Standort- und Objekt-Anlage (aus aktiver Vorlage), nur für Rolle
administration sichtbar (/auth/me liefert Rollen an AuthContext).
Bewusst kein Bereich/Kategorie/Material-CRUD - kommt aus Excel-Import.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Dritte Ist-Liste (Rucksack PAX SEG) hat abweichende Kopfzeilen-Struktur
(Objekttyp-Name in Spalte C statt Menge) - beim echten Import-Lauf
gefunden. Solche Zeilen sind Titel/Deckblatt, keine Artikelzeilen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Realer Fund beim ersten Import-Lauf gegen den Zielserver: vorlagenposition
erlaubt nur eine Zeile pro Material+Vorlage, Excel-Listen führen aber
manches Material in mehreren Fächern - UniqueViolationError. Fix: Mengen
addieren, Fächer als Liste zusammenführen statt zweite Zeile einzufügen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Import-Skript (backend/scripts/import_excel.py) liest die drei Ist-Listen
per stdlib zipfile/ElementTree (kein openpyxl), erzeugt idempotent
Objekttyp+Beladungsvorlage+Vorlagenpositionen je Datei, Material-Abgleich
über Artikelnummer. Rohdaten-Check bestätigt Mapping D=Artikelnummer
(testphasen.md ergänzt). Ausführung nur auf Zielsystem (Deployment-Regel).
CI: neuer frontend-build-Job (npm install + tsc --noEmit + vite build),
da React-PWA/Flutter bisher unabhängig von der Test-Pipeline waren.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
React/Vite-PWA (frontend/):
- Login, Objektliste (Prompt 11 Screen 1), Kontroll-Screen (Screens 2-6 kompakt:
Start/Objekt-Sperre-Übernahme, Positionserfassung, Abschluss/Abbruch)
- Offline-Härtung (Prompt 17): IndexedDB-Queue (src/offline/queue.ts) puffert
Positions-PUTs lokal, automatische Übertragung bei Reconnect + periodischem
Sync-Versuch, Statusanzeige je Position (nicht gespeichert/wird übertragen/
gespeichert/Fehler), Abschluss-Button bleibt gesperrt bis alles gespeichert
- Nutzt aus, dass PUT /kontrollen/{id}/positionen/{material_id} backend-seitig
idempotent ist (Upsert) - Queue kann beliebig oft retryen ohne Duplikate
- Service Worker via vite-plugin-pwa für App-Shell-Caching (Start im Feld ohne
Cold-Load); PWA-Icons als TODO vermerkt (noch keine echten Bilddateien)
- Client-Fehler (4xx) werden nicht automatisch wiederholt, nur Netzwerkfehler
Flutter-Android-Grundgerüst (flutter_app/), auf Nutzerwunsch parallel begonnen:
- Gleiche API als zweiter Client (Prompt 19 API-first), Login/Objektliste/
Kontroll-Screen als Dart-Äquivalent zur PWA
- Bewusst OHNE Offline-Queue in dieser ersten Fassung (siehe flutter_app/README.md)
- Plattform-Ordner (android/, ios/) nicht von Hand erzeugt - müssen auf dem
Zielsystem per `flutter create .` nachgezogen werden, sonst zu fehleranfällig
ohne Testlauf
Offen: Playwright-E2E (Testphase 4), Praktiker-Session, PWA-Icons, Flutter-
Offline-Queue - brauchen laufendes Deployment bzw. sind kein reiner Code-Schritt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
- benutzer.email ergänzt (additive Migration 0003) - Prompt 20 hatte kein E-Mail-
Feld vorgesehen, aber Karte 05 braucht es für Benachrichtigungen
- Dashboard-Kennzahlen (Prompt 12.2): offen/in_bearbeitung/nachgefuellt_teilweise
zählen gemeinsam als "offen", genehmigte Mindermengen separat, kürzlich erledigt
(7 Tage), problematische Objekte
- Ablaufdaten-Warnungen (Prompt 14): serverseitige Statusberechnung
gueltig/bald_ablaufend/abgelaufen je Objektposition, Standard-Warnzeitraum 30
Tage falls am Material nicht gesetzt, sortiert nach verbleibenden Tagen (E1:
einzige Zeit-/Fälligkeits-Sicht in V1, keine Kontrollintervall-Logik)
- GET /fehlbestaende um Filter (Standort/Objekt/Material/genehmigt) und
Alter-Sortierung erweitert (Prompt 12.3)
- Asynchrone E-Mail-Benachrichtigung bei neuem Fehlbestand (Karte 05): fire-and-
forget an aktive Materialverantwortliche/Leitungsverantwortliche mit hinterlegter
E-Mail; kein SMTP konfiguriert -> wird nur geloggt, kein harter Fehler
- Tests: Aggregationsregel, Ablaufdaten-Filterung/Sortierung, Rollenrechte,
E-Mail-Versand (SMTP gemockt, kein Docker/Test-Mailserver im Host-Runner
verfügbar - Aufruf mit korrekten Empfängern/Betreff wird geprüft)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
- MindermengenGenehmigung-Modell/Service (Prompt 04): reine fachliche Bewertung,
ändert NIEMALS Ist-Menge/Fehlmenge/Fehlbestand-Status (Leitplanke, U2)
- Automatischer Ablauf bei Start der nächsten Kontrolle desselben Objekts (Karte 08,
U6) - kein Zeit-Trigger, der Kontrollstart selbst ist der Ablauf-Zeitpunkt
- Genehmigung endet gemeinsam mit dem Fehlbestand, wenn dieser durch Nachfüllung
erledigt wird (U7), unabhängig davon ob vorher eine neue Kontrolle stattfand
- Historie-Service (app/services/historie.py) + Verdrahtung in die komplette
Kernkette: kontrolle_gestartet/-abgeschlossen/-abgebrochen/-uebernommen,
istmenge_erfasst, fehlbestand_entstanden/-erledigt, nachfuellung_erfasst,
mindermenge_genehmigt/-abgelaufen/-beendet_durch_erledigung (U14)
Hinweis: Stammdaten-/Vorlagen-/Benutzerverwaltung noch nicht retrofittet -
Sprint 5 deckt bewusst die im Testkonzept referenzierte Kernkette ab, keine
flächendeckende Audit-Abdeckung aller CRUD-Endpunkte.
- Tests: U2, U6, U7, vollständige Historie-Kette nach Prompt-13.3-Beispiel (U14)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
- Nachfuellung-Modell: einzige Aktion, die Objektposition.istmenge tatsächlich
ändert (Vier-Kernbegriffe). Deckt Sofort-Nachfüllung während Kontrolle (Karte 07)
und spätere/externe Nachfüllung (Prompt 02.5) mit derselben Logik ab
- Wichtiger Fix vor dem ersten Testlauf: Objektposition.istmenge wird bei Nachfüllung
auf den neuen Wert GESETZT (fehlbestand.istmenge + menge), nicht unabhängig
inkrementiert - sonst bliebe sie beim initialen "nicht kontrolliert"-Stand (0)
stehen, obwohl die Kontrolle bereits einen realen Zählwert kennt
- Statusübergänge: offen -> nachgefuellt_teilweise (Fehlmenge>0) -> erledigt
(Fehlmenge=0), ausschließlich automatisch bei Ist=Soll (U3)
- Überbestand durch Nachfüllung erlaubt, als Info gekennzeichnet, kein Fehler (E4)
- KontrollpositionRead liefert jetzt fehlbestand_id mit, damit die UI direkt eine
Sofort-Nachfüllung anbieten kann, ohne separat nachzufragen
- Tests: U3, U4 (Sofort-Nachfüllung ohne Zwischenstatus), U5 (Teilnachfüllung),
Überbestand-Fall, Nachfüllung auf bereits erledigten Fehlbestand -> 409
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
- Kontrolle/Kontrollposition-Modelle: Kontrolle ändert NIEMALS Objektposition.istmenge
(Vier-Kernbegriffe-Tabelle, Prompt 02.9) - reine Erfassung/Snapshot
- Objekt-Sperre (Prompt 02.8): Kontrolle.status='in_bearbeitung' + benutzer_id IST die
Sperre, kein separates Sperr-Modell. Zweiter Zugriff -> 409 mit wer/seit; Übernahme
bricht alte Kontrolle ab (Datensatz bleibt, Prompt 16.6-Analogie) und protokolliert
das Ereignis in einer neuen, minimalen Historie-Tabelle (volle Ausbaustufe Sprint 5)
- Automatische Fehlbestand-Erzeugung bei Ist < Soll (U1), Überbestand erzeugt
ausdrücklich KEINEN Fehlbestand (Prompt 02.4); Korrektur vor Abschluss möglich
(von derselben Kontrolle erzeugter Fehlbestand ist bis zum Abschluss nicht final)
- Abschluss verweigert bei unbestätigten Positionen, liefert deren IDs (U11)
- Abbruch verwirft Kontrollpositionen UND von dieser Kontrolle erzeugte Fehlbestände,
Kontrolle selbst bleibt als Datensatz mit Status "abgebrochen" erhalten (U12)
- Tests: U1, U11, U12, Sperre/409, Übernahme, Fremdzugriff/403, parallele Objekte
Mitarbeiter-UI (PWA-Frontend) ist bewusst noch nicht Teil dieses Commits - eigenes
Techstack-Setup, wird als nächster Schritt separat angegangen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
- Beladungsvorlage/Vorlagenposition-Modelle + Endpunkte: erstellen, neue Version
(Prompt 08 Punkt 4 - alte Version bleibt unverändert lesbar statt überschrieben)
- Objekt-Anlage aus Vorlage: Objektpositionen werden initialisiert, Ist-Menge startet
bei 0 ("nicht kontrolliert", Prompt 09)
- Duplizieren: Struktur/Sollmengen-Override kopiert, Ist-Menge/SN/Ablauf/Charge NICHT,
neuer Code Pflicht (409 bei Kollision)
- Sollmengen-Auflösung (Prompt 10): Override schlägt Vorlagenwert, sonst dynamisch aus
aktueller Vorlagenposition des Objekts
- GET /objekte/code/{code} bewusst vor /objekte/{id} registriert (Routing-Reihenfolge)
- Bugfix: app.models muss vollständig importiert werden (main.py + conftest.py), sonst
schlägt FK-Auflösung zwischen Modellen fehl, die noch von keinem Endpoint berührt
werden (Fund im dritten echten CI-Lauf)
- Tests: U8 (Override schlägt Vorlage), U9 (Duplizieren-Kopierregeln + Code-Konflikt),
U10 (neue Version, alte bleibt lesbar), Rollenrechte
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Der vorherige Fix (asyncio_default_fixture_loop_scope/asyncio_default_test_loop_scope)
griff nicht - letztere Option existiert in der gepinnten pytest-asyncio-Version
vermutlich noch nicht, Tests liefen weiterhin auf function-scoped Loops während die
Engine session-weit global war. Echte Ursache behoben: db_session erzeugt jetzt eine
frische AsyncEngine pro Test (und disposed sie danach), sodass Engine/Pool immer auf
derselben Loop laufen wie der Test selbst.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
1. passlib 1.7.4 liest bcrypt.__about__.__version__, das bcrypt>=4.1 entfernt hat -
führte zu irreführendem "password cannot be longer than 72 bytes". Fix: bcrypt<4.1 pinnen.
2. Globaler async engine in conftest.py + pytest-asyncios default function-scoped Event-Loop
führte zu "Task ... attached to a different loop" bei asyncpg. Fix: session-weiter
Loop-Scope für Fixtures und Tests.
Erster echter CI-Lauf (Host-Runner ohne Docker) kam bis zum pytest-Schritt durch,
beide Fehler dort aufgedeckt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Gitea-Actions-Runner läuft im Host-Modus (kein Docker installiert, Nutzerentscheidung
Prompt 19). Service-Container für PostgreSQL funktionieren damit nicht - CI nutzt
stattdessen die auf dem Runner-Host ohnehin laufende System-PostgreSQL und legt sich
eine eigene Test-Datenbank an/räumt sie danach wieder weg.
Hinweis: Runner läuft auf demselben Host wie die Produktionsinstanz (192.168.1.238) -
keine Isolation zwischen CI und Prod, akzeptabler Kompromiss für V1/Einzelserver-Setup.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
asyncpg (SQLAlchemy async) lässt keine Mehrfach-Statements in einem prepared
statement zu - "cannot insert multiple commands into a prepared statement".
Aufgetreten beim ersten echten alembic upgrade head auf dem Zielserver.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Zielserver (minimales Debian-13-LXC) hat kein sudo vorinstalliert; zusätzlich führte
die vorherige su -c "$*"-Konstruktion zu Word-Splitting der SQL-Statements. Fix:
psql läuft über su -s /bin/bash postgres -c "psql -tA -d '<db>'" mit SQL via stdin.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
Installer (deploy/):
- install_server.sh: PostgreSQL+nginx auf Debian 13, idempotente Rolle/DB-Anlage,
pgcrypto-Extension, Speicher-Tuning für 4GB-VPS, Zugangsdaten in chmod-600-Datei
statt stdout (postgres-expert/owasp-Review)
- nginx-Template mit Security-Headern + Rate-Limit auf /auth/login
- systemd-Unit-Template mit Sandboxing (NoNewPrivileges/ProtectSystem/PrivateTmp)
Backend-Fixes (fastapi-expert-Review):
- get_db: einheitliche commit/rollback-Konvention statt Endpunkt-Copy-Paste
- Test-Fixtures auf SQLAlchemy-2.0-Savepoint-Pattern umgestellt (join_transaction_mode),
da get_db jetzt selbst committet
- Lifespan-Handler: Startup-Guard gegen JWT-Secret-Platzhalter, engine.dispose() beim Shutdown
- JWT-Payload ohne ungenutztes roles-Claim (Rollen kommen immer frisch aus der DB)
Zusätzlich: Subagenten-Definitionen (~/.claude/agents/) auf lauffähiges Modell fixiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt