import enum import uuid from datetime import date from decimal import Decimal from sqlalchemy import Date, ForeignKey, Integer, Numeric, 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 ObjektpositionStatus(str, enum.Enum): aktiv = "aktiv" entfernt = "entfernt" objektposition_status_pg = PgEnum( ObjektpositionStatus, name="objektposition_status", create_type=False ) class Objektposition(Base): """Prompt 10: Ist-Bestand + individuelle Soll-Abweichung je Objekt/Material. UUID-PK (Karte 13: sync-relevant, siehe ergebnisse/20_datenbank_schema.md).""" __tablename__ = "objektposition" __table_args__ = (UniqueConstraint("objekt_id", "material_id"),) 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) material_id: Mapped[int] = mapped_column(ForeignKey("material.id"), nullable=False) # NULL = Sollmenge folgt sollmenge_vorlage (individuelle Abweichung, Prompt 10). sollmenge_override: Mapped[Decimal | None] = mapped_column(Numeric) # Fest kopierte Sollmenge aus der Vorlage bei Anlage/Duplizieren (Nutzer- # Vorgabe 2026-09-05: Vorlage direkt editierbar, ohne bestehende Objekte # rückwirkend zu verändern - vorher live aus Vorlagenposition gelesen). # NULL bei Positionen, die nicht aus einer Vorlage stammen (Zusatzposition, # dort ist sollmenge_override Pflicht). sollmenge_vorlage: Mapped[Decimal | None] = mapped_column(Numeric) ist_status: Mapped[ObjektpositionStatus] = mapped_column( objektposition_status_pg, nullable=False, default=ObjektpositionStatus.aktiv ) istmenge: Mapped[Decimal] = mapped_column(Numeric, nullable=False, default=0) seriennummer: Mapped[str | None] = mapped_column(String) ablaufdatum: Mapped[date | None] = mapped_column(Date) chargennummer: Mapped[str | None] = mapped_column(String) # Code128-Etikett für Geräte-Exemplare (Konzept-Ergänzung Karte 10/Prompt 09), # separat von seriennummer: Code identifiziert das gedruckte Label, SN das # Herstellerattribut - beide können divergieren (Etikett verloren/neu vergeben). code: Mapped[str | None] = mapped_column(String, unique=True) # Karte 14: Prüf-/Wartungspflicht, individuell überschreibbar je Objektposition # (wie sollmenge_override) - NULL = kein Prüfintervall (nicht prüfpflichtig, # oder Standard aus dem Material/der Vorlage folgt später bei Bedarf). pruefintervall_monate: Mapped[int | None] = mapped_column(Integer)