- Neues Feld products.package_label: pro Artikel einstellbar, wie das Gebinde heisst (Packung, Glas, Tuete, Flasche, Dose, Tube, ...) - Eingabe mit Vorschlagsliste, frei ueberschreibbar. Migration per ADD COLUMN IF NOT EXISTS. - Produkttabelle: Spalte "Packungen" heisst jetzt "Einheiten (ist / soll)" und zeigt Bestand und Mindestbestand als "11 / 12 Glas"; die separate Spalte "Mindest" entfaellt. Das "niedrig"-Badge sitzt jetzt dort. - Die Bezeichnung wird ueberall verwendet (Chargenanzeige, Einheiten-Umschalter beim Bearbeiten, Mindestbestand-Auswahl, Umrechnungshinweis). - Speichern/Abbrechen im Chargen-Editor sind jetzt gleich breit (.btn-pair). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
2.7 KiB
Python
93 lines
2.7 KiB
Python
from contextlib import asynccontextmanager
|
||
|
||
from fastapi import FastAPI
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from sqlalchemy import text
|
||
|
||
from .config import get_settings
|
||
from .database import Base, SessionLocal, engine
|
||
from .routers import (
|
||
auth,
|
||
groups,
|
||
locations,
|
||
products,
|
||
settings as settings_router,
|
||
stock,
|
||
units,
|
||
users,
|
||
views,
|
||
)
|
||
from .seed import ensure_builtin_units, ensure_first_admin
|
||
|
||
settings = get_settings()
|
||
|
||
|
||
def _ensure_schema() -> None:
|
||
"""Schonende Migration: fehlende Spalten auf bestehenden Tabellen nachziehen.
|
||
|
||
create_all() legt nur fehlende Tabellen an, ändert aber keine bestehenden.
|
||
Auf Postgres holen wir neue nullable-Spalten per ADD COLUMN IF NOT EXISTS nach,
|
||
damit vorhandene Installationen ihre Daten behalten. Auf SQLite (Tests) sind
|
||
die Spalten bereits durch create_all vorhanden.
|
||
"""
|
||
if engine.dialect.name != "postgresql":
|
||
return
|
||
stmts = [
|
||
"ALTER TABLE products ADD COLUMN IF NOT EXISTS display_unit_id INTEGER "
|
||
"REFERENCES units(id) ON DELETE SET NULL",
|
||
"ALTER TABLE groups ADD COLUMN IF NOT EXISTS min_stock_unit_id INTEGER "
|
||
"REFERENCES units(id) ON DELETE SET NULL",
|
||
"ALTER TABLE products ADD COLUMN IF NOT EXISTS min_stock_unit_id INTEGER "
|
||
"REFERENCES units(id) ON DELETE SET NULL",
|
||
"ALTER TABLE products ADD COLUMN IF NOT EXISTS min_stock_in_packages BOOLEAN "
|
||
"NOT NULL DEFAULT FALSE",
|
||
"ALTER TABLE products ADD COLUMN IF NOT EXISTS package_label VARCHAR(32)",
|
||
]
|
||
with engine.begin() as conn:
|
||
for stmt in stmts:
|
||
conn.execute(text(stmt))
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
# Tabellen anlegen (MVP: create_all statt Alembic-Migrationen).
|
||
Base.metadata.create_all(bind=engine)
|
||
_ensure_schema()
|
||
db = SessionLocal()
|
||
try:
|
||
ensure_builtin_units(db)
|
||
ensure_first_admin(db)
|
||
finally:
|
||
db.close()
|
||
yield
|
||
|
||
|
||
app = FastAPI(title="Project-Good – Lebensmittel-Lagerverwaltung", version="1.0.0", lifespan=lifespan)
|
||
|
||
origins = ["*"] if settings.cors_origins.strip() == "*" else [
|
||
o.strip() for o in settings.cors_origins.split(",") if o.strip()
|
||
]
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=origins,
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
|
||
@app.get("/health", tags=["meta"])
|
||
def health() -> dict:
|
||
return {"status": "ok"}
|
||
|
||
|
||
app.include_router(auth.router)
|
||
app.include_router(users.router)
|
||
app.include_router(products.router)
|
||
app.include_router(stock.router)
|
||
app.include_router(locations.router)
|
||
app.include_router(groups.router)
|
||
app.include_router(units.router)
|
||
app.include_router(views.router)
|
||
app.include_router(settings_router.router)
|