diff --git a/main.py b/main.py index c7214d8..1345e62 100644 --- a/main.py +++ b/main.py @@ -234,19 +234,19 @@ async def mark_consumed(meal_id: int, consumed_date: Optional[datetime.datetime] return JSONResponse(status_code=404, content={'message': 'Meal not found'}) await meals.mark_consumed(conn, meal, consumed_date or datetime.datetime.now().astimezone()) - await shopping.unrequest_meal(conn, meal) + await shopping.remove_request(conn, person, meal=meal) await conn.commit() return meal @app.delete("/api/meals/{meal_id}") -async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal: +async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> meals.Meal: meal = await meals.find_meal_by_id(conn, meal_id) if not meal: return JSONResponse(status_code=404, content={'message': 'Meal not found'}) + await shopping.remove_request(conn, person, meal=meal) await meals.delete_meal(conn, meal.id) - await shopping.unrequest_meal(conn, meal) await conn.commit() return meal @@ -291,9 +291,8 @@ 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) - result = PurchasedShoppingList(list=shopping_list) - shopping.remove_references(result.list.items, result.meals_lookup, result.recipes_lookup, result.ingredients_lookup) - return result + meals_lookup, recipes_lookup, ingredients_lookup = shopping.remove_references(shopping_list.items) + return PurchasedShoppingList(list=shopping_list, meals_lookup=meals_lookup, recipes_lookup=recipes_lookup, ingredients_lookup=ingredients_lookup) @app.post("/api/shopping/") async def purchase_ingredients(shopping_list: shopping.ShoppingList, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> PurchasedShoppingList: diff --git a/shopping/__init__.py b/shopping/__init__.py index 3a9c61b..5443f04 100644 --- a/shopping/__init__.py +++ b/shopping/__init__.py @@ -3,7 +3,11 @@ 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 = {}, recipes_lookup = {}, ingredients_lookup = {}) -> Tuple[Dict[int, Any], Dict[int, Any], Dict[int, Any]]: +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]]: + meals_lookup = meals_lookup or {} + recipes_lookup = recipes_lookup or {} + ingredients_lookup = ingredients_lookup or {} + for item in items: if item.meal and not item.meal.id in meals_lookup: meals_lookup[item.meal.id] = item.meal diff --git a/shopping/db.py b/shopping/db.py index e94da58..0aff51d 100644 --- a/shopping/db.py +++ b/shopping/db.py @@ -1,6 +1,6 @@ from common import BaseLinkedModel from recipes import Recipe -from meals import Meal, find_meal_by_id +from meals import Meal, find_meal_by_id, mark_purchased from ingredients import Ingredient, insert_ingredient from persons import Person from products import Product @@ -71,7 +71,6 @@ async def create(conn): FOREIGN KEY(recipe_id) REFERENCES Recipe(id) );''') - def validate_request(request: ShoppingListItem) -> None: if request.person_id < 0: raise ValueError('Requests must have a person') @@ -139,7 +138,40 @@ async def purchase(conn, shopping_list: ShoppingList) -> None: ''', (item.ingredient_id, shopping_list.id, item.person_id, item.meal_id, item.recipe_id, item.created_date.isoformat())) as cursor: item.id = cursor.lastrowid - # TODO: Calculate which meals have been fulfilled and update meal status + meal_ids = list({ item.meal_id for item in shopping_list.items if item.meal_id is not None and item.meal_id >= 0 }) + await update_purchased_meals(conn, meal_ids) + +async def update_purchased_meals(conn, meal_ids: List[int]) -> None: + if not meal_ids: + return + + purchased_ingredient_ids = {item.ingredient_id async for item in get_purchased_ingredients(conn, meal_ids)} + for meal_id in meal_ids: + meal = await find_meal_by_id(conn, meal_id) + ingredients = {ingredient.id for recipe in meal.recipes for ingredient in recipe.recipe.ingredients} | \ + {ingredient.id for ingredient in meal.extra_ingredients} + + remaining_ingredients = ingredients - purchased_ingredient_ids + if not remaining_ingredients: + await mark_purchased(conn, meal) + + # If all ingredients are purchased, update the meal status + await conn.execute(''' + UPDATE Meal + SET purchase_date = ? + WHERE id = ? + ''', (datetime.now().isoformat(), meal.id)) + +async def is_requested(conn, meal: Meal) -> bool: + if meal.id < 0: + return False + + async with conn.execute(''' + SELECT COUNT(*) FROM ShoppingListItem + WHERE meal_id = ? AND list_id IS NULL + ''', (meal.id,)) as cursor: + row = await cursor.fetchone() + return row[0] > 0 async def request(conn, person: Person, ingredient: Optional[Ingredient] = None, meal: Optional[Meal] = None) -> ShoppingListItem: if ingredient is not None and meal is not None: @@ -158,6 +190,9 @@ async def request(conn, person: Person, ingredient: Optional[Ingredient] = None, validate_request(item) + if meal is not None and await is_requested(conn, meal): + raise ValueError('Meal is already requested') + async with conn.execute(''' INSERT INTO ShoppingListItem (ingredient_id, person_id, meal_id, created_date) VALUES (?, ?, ?, ?)