Feature/shopping lists second try (#927)

* generate types

* use generated types

* ui updates

* init button link for common styles

* add links

* setup label views

* add delete confirmation

* reset when not saved

* link label to foods and auto set when adding to shopping list

* generate types

* use inheritence to manage exception handling

* fix schema generation and add test for open_api generation

* add header to api docs

* move list consilidation to service

* split list and list items controller

* shopping list/list item tests - PARTIAL

* enable recipe add/remove in shopping lists

* generate types

* linting

* init global utility components

* update types and add list item api

* fix import cycle and database error

* add container and border classes

* new recipe list component

* fix tests

* breakout item editor

* refactor item editor

* update bulk actions

* update input / color contrast

* type generation

* refactor controller dependencies

* include food/unit editor

* remove console.logs

* fix and update type generation

* fix incorrect type for column

* fix postgres error

* fix delete by variable

* auto remove refs

* fix typo
This commit is contained in:
Hayden
2022-01-16 15:24:24 -09:00
committed by GitHub
parent f794208862
commit 92cf97e401
66 changed files with 2556 additions and 685 deletions

View File

@@ -1,19 +1,96 @@
from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.group import ShoppingListOut
from mealie.schema.group.group_shopping_list import ShoppingListItemCreate
from mealie.schema.group import ShoppingListItemCreate, ShoppingListOut
from mealie.schema.group.group_shopping_list import (
ShoppingListItemOut,
ShoppingListItemRecipeRef,
ShoppingListItemUpdate,
)
class ShoppingListService:
def __init__(self, repos: AllRepositories):
self.repos = repos
self.repo = repos.group_shopping_lists
self.shopping_lists = repos.group_shopping_lists
self.list_items = repos.group_shopping_list_item
self.list_item_refs = repos.group_shopping_list_item_references
self.list_refs = repos.group_shopping_list_recipe_refs
@staticmethod
def can_merge(item1: ShoppingListItemOut, item2: ShoppingListItemOut) -> bool:
"""
can_merge checks if the two items can be merged together.
"""
# If no food or units are present check against the notes field.
if not all([item1.food, item1.unit, item2.food, item2.unit]):
return item1.note == item2.note
# If the items have the same food and unit they can be merged.
if item1.unit == item2.unit and item1.food == item2.food:
return True
# Otherwise Assume They Can't Be Merged
return False
def consolidate_list_items(self, item_list: list[ShoppingListItemOut]) -> list[ShoppingListItemOut]:
"""
itterates through the shopping list provided and returns
a consolidated list where all items that are matched against multiple values are
de-duplicated and only the first item is kept where the quantity is updated accoridngly.
"""
consolidated_list: list[ShoppingListItemOut] = []
checked_items: list[int] = []
for base_index, base_item in enumerate(item_list):
if base_index in checked_items:
continue
checked_items.append(base_index)
for inner_index, inner_item in enumerate(item_list):
if inner_index in checked_items:
continue
if ShoppingListService.can_merge(base_item, inner_item):
# Set Quantity
base_item.quantity += inner_item.quantity
# Set References
new_refs = []
for ref in inner_item.recipe_references:
ref.shopping_list_item_id = base_item.id
new_refs.append(ref)
base_item.recipe_references.extend(new_refs)
checked_items.append(inner_index)
consolidated_list.append(base_item)
return consolidated_list
def consolidate_and_save(self, data: list[ShoppingListItemUpdate]):
# TODO: Convert to update many with single call
all_updates = []
keep_ids = []
for item in self.consolidate_list_items(data):
updated_data = self.list_items.update(item.id, item)
all_updates.append(updated_data)
keep_ids.append(updated_data.id)
for item in data:
if item.id not in keep_ids:
self.list_items.delete(item.id)
return all_updates
# =======================================================================
# Methods
def add_recipe_ingredients_to_list(self, list_id: UUID4, recipe_id: int) -> ShoppingListOut:
recipe = self.repos.recipes.get_one(recipe_id, "id")
shopping_list = self.repo.get_one(list_id)
to_create = []
for ingredient in recipe.recipe_ingredient:
@@ -23,6 +100,12 @@ class ShoppingListService:
except AttributeError:
pass
label_id = None
try:
label_id = ingredient.food.label.id
except AttributeError:
pass
unit_id = None
try:
unit_id = ingredient.unit.id
@@ -32,19 +115,77 @@ class ShoppingListService:
to_create.append(
ShoppingListItemCreate(
shopping_list_id=list_id,
is_food=True,
is_food=not recipe.settings.disable_amount,
food_id=food_id,
unit_id=unit_id,
quantity=ingredient.quantity,
note=ingredient.note,
label_id=label_id,
recipe_id=recipe_id,
recipe_references=[
ShoppingListItemRecipeRef(
recipe_id=recipe_id,
recipe_quantity=ingredient.quantity,
)
],
)
)
shopping_list.list_items.extend(to_create)
return self.repo.update(shopping_list.id, shopping_list)
for item in to_create:
self.repos.group_shopping_list_item.create(item)
updated_list = self.shopping_lists.get_one(list_id)
updated_list.list_items = self.consolidate_and_save(updated_list.list_items)
not_found = True
for refs in updated_list.recipe_references:
if refs.recipe_id == recipe_id:
refs.recipe_quantity += 1
not_found = False
if not_found:
updated_list.recipe_references.append(ShoppingListItemRecipeRef(recipe_id=recipe_id, recipe_quantity=1))
updated_list = self.shopping_lists.update(updated_list.id, updated_list)
return updated_list
def remove_recipe_ingredients_from_list(self, list_id: UUID4, recipe_id: int) -> ShoppingListOut:
shopping_list = self.repo.get_one(list_id)
shopping_list.list_items = [x for x in shopping_list.list_items if x.recipe_id != recipe_id]
return self.repo.update(shopping_list.id, shopping_list)
shopping_list = self.shopping_lists.get_one(list_id)
for item in shopping_list.list_items:
found = False
for ref in item.recipe_references:
remove_qty = 0
if ref.recipe_id == recipe_id:
self.list_item_refs.delete(ref.id)
item.recipe_references.remove(ref)
found = True
remove_qty = ref.recipe_quantity
break # only remove one instance of the recipe for each item
# If the item was found decrement the quantity by the remove_qty
if found:
item.quantity = item.quantity - remove_qty
if item.quantity <= 0:
self.list_items.delete(item.id)
else:
self.list_items.update(item.id, item)
# Decrament the list recipe reference count
for ref in shopping_list.recipe_references:
if ref.recipe_id == recipe_id:
ref.recipe_quantity -= 1
if ref.recipe_quantity <= 0:
self.list_refs.delete(ref.id)
else:
self.list_refs.update(ref.id, ref)
break
# Save Changes
return self.shopping_lists.get(shopping_list.id)