Dateien nach "/" hochladen
This commit is contained in:
@@ -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]]
|
||||
@@ -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 = <satellit> 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 = <hauptserver>`.
|
||||
- **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]]
|
||||
@@ -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 <token>`.
|
||||
- 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]]
|
||||
Reference in New Issue
Block a user