"""Gemerkte Zuordnungen: Mealie-Lebensmittel → eigener Artikel oder Gruppe. Der unscharfe Abgleich in :mod:`services.rezepte` ist eine Heuristik und wird immer Fälle haben, die er nicht erraten kann — „Lime Curd", „Ras el Hanout", oder einfach ein Hausname. Hier liegt der Korrekturweg: Einmal von Hand zugeordnet, gilt es für jedes Rezept, in dem dasselbe Lebensmittel vorkommt. Der Schlüssel ist der normalisierte Name, nicht die Mealie-ID. Mealie-IDs sind UUIDs, die beim Neuanlegen eines Lebensmittels wechseln; der Name bleibt. Und nur über den Namen greift die Zuordnung auch für Zutaten, die in Mealie gar kein verknüpftes Lebensmittel haben (freier Text „2 Eier"). """ from __future__ import annotations from sqlalchemy.orm import Session from ..models import Group, MealieFoodMap, Product from .matching import normalisieren class ZuordnungFehler(ValueError): """Unbrauchbare Zuordnung – der Router macht daraus eine 400/404.""" def schluessel(name: str) -> str: """Normalisierter Schlüssel. Leer heisst „damit lässt sich nichts merken".""" return normalisieren(name)[:200] def lade(db: Session) -> dict[str, tuple[str, int]]: """Alle gültigen Zuordnungen als ``{schluessel: (art, id)}``. Zeilen, deren Ziel es nicht mehr gibt, fallen hier still heraus statt eine Ausnahme zu werfen: Auf SQLite greift kein Fremdschlüssel, und ein gelöschter Artikel darf nicht dazu führen, dass sich kein Rezept mehr öffnen lässt. """ ergebnis: dict[str, tuple[str, int]] = {} for zeile in db.query(MealieFoodMap).all(): if zeile.product_id is not None: if db.get(Product, zeile.product_id) is not None: ergebnis[zeile.food_key] = ("product", zeile.product_id) elif zeile.group_id is not None: if db.get(Group, zeile.group_id) is not None: ergebnis[zeile.food_key] = ("group", zeile.group_id) return ergebnis def ziel_name(db: Session, art: str, ziel_id: int) -> str: if art == "product": produkt = db.get(Product, ziel_id) return produkt.name if produkt is not None else "" gruppe = db.get(Group, ziel_id) return gruppe.name if gruppe is not None else "" def setze(db: Session, food: str, art: str, ziel_id: int) -> MealieFoodMap: """Zuordnung anlegen oder ändern. Prüft, dass das Ziel existiert.""" key = schluessel(food) if not key: raise ZuordnungFehler( "Zu dieser Zutat steht kein Name da – ohne Namen lässt sich die " "Zuordnung nicht merken." ) if art not in ("product", "group"): raise ZuordnungFehler("Zugeordnet wird auf einen Artikel oder eine Gruppe.") if art == "product" and db.get(Product, ziel_id) is None: raise ZuordnungFehler("Diesen Artikel gibt es nicht.") if art == "group" and db.get(Group, ziel_id) is None: raise ZuordnungFehler("Diese Gruppe gibt es nicht.") zeile = db.get(MealieFoodMap, key) if zeile is None: zeile = MealieFoodMap(food_key=key) db.add(zeile) # Immer beide Felder setzen, damit ein Wechsel Artikel → Gruppe nicht # beide gefüllt zurücklässt. zeile.food_name = (food or "").strip()[:200] zeile.product_id = ziel_id if art == "product" else None zeile.group_id = ziel_id if art == "group" else None db.commit() db.refresh(zeile) return zeile def loesche(db: Session, food: str) -> bool: """Zuordnung vergessen. ``False``, wenn es keine gab.""" zeile = db.get(MealieFoodMap, schluessel(food)) if zeile is None: return False db.delete(zeile) db.commit() return True