Files
MABEA/backend/app/models/fehlbestand.py
T
patrickandClaude Sonnet 5 8fd507cc5d
CI / backend-tests (push) Successful in 38s
Sprint 3: Kontroll-Kern (Statusmaschine, Objekt-Sperre, automatische Fehlbestand-Erzeugung)
- Kontrolle/Kontrollposition-Modelle: Kontrolle ändert NIEMALS Objektposition.istmenge
  (Vier-Kernbegriffe-Tabelle, Prompt 02.9) - reine Erfassung/Snapshot
- Objekt-Sperre (Prompt 02.8): Kontrolle.status='in_bearbeitung' + benutzer_id IST die
  Sperre, kein separates Sperr-Modell. Zweiter Zugriff -> 409 mit wer/seit; Übernahme
  bricht alte Kontrolle ab (Datensatz bleibt, Prompt 16.6-Analogie) und protokolliert
  das Ereignis in einer neuen, minimalen Historie-Tabelle (volle Ausbaustufe Sprint 5)
- Automatische Fehlbestand-Erzeugung bei Ist < Soll (U1), Überbestand erzeugt
  ausdrücklich KEINEN Fehlbestand (Prompt 02.4); Korrektur vor Abschluss möglich
  (von derselben Kontrolle erzeugter Fehlbestand ist bis zum Abschluss nicht final)
- Abschluss verweigert bei unbestätigten Positionen, liefert deren IDs (U11)
- Abbruch verwirft Kontrollpositionen UND von dieser Kontrolle erzeugte Fehlbestände,
  Kontrolle selbst bleibt als Datensatz mit Status "abgebrochen" erhalten (U12)
- Tests: U1, U11, U12, Sperre/409, Übernahme, Fremdzugriff/403, parallele Objekte

Mitarbeiter-UI (PWA-Frontend) ist bewusst noch nicht Teil dieses Commits - eigenes
Techstack-Setup, wird als nächster Schritt separat angegangen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
2026-09-03 23:40:26 +02:00

48 lines
2.2 KiB
Python

import enum
import uuid
from datetime import datetime
from decimal import Decimal
from sqlalchemy import ForeignKey, Numeric, 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 FehlbestandStatus(str, enum.Enum):
offen = "offen"
in_bearbeitung = "in_bearbeitung"
nachgefuellt_teilweise = "nachgefuellt_teilweise"
erledigt = "erledigt"
fehlbestand_status_pg = PgEnum(FehlbestandStatus, name="fehlbestand_status", create_type=False)
class Fehlbestand(Base):
"""Prompt 03: automatisch entstandener Vorgang bei Ist < Soll, ändert selbst
NIEMALS die Ist-Menge (nur Nachfüllung tut das, Sprint 4). Sollmenge/Istmenge/
Fehlmenge hier sind Snapshot-Werte zum Feststellungszeitpunkt, nicht live
referenziert (Prompt 03 Punkt 2: "Sollmenge zum Zeitpunkt der Feststellung")."""
__tablename__ = "fehlbestand"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
erzeugt_von_server_id: Mapped[int] = mapped_column(ForeignKey("systemknoten.id"), nullable=False)
objekt_id: Mapped[int] = mapped_column(ForeignKey("objekt.id"), nullable=False)
material_id: Mapped[int] = mapped_column(ForeignKey("material.id"), nullable=False)
standort_id: Mapped[int] = mapped_column(ForeignKey("standort.id"), nullable=False)
sollmenge: Mapped[Decimal] = mapped_column(Numeric, nullable=False)
istmenge: Mapped[Decimal] = mapped_column(Numeric, nullable=False)
fehlmenge: Mapped[Decimal] = mapped_column(Numeric, nullable=False)
entstanden_am: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), nullable=False)
festgestellt_von: Mapped[int] = mapped_column(ForeignKey("benutzer.id"), nullable=False)
kontrolle_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("kontrolle.id"))
ursache: Mapped[str | None] = mapped_column(String)
verantwortlicher_id: Mapped[int | None] = mapped_column(ForeignKey("benutzer.id"))
status: Mapped[FehlbestandStatus] = mapped_column(
fehlbestand_status_pg, nullable=False, default=FehlbestandStatus.offen
)
erledigt_am: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True))