Feature: Einstiegsseite, Einkaufsliste, Wochenplan-Live-Suche & Zufallsgenerator-Optimierung
This commit is contained in:
+173
-1
@@ -193,6 +193,20 @@ def init_db():
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
""")
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS shopping_list (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
item_name VARCHAR(255) NOT NULL,
|
||||
amount VARCHAR(50) DEFAULT '',
|
||||
unit VARCHAR(50) DEFAULT '',
|
||||
checked TINYINT(1) NOT NULL DEFAULT 0,
|
||||
source VARCHAR(255) DEFAULT '',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_shop_checked (checked)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
""")
|
||||
|
||||
cursor.execute("DELETE FROM dishes")
|
||||
else:
|
||||
cursor = conn.cursor()
|
||||
@@ -251,6 +265,19 @@ def init_db():
|
||||
)
|
||||
""")
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS shopping_list (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
item_name TEXT NOT NULL,
|
||||
amount TEXT DEFAULT '',
|
||||
unit TEXT DEFAULT '',
|
||||
checked INTEGER NOT NULL DEFAULT 0,
|
||||
source TEXT DEFAULT '',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
|
||||
cursor.execute("DELETE FROM dishes")
|
||||
conn.commit()
|
||||
finally:
|
||||
@@ -461,7 +488,9 @@ def query_recipe_candidates(chapter_number: Optional[int] = None, efforts: Optio
|
||||
if chapter_number is not None and chapter_number > 0:
|
||||
pool = [r for r in all_recipes if r.get("chapter_number") == chapter_number]
|
||||
else:
|
||||
pool = list(all_recipes)
|
||||
# Standard beim Zufallsgenerator: Kategorien Desserts (9), Kuchen & Gebäck (10) und Plätzchen (11) ausschließen
|
||||
EXCLUDED_SHUFFLE_CHAPTERS = {9, 10, 11}
|
||||
pool = [r for r in all_recipes if r.get("chapter_number") not in EXCLUDED_SHUFFLE_CHAPTERS]
|
||||
|
||||
if not pool:
|
||||
return []
|
||||
@@ -752,3 +781,146 @@ def clear_meal_plan_week(year: int, week: int) -> int:
|
||||
return res.get("rowcount", 0)
|
||||
|
||||
|
||||
# --- Shopping List (Einkaufsliste) ---
|
||||
|
||||
def get_shopping_list() -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Einträge der Einkaufsliste zurück, sortiert nach unvollständig/erledigt."""
|
||||
query = """
|
||||
SELECT id, item_name, amount, unit, checked, source, created_at, updated_at
|
||||
FROM shopping_list
|
||||
ORDER BY checked ASC, id DESC
|
||||
"""
|
||||
rows = execute_query(query, fetch="all")
|
||||
for r in rows:
|
||||
r["checked"] = bool(r.get("checked"))
|
||||
return rows
|
||||
|
||||
|
||||
def add_shopping_item(item_name: str, amount: str = "", unit: str = "", source: str = "") -> Dict[str, Any]:
|
||||
"""Fügt einen einzelnen Eintrag zur Einkaufsliste hinzu."""
|
||||
query = "INSERT INTO shopping_list (item_name, amount, unit, source, checked) VALUES (?, ?, ?, ?, 0)"
|
||||
res = execute_query(query, (item_name.strip(), amount.strip(), unit.strip(), source.strip()), fetch="none", commit=True)
|
||||
item_id = res.get("lastrowid")
|
||||
row = execute_query("SELECT * FROM shopping_list WHERE id = ?", (item_id,), fetch="one")
|
||||
if row:
|
||||
row["checked"] = bool(row.get("checked"))
|
||||
return row
|
||||
|
||||
|
||||
def add_shopping_items_bulk(items: List[Dict[str, Any]]) -> int:
|
||||
"""Fügt mehrere Zutaten/Einträge auf einmal zur Einkaufsliste hinzu."""
|
||||
if not items:
|
||||
return 0
|
||||
count = 0
|
||||
for it in items:
|
||||
name = (it.get("item_name") or it.get("name") or it.get("raw") or "").strip()
|
||||
if not name:
|
||||
continue
|
||||
amt = str(it.get("amount") or "").strip()
|
||||
unit = str(it.get("unit") or "").strip()
|
||||
src = str(it.get("source") or it.get("recipe_title") or "").strip()
|
||||
query = "INSERT INTO shopping_list (item_name, amount, unit, source, checked) VALUES (?, ?, ?, ?, 0)"
|
||||
execute_query(query, (name, amt, unit, src), fetch="none", commit=True)
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def toggle_shopping_item(item_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Schaltet den Erledigt-Status eines Eintrags um."""
|
||||
query = "UPDATE shopping_list SET checked = CASE WHEN checked = 1 THEN 0 ELSE 1 END WHERE id = ?"
|
||||
execute_query(query, (item_id,), fetch="none", commit=True)
|
||||
row = execute_query("SELECT * FROM shopping_list WHERE id = ?", (item_id,), fetch="one")
|
||||
if row:
|
||||
row["checked"] = bool(row.get("checked"))
|
||||
return row
|
||||
|
||||
|
||||
def delete_shopping_item(item_id: int) -> bool:
|
||||
"""Löscht einen einzelnen Eintrag aus der Einkaufsliste."""
|
||||
query = "DELETE FROM shopping_list WHERE id = ?"
|
||||
res = execute_query(query, (item_id,), fetch="none", commit=True)
|
||||
return res.get("rowcount", 0) > 0
|
||||
|
||||
|
||||
def delete_checked_shopping_items() -> int:
|
||||
"""Löscht alle bereits abgehakten Einträge aus der Einkaufsliste."""
|
||||
query = "DELETE FROM shopping_list WHERE checked = 1"
|
||||
res = execute_query(query, fetch="none", commit=True)
|
||||
return res.get("rowcount", 0)
|
||||
|
||||
|
||||
def clear_shopping_list() -> int:
|
||||
"""Leert die gesamte Einkaufsliste."""
|
||||
query = "DELETE FROM shopping_list"
|
||||
res = execute_query(query, fetch="none", commit=True)
|
||||
return res.get("rowcount", 0)
|
||||
|
||||
|
||||
def get_meal_plan_ingredients(year: int, week: int) -> List[Dict[str, Any]]:
|
||||
"""Gibt alle Zutaten aller Rezepte des Wochenplans strukturiert für die Einkaufsliste zurück."""
|
||||
slots = get_meal_plan(year, week)
|
||||
if not slots:
|
||||
return []
|
||||
|
||||
from .ingredients import parse_ingredients_block
|
||||
|
||||
all_recipes = {r["id"]: r for r in get_all_recipes()}
|
||||
day_names = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"]
|
||||
meal_labels = {"frueh": "Früh", "mittag": "Mittag", "kaffee": "Kaffee & Kuchen", "abend": "Abend"}
|
||||
|
||||
recipe_groups = []
|
||||
seen_slots = set()
|
||||
|
||||
for s in slots:
|
||||
r_id = s.get("recipe_id")
|
||||
if not r_id or r_id not in all_recipes:
|
||||
continue
|
||||
|
||||
recipe = all_recipes[r_id]
|
||||
d_idx = s.get("day_of_week", 0)
|
||||
day_str = day_names[d_idx] if 0 <= d_idx < 7 else f"Tag {d_idx}"
|
||||
m_type = s.get("meal_type", "")
|
||||
m_label = meal_labels.get(m_type, m_type.capitalize())
|
||||
slot_key = f"{d_idx}_{m_type}_{r_id}"
|
||||
|
||||
if slot_key in seen_slots:
|
||||
continue
|
||||
seen_slots.add(slot_key)
|
||||
|
||||
slot_label = f"{day_str} {m_label}"
|
||||
parsed = parse_ingredients_block(recipe.get("ingredients", ""))
|
||||
|
||||
items = []
|
||||
for line in parsed:
|
||||
if line.get("is_header"):
|
||||
continue
|
||||
raw_text = line.get("raw", "").strip()
|
||||
if not raw_text:
|
||||
continue
|
||||
|
||||
name = line.get("name") or raw_text
|
||||
amt = str(line.get("amount") or "") if line.get("amount") is not None else ""
|
||||
unit = line.get("unit") or ""
|
||||
|
||||
items.append({
|
||||
"recipe_id": recipe["id"],
|
||||
"recipe_title": recipe["title"],
|
||||
"slot_label": slot_label,
|
||||
"raw": raw_text,
|
||||
"name": name,
|
||||
"amount": amt,
|
||||
"unit": unit
|
||||
})
|
||||
|
||||
if items:
|
||||
recipe_groups.append({
|
||||
"recipe_id": recipe["id"],
|
||||
"recipe_title": recipe["title"],
|
||||
"slot_label": slot_label,
|
||||
"items": items
|
||||
})
|
||||
|
||||
return recipe_groups
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user