munch-ease-backend/shopping/__init__.py

199 lines
7 KiB
Python
Raw Normal View History

2025-10-18 03:26:42 +00:00
from typing import Any, Dict, Iterable, Iterator, List, Tuple
2024-05-17 09:09:03 +00:00
2025-10-18 03:26:42 +00:00
import ingredients
import meals
import recipes
Squashed commit of the following: commit fcd005b8624023547f28b7b28e59e6099bcfc7d4 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 20:24:07 2025 +1100 Openapi tightening commit f93bd8f641d561052c7bd075bae321b4ff3b676d Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 19:03:52 2025 +1100 Removed refactor strategy doc commit 0c5a61092f522be0c47cbbe86917c8a7e4e2d339 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 17:48:33 2025 +1100 mypy & ruff checks commit 23d66d6b18984127e17c73c3063f6120385935e9 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 16:49:35 2025 +1100 Final removal of db.py files commit f454aed1ca9783cc558cc203f29a7fe31b62a975 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:42:31 2025 +1100 Finalise restructure, remove db.py files commit 7187f6dd89489521538791c6bdebb426514beb99 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:34:54 2025 +1100 commit 6fea227ae20d32b8eb1e7a4885006a620fcc7bb1 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:32:53 2025 +1100 commit 27415e7e02d89195ad514cb017a9dbbf84d7a5e4 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:31:10 2025 +1100 commit b773428033d855f9ad82005602e049c1a2e3c585 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:28:58 2025 +1100 commit 116592c95278d995f4c516e87f2cea43cf5b7735 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:25:21 2025 +1100 commit 03ec565faea088971968ee2f9bb83e2de16b21f3 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:21:29 2025 +1100 Plan
2025-10-19 09:24:23 +00:00
from shopping.models import ShoppingList as ShoppingList, ShoppingListItem as ShoppingListItem
from shopping.repository import (
2025-10-18 03:26:42 +00:00
find_items_by_list_id as _find_items_by_list_id,
find_items_by_list_id_scoped as _find_items_by_list_id_scoped,
2025-10-18 03:26:42 +00:00
get_purchased_ingredients as _get_purchased_ingredients,
get_purchased_ingredients_scoped as _get_purchased_ingredients_scoped,
2025-10-18 03:26:42 +00:00
is_requested as is_requested,
request_meal_scoped as request_meal_scoped,
2025-11-01 07:47:45 +00:00
request_ingredient_scoped as request_ingredient_scoped,
remove_meal_request_scoped as remove_meal_request_scoped,
2025-10-18 03:26:42 +00:00
load_shopping_list as load_shopping_list,
load_shopping_list_scoped as load_shopping_list_scoped,
2025-10-18 03:26:42 +00:00
purchase as purchase,
remove_request as remove_request,
request as request,
update_purchased_meals as update_purchased_meals,
Squashed commit of the following: commit fcd005b8624023547f28b7b28e59e6099bcfc7d4 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 20:24:07 2025 +1100 Openapi tightening commit f93bd8f641d561052c7bd075bae321b4ff3b676d Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 19:03:52 2025 +1100 Removed refactor strategy doc commit 0c5a61092f522be0c47cbbe86917c8a7e4e2d339 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 17:48:33 2025 +1100 mypy & ruff checks commit 23d66d6b18984127e17c73c3063f6120385935e9 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 16:49:35 2025 +1100 Final removal of db.py files commit f454aed1ca9783cc558cc203f29a7fe31b62a975 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:42:31 2025 +1100 Finalise restructure, remove db.py files commit 7187f6dd89489521538791c6bdebb426514beb99 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:34:54 2025 +1100 commit 6fea227ae20d32b8eb1e7a4885006a620fcc7bb1 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:32:53 2025 +1100 commit 27415e7e02d89195ad514cb017a9dbbf84d7a5e4 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:31:10 2025 +1100 commit b773428033d855f9ad82005602e049c1a2e3c585 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:28:58 2025 +1100 commit 116592c95278d995f4c516e87f2cea43cf5b7735 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:25:21 2025 +1100 commit 03ec565faea088971968ee2f9bb83e2de16b21f3 Author: jableader <jacobdunk@gmail.com> Date: Sun Oct 19 15:21:29 2025 +1100 Plan
2025-10-19 09:24:23 +00:00
validate_request as validate_request,
2025-10-18 03:26:42 +00:00
)
2025-07-30 08:19:20 +00:00
2025-10-18 03:26:42 +00:00
async def to_lookups(
conn,
items: List[ShoppingListItem],
meals_lookup: Dict[int, Any] | None = None,
recipes_lookup: Dict[int, Any] | None = None,
ingredients_lookup: Dict[int, Any] | None = None,
) -> Tuple[Dict[int, Any], Dict[int, Any], Dict[int, Any]]:
meals_lookup = meals_lookup or {}
recipes_lookup = recipes_lookup or {}
ingredients_lookup = ingredients_lookup or {}
await _ensure_lookups_populated(conn, items, meals_lookup, recipes_lookup, ingredients_lookup)
return meals_lookup, recipes_lookup, ingredients_lookup
2025-07-30 08:19:20 +00:00
2025-10-18 03:26:42 +00:00
async def _ensure_lookups_populated(
conn, items: List[ShoppingListItem], meals_lookup, recipes_lookup, ingredients_lookup
):
2025-07-30 08:19:20 +00:00
for item in items:
# If the any item is not in the lookup, we need to add it
if item.meal_id and item.meal_id not in meals_lookup:
meals_lookup[item.meal_id] = await meals.find_meal_by_id(conn, item.meal_id)
if item.recipe_id and item.recipe_id not in recipes_lookup:
recipes_lookup[item.recipe_id] = await recipes.find_recipe_by_id(conn, item.recipe_id)
if item.ingredient_id and item.ingredient_id not in ingredients_lookup:
2025-10-18 03:26:42 +00:00
ingredients_lookup[item.ingredient_id] = await ingredients.find_ingredient_by_id(
conn, item.ingredient_id
)
async def get_persons_requests(conn, person_id: int) -> List[ingredients.Ingredient]:
2025-10-18 03:26:42 +00:00
ids = [
item.ingredient_id
async for item in _find_items_by_list_id(conn, None)
if item.person_id == person_id and item.ingredient_id is not None and item.meal_id is None
]
return [
ing
for ing in [
await ingredients.find_ingredient_by_id(conn, ingredient_id) for ingredient_id in ids
]
if ing is not None
]
2025-10-18 03:26:42 +00:00
def flatten_items(
items: Iterable[ShoppingListItem], meals_lookup: Dict[int, Any]
) -> Iterator[ShoppingListItem]:
for item in items:
if item.meal_id and item.meal_id in meals_lookup:
meal = meals_lookup[item.meal_id]
for mealRecipe in meal.recipes:
for ingredient in mealRecipe.recipe.ingredients:
yield ShoppingListItem(
2025-10-18 03:26:42 +00:00
ingredient_id=ingredient.id,
meal_id=item.meal_id,
recipe_id=mealRecipe.recipe.id,
person_id=item.person_id,
created_date=item.created_date,
)
for ingredient in meal.extra_ingredients:
yield ShoppingListItem(
2025-10-18 03:26:42 +00:00
ingredient_id=ingredient.id,
meal_id=item.meal_id,
person_id=item.person_id,
created_date=item.created_date,
)
else:
yield item
2025-10-18 03:26:42 +00:00
async def get_outstanding_requests(
conn,
) -> Tuple[
List[ShoppingListItem],
List[ShoppingListItem],
List[ShoppingListItem],
Dict[int, Any],
Dict[int, Any],
Dict[int, Any],
]:
current_requests = [r async for r in _find_items_by_list_id(conn, None)]
meal_requests = [r for r in current_requests if r.meal_id is not None and r.meal_id > 0]
2025-10-18 03:26:42 +00:00
# Get lookups for meals to enable flattening
meals_lookup, recipes_lookup, ingredients_lookup = await to_lookups(conn, current_requests)
2025-10-18 03:26:42 +00:00
meal_ids = [r.meal_id for r in meal_requests if r.meal_id]
2025-10-18 03:26:42 +00:00
purchased_ingredients = {
(r.ingredient_id, r.meal_id, r.recipe_id): r
async for r in _get_purchased_ingredients(conn, meal_ids)
}
2025-07-30 08:19:20 +00:00
outstanding_items = []
purchased_items = []
flattened = list(flatten_items(current_requests, meals_lookup))
2025-10-18 03:26:42 +00:00
# Now ensure that all ingredients from the flattened items are in the lookup
2025-10-18 03:26:42 +00:00
await _ensure_lookups_populated(
conn, flattened, meals_lookup, recipes_lookup, ingredients_lookup
)
2025-07-30 08:19:20 +00:00
for r in flattened:
# Meal ingredients may have already been purchased
if r.meal_id is not None and r.meal_id > 0:
purchased_item = purchased_ingredients.get((r.ingredient_id, r.meal_id, r.recipe_id))
if purchased_item:
purchased_items.append(purchased_item)
continue
outstanding_items.append(r)
2025-10-18 03:26:42 +00:00
return (
outstanding_items,
purchased_items,
meal_requests,
meals_lookup,
recipes_lookup,
ingredients_lookup,
)
async def get_outstanding_requests_scoped(
conn,
household_id: int,
) -> Tuple[
List[ShoppingListItem],
List[ShoppingListItem],
List[ShoppingListItem],
Dict[int, Any],
Dict[int, Any],
Dict[int, Any],
]:
current_requests = [r async for r in _find_items_by_list_id_scoped(conn, None, household_id)]
meal_requests = [r for r in current_requests if r.meal_id is not None and r.meal_id > 0]
# Get lookups for meals to enable flattening
meals_lookup, recipes_lookup, ingredients_lookup = await to_lookups(conn, current_requests)
meal_ids = [r.meal_id for r in meal_requests if r.meal_id]
purchased_ingredients = {
(r.ingredient_id, r.meal_id, r.recipe_id): r
async for r in _get_purchased_ingredients_scoped(conn, meal_ids, household_id)
}
outstanding_items = []
purchased_items = []
flattened = list(flatten_items(current_requests, meals_lookup))
# Now ensure that all ingredients from the flattened items are in the lookup
await _ensure_lookups_populated(
conn, flattened, meals_lookup, recipes_lookup, ingredients_lookup
)
for r in flattened:
# Meal ingredients may have already been purchased (by list in the same household)
if r.meal_id is not None and r.meal_id > 0:
purchased_item = purchased_ingredients.get((r.ingredient_id, r.meal_id, r.recipe_id))
if purchased_item:
purchased_items.append(purchased_item)
continue
outstanding_items.append(r)
return (
outstanding_items,
purchased_items,
meal_requests,
meals_lookup,
recipes_lookup,
ingredients_lookup,
)