"""Initiales Schema (Prompt 20 / ergebnisse/20_datenbank_schema.md) Revision ID: 0001_initial_schema Revises: Create Date: 2026-09-03 Vollständiges Ziel-Schema aus Prompt 20 (inkl. Karte-13-Vorbereitung: UUID auf sync-relevanten Tabellen, systemknoten). Sprint 0 legt das komplette Schema an, auch wenn erst spätere Sprints die zugehörigen Endpunkte/Business-Logik bauen – so entfällt ein späterer Schema-Umbau (Designziel, Prompt 06/19). """ from typing import Sequence, Union from alembic import op revision: str = "0001_initial_schema" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None UPGRADE_SQL = """ -- 1. Stammdaten 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, warnzeitraum_tage INTEGER, aktiv BOOLEAN NOT NULL DEFAULT TRUE ); -- 2. Vorlagen 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, sollmenge NUMERIC NOT NULL, UNIQUE (vorlage_id, material_id) ); -- 3. Objekte CREATE TYPE knoten_typ AS ENUM ('haupt', 'satellit'); CREATE TABLE systemknoten ( id SERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE, typ knoten_typ NOT NULL DEFAULT 'satellit' ); CREATE TYPE objekt_status AS ENUM ('aktiv', 'ausser_dienst'); CREATE TABLE objekt ( id SERIAL PRIMARY KEY, code TEXT NOT NULL UNIQUE, 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) ); CREATE TYPE objektposition_status AS ENUM ('aktiv', 'entfernt'); CREATE TABLE objektposition ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), objekt_id INTEGER NOT NULL REFERENCES objekt(id), material_id INTEGER NOT NULL REFERENCES material(id), sollmenge_override NUMERIC, ist_status objektposition_status NOT NULL DEFAULT 'aktiv', istmenge NUMERIC NOT NULL DEFAULT 0, seriennummer TEXT, ablaufdatum DATE, chargennummer TEXT, zuletzt_geaendert_am TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (objekt_id, material_id) ); -- 4. Zuständigkeiten, Benutzer, Rollen 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) ); 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), gruppe TEXT ); -- 5. Kontrolle CREATE TYPE kontroll_status AS ENUM ('nicht_gestartet', 'in_bearbeitung', 'abgeschlossen', 'abgebrochen'); CREATE TABLE kontrolle ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 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 ); CREATE TABLE kontrollposition ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 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 CREATE TYPE fehlbestand_status AS ENUM ('offen', 'in_bearbeitung', 'nachgefuellt_teilweise', 'erledigt'); CREATE TABLE fehlbestand ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 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(), 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(), 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(), 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 CREATE TABLE historie ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), erzeugt_von_server_id INTEGER NOT NULL REFERENCES systemknoten(id), zeitpunkt TIMESTAMPTZ NOT NULL DEFAULT now(), benutzer_id INTEGER REFERENCES benutzer(id), ereignistyp TEXT NOT NULL, entitaet_typ TEXT NOT NULL, entitaet_id TEXT NOT NULL, alter_wert JSONB, neuer_wert JSONB, begruendung TEXT ); -- 8. Indizes 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); 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); """ DOWNGRADE_SQL = """ DROP TABLE IF EXISTS historie; DROP TABLE IF EXISTS mindermengen_genehmigung; DROP TABLE IF EXISTS nachfuellung; DROP TABLE IF EXISTS fehlbestand; DROP TABLE IF EXISTS kontrollposition; DROP TABLE IF EXISTS kontrolle; DROP TABLE IF EXISTS kontrollverantwortung; DROP TABLE IF EXISTS zustaendigkeit; DROP TABLE IF EXISTS benutzer_rolle; DROP TABLE IF EXISTS benutzer; DROP TABLE IF EXISTS objektposition; DROP TABLE IF EXISTS objekt; DROP TABLE IF EXISTS systemknoten; DROP TABLE IF EXISTS vorlagenposition; DROP TABLE IF EXISTS beladungsvorlage; DROP TABLE IF EXISTS material; DROP TABLE IF EXISTS objekttyp; DROP TABLE IF EXISTS standort; DROP TABLE IF EXISTS kategorie; DROP TABLE IF EXISTS bereich; DROP TYPE IF EXISTS mindermenge_status; DROP TYPE IF EXISTS fehlbestand_status; DROP TYPE IF EXISTS kontroll_status; DROP TYPE IF EXISTS rolle_typ; DROP TYPE IF EXISTS objektposition_status; DROP TYPE IF EXISTS objekt_status; DROP TYPE IF EXISTS knoten_typ; DROP TYPE IF EXISTS vorlage_status; DROP TYPE IF EXISTS materialtyp; """ def upgrade() -> None: op.execute(UPGRADE_SQL) def downgrade() -> None: op.execute(DOWNGRADE_SQL)