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>
65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
"""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
|