Files
MABEA/backend/app/models/dokument.py
T
patrickandClaude Sonnet 5 ae09433c11
CI / backend-tests (push) Successful in 2m16s
CI / frontend-build (push) Successful in 27s
feat(dokumente): DOC-004 Original-vs-Kopie-Kennzeichnung
Neues Feld ist_original (Boolean, Migration 0029) - explizit beim Upload
und beim Ersetzen (DOC-003) setzbar, kein Default in eine Richtung erraten.
Wird beim Ersetzen bewusst NICHT vom Vorgänger übernommen (neue Version
könnte ein nachgereichtes Original sein, das eine bisherige Kopie ersetzt,
oder umgekehrt). Frontend: Checkbox "Original (nicht nur Kopie/Scan)" im
Upload-Formular, "Original"-Badge in der Dokumentenliste.

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

75 lines
3.7 KiB
Python

import enum
import uuid
from datetime import datetime
from sqlalchemy import Boolean, ForeignKey, Integer, 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 DokumentTyp(str, enum.Enum):
"""DOC-002: feste Kategorie statt Freitext - macht Dokumente filter-/
wiederfindbar (Nutzer-Vorgabe Modul 10: Prüfprotokolle/Wartungsberichte/
Bedienungsanleitungen/Rechnungen/Zulassungsdokumente)."""
pruefprotokoll = "pruefprotokoll"
wartungsbericht = "wartungsbericht"
bedienungsanleitung = "bedienungsanleitung"
rechnung = "rechnung"
zulassungsdokument = "zulassungsdokument"
sonstiges = "sonstiges"
dokument_typ_pg = PgEnum(DokumentTyp, name="dokument_typ", create_type=False)
# DOC-005: Rechnungen sind finanziell sensibel - nur Materialverantwortliche/
# Leitungsverantwortliche/Administration dürfen sie sehen/herunterladen,
# Mitarbeiter nicht (dürfen sie aber weiterhin hochladen, z.B. direkt beim
# Wareneingang scannen). Andere Dokumenttypen (Prüfprotokolle, Anleitungen,
# ...) bleiben für alle mit Zugriff auf die Akte sichtbar - die werden im
# Feld gebraucht.
EINGESCHRAENKTE_DOKUMENTTYPEN = {DokumentTyp.rechnung}
class Dokument(Base):
"""Roadmap Phase 5 (Modul Dokumente): polymorpher Datei-Anhang an beliebige
Ressource (Objekt, Objektposition, Geräteinstanz, Mangel, Fahrzeugdetails,
Benutzer, ...) - gleiches entitaet_typ/entitaet_id-Muster wie Historie/
Mangel. Datei liegt lokal auf Platte (self-hosted, kein Cloud-Zwang),
speicherpfad ist relativ zu settings.upload_dir."""
__tablename__ = "dokument"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
entitaet_typ: Mapped[str] = mapped_column(String, nullable=False)
entitaet_id: Mapped[str] = mapped_column(String, nullable=False)
dateiname: Mapped[str] = mapped_column(String, nullable=False)
speicherpfad: Mapped[str] = mapped_column(String, nullable=False)
mime_type: Mapped[str] = mapped_column(String, nullable=False)
groesse_bytes: Mapped[int] = mapped_column(Integer, nullable=False)
# SHA-256 des Dateiinhalts (DOC-001: Duplikat-Erkennung) - hex-codiert, 64
# Zeichen. Vergleich nur innerhalb derselben entitaet_typ/entitaet_id, nicht
# global (gleiche Datei an zwei verschiedenen Objekten ist kein Duplikat).
# Nullable wegen Altbestand vor dieser Migration (Hash nachträglich ohne
# Dateizugriff nicht sinnvoll befüllbar); jeder neue Upload setzt ihn.
dateihash: Mapped[str | None] = mapped_column(String(64))
dokumenttyp: Mapped[DokumentTyp] = mapped_column(
dokument_typ_pg, nullable=False, default=DokumentTyp.sonstiges
)
beschreibung: Mapped[str | None] = mapped_column(String)
hochgeladen_von: Mapped[int] = mapped_column(ForeignKey("benutzer.id"), nullable=False)
hochgeladen_am: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), nullable=False)
# DOC-003: Versionierung - zeigt auf das Dokument, das diese Version ersetzt.
# Alte Version bleibt als eigene Zeile erhalten (kein Überschreiben/Löschen),
# "aktuell" ist jedes Dokument, auf das kein anderes per vorgaenger_id zeigt.
vorgaenger_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("dokument.id"), nullable=True
)
# DOC-004: rechtlich/organisatorisch relevant bei Zulassungsdokumenten/
# Rechnungen - ist das hier das Original (z.B. eingescannt vom Papier-
# Original) oder nur eine Kopie/ein Duplikat davon? Explizite Angabe beim
# Upload statt Annahme, kein Default in eine Richtung.
ist_original: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)