feat(lager): Warehouse-Epic Kern (WH-001..004+007)
CI / backend-tests (push) Successful in 1m56s
CI / frontend-build (push) Successful in 18s

Lagerort-Hierarchie (organisation/standort/gebaeude/raum/lager, self-referenzierend),
Lagerplätze mit eindeutigem Code, Bestand je Lagerplatz+Material, Ein-/Auslagerung
mit lückenlosem Materialbewegungsprotokoll (append-only, analog Lagerbewegung
auf Objekt-Ebene, hier aber Material-/Lagerplatz-fein).

- Migration 0019: 4 neue Tabellen (lagerort, lagerplatz, bestand, materialbewegung)
- Backend: CRUD Lagerort/Lagerplatz, Bestandsabfrage, Ein-/Auslagern-Endpunkte,
  Negativbestand vorab abgefangen (kein DB-Constraint-Exception-Pfad, gleiches
  Muster wie objekte.py-Fix vom 2026-09-05)
- Frontend: neuer Admin-Tab "Lager" (Lagerort/-platz-Verwaltung, Bestand+Buchung
  je Lagerplatz)
- 2 neue Tests, 139 Tests weiterhin grün

WH-005 (Umlagerung) und WH-006 (Inventur) bewusst zurückgestellt - Kern zuerst,
laut Nutzer-Entscheidung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-05 21:42:37 +02:00
co-authored by Claude Sonnet 5
parent ca0befd4a0
commit f7367f6df3
11 changed files with 918 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import uuid
from datetime import datetime
from decimal import Decimal
from pydantic import BaseModel, ConfigDict
from app.models.lager import LagerortTyp
class LagerortCreate(BaseModel):
name: str
parent_id: int | None = None
typ: LagerortTyp
class LagerortUpdate(BaseModel):
name: str | None = None
parent_id: int | None = None
typ: LagerortTyp | None = None
class LagerortRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
parent_id: int | None
typ: LagerortTyp
class LagerplatzCreate(BaseModel):
lagerort_id: int
code: str
bezeichnung: str | None = None
class LagerplatzRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
lagerort_id: int
code: str
bezeichnung: str | None
class BestandRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
lagerplatz_id: int
material_id: int
menge: Decimal
class EinAuslagernRequest(BaseModel):
material_id: int
menge: Decimal
grund: str | None = None
class MaterialbewegungRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
material_id: int
menge: Decimal
von_lagerplatz_id: int | None
nach_lagerplatz_id: int | None
benutzer_id: int
zeitpunkt: datetime
grund: str | None