Files
MABEA/backend/alembic/versions/0012_personal_qualifikationen.py
patrickandClaude Sonnet 5 d71d95e17f
CI / backend-tests (push) Failing after 1m32s
CI / frontend-build (push) Successful in 17s
feat(personal): Personal/Qualifikationen-Modul (Roadmap Phase 1)
Neues Modul für die BOS-Ressourcenplattform-Erweiterung: Einheit (Zug/Gruppe,
self-referenzierend), Qualifikationstyp (Führerschein/Lehrgang/Berechtigung),
BenutzerQualifikation (mit Gültigkeit) und ObjekttypQualifikationsanforderung
(M:N) - beantwortet "wer darf dieses Fahrzeug fahren?" über
GET /objekte/{id}/berechtigung/{benutzer_id}.

Benutzer und Objekt bekommen optionale einheit_id (additiv, analog
fahrzeug_id-Muster). Nebenbei Bugfix: PATCH /benutzer konnte einheit_id nicht
auf null setzen (Feld-vorhanden-Check via model_fields_set statt is not None).

Frontend: neuer Admin-Tab "Personal" (Einheiten, Qualifikationstypen,
Benutzer-Qualifikationszuordnung), Einheit-Auswahl im Benutzer-Formular.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
2026-09-05 11:13:37 +02:00

87 lines
3.2 KiB
Python

"""Personal/Qualifikationen-Modul (Roadmap-Erweiterung, Phase 1 nach BOS-
Ressourcenplattform-Anforderung): Einheit-Hierarchie, Qualifikationstypen
(Führerschein/Lehrgang/Berechtigung), Benutzer-Qualifikationszuordnung mit
Gültigkeit, sowie welche Qualifikation ein Objekttyp (z.B. Fahrzeug) zum
Bedienen voraussetzt - beantwortet "wer darf dieses Fahrzeug fahren?".
Revision ID: 0012_personal_qualifikationen
Revises: 0011_objekttyp_zugfahrzeug
Create Date: 2026-09-05
"""
from typing import Sequence, Union
from alembic import op
revision: str = "0012_personal_qualifikationen"
down_revision: Union[str, None] = "0011_objekttyp_zugfahrzeug"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.execute("CREATE TYPE qualifikationskategorie AS ENUM ('fuehrerschein', 'lehrgang', 'berechtigung')")
op.execute(
"""
CREATE TABLE einheit (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
uebergeordnete_einheit_id INTEGER REFERENCES einheit(id),
standort_id INTEGER REFERENCES standort(id),
CONSTRAINT einheit_kein_selbstbezug CHECK (uebergeordnete_einheit_id <> id)
)
"""
)
op.execute("CREATE INDEX idx_einheit_uebergeordnet ON einheit (uebergeordnete_einheit_id)")
op.execute("ALTER TABLE benutzer ADD COLUMN einheit_id INTEGER REFERENCES einheit(id)")
# Fahrzeuge/Objekte künftig einer Einheit zuordenbar (analog fahrzeug_id-Muster
# aus Migration 0010: additiv, NULL = keiner Einheit zugeordnet).
op.execute("ALTER TABLE objekt ADD COLUMN einheit_id INTEGER REFERENCES einheit(id)")
op.execute(
"""
CREATE TABLE qualifikationstyp (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
kategorie qualifikationskategorie NOT NULL,
gueltigkeit_monate INTEGER
)
"""
)
op.execute(
"""
CREATE TABLE benutzer_qualifikation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
benutzer_id INTEGER NOT NULL REFERENCES benutzer(id),
qualifikationstyp_id INTEGER NOT NULL REFERENCES qualifikationstyp(id),
erworben_am DATE NOT NULL,
gueltig_bis DATE,
nachweis_nummer TEXT,
UNIQUE (benutzer_id, qualifikationstyp_id, erworben_am)
)
"""
)
op.execute("CREATE INDEX idx_benutzer_qualifikation_benutzer ON benutzer_qualifikation (benutzer_id)")
op.execute(
"""
CREATE TABLE objekttyp_qualifikationsanforderung (
objekttyp_id INTEGER NOT NULL REFERENCES objekttyp(id),
qualifikationstyp_id INTEGER NOT NULL REFERENCES qualifikationstyp(id),
PRIMARY KEY (objekttyp_id, qualifikationstyp_id)
)
"""
)
def downgrade() -> None:
op.execute("DROP TABLE objekttyp_qualifikationsanforderung")
op.execute("DROP TABLE benutzer_qualifikation")
op.execute("DROP TABLE qualifikationstyp")
op.execute("ALTER TABLE objekt DROP COLUMN einheit_id")
op.execute("ALTER TABLE benutzer DROP COLUMN einheit_id")
op.execute("DROP TABLE einheit")
op.execute("DROP TYPE IF EXISTS qualifikationskategorie")