Gegenstands-Verwaltung (Non-Food) neben Lebensmitteln
Die Kategorie bestimmt die Verwaltungsart (food/object). Gegenstaende: Menge je Lagerort statt Chargen/MHD, Umlagern (ohne Grund) und Entfernen mit Pflicht-Grund samt Entnahme-Statistik. Beliebig viele eigene Felder je Kategorie (vererbt an Unterkategorien), "gekauft bei" ueber eine verwaltbare Shop-Liste und ein Produktlink. Barcode-Lookup zusaetzlich ueber Open Products Facts. Der Lebensmittel-Teil (Chargen/MHD/FEFO) bleibt unveraendert. Umgesetzt in Backend (FastAPI, +Tests gruen), Web (React) und iOS (SwiftUI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
"""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, PackageType, Role, Unit, UnitKind, User
|
||||
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]] = [
|
||||
@@ -87,12 +90,99 @@ def ensure_builtin_categories(db: Session) -> None:
|
||||
"""
|
||||
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)
|
||||
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))
|
||||
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"}]},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Elektronik",
|
||||
"fields": [
|
||||
{"label": "Kaufdatum", "type": "date"},
|
||||
{"label": "Garantie bis", "type": "date"},
|
||||
],
|
||||
"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()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user