Fixed purchased item matching

This commit is contained in:
jableader 2025-07-30 18:19:20 +10:00
parent 4fa88335d1
commit 410b097d5c
5 changed files with 52 additions and 11 deletions

View file

@ -1,4 +1,4 @@
from ingredients.db import Ingredient, find_ingredients_by_meal_id, find_ingredients_by_recipe_id, insert_ingredient, delete_ingredients_by_meal_id
from ingredients.db import Ingredient, find_ingredient_by_id, find_ingredients_by_meal_id, find_ingredients_by_recipe_id, insert_ingredient, delete_ingredients_by_meal_id
import units
from products import Product, find_product_by_tag, get_or_create, add_missing_tags

View file

@ -37,7 +37,7 @@ async def insert_ingredient(conn, ingredient: Ingredient):
if ingredient.product:
ingredient.product_id = ingredient.product.id
if ingredient.product_id < 0:
if ingredient.product_id is None or ingredient.product_id < 0:
ingredient.product_id = None
async with conn.execute('''
@ -46,6 +46,21 @@ async def insert_ingredient(conn, ingredient: Ingredient):
''', (ingredient.name, ingredient.line, ingredient.preparation, ingredient.unit, ingredient.quantity, ingredient.product_id, ingredient.recipe_id, ingredient.meal_id)) as cursor:
ingredient.id = cursor.lastrowid
async def find_ingredient_by_id(conn, ingredient_id: int) -> Optional[Ingredient]:
ingredient_keys = [f'ingredient.{key}' for key in Ingredient.KEYS]
product_keys = [f'product.{key}' for key in Product.KEYS]
async with conn.execute(f'''
SELECT {','.join(ingredient_keys + product_keys)} FROM Ingredient
LEFT JOIN Product ON Ingredient.product_id = Product.id
WHERE id = ?
''', (ingredient_id,)) as cursor:
async for row in cursor:
product_keys = {k:v for k,v in zip(Product.KEYS, row[len(Ingredient.KEYS):])}
product = Product(**product_keys) if product_keys['id'] else None
return Ingredient(**{k:v for k,v in zip(Ingredient.KEYS, row[:len(Ingredient.KEYS)])}, product=product)
return None
async def find_ingredients_by_recipe_id(conn, recipe_id: int) -> AsyncIterator[Ingredient]:
ingredient_keys = [f'ingredient.{key}' for key in Ingredient.KEYS]
product_keys = [f'product.{key}' for key in Product.KEYS]

View file

@ -270,7 +270,7 @@ async def get_current_shopping_list(conn: sqlite3.Connection = Depends(get_db))
# Reduce the data structure to items and lookups
items = meal_requests + outstanding_requests + purchased_requests + [item for sl in shopping_list_lookup.values() for item in sl.items]
meals_lookup, recipes_lookup, ingredients_lookup = shopping.remove_references(items)
meals_lookup, recipes_lookup, ingredients_lookup = await shopping.to_lookups(conn, items)
return CurrentShoppingList(
outstanding_items=outstanding_requests,
@ -291,7 +291,7 @@ class PurchasedShoppingList(BaseModel):
@app.get("/api/shopping/{list_id}")
async def get_shopping_list(list_id: int, conn: sqlite3.Connection = Depends(get_db)) -> PurchasedShoppingList:
shopping_list = await shopping.load_shopping_list(conn, list_id)
meals_lookup, recipes_lookup, ingredients_lookup = shopping.remove_references(shopping_list.items)
meals_lookup, recipes_lookup, ingredients_lookup = await shopping.to_lookups(conn, shopping_list.items)
return PurchasedShoppingList(list=shopping_list, meals_lookup=meals_lookup, recipes_lookup=recipes_lookup, ingredients_lookup=ingredients_lookup)
@app.post("/api/shopping/")
@ -302,7 +302,7 @@ async def purchase_ingredients(shopping_list: shopping.ShoppingList, conn: sqlit
await conn.commit()
result = PurchasedShoppingList(list=shopping_list)
shopping.remove_references(shopping_list.items, result.meals_lookup, result.recipes_lookup, result.ingredients_lookup)
await shopping.to_lookups(conn, shopping_list.items, result.meals_lookup, result.recipes_lookup, result.ingredients_lookup)
return result
@app.get("/api/shopping/current/me/ingredients")

View file

@ -3,11 +3,20 @@ from shopping.db import ShoppingList, ShoppingListItem, load_shopping_list, purc
from shopping.db import find_items_by_list_id as _find_items_by_list_id, get_purchased_ingredients as _get_purchased_ingredients
def remove_references(items: List[ShoppingListItem], meals_lookup = None, recipes_lookup = None, ingredients_lookup = None) -> Tuple[Dict[int, Any], Dict[int, Any], Dict[int, Any]]:
import meals, recipes, ingredients
async def to_lookups(conn, items: List[ShoppingListItem], meals_lookup: Dict[int, Any] = None, recipes_lookup: Dict[int, Any] = None, ingredients_lookup: Dict[int, Any] = 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 {}
lookups = (meals_lookup, recipes_lookup, ingredients_lookup)
_move_refs_to_lookups(items, *lookups)
await _ensure_lookups_populated(conn, items, *lookups)
return lookups
def _move_refs_to_lookups(items: List[ShoppingListItem], meals_lookup: Dict[int, Any], recipes_lookup: Dict[int, Any], ingredients_lookup: Dict[int, Any]):
for item in items:
if item.meal and not item.meal.id in meals_lookup:
meals_lookup[item.meal.id] = item.meal
@ -21,7 +30,15 @@ def remove_references(items: List[ShoppingListItem], meals_lookup = None, recipe
recipes_lookup[item.recipe.id] = item.recipe
item.recipe = None
return meals_lookup, recipes_lookup, ingredients_lookup
async def _ensure_lookups_populated(conn, items: List[ShoppingListItem], meals_lookup, recipes_lookup, ingredients_lookup):
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:
ingredients_lookup[item.ingredient_id] = await ingredients.find_ingredient_by_id(conn, item.ingredient_id)
async def get_persons_requests(conn, person_id: int) -> AsyncIterator[ShoppingListItem]:
async for item in _find_items_by_list_id(conn, None):
@ -44,10 +61,19 @@ async def get_outstanding_requests(conn) -> Tuple[List[ShoppingListItem], List[S
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 and r.meal is not None]
meals = {r.meal_id: r.meal for r in meal_requests}
purchased_ingredients = {r.ingredient_id async for r in _get_purchased_ingredients(conn, list(meals.keys()))}
purchased_ingredients = {(r.ingredient_id, r.meal_id, r.recipe_id): r async for r in _get_purchased_ingredients(conn, list(meals.keys()))}
outstanding_items = []
purchased_items = []
flattened = flatten_items(current_requests)
outstanding_items = [r for r in flattened if r.ingredient_id not in purchased_ingredients]
purchased_items = [r for r in flattened if r.ingredient_id in purchased_ingredients]
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)
return outstanding_items, purchased_items, meal_requests

View file

@ -10,7 +10,7 @@ from typing import AsyncIterator, List, ClassVar, Optional
from datetime import datetime
class ShoppingListItem(BaseLinkedModel):
KEYS: ClassVar[List[str]] = ['id', 'ingredient_id', 'list_id', 'person_id', 'meal_id', 'created_date']
KEYS: ClassVar[List[str]] = ['id', 'ingredient_id', 'list_id', 'person_id', 'meal_id', 'recipe_id', 'created_date']
id: int = -1
list_id: Optional[int] = None