Files
MABEA/backend/app/models/personal.py
T
patrickandClaude Sonnet 5 15f783fb5e
CI / backend-tests (push) Successful in 2m17s
CI / frontend-build (push) Successful in 26s
feat(personal): PERS-004 Einsatzfunktionen (Gruppenführer/Melder/...)
Neue Stammdaten-Tabelle einsatzfunktion (Migration 0031), benutzer bekommt
optionales einsatzfunktion_id-Feld - analog zu einheit_id modelliert, eine
Person hat im Regelfall genau eine primäre Einsatzfunktion. Bewusst
getrennt von den technischen App-Rollen (RolleTyp), die nur steuern, was
jemand in MABEA tun darf, nicht welche Funktion er im Einsatz hat.

CRUD-Endpunkt /einsatzfunktionen nach Kategorie-Muster, Duplikat-Prüfung
vorab (Projekt-Konvention). Admin-UI: Auswahl + Inline-Neuanlage in der
Benutzerverwaltung (BenutzerSection).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
2026-09-08 20:07:16 +02:00

102 lines
4.1 KiB
Python

import enum
import uuid
from datetime import date
from sqlalchemy import CheckConstraint, Date, ForeignKey, String, UniqueConstraint
from sqlalchemy.dialects.postgresql import ENUM as PgEnum, UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class Qualifikationskategorie(str, enum.Enum):
fuehrerschein = "fuehrerschein"
lehrgang = "lehrgang"
berechtigung = "berechtigung"
qualifikationskategorie_pg = PgEnum(
Qualifikationskategorie, name="qualifikationskategorie", create_type=False
)
class Einheit(Base):
"""Roadmap-Erweiterung (Personal-Modul): Organisationseinheit (Zug/Gruppe),
self-referenzierend für Hierarchie (analog Kategorie.ueberkategorie_id).
Bewusst eigenständig statt an Standort gekoppelt - eine Einheit kann
ortsübergreifend sein, ein Standort mehrere Einheiten beherbergen."""
__tablename__ = "einheit"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String, nullable=False)
uebergeordnete_einheit_id: Mapped[int | None] = mapped_column(ForeignKey("einheit.id"))
standort_id: Mapped[int | None] = mapped_column(ForeignKey("standort.id"))
__table_args__ = (
CheckConstraint("uebergeordnete_einheit_id <> id", name="einheit_kein_selbstbezug"),
)
class Qualifikationstyp(Base):
"""Führerschein/Lehrgang/Berechtigung als ein Stammdaten-Konzept, damit
"welche Qualifikation braucht Objekttyp X" generisch bleibt (kein separates
Führerschein-Feld am Fahrzeug nötig, Karte-Nachtrag Personal-Modul)."""
__tablename__ = "qualifikationstyp"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String, nullable=False)
kategorie: Mapped[Qualifikationskategorie] = mapped_column(
qualifikationskategorie_pg, nullable=False
)
# NULL = unbefristet (z.B. manche Berechtigungen), sonst Gültigkeitsdauer in
# Monaten ab erworben_am - Ablauf wird nicht hier, sondern je
# BenutzerQualifikation über gueltig_bis geführt (individuelles Datum je
# Nachweis, nicht rein rechnerisch aus dem Typ abgeleitet).
gueltigkeit_monate: Mapped[int | None] = mapped_column()
class BenutzerQualifikation(Base):
__tablename__ = "benutzer_qualifikation"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
benutzer_id: Mapped[int] = mapped_column(ForeignKey("benutzer.id"), nullable=False)
qualifikationstyp_id: Mapped[int] = mapped_column(ForeignKey("qualifikationstyp.id"), nullable=False)
erworben_am: Mapped[date] = mapped_column(Date, nullable=False)
gueltig_bis: Mapped[date | None] = mapped_column(Date)
nachweis_nummer: Mapped[str | None] = mapped_column(String)
__table_args__ = (
UniqueConstraint(
"benutzer_id", "qualifikationstyp_id", "erworben_am", name="uq_benutzer_qualifikation"
),
)
class Einsatzfunktion(Base):
"""PERS-004: Funktion im Einsatzkontext (z.B. Gruppenführer, Truppführer,
Melder, Maschinist, Atemschutzgeräteträger) - unabhängig von den
technischen App-Rollen (RolleTyp: mitarbeiter/materialverantwortlicher/
...), die nur steuern, was jemand in MABEA tun darf, nicht welche Funktion
er im Einsatz hat. Bewusst analog zu Benutzer.einheit_id als einzelnes
optionales Feld modelliert, nicht als M:N - eine Person hat im Regelfall
genau eine primäre Einsatzfunktion, keine Historie/Zeitraum nötig."""
__tablename__ = "einsatzfunktion"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
class ObjekttypQualifikationsanforderung(Base):
"""M:N: welche Qualifikationstypen braucht man, um Objekte dieses Objekttyps
zu bedienen/fahren (Nutzer-Beispiel: "wer darf GW-San 01 fahren?")."""
__tablename__ = "objekttyp_qualifikationsanforderung"
objekttyp_id: Mapped[int] = mapped_column(ForeignKey("objekttyp.id"), primary_key=True)
qualifikationstyp_id: Mapped[int] = mapped_column(
ForeignKey("qualifikationstyp.id"), primary_key=True
)