feat(lager): Warehouse-Epic Kern (WH-001..004+007)
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:
@@ -0,0 +1,86 @@
|
||||
"""Warehouse-Epic Kern (WH-001..004+007): Lagerort-Hierarchie, Lagerplätze,
|
||||
Bestand je Lagerplatz, Materialbewegungsprotokoll. Additiv, ersetzt nicht
|
||||
`standort`/`Lagerbewegung` (Objekt-Ebene bleibt unverändert).
|
||||
|
||||
Revision ID: 0019_lager
|
||||
Revises: 0018_funkkenner
|
||||
Create Date: 2026-09-05
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "0019_lager"
|
||||
down_revision: Union[str, None] = "0018_funkkenner"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"CREATE TYPE lagerort_typ AS ENUM ('organisation', 'standort', 'gebaeude', 'raum', 'lager')"
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
CREATE TABLE lagerort (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
parent_id INTEGER REFERENCES lagerort(id) ON DELETE SET NULL,
|
||||
typ lagerort_typ NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
op.execute("CREATE INDEX idx_lagerort_parent ON lagerort (parent_id)")
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
CREATE TABLE lagerplatz (
|
||||
id SERIAL PRIMARY KEY,
|
||||
lagerort_id INTEGER NOT NULL REFERENCES lagerort(id),
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
bezeichnung TEXT
|
||||
)
|
||||
"""
|
||||
)
|
||||
op.execute("CREATE INDEX idx_lagerplatz_lagerort ON lagerplatz (lagerort_id)")
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
CREATE TABLE bestand (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
lagerplatz_id INTEGER NOT NULL REFERENCES lagerplatz(id),
|
||||
material_id INTEGER NOT NULL REFERENCES material(id),
|
||||
menge NUMERIC NOT NULL DEFAULT 0,
|
||||
CONSTRAINT bestand_lagerplatz_material_uq UNIQUE (lagerplatz_id, material_id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
op.execute("CREATE INDEX idx_bestand_material ON bestand (material_id)")
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
CREATE TABLE materialbewegung (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
material_id INTEGER NOT NULL REFERENCES material(id),
|
||||
menge NUMERIC NOT NULL,
|
||||
von_lagerplatz_id INTEGER REFERENCES lagerplatz(id),
|
||||
nach_lagerplatz_id INTEGER REFERENCES lagerplatz(id),
|
||||
benutzer_id INTEGER NOT NULL REFERENCES benutzer(id),
|
||||
zeitpunkt TIMESTAMPTZ NOT NULL,
|
||||
grund TEXT,
|
||||
CONSTRAINT materialbewegung_von_oder_nach
|
||||
CHECK (von_lagerplatz_id IS NOT NULL OR nach_lagerplatz_id IS NOT NULL)
|
||||
)
|
||||
"""
|
||||
)
|
||||
op.execute("CREATE INDEX idx_materialbewegung_material ON materialbewegung (material_id)")
|
||||
op.execute("CREATE INDEX idx_materialbewegung_von ON materialbewegung (von_lagerplatz_id)")
|
||||
op.execute("CREATE INDEX idx_materialbewegung_nach ON materialbewegung (nach_lagerplatz_id)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP TABLE materialbewegung")
|
||||
op.execute("DROP TABLE bestand")
|
||||
op.execute("DROP TABLE lagerplatz")
|
||||
op.execute("DROP TABLE lagerort")
|
||||
op.execute("DROP TYPE lagerort_typ")
|
||||
Reference in New Issue
Block a user