Files
MABEA/backend/app/models/objektposition.py
T
patrickandClaude Sonnet 5 0249921bd5
CI / backend-tests (push) Successful in 2m0s
CI / frontend-build (push) Successful in 18s
feat(vorlage): Vorlage direkt editierbar ohne Versionszwang (Nutzer-Vorgabe)
Bisher wurde die Sollmenge einer Objektposition live aus der Vorlagenposition
gelesen (objekt.vorlage_id -> vorlagenposition.sollmenge), Bearbeiten der
Vorlage erzeugte deshalb zwingend eine neue Version - sonst hätte sich jede
Änderung rückwirkend auf alle Objekte ausgewirkt, die diese Vorlage nutzen.

- Neue Spalte objektposition.sollmenge_vorlage (Migration 0020): kopiert die
  Sollmenge fest bei Anlage/Duplizieren statt live nachzuschlagen
- sollmenge_effektiv(position) liest jetzt nur noch override/sollmenge_vorlage,
  kein DB-Lookup mehr nötig (sollmenge_map() entfernt)
- Neuer Endpoint PUT /vorlagen/{id}/positionen: ersetzt Positionen direkt,
  ohne neue Version. Bestehende Objekte unbetroffen, nur neu angelegte nutzen
  die aktualisierten Werte (Test: test_aktualisiere_positionen_wirkt_nicht_
  rueckwirkend_auf_bestehendes_objekt)
- POST /vorlagen/{id}/neue-version bleibt zusätzlich bestehen (nicht entfernt)
- Frontend: "Neue Version speichern" -> "Positionen speichern" (PUT statt POST)
- 140 Tests grün

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

56 lines
2.7 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 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)