Release Nouri 1.3.3 with shopping articles

This commit is contained in:
2026-05-01 14:32:30 +02:00
parent 6b2c495cf2
commit aff40eff49
10 changed files with 334 additions and 40 deletions
+102 -23
View File
@@ -57,7 +57,7 @@ from .constants import (
WEEKDAY_OPTIONS,
WEEK_TEMPLATE_NAME_SUGGESTIONS,
)
from .db import get_db
from .db import get_db, infer_food_flavor_profile, infer_food_profile
from .images import (
allowed_image_file,
save_photo_with_variants,
@@ -1005,6 +1005,10 @@ def normalize_shopping_note(value: str | None) -> str:
return " ".join((value or "").strip().split())[:80]
def normalize_new_item_name(value: str | None) -> str:
return " ".join((value or "").strip().split())[:120]
def schedule_shopping_need(
*,
item_id: int,
@@ -1298,8 +1302,8 @@ def fetch_items_by_ids(item_ids: list[int]) -> list[dict]:
return [items_by_id[item_id] for item_id in normalized_ids if item_id in items_by_id]
def find_shopping_food_by_name(name: str) -> dict | None:
normalized_name = name.strip().lower()
def find_shopping_item_by_name(name: str) -> dict | None:
normalized_name = normalize_new_item_name(name).lower()
if not normalized_name:
return None
row = get_db().execute(
@@ -1317,11 +1321,11 @@ def find_shopping_food_by_name(name: str) -> dict | None:
FROM items
LEFT JOIN users AS owner ON owner.id = items.owner_user_id
LEFT JOIN users AS target ON target.id = items.target_user_id
WHERE items.kind = 'food'
WHERE items.kind IN ('food', 'shopping')
AND items.is_archived = 0
AND LOWER(items.name) = ?
AND {visible_clause('items')}
ORDER BY LOWER(items.name), items.id
ORDER BY CASE items.kind WHEN 'food' THEN 0 ELSE 1 END, LOWER(items.name), items.id
LIMIT 1
""",
[normalized_name, *visible_params()],
@@ -1331,6 +1335,64 @@ def find_shopping_food_by_name(name: str) -> dict | None:
return attach_builder_keys(attach_dayparts(describe_records([row])))[0]
def create_shopping_search_item(name: str, kind: str) -> dict:
normalized_name = normalize_new_item_name(name)
if not normalized_name:
raise ValueError("Bitte gib zuerst einen Namen ein.")
if kind not in {"food", "shopping"}:
raise ValueError("Bitte wähle aus, ob es ein Lebensmittel oder ein Einkaufsartikel ist.")
existing = find_shopping_item_by_name(normalized_name)
if existing is not None:
return existing
if kind == "food":
profile = infer_food_profile(normalized_name, "Unsortiert", "neutral")
category = "Unsortiert"
note = "Aus der Einkaufssuche angelegt. Details später ergänzen."
is_quick_added = 1
else:
profile = {
"base_type": "neutral",
"suggestion_role": "cooking",
"suggestion_priority": "never",
"can_be_meal_core": 0,
}
category = "Einkaufsartikel"
note = "Einkaufsartikel ohne Rezeptlogik."
is_quick_added = 0
cursor = get_db().execute(
"""
INSERT INTO items (
household_id, owner_user_id, visibility, kind, name, category,
base_type, flavor_profile, suggestion_role, suggestion_priority,
can_be_meal_core, energy_density, availability_state, note,
is_quick_added, created_by, updated_by
)
VALUES (?, ?, 'shared', ?, ?, ?, ?, ?, ?, ?, ?, 'neutral', 'idea', ?, ?, ?, ?)
""",
(
current_household_id(),
g.user["id"],
kind,
normalized_name,
category,
profile["base_type"],
infer_food_flavor_profile(normalized_name, category, profile["base_type"], profile["suggestion_role"]),
profile["suggestion_role"],
profile["suggestion_priority"],
profile["can_be_meal_core"],
note,
is_quick_added,
g.user["id"],
g.user["id"],
),
)
get_db().commit()
return get_item(int(cursor.lastrowid))
def fetch_shopping_entries():
rows = get_db().execute(
f"""
@@ -4390,18 +4452,19 @@ def mark_shopping_entry_checked(entry_id: int) -> dict:
"UPDATE shopping_entries SET is_checked = 1, checked_at = CURRENT_TIMESTAMP, checked_by = ? WHERE id = ?",
(g.user["id"], entry_id),
)
get_db().execute(
"""
UPDATE items
SET availability_state = 'home',
is_archived = 0,
is_quick_added = 0,
updated_by = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
""",
(g.user["id"], item["id"]),
)
if item["kind"] != "shopping":
get_db().execute(
"""
UPDATE items
SET availability_state = 'home',
is_archived = 0,
is_quick_added = 0,
updated_by = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
""",
(g.user["id"], item["id"]),
)
get_db().commit()
return item
@@ -4495,7 +4558,10 @@ def item_mark_bought(item_id: int):
except (ValueError, PermissionError) as exc:
flash(str(exc), "error")
return redirect(request.referrer or url_for("main.shopping_list"))
flash(f"{item['name']} ist jetzt als Zuhause vorhanden markiert.", "success")
if item["kind"] == "shopping":
flash(f"{item['name']} wurde als eingekauft markiert.", "success")
else:
flash(f"{item['name']} ist jetzt als Zuhause vorhanden markiert.", "success")
return redirect(request.referrer or url_for("main.shopping_list"))
@@ -4528,17 +4594,24 @@ def shopping_list():
if request.method == "POST":
selected_item_id = request.form.get("item_id", "").strip()
item_search = request.form.get("item_search", "").strip()
create_as = request.form.get("create_as", "").strip()
create_item_name = normalize_new_item_name(request.form.get("create_item_name") or item_search)
shopping_note = normalize_shopping_note(request.form.get("shopping_note"))
item = None
if selected_item_id.isdigit():
if create_as in {"food", "shopping"}:
try:
item = create_shopping_search_item(create_item_name, create_as)
except ValueError as exc:
flash(str(exc), "error")
elif selected_item_id.isdigit():
try:
item = get_item(int(selected_item_id))
except ValueError as exc:
flash(str(exc), "error")
elif item_search:
item = find_shopping_food_by_name(item_search)
item = find_shopping_item_by_name(item_search)
if item is None:
flash("Bitte ein Lebensmittel aus der Suche auswählen.", "error")
flash("Bitte einen Treffer auswählen oder den Begriff als Lebensmittel bzw. Einkaufsartikel anlegen.", "error")
else:
flash("Bitte zuerst etwas auswählen.", "error")
@@ -4560,7 +4633,10 @@ def shopping_list():
entries = fetch_shopping_entries()
upcoming_entries = fetch_upcoming_shopping_needs()
addable_items = fetch_items(kind="food", include_archived=False, include_quick_added=True)
addable_items = [
item for item in fetch_items(include_archived=False, include_quick_added=True)
if item["kind"] in {"food", "shopping"}
]
household_settings = get_household_settings()
shopping_weekday_label = dict(WEEKDAY_OPTIONS).get(household_settings["shopping_weekday"], "gesetzt")
return render_template(
@@ -4581,7 +4657,10 @@ def shopping_check(entry_id: int):
except (ValueError, PermissionError) as exc:
flash(str(exc), "error")
return redirect(url_with_scroll_position(url_for("main.shopping_list")))
flash(f"{item['name']} ist jetzt als Zuhause vorhanden markiert.", "success")
if item["kind"] == "shopping":
flash(f"{item['name']} wurde als eingekauft markiert.", "success")
else:
flash(f"{item['name']} ist jetzt als Zuhause vorhanden markiert.", "success")
return redirect(url_with_scroll_position(url_for("main.shopping_list")))