"""Einheiten-Umrechnung auf Basis der verwaltbaren Unit-Tabelle. Bestände werden immer in der kanonischen Basiseinheit der jeweiligen Art gespeichert: Stück (count), Gramm (weight), Milliliter (volume). Verwaltete Einheiten (z.B. Kilogramm, Liter, Pfund) rechnen über ihren Faktor dorthin um. """ from __future__ import annotations from sqlalchemy import func from sqlalchemy.orm import Session from ..models import BaseUnit, Product, Unit, UnitKind BASE_OF_KIND: dict[UnitKind, BaseUnit] = { UnitKind.count: BaseUnit.piece, UnitKind.weight: BaseUnit.gram, UnitKind.volume: BaseUnit.milliliter, } KIND_OF_BASE: dict[BaseUnit, UnitKind] = {v: k for k, v in BASE_OF_KIND.items()} BASE_LABEL: dict[BaseUnit, str] = { BaseUnit.piece: "Stück", BaseUnit.gram: "Gramm", BaseUnit.milliliter: "Milliliter", } PACKAGE_TOKENS = {"package", "packung", "pkg", "pack"} # Kürzel/Aliase, die direkt auf eingebaute Einheitennamen zeigen. _ALIAS = { "g": "gramm", "gram": "gramm", "gramm": "gramm", "kg": "kilogramm", "kilogramm": "kilogramm", "ml": "milliliter", "milliliter": "milliliter", "l": "liter", "liter": "liter", "stück": "stück", "stueck": "stück", "st": "stück", "stk": "stück", "piece": "stück", } class ConversionError(ValueError): """Fachlicher Fehler bei der Einheiten-Umrechnung.""" def kind_of_product(product: Product) -> UnitKind: return KIND_OF_BASE[product.base_unit] def find_unit(db: Session, token: str) -> Unit | None: t = token.strip().lower() t = _ALIAS.get(t, t) return db.query(Unit).filter(func.lower(Unit.name) == t).first() def to_base(db: Session, product: Product, quantity: float, unit_token: str) -> float: """Rechnet eine Menge (in unit_token) in die Basiseinheit des Produkts um.""" if quantity <= 0: raise ConversionError("Menge muss größer als 0 sein") t = (unit_token or "").strip().lower() if t in PACKAGE_TOKENS: if not product.package_size or product.package_size <= 0: raise ConversionError( "Für dieses Produkt ist keine Packungsgröße hinterlegt." ) return quantity * product.package_size unit = find_unit(db, t) if unit is None: raise ConversionError(f"Unbekannte Einheit: {unit_token}") if unit.kind != kind_of_product(product): raise ConversionError( f"Einheit '{unit.name}' passt nicht zur Art des Produkts " f"({BASE_LABEL[product.base_unit]})." ) return quantity * unit.factor def resolve_product_unit(db: Session, unit_id: int) -> tuple[BaseUnit, int]: """Ermittelt zu einer gewählten Einheit die kanonische Basiseinheit + Anzeige-ID.""" unit = db.get(Unit, unit_id) if unit is None: raise ConversionError("Einheit nicht gefunden") return BASE_OF_KIND[unit.kind], unit.id def display_unit_info(product: Product) -> tuple[str, float]: """Name und Faktor der Anzeigeeinheit des Produkts (Fallback: Basiseinheit).""" if product.display_unit is not None: return product.display_unit.name, product.display_unit.factor return BASE_LABEL[product.base_unit], 1.0