"""Startup-Seeds: erster Admin-Benutzer und die eingebauten Einheiten.""" import json from sqlalchemy.orm import Session from .config import get_settings from .models import Category, CategoryTracking, FieldDefinition, PackageType, Role, Unit, UnitKind, User from .security import hash_password from .services.fields import slugify # (Name, Art, Faktor zur kanonischen Basiseinheit) BUILTIN_UNITS: list[tuple[str, UnitKind, float]] = [ ("Stück", UnitKind.count, 1.0), ("Gramm", UnitKind.weight, 1.0), ("Kilogramm", UnitKind.weight, 1000.0), ("Milliliter", UnitKind.volume, 1.0), ("Liter", UnitKind.volume, 1000.0), ] # Gebinde in Einzahl und Mehrzahl. Bewusst nur die gaengigen - eigene legt man # unter Verwaltung > Gebinde an. Wo Einzahl und Mehrzahl gleich lauten # ("2 Becher"), steht das hier ausdruecklich, damit es niemand fuer vergessen # haelt und "Bechers" daraus macht. BUILTIN_PACKAGE_TYPES: list[tuple[str, str]] = [ ("Packung", "Packungen"), ("Glas", "Gläser"), ("Flasche", "Flaschen"), ("Dose", "Dosen"), ("Tüte", "Tüten"), ("Beutel", "Beutel"), ("Becher", "Becher"), ("Tube", "Tuben"), ("Karton", "Kartons"), ("Riegel", "Riegel"), ("Rolle", "Rollen"), ("Sack", "Säcke"), ("Kiste", "Kisten"), ("Bund", "Bund"), ("Stück", "Stück"), ] def ensure_builtin_package_types(db: Session) -> None: changed = False for singular, plural in BUILTIN_PACKAGE_TYPES: if not db.query(PackageType).filter(PackageType.singular == singular).first(): db.add(PackageType(singular=singular, plural=plural, is_builtin=True)) changed = True if changed: db.commit() def ensure_builtin_units(db: Session) -> None: changed = False for name, kind, factor in BUILTIN_UNITS: if not db.query(Unit).filter(Unit.name == name).first(): db.add(Unit(name=name, kind=kind, factor=factor, is_builtin=True)) changed = True if changed: db.commit() # Startliste für den Überblick über den Vorrat. Zweistufig, damit die # Verschachtelung von Anfang an sichtbar ist. Alles frei änderbar – # umbenennen, löschen und eigene ergänzen ist ausdrücklich vorgesehen. BUILTIN_CATEGORIES: list[tuple[str, list[str]]] = [ ("Obst & Gemüse", []), ("Molkereiprodukte", ["Käse", "Joghurt & Quark"]), ("Fleisch & Fisch", []), ("Brot & Backwaren", []), ("Trockenwaren & Teigwaren", ["Mehl & Zucker", "Nudeln & Reis"]), ("Konserven & Gläser", []), ("Tiefkühl", []), ("Süßwaren & Snacks", ["Schokolade", "Chips & Salziges"]), ("Getränke", ["Kaffee & Tee"]), ("Gewürze & Saucen", []), ("Frühstück", []), ("Haushalt & Hygiene", []), ] def ensure_builtin_categories(db: Session) -> None: """Legt nur an, was fehlt – gelöschte Kategorien kommen nicht zurück. Anders als bei den Einheiten prüfen wir zusätzlich, ob überhaupt schon Kategorien existieren: Wer die Startliste bewusst leergeräumt hat, soll sie beim nächsten Start nicht wiederfinden. """ if db.query(Category).first() is not None: return food = CategoryTracking.food.value for name, children in BUILTIN_CATEGORIES: parent = Category(name=name, is_builtin=True, tracking=food) db.add(parent) db.flush() for child in children: db.add(Category(name=child, parent_id=parent.id, is_builtin=True, tracking=food)) db.commit() # Beispiel-Kategorien für Gegenstände (Non-Food) mit ein paar sinnvollen Feldern. # Frei änderbar; wer sie löscht, bekommt sie nicht zurück (siehe Guard unten). OBJECT_EXAMPLES: list[dict] = [ { "name": "Kleidung", "fields": [ {"label": "Größe", "type": "select", "options": ["XS", "S", "M", "L", "XL", "XXL"]}, {"label": "Farbe", "type": "text"}, ], "children": [ {"name": "Schuhe", "fields": [{"label": "Schuhgröße", "type": "number", "unit": "EU"}]}, ], }, { # Kaufdatum/Garantie sind je Einzelstück (Item), nicht am Modell – deshalb # hier bewusst keine solchen Felder. Modell-Felder = gemeinsame Merkmale. "name": "Elektronik", "fields": [], "children": [ {"name": "Powerbank", "fields": [{"label": "Kapazität", "type": "number", "unit": "mAh"}]}, {"name": "Kabel", "fields": [ {"label": "Länge", "type": "number", "unit": "cm"}, {"label": "Anschluss", "type": "text"}, ]}, ], }, {"name": "Werkzeug", "fields": []}, {"name": "Bücher & Medien", "fields": []}, ] def _add_fields(db: Session, category_id: int, felder: list[dict]) -> None: for pos, f in enumerate(felder): optionen = f.get("options") db.add( FieldDefinition( category_id=category_id, label=f["label"], key=slugify(f["label"]), field_type=f.get("type", "text"), unit=f.get("unit"), options=json.dumps(optionen) if optionen else None, required=bool(f.get("required")), position=pos, is_builtin=True, ) ) def ensure_example_object_categories(db: Session) -> None: """Legt Beispiel-Gegenstandskategorien mit Feldern an – nur, wenn es noch keine gibt. So sieht man das Gegenstands-Feature gleich in Aktion; wer die Beispiele bewusst entfernt, bekommt sie beim nächsten Start nicht zurück. """ if ( db.query(Category) .filter(Category.tracking == CategoryTracking.object.value) .first() is not None ): return for top in OBJECT_EXAMPLES: parent = Category( name=top["name"], tracking=CategoryTracking.object.value, is_builtin=True ) db.add(parent) db.flush() _add_fields(db, parent.id, top.get("fields", [])) for child in top.get("children", []): node = Category( name=child["name"], parent_id=parent.id, tracking=CategoryTracking.object.value, is_builtin=True, ) db.add(node) db.flush() _add_fields(db, node.id, child.get("fields", [])) db.commit() def ensure_first_admin(db: Session) -> None: settings = get_settings() has_users = db.query(User).first() is not None if has_users: return admin = User( username=settings.admin_username, password_hash=hash_password(settings.admin_password), role=Role.admin, ) db.add(admin) db.commit()