Schritt 1: Fundament der Lebensmittel-Lagerverwaltung (Pantry)

Backend (FastAPI + PostgreSQL): Chargen mit MHD, FEFO-Auslagern,
Einheiten-Umrechnung (Stueck/g/ml + Packungen), Open-Food-Facts-Lookup
mit lokalem Fallback, JWT-Auth mit Rollen (Admin/Nutzer), erster Admin
beim Setup, Einkaufsliste, Ablaufwarnung, Lagerorte, pytest fuer FEFO.

Web-UI (React/Vite): Login, Dashboard, Ein-/Auslagern, Produkte,
Lagerorte, Benutzerverwaltung, Einkaufsliste - rollenabhaengig.

Deploy: docker-compose + install.sh (Docker-Autoinstall, Secrets),
README und Roadmap fuer Schritt 2 (iOS) und Schritt 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-22 09:14:14 +02:00
commit 639126468f
57 changed files with 3460 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
"""Einheiten-Umrechnung.
Der Bestand einer Charge (Lot) wird immer in der Basiseinheit des Produkts
gespeichert (Stück, Gramm oder Milliliter). Nutzer geben Mengen entweder in der
Basiseinheit oder in "Packungen" (package) an. Eine Packung entspricht
``product.package_size`` Basiseinheiten.
"""
from __future__ import annotations
from ..models import BaseUnit, Product
# Aliase, die als "Basiseinheit" akzeptiert werden.
_BASE_ALIASES: dict[str, BaseUnit] = {
"piece": BaseUnit.piece,
"pieces": BaseUnit.piece,
"stück": BaseUnit.piece,
"stueck": BaseUnit.piece,
"st": BaseUnit.piece,
"g": BaseUnit.gram,
"gram": BaseUnit.gram,
"gramm": BaseUnit.gram,
"ml": BaseUnit.milliliter,
"milliliter": BaseUnit.milliliter,
}
PACKAGE_UNITS = {"package", "packung", "pkg", "pack"}
class UnitError(ValueError):
"""Wird geworfen, wenn eine Einheit nicht zum Produkt passt."""
def to_base_quantity(product: Product, quantity: float, unit: str) -> float:
"""Rechnet eine vom Nutzer angegebene Menge in Basiseinheiten um."""
if quantity <= 0:
raise UnitError("Menge muss größer als 0 sein")
unit_norm = unit.strip().lower()
if unit_norm in PACKAGE_UNITS:
if not product.package_size or product.package_size <= 0:
raise UnitError(
"Für dieses Produkt ist keine Packungsgröße hinterlegt, "
"Packungen können nicht umgerechnet werden"
)
return quantity * product.package_size
mapped = _BASE_ALIASES.get(unit_norm)
if mapped is None:
raise UnitError(f"Unbekannte Einheit: {unit}")
if mapped != product.base_unit:
raise UnitError(
f"Einheit '{unit}' passt nicht zur Basiseinheit "
f"'{product.base_unit.value}' des Produkts"
)
return float(quantity)
def base_to_packages(product: Product, quantity_base: float) -> float | None:
"""Rechnet Basiseinheiten in (ggf. gebrochene) Packungen um, falls möglich."""
if not product.package_size or product.package_size <= 0:
return None
return quantity_base / product.package_size