Dokument-Versionierung: vorgaenger_id verkettet Versionen (Migration 0027),
POST /dokumente/{id}/ersetzen legt neue Version an statt zu überschreiben,
alte Fassung bleibt als Historie erhalten. GET /dokumente/{id}/versionen
liefert die Kette (neueste zuerst), respektiert DOC-005-Zugriffsrechte.
Liste zeigt nur die jeweils aktuelle Version. Löschen der aktuellen Version
gibt automatisch den Vorgänger als neue "aktuelle" frei. Frontend:
"Ersetzen"-Button + aufklappbare Versionshistorie im DokumentePanel.
QR-Code-Etiketten: bisher nur Code128-Barcode möglich. generiere_qr_label_pdf
(reportlab-eigenes QR-Widget, keine neue Abhängigkeit) als Alternative,
?format=qr|code128 an /objekte/{id}/label.pdf und /objektpositionen/{id}/
label.pdf. AktePage bekommt "Etikett (Barcode)"/"Etikett (QR)"-Buttons
(vorher gab es dafür noch gar keinen UI-Zugang im Web-Frontend).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
70 lines
3.3 KiB
Python
70 lines
3.3 KiB
Python
import enum
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import 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
|
|
)
|