Files
patrickandClaude Sonnet 5 eb20affd5d
CI / backend-tests (push) Failing after 1m37s
CI / frontend-build (push) Successful in 18s
feat(mangel): Mangel-Ticket-System (Roadmap Phase 2)
Eigener Workflow für Schäden/Defekte, getrennt von Fehlbestand (Mengen-
Abweichung Soll/Ist). Status: neu → in_bearbeitung → ersatzteil_bestellt/
reparatur_geplant → erledigt. entitaet_typ/entitaet_id optional (Objektposition
oder Geräteinstanz), analog zum bestehenden historie.entitaet_typ-Muster,
objekt_id immer gesetzt für Dashboard-Abfragen.

Ein offener Mangel mit prioritaet=hoch macht das Objekt "nicht einsatzbereit"
(neuer Grund mangel_kritisch_offen in dashboard.einsatzbereitschaft) -
niedrige/normale Mängel blockieren nicht.

Melden/Lesen: alle Mitarbeiter+. Status ändern: Materialverantwortliche+.
Neue Seite /maengel für alle eingeloggten Nutzer (Nav-Link "Mängel").

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

62 lines
2.5 KiB
Python

import enum
import uuid
from datetime import datetime
from sqlalchemy import CheckConstraint, ForeignKey, String
from sqlalchemy.dialects.postgresql import ENUM as PgEnum, TIMESTAMP, UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class MangelStatus(str, enum.Enum):
neu = "neu"
in_bearbeitung = "in_bearbeitung"
ersatzteil_bestellt = "ersatzteil_bestellt"
reparatur_geplant = "reparatur_geplant"
erledigt = "erledigt"
class MangelPrioritaet(str, enum.Enum):
niedrig = "niedrig"
normal = "normal"
hoch = "hoch"
mangel_status_pg = PgEnum(MangelStatus, name="mangel_status", create_type=False)
mangel_prioritaet_pg = PgEnum(MangelPrioritaet, name="mangel_prioritaet", create_type=False)
class Mangel(Base):
"""Roadmap Phase 2: Schäden/Defekte als eigener Workflow, getrennt von
Fehlbestand (Mengen-Abweichung). entitaet_typ/entitaet_id optional und als
String-Polymorphie geführt (Muster wie historie.entitaet_typ/-id) -
verweist z.B. auf 'objektposition'/uuid oder 'geraet_instanz'/uuid, wenn
der Mangel eine konkrete Teilressource betrifft statt das ganze Objekt.
objekt_id ist immer gesetzt (auch bei feingranularem Verweis), damit die
Einsatzbereitschaft (Dashboard) direkt filtern kann, ohne die polymorphe
Spalte auflösen zu müssen."""
__tablename__ = "mangel"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
objekt_id: Mapped[int] = mapped_column(ForeignKey("objekt.id"), nullable=False)
entitaet_typ: Mapped[str | None] = mapped_column(String)
entitaet_id: Mapped[str | None] = mapped_column(String)
beschreibung: Mapped[str] = mapped_column(String, nullable=False)
prioritaet: Mapped[MangelPrioritaet] = mapped_column(
mangel_prioritaet_pg, nullable=False, default=MangelPrioritaet.normal
)
status: Mapped[MangelStatus] = mapped_column(mangel_status_pg, nullable=False, default=MangelStatus.neu)
gemeldet_von: Mapped[int] = mapped_column(ForeignKey("benutzer.id"), nullable=False)
gemeldet_am: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), nullable=False)
erledigt_von: Mapped[int | None] = mapped_column(ForeignKey("benutzer.id"))
erledigt_am: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True))
__table_args__ = (
CheckConstraint(
"(entitaet_typ IS NULL AND entitaet_id IS NULL) OR (entitaet_typ IS NOT NULL AND entitaet_id IS NOT NULL)",
name="mangel_entitaet_beide_oder_keins",
),
)