Files
MABEA/backend/app/models/objektposition.py
T
patrickandClaude Sonnet 5 d61aabadf3
CI / backend-tests (push) Successful in 1m13s
CI / frontend-build (push) Successful in 23s
Karte 14 Schritt 1: geraet_instanz-Tabelle (additiv)
objektposition.seriennummer war ein Einzelfeld, konnte nur EIN Gerät pro
Materialtyp/Objekt abbilden (z.B. 2 Pulsoxymeter im selben Rucksack nicht
darstellbar). Neue Tabelle geraet_instanz erlaubt beliebig viele Exemplare
pro Position (Seriennummer, Prüfdatum, nächste Prüfung, Status). Zusätzlich
objektposition.pruefintervall_monate (individuell überschreibbar).

Bewusst additiv: seriennummer-Spalte bleibt vorerst stehen (Service/
Endpunkte/Frontend hängen noch daran), bestehende Werte werden per
INSERT...SELECT nach geraet_instanz kopiert. Drop + Umbau der abhängigen
Schichten folgt in Umsetzungsschritt 2 (siehe arbeitskarten/14_...).

Migration von postgres-expert gegengeprüft (ON CONFLICT DO NOTHING als
Sicherheitsnetz, DROP TYPE IF EXISTS ergänzt), Tests decken Mehrfach-
Instanzen, UNIQUE-Constraint je Position, gleiche SN an verschiedenen
Positionen und pruefintervall_monate ab.

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

50 lines
2.2 KiB
Python

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 dynamisch der aktuellen Vorlagenposition (Prompt 10).
sollmenge_override: 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)