Add shopping entry notes
This commit is contained in:
+122
-20
@@ -1001,6 +1001,10 @@ def should_activate_shopping_need(needed_date: date, today: date | None = None)
|
||||
return (today or date.today()) >= shopping_activation_date_for(needed_date)
|
||||
|
||||
|
||||
def normalize_shopping_note(value: str | None) -> str:
|
||||
return " ".join((value or "").strip().split())[:80]
|
||||
|
||||
|
||||
def schedule_shopping_need(
|
||||
*,
|
||||
item_id: int,
|
||||
@@ -1082,14 +1086,16 @@ def add_to_shopping_list(
|
||||
visibility_override: str | None = None,
|
||||
needed_for_date: str | None = None,
|
||||
needed_for_daypart_id: int | None = None,
|
||||
shopping_note: str | None = None,
|
||||
) -> bool:
|
||||
item = get_item(item_id)
|
||||
normalized_note = normalize_shopping_note(shopping_note)
|
||||
existing = get_db().execute(
|
||||
"""
|
||||
SELECT id FROM shopping_entries
|
||||
WHERE item_id = ? AND is_checked = 0
|
||||
WHERE item_id = ? AND shopping_note = ? AND is_checked = 0
|
||||
""",
|
||||
(item_id,),
|
||||
(item_id, normalized_note),
|
||||
).fetchone()
|
||||
if existing:
|
||||
return False
|
||||
@@ -1099,15 +1105,16 @@ def add_to_shopping_list(
|
||||
get_db().execute(
|
||||
"""
|
||||
INSERT INTO shopping_entries (
|
||||
household_id, owner_user_id, visibility, item_id, added_by, needed_for_date, needed_for_daypart_id
|
||||
household_id, owner_user_id, visibility, item_id, shopping_note, added_by, needed_for_date, needed_for_daypart_id
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
current_household_id(),
|
||||
owner_user_id,
|
||||
visibility,
|
||||
item_id,
|
||||
normalized_note,
|
||||
user_id,
|
||||
needed_for_date,
|
||||
needed_for_daypart_id,
|
||||
@@ -1150,6 +1157,7 @@ def ensure_item_or_missing_components_are_shopped(
|
||||
needed_for_date: str | None = None,
|
||||
needed_for_daypart_id: int | None = None,
|
||||
source_item_id: int | None = None,
|
||||
shopping_note: str | None = None,
|
||||
) -> dict:
|
||||
item = get_item(item_id)
|
||||
if item["kind"] == "meal":
|
||||
@@ -1219,6 +1227,7 @@ def ensure_item_or_missing_components_are_shopped(
|
||||
visibility_override=visibility,
|
||||
needed_for_date=needed_for_date,
|
||||
needed_for_daypart_id=needed_for_daypart_id,
|
||||
shopping_note=shopping_note,
|
||||
)
|
||||
return {
|
||||
"added": added,
|
||||
@@ -1289,6 +1298,39 @@ 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()
|
||||
if not normalized_name:
|
||||
return None
|
||||
row = get_db().execute(
|
||||
f"""
|
||||
SELECT items.*,
|
||||
owner.display_name AS owner_display_name,
|
||||
owner.username AS owner_username,
|
||||
target.display_name AS target_display_name,
|
||||
target.username AS target_username,
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM shopping_entries
|
||||
WHERE shopping_entries.item_id = items.id AND shopping_entries.is_checked = 0
|
||||
) AS is_on_shopping_list
|
||||
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'
|
||||
AND items.is_archived = 0
|
||||
AND LOWER(items.name) = ?
|
||||
AND {visible_clause('items')}
|
||||
ORDER BY LOWER(items.name), items.id
|
||||
LIMIT 1
|
||||
""",
|
||||
[normalized_name, *visible_params()],
|
||||
).fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
return attach_builder_keys(attach_dayparts(describe_records([row])))[0]
|
||||
|
||||
|
||||
def fetch_shopping_entries():
|
||||
rows = get_db().execute(
|
||||
f"""
|
||||
@@ -4385,6 +4427,56 @@ def remove_shopping_entry(entry_id: int) -> None:
|
||||
get_db().commit()
|
||||
|
||||
|
||||
@main_bp.post("/shopping/<int:entry_id>/note")
|
||||
@login_required
|
||||
def shopping_update_note(entry_id: int):
|
||||
entry = get_db().execute(
|
||||
f"""
|
||||
SELECT shopping_entries.*,
|
||||
owner.display_name AS owner_display_name,
|
||||
owner.username AS owner_username
|
||||
FROM shopping_entries
|
||||
LEFT JOIN users AS owner ON owner.id = shopping_entries.owner_user_id
|
||||
WHERE shopping_entries.id = ? AND {visible_clause('shopping_entries')}
|
||||
""",
|
||||
[entry_id, *visible_params()],
|
||||
).fetchone()
|
||||
if entry is None:
|
||||
flash("Der Einkaufseintrag wurde nicht gefunden.", "error")
|
||||
return redirect(url_with_scroll_position(url_for("main.shopping_list")))
|
||||
|
||||
try:
|
||||
ensure_can_edit(describe_record(dict(entry)), "Diesen Einkaufseintrag kannst du gerade nicht ändern.")
|
||||
except PermissionError as exc:
|
||||
flash(str(exc), "error")
|
||||
return redirect(url_with_scroll_position(url_for("main.shopping_list")))
|
||||
|
||||
shopping_note = normalize_shopping_note(request.form.get("shopping_note"))
|
||||
duplicate = get_db().execute(
|
||||
"""
|
||||
SELECT id
|
||||
FROM shopping_entries
|
||||
WHERE item_id = ?
|
||||
AND shopping_note = ?
|
||||
AND is_checked = 0
|
||||
AND id != ?
|
||||
LIMIT 1
|
||||
""",
|
||||
(entry["item_id"], shopping_note, entry_id),
|
||||
).fetchone()
|
||||
if duplicate:
|
||||
flash("Dieser Hinweis steht für das Lebensmittel schon auf der Einkaufsliste.", "info")
|
||||
return redirect(url_with_scroll_position(url_for("main.shopping_list")))
|
||||
|
||||
get_db().execute(
|
||||
"UPDATE shopping_entries SET shopping_note = ? WHERE id = ?",
|
||||
(shopping_note, entry_id),
|
||||
)
|
||||
get_db().commit()
|
||||
flash("Der Einkaufshinweis wurde gespeichert.", "success")
|
||||
return redirect(url_with_scroll_position(url_for("main.shopping_list")))
|
||||
|
||||
|
||||
@main_bp.post("/items/<int:item_id>/shopping/bought")
|
||||
@login_required
|
||||
def item_mark_bought(item_id: int):
|
||||
@@ -4436,30 +4528,40 @@ def item_remove_from_shopping(item_id: int):
|
||||
def shopping_list():
|
||||
if request.method == "POST":
|
||||
selected_item_id = request.form.get("item_id", "").strip()
|
||||
if not selected_item_id.isdigit():
|
||||
flash("Bitte zuerst etwas auswählen.", "error")
|
||||
else:
|
||||
item_search = request.form.get("item_search", "").strip()
|
||||
shopping_note = normalize_shopping_note(request.form.get("shopping_note"))
|
||||
item = None
|
||||
if selected_item_id.isdigit():
|
||||
try:
|
||||
item = get_item(int(selected_item_id))
|
||||
result = ensure_item_or_missing_components_are_shopped(
|
||||
item["id"],
|
||||
g.user["id"],
|
||||
item["visibility"],
|
||||
)
|
||||
if result["count"]:
|
||||
flash(f"Die Einkaufsliste wurde ergänzt: {', '.join(result['names'][:4])}.", "success")
|
||||
elif result["scheduled_count"]:
|
||||
flash("Ein paar Dinge sind für einen späteren Einkauf vorgemerkt.", "info")
|
||||
else:
|
||||
flash("Dafür ist gerade nichts zusätzlich nötig.", "info")
|
||||
except ValueError as exc:
|
||||
flash(str(exc), "error")
|
||||
elif item_search:
|
||||
item = find_shopping_food_by_name(item_search)
|
||||
if item is None:
|
||||
flash("Bitte ein Lebensmittel aus der Suche auswählen.", "error")
|
||||
else:
|
||||
flash("Bitte zuerst etwas auswählen.", "error")
|
||||
|
||||
if item is not None:
|
||||
result = ensure_item_or_missing_components_are_shopped(
|
||||
item["id"],
|
||||
g.user["id"],
|
||||
item["visibility"],
|
||||
shopping_note=shopping_note,
|
||||
)
|
||||
if result["count"]:
|
||||
note_suffix = f" ({shopping_note})" if shopping_note else ""
|
||||
flash(f"Die Einkaufsliste wurde ergänzt: {', '.join(result['names'][:4])}{note_suffix}.", "success")
|
||||
elif result["scheduled_count"]:
|
||||
flash("Ein paar Dinge sind für einen späteren Einkauf vorgemerkt.", "info")
|
||||
else:
|
||||
flash("Dieser Einkaufseintrag steht so schon auf der Liste.", "info")
|
||||
return redirect(url_with_scroll_position(url_for("main.shopping_list")))
|
||||
|
||||
entries = fetch_shopping_entries()
|
||||
upcoming_entries = fetch_upcoming_shopping_needs()
|
||||
addable_items = fetch_items(include_archived=True, include_quick_added=True)
|
||||
addable_items = [item for item in addable_items if item["kind"] == "food" and not item["is_on_shopping_list"]]
|
||||
addable_items = fetch_items(kind="food", include_archived=False, include_quick_added=True)
|
||||
household_settings = get_household_settings()
|
||||
shopping_weekday_label = dict(WEEKDAY_OPTIONS).get(household_settings["shopping_weekday"], "gesetzt")
|
||||
return render_template(
|
||||
|
||||
Reference in New Issue
Block a user