From 0d69f5094b6f46db5e34b07db0d15930a5e5bd9b Mon Sep 17 00:00:00 2001 From: patrick Date: Thu, 3 Sep 2026 17:26:55 +0200 Subject: [PATCH] Dateien nach "/" hochladen --- 19_technische_architektur.md | 85 +++++++++++ 20_datenbank_schema.md | 288 +++++++++++++++++++++++++++++++++++ 21_api.md | 107 +++++++++++++ 3 files changed, 480 insertions(+) create mode 100644 19_technische_architektur.md create mode 100644 20_datenbank_schema.md create mode 100644 21_api.md diff --git a/19_technische_architektur.md b/19_technische_architektur.md new file mode 100644 index 0000000..db78c29 --- /dev/null +++ b/19_technische_architektur.md @@ -0,0 +1,85 @@ +# Prompt 19 – Technische Architektur + +Bezug: alle fachlichen Ergebnisse 01-18. Erste Technikentscheidung, Basis für Prompt 20-22. + +## 0. Techstack-Entscheidung (Nutzer) +- Backend: **Python, FastAPI** +- Datenbank: **PostgreSQL** +- Frontend: **Responsive Web-App/PWA** zuerst, native App (iOS/Android) als mögliche spätere Ergänzung +- Hosting: **normaler Linux-Server/VPS**, noch kein konkreter Anbieter — Architektur muss anbieterunabhängig/portabel sein; Docker dabei optional, keine Pflicht (siehe 9.) +- Deployment-Regel (Merkregel): ab hier nur Code/Konfigurationsdateien erzeugen, keine lokale Installation/Ausführung, Ziel ist externer Host. + +## 1. Frontend +- Responsive PWA (Progressive Web App): eine Codebasis für PC, Tablet, Smartphone. +- PWA-Eigenschaften nutzen: Installierbarkeit auf Homescreen, Kamera-Zugriff für QR-/Barcode-Scan (Karte 10) via Browser-API (`getUserMedia`), Service Worker für Kurzzeit-Netzausfall-Pufferung (Prompt 17). +- Framework-Wahl offen für Umsetzungsphase (z. B. React/Vue), hier nur Architekturebene: Single-Page-App, spricht ausschließlich mit Backend über die API (Prompt 21). +- Native App (Roadmap) würde später dieselbe API nutzen, keine Backend-Änderung nötig — Grund, warum API-first-Ansatz (siehe 3.) architektonisch wichtig ist. + +## 2. Backend +- Python, FastAPI (asynchron, automatische OpenAPI-Doku passt zu Prompt 21). +- Schichtenmodell: API-Layer (FastAPI Routen) → Service-/Domänenlogik-Layer (Statusmaschinen Kontrolle/Fehlbestand/Mindermenge, Prompt 02-04) → Datenzugriffs-Layer (ORM, z. B. SQLAlchemy) → PostgreSQL. +- Domänenlogik zentral gekapselt (z. B. eigenes Modul „Fehlbestand-Statusmaschine"), damit die zentrale Leitplanke „Mindermenge ≠ erledigt" (reihenfolge.md) an genau einer Stelle im Code durchgesetzt wird, nicht verstreut in mehreren Endpunkten. +- Authentifizierung (Karte 09): eigenes Auth-Modul, Passwort-Hashing (z. B. bcrypt/argon2), Session/Token-basiert (JWT oder Server-Session, Detail Prompt 21). + +## 3. API-first-Prinzip +- Backend bietet ausschließlich eine dokumentierte REST-API (Prompt 21), kein serverseitiges HTML-Rendering. +- Vorteil: Web-PWA und später native App sind austauschbare Clients derselben API, kein Doppelaufwand bei Geschäftslogik. + +## 4. Datenbank +- PostgreSQL, passend zum stark strukturierten, relationalen Datenmodell (Prompt 06): klare Entitäten, Fremdschlüssel, Historie/Audit-Anforderungen (Prompt 13) profitieren von ACID-Transaktionen. +- Migrationswerkzeug vorgesehen (z. B. Alembic) für versionierte Schemaänderungen, wichtig bei Vorlagen-Versionierung (Prompt 08) und künftigen Erweiterungen (Medikamente, Lager, Prüfungen). + +## 5. Rollen/Rechte technisch +- Rollenmodell (Prompt 05) als Middleware/Dependency in FastAPI: jede Route deklariert benötigte Rolle(n)/Rechte, zentrale Prüfung statt verstreuter if-Abfragen. +- Zuständigkeits-Zuordnung (Karte 04) als Datenbank-gestützte Prüfung (n:m Benutzer↔Standort/Objekt), nicht hart codiert. + +## 6. Historie/Auditierung technisch +- Eigene Tabelle(n) für Historieneinträge (Prompt 13), Append-only durch Anwendungslogik erzwungen (kein UPDATE/DELETE-Recht auf Historie-Tabellen für die Anwendung, nur INSERT). +- Serverseitige Zeitstempel (`now()` in der Datenbank oder Server-Uhrzeit im Backend, nie Client-Wert). + +## 7. Benachrichtigungen +- E-Mail bei neuem Fehlbestand (Karte 05): asynchroner Versand (z. B. Hintergrundtask in FastAPI oder einfache Queue), damit Kontroll-Workflow nicht durch E-Mail-Versand blockiert wird. +- Kein Push in V1 (Karte 05) – kein zusätzlicher Dienst (Push-Server) nötig. + +## 8. Offline-Unterstützung (Prompt 17) +- Kurzzeit-Pufferung im Frontend (Service Worker/lokaler Speicher im Browser), Übertragung an Backend bei wiederhergestellter Verbindung – rein clientseitiges Konzept, keine Backend-Sonderlogik nötig außer normalen, idempotenten API-Aufrufen. + +## 9. Deployment-Architektur (anbieterunabhängig) +- Docker ist keine Priorität (Nutzerentscheidung) – Deployment stattdessen direkt auf dem Linux-Server/VPS: Backend als Python-Prozess (z. B. Uvicorn hinter systemd-Service), PostgreSQL als regulär installierter Dienst auf demselben oder separatem Server, Reverse-Proxy (z. B. nginx) für HTTPS/Routing. +- Statische PWA-Dateien vom Reverse-Proxy oder vom Backend selbst ausgeliefert – Detailentscheidung bei Umsetzung. +- Docker bleibt als spätere Option offen (z. B. falls Hosting-Umgebung es verlangt), ist aber kein Architekturzwang mehr. +- Wichtig (Merkregel): in dieser Konversation werden nur Code-/Konfigurationsdateien erzeugt, kein lokaler Serverstart, keine lokale DB-Installation – Zielsystem ist der externe Host. + +## 10. Modularität für zukünftige Erweiterungen +- Neue Domänen (Medikamente, Lager, Prüfungen, weitere Ressourcentypen, Karte 11/Prompt 15/24) werden als zusätzliche Service-/Router-Module ergänzt, ohne Kernmodule (Kontrolle/Fehlbestand/Mindermenge) anzufassen — direkte Umsetzung des Designziels aus Prompt 06. + +## 11. Hardware-Anforderungen + +### Endgeräte (Mitarbeiter/Verantwortliche) +- Tablet/Smartphone mit Kamera (Barcode-Scan Code128 per Kamera, Prompt 17/Karte 10) – kein Spezialgerät, handelsübliches Gerät mit aktuellem Browser reicht (PWA-Ansatz). +- PC/Notebook für Dashboard/Administration – normaler Browser reicht, keine Installation nötig. + +### Server (extern, Zielsystem) +- Linux-VPS, Größe skaliert mit Nutzerzahl; für V1/kleine-mittlere Organisation grobe Hausnummer 2 vCPU/4GB RAM, konkret erst bei Umsetzung/Lasttest zu verifizieren. +- PostgreSQL auf demselben oder separatem Server. +- Kein Docker-Zwang (siehe 9.), Backend läuft direkt als Python-Prozess (Uvicorn/systemd). + +### Sonstiges +- Label-Drucker für QR-/Barcode-Labels (Karte 10, Roadmap Prompt 24) – Anforderung an Druckqualität/Kontrast dort bereits vermerkt. +- Kein USB-Handscanner nötig (Entscheidung Karte 10: Kamera-Scan statt Hardware-Scanner). + +### Satelliten-Server (Karte 13, Erweiterung) +- Kleiner lokaler Rechner/Mini-PC vor Ort (Einsatzort/Wache), betreibt dieselbe Software wie der Hauptserver (identisches Backend+DB-Schema), keine reduzierte Sonderversion. +- Muss vor Trennung mit den relevanten Stammdaten (Materialstamm, Vorlagen der betroffenen Objekte) bestückt sein – Vorbereitung Teil der Auslagerung, Detail offen (siehe Karte 13). +- Anforderung eher gering (lokales Mehrbenutzer-Team, kein großer Lastfall) – z. B. Mini-PC/NUC-Klasse ausreichend, konkrete Spezifikation bei Umsetzung. + +## 12. Mehrserver-Architektur: Hauptserver + Satelliten (Karte 13) +Ergänzung zur reinen Ein-Server-Architektur (Punkt 9): Neben dem zentralen Hauptserver können **Satelliten-Server** betrieben werden – vollwertige Klone derselben Anwendung, temporär vom Hauptserver getrennt einsetzbar (Details fachlich in [[17_mobile_offline]] Punkt 7, [[13_satelliten_server]]). + +- **Objekt-Zuordnung als Kernmechanismus:** jedes Objekt trägt ein Feld „aktuell zuständiger Server" (Hauptserver oder eine bestimmte Satelliten-ID). Nur der zuständige Server nimmt Schreiboperationen für dieses Objekt an – der jeweils andere lehnt sie ab bzw. zeigt es nicht als bearbeitbar. +- **Eindeutige IDs von Anfang an:** damit am Satelliten neu erzeugte Datensätze (Kontrollen, Fehlbestände, Nachfüllungen, Historie) beim Zusammenführen nicht mit Hauptserver-IDs kollidieren, werden global eindeutige IDs verwendet (z. B. UUID statt fortlaufender Integer-Primärschlüssel für sync-relevante Tabellen) – Abweichung vom reinen `SERIAL`-Schema aus Prompt 20 für genau diese Tabellen, bei Umsetzung zu berücksichtigen. +- **Synchronisation:** bei wiederhergestellter Verbindung überträgt der Satellit alle neu entstandenen Datensätze an den Hauptserver (einfacher Append-Vorgang, kein Merge/Konfliktlösung nötig, da Objekt-Zuordnung Überschneidungen strukturell ausschließt), danach wird die Objekt-Zuordnung wieder auf den Hauptserver zurückgesetzt. +- **Kein Zeitlimit:** Architektur darf keine Annahme über maximale Trennungsdauer treffen (Nutzervorgabe) – Synchronisationslogik muss auch nach Tagen/unbestimmter Zeit funktionieren. + +## Referenzen +Bezug: alle Ergebnisse 01-18, [[13_satelliten_server]] diff --git a/20_datenbank_schema.md b/20_datenbank_schema.md new file mode 100644 index 0000000..bbae623 --- /dev/null +++ b/20_datenbank_schema.md @@ -0,0 +1,288 @@ +# Prompt 20 – Datenbankschema + +Bezug: [[06_datenmodell]], [[07_materialstamm]], [[08_beladungsvorlagen]], [[09_duplizieren]], [[10_individuelle_beladung]], [[03_fehlbestandsmanagement]], [[04_mindermengen]], [[05_rollen_rechte]], [[13_historie_audit]]. Basis: PostgreSQL (Prompt 19). + +Konkretes relationales Schema, abgeleitet aus dem fachlichen Datenmodell. Migrationstool (z. B. Alembic) wird bei Code-Umsetzung eingesetzt, hier reines Ziel-Schema. + +**Update (Karte 13 – Satelliten-Server):** Tabellen, die auch an einem getrennten Satelliten-Server neue Zeilen erzeugen können (Kontrolle, Kontrollposition, Fehlbestand, Nachfüllung, Mindermengen-Genehmigung, Historie, **sowie Objektposition**), verwenden `UUID` statt `SERIAL` als Primärschlüssel, damit beim späteren Zusammenführen keine ID-Kollisionen zwischen Hauptserver und Satellit entstehen. Reine Stammdaten-Tabellen (Bereich, Kategorie, Standort, Objekttyp, Material, Vorlage, Benutzer, Objekt selbst) bleiben `SERIAL`, da sie nur zentral am Hauptserver gepflegt werden. `gen_random_uuid()` ist seit PostgreSQL 13 fest eingebaut, keine Extension nötig (bei älteren Versionen `CREATE EXTENSION pgcrypto`). + +**Korrektur nach Gesamtprüfung (siehe Punkt 10/11):** Reines Batch-Insert reicht als Sync-Mechanismus NICHT aus, da am Satelliten auch bestehende `objektposition`-Zeilen per UPDATE verändert werden (Ist-Menge, Seriennummer, Ablaufdatum, Chargennummer). Details siehe Punkt 10. + +## 1. Stammdaten + +```sql +CREATE TABLE bereich ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + beschreibung TEXT +); + +CREATE TABLE kategorie ( + id SERIAL PRIMARY KEY, + bereich_id INTEGER NOT NULL REFERENCES bereich(id), + name TEXT NOT NULL, + ueberkategorie_id INTEGER REFERENCES kategorie(id), + UNIQUE (bereich_id, name) +); + +CREATE TABLE standort ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + adresse TEXT +); + +CREATE TABLE objekttyp ( + id SERIAL PRIMARY KEY, + bereich_id INTEGER NOT NULL REFERENCES bereich(id), + kategorie_id INTEGER REFERENCES kategorie(id), + name TEXT NOT NULL, + UNIQUE (bereich_id, name) +); + +CREATE TYPE materialtyp AS ENUM ('standard', 'ablauf_charge', 'geraet_sn'); + +CREATE TABLE material ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + artikelnummer TEXT, + einheit TEXT NOT NULL, + materialtyp materialtyp NOT NULL, + kategorie_id INTEGER REFERENCES kategorie(id), + hersteller TEXT, + beschreibung TEXT, + code TEXT UNIQUE, -- QR/Barcode, Code128 (Karte 10) + warnzeitraum_tage INTEGER, -- Standard-Vorwarnung bei Ablaufdatum (Prompt 14) + aktiv BOOLEAN NOT NULL DEFAULT TRUE +); +``` + +## 2. Vorlagen + +```sql +CREATE TYPE vorlage_status AS ENUM ('aktiv', 'veraltet'); + +CREATE TABLE beladungsvorlage ( + id SERIAL PRIMARY KEY, + objekttyp_id INTEGER NOT NULL REFERENCES objekttyp(id), + name TEXT NOT NULL, + version INTEGER NOT NULL, + gueltig_ab TIMESTAMPTZ NOT NULL DEFAULT now(), + status vorlage_status NOT NULL DEFAULT 'aktiv', + UNIQUE (objekttyp_id, name, version) +); + +CREATE TABLE vorlagenposition ( + id SERIAL PRIMARY KEY, + vorlage_id INTEGER NOT NULL REFERENCES beladungsvorlage(id), + material_id INTEGER NOT NULL REFERENCES material(id), + fach TEXT, -- Kategorie/Fach innerhalb der Vorlage + sollmenge NUMERIC NOT NULL, + UNIQUE (vorlage_id, material_id) +); +``` + +## 3. Objekte (Rucksäcke/Fahrzeuge/Ressourcen) + +```sql +CREATE TYPE knoten_typ AS ENUM ('haupt', 'satellit'); + +CREATE TABLE systemknoten ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE, -- z. B. 'Hauptserver', 'Satellit Einsatzort Nord' + typ knoten_typ NOT NULL DEFAULT 'satellit' +); +-- genau eine Zeile mit typ='haupt' vorgesehen. + +CREATE TYPE objekt_status AS ENUM ('aktiv', 'ausser_dienst'); + +CREATE TABLE objekt ( + id SERIAL PRIMARY KEY, + code TEXT NOT NULL UNIQUE, -- QR/Barcode, Code128 (Karte 10) + name TEXT NOT NULL, + objekttyp_id INTEGER NOT NULL REFERENCES objekttyp(id), + vorlage_id INTEGER REFERENCES beladungsvorlage(id), + standort_id INTEGER NOT NULL REFERENCES standort(id), + status objekt_status NOT NULL DEFAULT 'aktiv', + zustaendiger_server_id INTEGER NOT NULL REFERENCES systemknoten(id) -- Karte 13: aktuell schreibberechtigter Knoten +); + +-- objektposition ist UUID (nicht SERIAL): kann am Satelliten sowohl per UPDATE (Ist-Menge/SN/Ablauf) +-- als auch per INSERT (neues Material am Objekt hinzugefügt, Prompt 10) verändert werden. +CREATE TYPE objektposition_status AS ENUM ('aktiv', 'entfernt'); + +CREATE TABLE objektposition ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Karte 13: sync-relevant + objekt_id INTEGER NOT NULL REFERENCES objekt(id), + material_id INTEGER NOT NULL REFERENCES material(id), + sollmenge_override NUMERIC, -- NULL = folgt Vorlage dynamisch + ist_status objektposition_status NOT NULL DEFAULT 'aktiv', -- korrigiert: Standard ist 'aktiv', nicht 'entfernt' (Prompt 10) + istmenge NUMERIC NOT NULL DEFAULT 0, + seriennummer TEXT, -- nur Materialtyp geraet_sn + ablaufdatum DATE, -- nur Materialtyp ablauf_charge + chargennummer TEXT, -- nur Materialtyp ablauf_charge + zuletzt_geaendert_am TIMESTAMPTZ NOT NULL DEFAULT now(), -- Basis für Delta-Sync (Punkt 10) + UNIQUE (objekt_id, material_id) +); +``` + +## 4. Zuständigkeiten, Benutzer, Rollen + +```sql +CREATE TABLE benutzer ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + login TEXT NOT NULL UNIQUE, + passwort_hash TEXT NOT NULL, + aktiv BOOLEAN NOT NULL DEFAULT TRUE +); + +CREATE TYPE rolle_typ AS ENUM ('mitarbeiter', 'materialverantwortlicher', 'leitungsverantwortlicher', 'administration'); + +CREATE TABLE benutzer_rolle ( + benutzer_id INTEGER NOT NULL REFERENCES benutzer(id), + rolle rolle_typ NOT NULL, + PRIMARY KEY (benutzer_id, rolle) +); + +-- zustaendigkeit/kontrollverantwortung sind UUID: Administration könnte theoretisch auch am +-- Satelliten Zuordnungen anlegen/ändern (z. B. Kontrollverantwortung vor Ort neu vergeben). +CREATE TABLE zustaendigkeit ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + benutzer_id INTEGER NOT NULL REFERENCES benutzer(id), + standort_id INTEGER REFERENCES standort(id), + objekt_id INTEGER REFERENCES objekt(id), + CHECK (standort_id IS NOT NULL OR objekt_id IS NOT NULL) +); + +CREATE TABLE kontrollverantwortung ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + objekt_id INTEGER NOT NULL REFERENCES objekt(id), + benutzer_id INTEGER REFERENCES benutzer(id), -- Einzelperson, ODER + gruppe TEXT -- Gruppe (frei benannt, V1-Vereinfachung ohne eigene Gruppen-Entität; echte Gruppen-Verwaltung Roadmap), Karte 01 +); +``` + +## 5. Kontrolle + +```sql +CREATE TYPE kontroll_status AS ENUM ('nicht_gestartet', 'in_bearbeitung', 'abgeschlossen', 'abgebrochen'); + +CREATE TABLE kontrolle ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Karte 13: sync-relevant + erzeugt_von_server_id INTEGER NOT NULL REFERENCES systemknoten(id), + objekt_id INTEGER NOT NULL REFERENCES objekt(id), + benutzer_id INTEGER NOT NULL REFERENCES benutzer(id), + status kontroll_status NOT NULL DEFAULT 'in_bearbeitung', + gestartet_am TIMESTAMPTZ NOT NULL DEFAULT now(), + beendet_am TIMESTAMPTZ, + abbruch_grund TEXT, + signatur BYTEA -- optionales Touch-Signaturbild am Kontrollnachweis (Prompt 16), nullable, standardmäßig ungenutzt +); + +CREATE TABLE kontrollposition ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Karte 13: sync-relevant + kontrolle_id UUID NOT NULL REFERENCES kontrolle(id), + material_id INTEGER NOT NULL REFERENCES material(id), + sollmenge_snapshot NUMERIC NOT NULL, + istmenge_erfasst NUMERIC NOT NULL, + abweichung BOOLEAN NOT NULL +); +``` + +## 6. Fehlbestand, Nachfüllung, Mindermenge + +```sql +CREATE TYPE fehlbestand_status AS ENUM ('offen', 'in_bearbeitung', 'nachgefuellt_teilweise', 'erledigt'); + +CREATE TABLE fehlbestand ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Karte 13: sync-relevant + erzeugt_von_server_id INTEGER NOT NULL REFERENCES systemknoten(id), + objekt_id INTEGER NOT NULL REFERENCES objekt(id), + material_id INTEGER NOT NULL REFERENCES material(id), + standort_id INTEGER NOT NULL REFERENCES standort(id), + sollmenge NUMERIC NOT NULL, + istmenge NUMERIC NOT NULL, + fehlmenge NUMERIC NOT NULL, + entstanden_am TIMESTAMPTZ NOT NULL DEFAULT now(), -- Basis Eskalation, Karte 12 + festgestellt_von INTEGER NOT NULL REFERENCES benutzer(id), + kontrolle_id UUID REFERENCES kontrolle(id), + ursache TEXT, + verantwortlicher_id INTEGER REFERENCES benutzer(id), + status fehlbestand_status NOT NULL DEFAULT 'offen', + erledigt_am TIMESTAMPTZ +); + +CREATE TABLE nachfuellung ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Karte 13: sync-relevant + fehlbestand_id UUID REFERENCES fehlbestand(id), + objekt_id INTEGER NOT NULL REFERENCES objekt(id), + material_id INTEGER NOT NULL REFERENCES material(id), + menge NUMERIC NOT NULL, + benutzer_id INTEGER NOT NULL REFERENCES benutzer(id), + zeitpunkt TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TYPE mindermenge_status AS ENUM ('aktiv', 'abgelaufen', 'beendet_durch_erledigung'); + +CREATE TABLE mindermengen_genehmigung ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Karte 13: sync-relevant + fehlbestand_id UUID NOT NULL REFERENCES fehlbestand(id), + genehmigt_von INTEGER NOT NULL REFERENCES benutzer(id), + begruendung TEXT NOT NULL, + genehmigt_am TIMESTAMPTZ NOT NULL DEFAULT now(), + ausloesende_kontrolle_id UUID NOT NULL REFERENCES kontrolle(id), + status mindermenge_status NOT NULL DEFAULT 'aktiv', + beendet_am TIMESTAMPTZ, + beendende_kontrolle_id UUID REFERENCES kontrolle(id) +); +``` + +## 7. Historie/Audit + +```sql +CREATE TABLE historie ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Karte 13: sync-relevant + erzeugt_von_server_id INTEGER NOT NULL REFERENCES systemknoten(id), + zeitpunkt TIMESTAMPTZ NOT NULL DEFAULT now(), + benutzer_id INTEGER REFERENCES benutzer(id), -- NULL bei System-Ereignissen + ereignistyp TEXT NOT NULL, -- z. B. 'fehlbestand_entstanden', 'mindermenge_genehmigt', ... + entitaet_typ TEXT NOT NULL, -- z. B. 'fehlbestand', 'objektposition', 'vorlage' + entitaet_id TEXT NOT NULL, -- TEXT statt INTEGER: referenzierte Entität kann INTEGER- oder UUID-ID haben + alter_wert JSONB, + neuer_wert JSONB, + begruendung TEXT +); +-- Append-only: Anwendungs-DB-Rolle erhält nur INSERT-Recht auf diese Tabelle, kein UPDATE/DELETE. +``` + +## 8. Indizes (Auswahl, wichtigste Zugriffspfade) + +```sql +CREATE INDEX idx_fehlbestand_status ON fehlbestand(status); +CREATE INDEX idx_fehlbestand_objekt ON fehlbestand(objekt_id); +CREATE INDEX idx_fehlbestand_entstanden ON fehlbestand(entstanden_am); -- für Alter-Sortierung, Prompt 12 +CREATE INDEX idx_objektposition_objekt ON objektposition(objekt_id); +CREATE INDEX idx_historie_entitaet ON historie(entitaet_typ, entitaet_id); +CREATE INDEX idx_zustaendigkeit_benutzer ON zustaendigkeit(benutzer_id); +``` + +## 9. Wichtige Constraints/Prinzipien +- Kein Fremdschlüssel-Löschen mit CASCADE auf historie-relevante Tabellen (fehlbestand, kontrolle, historie) – Löschung von Stammdaten (Material/Objekt) erfolgt nur über `aktiv = false`, nie physisches DELETE, damit Historie referenzierbar bleibt (Prompt 13). +- `mindermengen_genehmigung` referenziert Fehlbestand 1:0..n, in der Praxis max. 1 mit Status `aktiv` gleichzeitig – wird auf Anwendungsebene erzwungen (kein reiner DB-Constraint, da Historie mehrerer vergangener Genehmigungen erhalten bleiben muss). +- `objektposition.sollmenge_override IS NULL` bedeutet: Sollmenge wird zur Laufzeit aus aktueller `vorlagenposition` der referenzierten Vorlage berechnet (Prompt 10). + +## 10. Satelliten-Server – Objekt-Sperre und Synchronisation (Karte 13) +- `objekt.zustaendiger_server_id` bestimmt, welcher Server (Haupt oder ein bestimmter Satellit) aktuell Schreibrechte für dieses Objekt hat. Anwendungslogik (nicht reiner DB-Constraint) prüft vor jedem Schreibzugriff auf `kontrolle`, `fehlbestand`, `nachfuellung`, `mindermengen_genehmigung`, `objektposition`, ob der ausführende Server mit `objekt.zustaendiger_server_id` übereinstimmt. +- „Objekt auslagern": `UPDATE objekt SET zustaendiger_server_id = WHERE id = ...` – ab diesem Zeitpunkt lehnt der Hauptserver Schreibzugriffe auf dieses Objekt ab. +- **Synchronisation ist NICHT reines Batch-Insert** (Korrektur nach Gesamtprüfung): zwei unterschiedliche Sync-Fälle müssen unterschieden werden: + 1. **Neue Zeilen** (Kontrolle, Kontrollposition, Fehlbestand, Nachfüllung, Mindermengen-Genehmigung, Historie, neu hinzugefügte Objektposition): einfacher Insert am Hauptserver, da UUID bereits eindeutig vergeben – keine Kollision möglich. + 2. **Geänderte bestehende Zeilen** (`objektposition`: Ist-Menge, Seriennummer, Ablaufdatum, Chargennummer – vom Hauptserver vor Auslagerung an den Satelliten kopiert, dort per UPDATE verändert): werden beim Sync per **Upsert** übertragen (`INSERT ... ON CONFLICT (id) DO UPDATE`), nicht als reiner Insert. `zuletzt_geaendert_am` dient als Erkennungsmerkmal, welche Zeilen sich am Satelliten geändert haben. + - Da ein Objekt während der Auslagerung exklusiv einem Server zugeordnet ist (siehe Objekt-Sperre), gibt es keine gleichzeitige Änderung derselben Zeile an zwei Orten – Upsert ist damit konfliktfrei, kein Merge nötig. + - Reihenfolge beim Sync: zuerst Objektpositionen-Upsert, danach neue Kontrollen/Fehlbestände/etc. per Insert (referenzielle Integrität). +- Nach vollständiger Übertragung: `UPDATE objekt SET zustaendiger_server_id = `. +- **Einschränkung für Struktur­änderungen am Satelliten:** Anlegen komplett neuer Vorlagen/Materialstamm-Einträge bleibt dem Hauptserver vorbehalten (diese Tabellen sind SERIAL, nicht sync-fähig). Am Satelliten sind nur Änderungen an bereits vorhandenen `objektposition`-Zeilen sowie das Hinzufügen individueller Zusatzpositionen (Prompt 10, eigene UUID-Zeile) möglich. +- Benötigte PostgreSQL-Version: ≥ 13 für eingebautes `gen_random_uuid()` (siehe oben). +- Der Satellit selbst führt eine vollständige, aber auf die ihm zugeordneten Objekte beschränkte Kopie der relevanten Stammdaten (Material, Vorlagen, betroffene Objekte/Objektpositionen) – Details der Vorab-Bestückung offen (siehe [[13_satelliten_server]]). + +## Referenzen +Bezug: [[06_datenmodell]], [[19_technische_architektur]], [[13_satelliten_server]] diff --git a/21_api.md b/21_api.md new file mode 100644 index 0000000..20fdb92 --- /dev/null +++ b/21_api.md @@ -0,0 +1,107 @@ +# Prompt 21 – API-Konzept + +Bezug: [[19_technische_architektur]], [[20_datenbank_schema]], [[05_rollen_rechte]]. Basis: FastAPI, REST, API-first (Web-PWA + spätere native App als Clients). + +## 1. Grundprinzipien +- REST, JSON, versioniert unter `/api/v1/...`. +- Auth: Login liefert Token (JWT), jede weitere Anfrage mit `Authorization: Bearer `. +- Jede Route deklariert benötigte Rolle(n) (Prompt 05) über FastAPI-Dependency, zentral geprüft. +- Fehlerformat einheitlich: `{ "error": { "code": "...", "message": "..." } }`, HTTP-Status passend (400/401/403/404/409/422). +- Historieneinträge (Prompt 13) werden serverseitig automatisch bei jeder relevanten Statusänderung erzeugt, nicht über eigene API-Aufrufe des Clients. + +## 2. Auth + +| Methode | Pfad | Rolle | Zweck | +|---|---|---|---| +| POST | /api/v1/auth/login | - | Login (Login-Name+Passwort/PIN), liefert Token | +| POST | /api/v1/auth/logout | alle | Token invalidieren | +| GET | /api/v1/auth/me | alle | eigene Benutzerdaten+Rollen | + +## 3. Stammdaten (Ressourcen) + +| Methode | Pfad | Rolle | Zweck | +|---|---|---|---| +| GET/POST | /api/v1/bereiche | Administration (POST), alle (GET) | Bereiche | +| GET/POST | /api/v1/kategorien | Administration (POST), alle (GET) | Kategorien | +| GET/POST | /api/v1/standorte | Administration (POST), alle (GET) | Standorte | +| GET/POST | /api/v1/objekttypen | Administration (POST), alle (GET) | Objekttypen | +| GET/POST/PATCH | /api/v1/materialien | Administration | Materialstamm (Prompt 07) | +| GET | /api/v1/materialien/{id} | alle | Detail | + +## 4. Beladungsvorlagen + +| Methode | Pfad | Rolle | Zweck | +|---|---|---|---| +| GET | /api/v1/vorlagen | alle | Liste, filterbar nach Objekttyp/Status | +| POST | /api/v1/vorlagen | Administration, Materialverantwortlicher | neue Vorlage/Version anlegen (Prompt 08) | +| GET | /api/v1/vorlagen/{id} | alle | Detail inkl. Positionen | +| POST | /api/v1/vorlagen/{id}/positionen | Administration, Materialverantwortlicher | Position hinzufügen | +| PATCH | /api/v1/vorlagen/{id}/positionen/{pos_id} | Administration, Materialverantwortlicher | Sollmenge ändern (führt zu neuer Version, Prompt 08) | +| GET | /api/v1/vorlagen/{id}/diff-vorschlaege | Materialverantwortlicher, Leitung | Diff zu bestehenden Objekten (Prompt 08 Punkt 6) | + +## 5. Objekte + +| Methode | Pfad | Rolle | Zweck | +|---|---|---|---| +| GET | /api/v1/objekte | alle | Liste, Filter Standort/Typ, Suche nach Code | +| GET | /api/v1/objekte/{id} | alle | Detail inkl. Objektpositionen | +| GET | /api/v1/objekte/code/{code} | alle | Lookup per QR/Barcode-Code (Karte 10) — Route muss in FastAPI VOR `/objekte/{id}` registriert werden, sonst greift die generische ID-Route zuerst | +| POST | /api/v1/objekte | Administration | neu anlegen | +| POST | /api/v1/objekte/{id}/duplizieren | Administration | Duplizieren (Prompt 09), Body: neuer Name/Code/Standort | +| PATCH | /api/v1/objekte/{id}/positionen/{pos_id} | Materialverantwortlicher | Sollmengen-Override/Material hinzufügen/entfernen (Prompt 10) | + +## 6. Kontrolle + +| Methode | Pfad | Rolle | Zweck | +|---|---|---|---| +| POST | /api/v1/objekte/{id}/kontrollen | Mitarbeiter+ | Kontrolle starten (Prompt 02) | +| GET | /api/v1/kontrollen/{id} | Mitarbeiter+ | Status/Fortschritt | +| PUT | /api/v1/kontrollen/{id}/positionen/{material_id} | Mitarbeiter+ | Ist-Menge erfassen/bestätigen (idempotent, wichtig für Prompt 17 Wiederholung nach Netzausfall) | +| POST | /api/v1/kontrollen/{id}/abschliessen | Mitarbeiter+ | Abschluss (Prompt 16), löst Kontrollnachweis aus | +| POST | /api/v1/kontrollen/{id}/abbrechen | Mitarbeiter+ | Abbruch (Prompt 16), optional Grund im Body | + +## 7. Fehlbestand / Nachfüllung / Mindermenge + +| Methode | Pfad | Rolle | Zweck | +|---|---|---|---| +| GET | /api/v1/fehlbestaende | Materialverantwortlicher, Leitung, Administration | Liste, Filter Standort/Objekt/Material/Alter/Status (Prompt 12) | +| GET | /api/v1/fehlbestaende/{id} | s.o. + Mitarbeiter (eigene) | Detail inkl. Historie | +| POST | /api/v1/fehlbestaende/{id}/nachfuellungen | Mitarbeiter+ | Nachfüllung erfassen (Prompt 02), aktualisiert Fehlmenge/Status automatisch | +| POST | /api/v1/fehlbestaende/{id}/mindermenge | Materialverantwortlicher, Leitung | Mindermenge genehmigen, Body: Begründung (Prompt 04) | +| DELETE | /api/v1/fehlbestaende/{id}/mindermenge | - | NICHT vorgesehen – Genehmigung läuft nur automatisch ab (Prompt 04), kein manuelles Löschen | + +## 7a. Verwaltung (Administration) – ergänzt nach Gesamtprüfung +Fehlte bisher, obwohl in der Berechtigungsmatrix (Prompt 05) gefordert. + +| Methode | Pfad | Rolle | Zweck | +|---|---|---|---| +| GET/POST | /api/v1/benutzer | Administration | Benutzer verwalten | +| PATCH | /api/v1/benutzer/{id} | Administration | Rollen zuweisen, aktiv/inaktiv setzen | +| GET/POST | /api/v1/zustaendigkeiten | Administration | Zuständigkeits-Zuordnung (Karte 04) | +| DELETE | /api/v1/zustaendigkeiten/{id} | Administration | Zuordnung entfernen | +| GET/POST | /api/v1/kontrollverantwortung | Administration, Materialverantwortlicher (eigener Bereich) | Kontrollverantwortung zuweisen (Karte 01) | +| GET/POST | /api/v1/systemknoten | Administration | Satelliten-Server anlegen/verwalten (Karte 13) | +| POST | /api/v1/objekte/{id}/auslagern | Administration | Objekt einem Satelliten zuordnen | +| POST | /api/v1/objekte/{id}/zurueckholen | Administration | Rücksynchronisation anstoßen, Zuordnung zurück auf Hauptserver | + +## 8. Dashboard/Historie + +| Methode | Pfad | Rolle | Zweck | +|---|---|---|---| +| GET | /api/v1/dashboard/kennzahlen | Materialverantwortlicher, Leitung, Administration | Kennzahlen (Prompt 12) | +| GET | /api/v1/dashboard/ablaufdaten | s.o. | bevorstehende Ablaufdaten (Prompt 14) | +| GET | /api/v1/historie | s.o. (gefiltert nach Zuständigkeit) | globale Audit-Suche (Prompt 13) | +| GET | /api/v1/objekte/{id}/historie | alle (eigene Objekte) | objektbezogene Historie | + +## 9. Validierung/Fehlerfälle (Beispiele) +- Mindermenge genehmigen ohne Begründung → 422, Feld `begruendung` Pflicht (Prompt 04). +- Mindermenge genehmigen durch Mitarbeiter-Rolle → 403 (Prompt 05). +- Nachfüllung mit Menge, die Ist über Soll hebt → 422 oder Warnung (fachlich zu entscheiden bei Umsetzung, aus Prompt 02 nicht explizit als Fehler definiert – konservativ: erlauben, aber als „Überbestand“-Info kennzeichnen, kein Fehlbestand). +- Kontrolle abschließen, obwohl Positionen unbearbeitet → 409, Liste fehlender Positionen im Response. +- Doppeltes Duplizieren mit gleichem Code → 409 (Code muss eindeutig sein, Prompt 09). + +## 10. Auditierung auf API-Ebene +- Jede schreibende Aktion protokolliert automatisch Server-Zeitstempel + Benutzer-ID aus Token (nie Client-Wert) in `historie` (Prompt 13/20). + +## Referenzen +Bezug: [[19_technische_architektur]], [[20_datenbank_schema]], [[05_rollen_rechte]]