From 88f0c35b4d5a7564a5a327804f09d9f2a94b0ca0 Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Thu, 28 Dec 2023 13:52:35 +0530 Subject: [PATCH] Feature: Download nutritional information and store description and allergen information in products --- .../alembic/versions/66abfc21db73_allergen.py | 27 ++ brewman/brewman/models/product.py | 6 + .../brewman/routers/calculate_nutrition.py | 249 ++++++++++++++++++ brewman/brewman/routers/product.py | 8 + brewman/brewman/routers/recipe.py | 91 ++++++- .../schemas/nutritional_information.py | 28 ++ brewman/brewman/schemas/product.py | 2 + overlord/src/app/core/product.ts | 3 + .../product-detail.component.html | 12 + .../product-detail.component.ts | 8 + .../recipe-list/recipe-list.component.html | 3 + 11 files changed, 434 insertions(+), 3 deletions(-) create mode 100644 brewman/alembic/versions/66abfc21db73_allergen.py create mode 100644 brewman/brewman/routers/calculate_nutrition.py create mode 100644 brewman/brewman/schemas/nutritional_information.py diff --git a/brewman/alembic/versions/66abfc21db73_allergen.py b/brewman/alembic/versions/66abfc21db73_allergen.py new file mode 100644 index 00000000..b7195467 --- /dev/null +++ b/brewman/alembic/versions/66abfc21db73_allergen.py @@ -0,0 +1,27 @@ +"""allergen + +Revision ID: 66abfc21db73 +Revises: 2438cd581f00 +Create Date: 2023-12-28 12:45:01.275322 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "66abfc21db73" +down_revision = "2438cd581f00" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("products", sa.Column("allergen", sa.Text(), server_default="", nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("products", "allergen") + # ### end Alembic commands ### diff --git a/brewman/brewman/models/product.py b/brewman/brewman/models/product.py index 3cca8a4e..e1a7be44 100644 --- a/brewman/brewman/models/product.py +++ b/brewman/brewman/models/product.py @@ -39,6 +39,8 @@ class Product: product_group: Mapped["ProductGroup"] = relationship("ProductGroup", back_populates="products") account: Mapped["Account"] = relationship("Account", back_populates="products") + allergen: Mapped[str] = mapped_column(Text, nullable=False) + protein: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) carbohydrate: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) total_sugar: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) @@ -56,12 +58,14 @@ class Product: def __init__( self, name: str, + description: str | None, fraction_units: str, product_group_id: uuid.UUID, account_id: uuid.UUID, is_active: bool, is_purchased: bool, is_sold: bool, + allergen: str = "", protein: Decimal = 0, carbohydrate: Decimal = 0, total_sugar: Decimal = 0, @@ -82,6 +86,7 @@ class Product: if code is not None: self.code = code self.name = name + self.description = description self.fraction_units = fraction_units self.product_group_id = product_group_id self.account_id = account_id @@ -89,6 +94,7 @@ class Product: self.is_purchased = is_purchased self.is_sold = is_sold + self.allergen = allergen self.protein = protein self.carbohydrate = carbohydrate self.total_sugar = total_sugar diff --git a/brewman/brewman/routers/calculate_nutrition.py b/brewman/brewman/routers/calculate_nutrition.py new file mode 100644 index 00000000..b9820f82 --- /dev/null +++ b/brewman/brewman/routers/calculate_nutrition.py @@ -0,0 +1,249 @@ +import uuid + +from ..schemas.nutritional_information import NutritionalInformation +from ..models.product_group import ProductGroup + +from fastapi import HTTPException, status +from sqlalchemy import distinct, or_, select, update +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import Session + +from ..models.product import Product +from ..models.recipe import Recipe +from ..models.recipe_item import RecipeItem +from ..models.stock_keeping_unit import StockKeepingUnit + + +def calculate_nutrition(db: Session): + try: + # Get all recipes that have nutritional values + products = set( + db.execute( + select(distinct(StockKeepingUnit.product_id)) + .join(StockKeepingUnit.recipes) + .join(StockKeepingUnit.product) + .join(Product.product_group) + .where(or_(ProductGroup.nutritional == True, ProductGroup.ice_cream == True)) # noqa: E712 + ) + .scalars() + .all() + ) + db.flush() + while len(products) > 0: + calculate(products, db) + except SQLAlchemyError as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +def calculate(products: set[uuid.UUID], db: Session) -> None: + sq = select(RecipeItem.recipe_id).where(RecipeItem.product_id.in_(products)) + recipes = ( + db.execute( + select(Recipe).join(Recipe.sku).where(StockKeepingUnit.product_id.in_(products), Recipe.id.notin_(sq)) + ) + .scalars() + .all() + ) + for recipe in recipes: + protein = sum(i.quantity * i.product.protein for i in recipe.items) / recipe.sku.fraction + carbohydrate = sum(i.quantity * i.product.carbohydrate for i in recipe.items) / recipe.sku.fraction + total_sugar = sum(i.quantity * i.product.total_sugar for i in recipe.items) / recipe.sku.fraction + added_sugar = sum(i.quantity * i.product.added_sugar for i in recipe.items) / recipe.sku.fraction + total_fat = sum(i.quantity * i.product.total_fat for i in recipe.items) / recipe.sku.fraction + saturated_fat = sum(i.quantity * i.product.saturated_fat for i in recipe.items) / recipe.sku.fraction + trans_fat = sum(i.quantity * i.product.trans_fat for i in recipe.items) / recipe.sku.fraction + cholestrol = sum(i.quantity * i.product.cholestrol for i in recipe.items) / recipe.sku.fraction + sodium = sum(i.quantity * i.product.sodium for i in recipe.items) / recipe.sku.fraction + msnf = sum(i.quantity * i.product.msnf for i in recipe.items) / recipe.sku.fraction + other_solids = sum(i.quantity * i.product.other_solids for i in recipe.items) / recipe.sku.fraction + total_solids = sum(i.quantity * i.product.total_solids for i in recipe.items) / recipe.sku.fraction + water = sum(i.quantity * i.product.water for i in recipe.items) / recipe.sku.fraction + + db.execute( + update(Product) + .where(Product.id == recipe.sku.product_id) + .values( + protein=protein, + carbohydrate=carbohydrate, + total_sugar=total_sugar, + added_sugar=added_sugar, + total_fat=total_fat, + saturated_fat=saturated_fat, + trans_fat=trans_fat, + cholestrol=cholestrol, + sodium=sodium, + msnf=msnf, + other_solids=other_solids, + total_solids=total_solids, + water=water, + ) + ) + + products.remove(recipe.sku.product_id) + db.flush() + + +def report_nutrition(db: Session) -> list[NutritionalInformation]: + try: + # Get all recipes that have nutritional values + products = set( + db.execute( + select(distinct(StockKeepingUnit.product_id)) + .join(StockKeepingUnit.recipes) + .join(StockKeepingUnit.product) + .join(Product.product_group) + ) + .scalars() + .all() + ) + final, semi, ingredients = nut_final(db), nut_semi(db), nut_ingredients(db) + return report(products, final, semi, ingredients, db) + except SQLAlchemyError as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + + +def report( + products: set[uuid.UUID], final: set[uuid.UUID], semi: set[uuid.UUID], ingredients: set[uuid.UUID], db: Session +) -> list[NutritionalInformation]: + _list: list[NutritionalInformation] = [] + ingredient_dict: dict[str, set[str]] = {} + allergen_dict: dict[str, set[str]] = {} + ings = db.execute(select(Product).where(Product.id.in_(ingredients))).scalars().all() + for ingredient in ings: + if ingredient.id in products: + raise ValueError("Ingredient cannot be in a recipe") + ingredients.remove(ingredient.id) + ingredient_dict[ingredient.name] = set([ingredient.name]) + allergen_dict[ingredient.name] = set(a.strip() for a in ingredient.allergen.split(",")) - set([""]) + _list.append( + NutritionalInformation( + name=ingredient.name, + units=ingredient.fraction_units, + product_group="Ingredients", + description=ingredient.description, + ingredients=[ingredient.name], + allergen=list(allergen_dict[ingredient.name]), + protein=ingredient.protein, + carbohydrate=ingredient.carbohydrate, + total_sugar=ingredient.total_sugar, + added_sugar=ingredient.added_sugar, + total_fat=ingredient.total_fat, + saturated_fat=ingredient.saturated_fat, + trans_fat=ingredient.trans_fat, + cholestrol=ingredient.cholestrol, + sodium=ingredient.sodium, + msnf=ingredient.msnf, + other_solids=ingredient.other_solids, + total_solids=ingredient.total_solids, + water=ingredient.water, + ) + ) + while len(semi) > 0: + semi_products = ( + db.execute(select(Recipe).join(Recipe.sku).where(StockKeepingUnit.product_id.in_(semi))).scalars().all() + ) + for semi_recipe in semi_products: + semi_product = semi_recipe.sku.product + products.remove(semi_product.id) + semi.remove(semi_product.id) + ingredient_dict[semi_product.name] = set.union( + *[ingredient_dict[i.product.name] for i in semi_recipe.items] + ) + allergen_dict[semi_product.name] = set.union( + *[allergen_dict[i.product.name] for i in semi_recipe.items] + ) - set([""]) + + _list.append( + NutritionalInformation( + name=semi_product.name, + units=semi_product.fraction_units, + product_group="Semi", + description=semi_product.description, + ingredients=list(ingredient_dict[semi_product.name]), + allergen=list(allergen_dict[semi_product.name]), + protein=semi_product.protein, + carbohydrate=semi_product.carbohydrate, + total_sugar=semi_product.total_sugar, + added_sugar=semi_product.added_sugar, + total_fat=semi_product.total_fat, + saturated_fat=semi_product.saturated_fat, + trans_fat=semi_product.trans_fat, + cholestrol=semi_product.cholestrol, + sodium=semi_product.sodium, + msnf=semi_product.msnf, + other_solids=semi_product.other_solids, + total_solids=semi_product.total_solids, + water=semi_product.water, + ) + ) + recipes = db.execute(select(Recipe).join(Recipe.sku).where(StockKeepingUnit.product_id.in_(final))).scalars().all() + for recipe in recipes: + recipe_product = recipe.sku.product + products.remove(recipe_product.id) + final.remove(recipe_product.id) + ingredient_dict[recipe_product.name] = set.union(*[ingredient_dict[i.product.name] for i in recipe.items]) + allergen_dict[recipe_product.name] = set.union(*[allergen_dict[i.product.name] for i in recipe.items]) - set( + [""] + ) + _list.append( + NutritionalInformation( + name=recipe_product.name, + units=recipe_product.fraction_units, + product_group=recipe_product.product_group.name, + description=recipe_product.description, + ingredients=list(ingredient_dict[recipe_product.name]), + allergen=list(allergen_dict[recipe_product.name]), + protein=recipe_product.protein, + carbohydrate=recipe_product.carbohydrate, + total_sugar=recipe_product.total_sugar, + added_sugar=recipe_product.added_sugar, + total_fat=recipe_product.total_fat, + saturated_fat=recipe_product.saturated_fat, + trans_fat=recipe_product.trans_fat, + cholestrol=recipe_product.cholestrol, + sodium=recipe_product.sodium, + msnf=recipe_product.msnf, + other_solids=recipe_product.other_solids, + total_solids=recipe_product.total_solids, + water=recipe_product.water, + ) + ) + if len(products) > 0 or len(final) > 0 or len(semi) > 0 or len(ingredients) > 0: + raise ValueError("They cannot be more than 0") + return _list + + +def nut_final(db: Session) -> set[uuid.UUID]: + sq = select(StockKeepingUnit.product_id).where(StockKeepingUnit.id.in_(select(Recipe.sku_id))) + ingredients = ( + db.execute(select(Product.id).where(Product.id.notin_(select(RecipeItem.product_id)), Product.id.in_(sq))) + .scalars() + .all() + ) + return set(ingredients) + + +def nut_ingredients(db: Session) -> set[uuid.UUID]: + sq = select(StockKeepingUnit.product_id).where(StockKeepingUnit.id.in_(select(Recipe.sku_id))) + ingredients = ( + db.execute(select(Product.id).where(Product.id.in_(select(RecipeItem.product_id)), Product.id.notin_(sq))) + .scalars() + .all() + ) + return set(ingredients) + + +def nut_semi(db: Session) -> set[uuid.UUID]: + sq = select(StockKeepingUnit.product_id).where(StockKeepingUnit.id.in_(select(Recipe.sku_id))) + ingredients = ( + db.execute(select(Product.id).where(Product.id.in_(select(RecipeItem.product_id)), Product.id.in_(sq))) + .scalars() + .all() + ) + return set(ingredients) diff --git a/brewman/brewman/routers/product.py b/brewman/brewman/routers/product.py index 86435df6..e82e8d93 100644 --- a/brewman/brewman/routers/product.py +++ b/brewman/brewman/routers/product.py @@ -34,12 +34,14 @@ def save( with SessionFuture() as db: item = Product( name=data.name, + description=data.description, fraction_units=data.fraction_units, product_group_id=data.product_group.id_, account_id=Account.all_purchases(), is_active=data.is_active, is_purchased=data.is_purchased, is_sold=data.is_sold, + allergen=data.allergen, protein=data.protein, carbohydrate=data.carbohydrate, total_sugar=data.total_sugar, @@ -96,6 +98,7 @@ def update_route( detail=f"{item.name} is a fixture and cannot be edited or deleted.", ) item.name = data.name + item.description = data.description item.fraction_units = data.fraction_units item.product_group_id = data.product_group.id_ item.account_id = Account.all_purchases() @@ -103,6 +106,7 @@ def update_route( item.is_purchased = data.is_purchased item.is_sold = data.is_sold + item.allergen = data.allergen item.protein = data.protein item.carbohydrate = data.carbohydrate item.total_sugar = data.total_sugar @@ -305,6 +309,7 @@ def product_info(product: Product) -> schemas.Product: id_=product.id, code=product.code, name=product.name, + description=product.description, fraction_units=product.fraction_units, skus=[ schemas.StockKeepingUnit( @@ -322,6 +327,7 @@ def product_info(product: Product) -> schemas.Product: is_purchased=product.is_purchased, is_sold=product.is_sold, product_group=schemas.ProductGroupLink(id_=product.product_group.id, name=product.product_group.name), + allergen=product.allergen, protein=product.protein, carbohydrate=product.carbohydrate, total_sugar=product.total_sugar, @@ -341,12 +347,14 @@ def product_info(product: Product) -> schemas.Product: def product_blank() -> schemas.ProductBlank: return schemas.ProductBlank( name="", + description="", fraction_units="", skus=[], is_active=True, is_purchased=True, is_sold=False, is_fixture=False, + allergen="", protein=0, carbohydrate=0, total_sugar=0, diff --git a/brewman/brewman/routers/recipe.py b/brewman/brewman/routers/recipe.py index a8bdf647..1087ad7c 100644 --- a/brewman/brewman/routers/recipe.py +++ b/brewman/brewman/routers/recipe.py @@ -8,11 +8,14 @@ from decimal import Decimal from io import BytesIO from typing import Sequence +from ..schemas.nutritional_information import NutritionalInformation +from ..routers.calculate_nutrition import calculate_nutrition, report_nutrition + import brewman.schemas.recipe as schemas import brewman.schemas.recipe_item as rischemas -from brewman.models.price import Price -from brewman.routers.calculate_prices import calculate_prices +from ..models.price import Price +from ..routers.calculate_prices import calculate_prices from fastapi import APIRouter, Depends, HTTPException, Request, Security, status from fastapi.responses import FileResponse, StreamingResponse from openpyxl import Workbook @@ -26,7 +29,6 @@ from ..db.session import SessionFuture from ..models.product import Product from ..models.recipe import Recipe from ..models.recipe_item import RecipeItem -from ..models.recipe_template import RecipeTemplate from ..models.stock_keeping_unit import StockKeepingUnit from ..schemas.user import UserToken @@ -352,6 +354,89 @@ def excel(prices: list[tuple[str, str, Decimal, Decimal, Decimal]], recipes: lis return virtual_workbook +@router.get("/nutrition", response_class=StreamingResponse) +def get_nutrition( + p: uuid.UUID | None = None, +) -> StreamingResponse: + with SessionFuture() as db: + calculate_nutrition(db) + db.commit() + list_: list[NutritionalInformation] = [] + with SessionFuture() as db: + list_ = report_nutrition(db) + # q = ( + # select(Recipe) + # .join(Recipe.sku) + # .join(StockKeepingUnit.product) + # .join(Product.product_group) + # .where(or_(ProductGroup.nutritional == True, ProductGroup.ice_cream == True)) # noqa: E712 + # .options( + # contains_eager(Recipe.sku, StockKeepingUnit.product, Product.product_group), + # ) + # ) + # list_ = [ + # NutritionalInformation( + # name=i.sku.product.name, + # units=i.sku.units, + # product_group=i.sku.product.product_group.name, + # **i.sku.product, + # ) + # for i in db.execute(q).unique().scalars().all() + # ] + e = nut(sorted(list_, key=lambda r: r.name)) + e.seek(0) + + headers = {"Content-Disposition": "attachment; filename = nutritional.xlsx"} + return StreamingResponse(e, media_type="text/xlsx", headers=headers) + + +def nut(products: list[NutritionalInformation]) -> BytesIO: + wb = Workbook() + wb.active.title = "Ingredients" + pgs = set([x.product_group for x in products]) + pgs.remove("Ingredients") + for pg in pgs: + wb.create_sheet(pg) + rows = defaultdict(lambda: 1) + register_styles(wb) + for item in products: + ws = wb[item.product_group] + row = rows[item.product_group] + if row == 1: + ws.cell(row=row, column=1, value="Product").style = "header" + ws.cell(row=row, column=2, value="Description").style = "header" + ws.cell(row=row, column=3, value="Ingredients").style = "header" + ws.cell(row=row, column=4, value="Allergen").style = "header" + ws.cell(row=row, column=5, value="Protein").style = "header" + ws.cell(row=row, column=6, value="Carbohydrate").style = "header" + ws.cell(row=row, column=7, value="Total Sugar").style = "header" + ws.cell(row=row, column=8, value="Added Sugar").style = "header" + ws.cell(row=row, column=9, value="Total Fat").style = "header" + ws.cell(row=row, column=10, value="Saturated Fat").style = "header" + ws.cell(row=row, column=11, value="Trans Fat").style = "header" + ws.cell(row=row, column=12, value="Cholestrol").style = "header" + ws.cell(row=row, column=13, value="Sodium").style = "header" + row += 1 + ws.cell(row=row, column=1, value=f"{item.name} ({item.units})").style = "ing" + ws.cell(row=row, column=2, value=item.description).style = "ing" + ws.cell(row=row, column=3, value=", ".join(item.ingredients)).style = "ing" + ws.cell(row=row, column=4, value=", ".join(item.allergen)).style = "ing" + ws.cell(row=row, column=5, value=item.protein).style = "ing" + ws.cell(row=row, column=6, value=item.carbohydrate).style = "ing" + ws.cell(row=row, column=7, value=item.total_sugar).style = "ing" + ws.cell(row=row, column=8, value=item.added_sugar).style = "ing" + ws.cell(row=row, column=9, value=item.total_fat).style = "ing" + ws.cell(row=row, column=10, value=item.saturated_fat).style = "ing" + ws.cell(row=row, column=11, value=item.trans_fat).style = "ing" + ws.cell(row=row, column=12, value=item.cholestrol).style = "ing" + ws.cell(row=row, column=13, value=item.sodium).style = "ing" + row += 1 + rows[item.product_group] = row + 1 + virtual_workbook = BytesIO() + wb.save(virtual_workbook) + return virtual_workbook + + def register_styles(wb: Workbook) -> tuple[NamedStyle, NamedStyle, NamedStyle, NamedStyle, NamedStyle]: bd = Side(style="thin", color="000000") diff --git a/brewman/brewman/schemas/nutritional_information.py b/brewman/brewman/schemas/nutritional_information.py new file mode 100644 index 00000000..9bcb9924 --- /dev/null +++ b/brewman/brewman/schemas/nutritional_information.py @@ -0,0 +1,28 @@ +from decimal import Decimal + +from pydantic import BaseModel + + +class NutritionalInformation(BaseModel): + name: str + units: str + product_group: str + + description: str | None + ingredients: list[str] + + allergen: list[str] + protein: Decimal + carbohydrate: Decimal + total_sugar: Decimal + added_sugar: Decimal + total_fat: Decimal + saturated_fat: Decimal + trans_fat: Decimal + cholestrol: Decimal + sodium: Decimal + + msnf: Decimal + other_solids: Decimal + total_solids: Decimal + water: Decimal diff --git a/brewman/brewman/schemas/product.py b/brewman/brewman/schemas/product.py index 6787ec6e..b78c1685 100644 --- a/brewman/brewman/schemas/product.py +++ b/brewman/brewman/schemas/product.py @@ -17,6 +17,7 @@ class ProductLink(BaseModel): class ProductIn(BaseModel): name: str = Field(..., min_length=1) + description: str | None fraction_units: str = Field(..., min_length=1) skus: list[StockKeepingUnit] product_group: ProductGroupLink = Field(...) @@ -24,6 +25,7 @@ class ProductIn(BaseModel): is_purchased: bool is_sold: bool + allergen: str protein: Decimal carbohydrate: Decimal total_sugar: Decimal diff --git a/overlord/src/app/core/product.ts b/overlord/src/app/core/product.ts index 3127fc47..f0fefe4b 100644 --- a/overlord/src/app/core/product.ts +++ b/overlord/src/app/core/product.ts @@ -21,6 +21,7 @@ export class Product { id: string | undefined; code: number; name: string; + description: string | undefined; skus: StockKeepingUnit[]; fractionUnits: string | undefined; @@ -30,6 +31,7 @@ export class Product { isSold: boolean; productGroup?: ProductGroup; + allergen: string; protein: number; carbohydrate: number; totalSugar: number; @@ -55,6 +57,7 @@ export class Product { this.isPurchased = true; this.isSold = false; + this.allergen = ''; this.protein = 0; this.carbohydrate = 0; this.totalSugar = 0; diff --git a/overlord/src/app/product/product-detail/product-detail.component.html b/overlord/src/app/product/product-detail/product-detail.component.html index 642f8b40..7d3027b7 100644 --- a/overlord/src/app/product/product-detail/product-detail.component.html +++ b/overlord/src/app/product/product-detail/product-detail.component.html @@ -20,6 +20,12 @@ +
+ + Description + + +
Is Purchased? Is Sold? @@ -35,6 +41,12 @@
+
+ + Allergen + + +

Nutritional Information

; name: FormControl; + description: FormControl; fractionUnits: FormControl; addRow: FormGroup<{ units: FormControl; @@ -36,6 +37,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { isActive: FormControl; productGroup: FormControl; + allergen: FormControl; protein: FormControl; carbohydrate: FormControl; totalSugar: FormControl; @@ -69,6 +71,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { this.form = new FormGroup({ code: new FormControl({ value: 0, disabled: true }, { nonNullable: true }), name: new FormControl(null), + description: new FormControl(null), fractionUnits: new FormControl(null), addRow: new FormGroup({ units: new FormControl(null), @@ -82,6 +85,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { isActive: new FormControl(true, { nonNullable: true }), productGroup: new FormControl(null), + allergen: new FormControl(null), protein: new FormControl(0, { nonNullable: true }), carbohydrate: new FormControl(0, { nonNullable: true }), totalSugar: new FormControl(0, { nonNullable: true }), @@ -116,6 +120,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { this.form.setValue({ code: this.item.code || '(Auto)', name: this.item.name, + description: this.item.description || '', fractionUnits: this.item.fractionUnits ?? '', addRow: { units: '', @@ -129,6 +134,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { isActive: this.item.isActive, productGroup: this.item.productGroup ? this.item.productGroup.id ?? '' : '', + allergen: this.item.allergen ?? '', protein: this.item.protein, carbohydrate: this.item.carbohydrate, totalSugar: this.item.totalSugar, @@ -266,11 +272,13 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { getItem(): Product { const formModel = this.form.value; this.item.name = formModel.name ?? ''; + this.item.description = formModel.description ?? ''; this.item.fractionUnits = formModel.fractionUnits ?? ''; this.item.isPurchased = formModel.isPurchased ?? true; this.item.isSold = formModel.isSold ?? false; this.item.isActive = formModel.isActive ?? true; + this.item.allergen = formModel.allergen ?? ''; this.item.protein = formModel.protein ?? 0; this.item.carbohydrate = formModel.carbohydrate ?? 0; this.item.totalSugar = formModel.totalSugar ?? 0; diff --git a/overlord/src/app/recipe/recipe-list/recipe-list.component.html b/overlord/src/app/recipe/recipe-list/recipe-list.component.html index 8a9bb52b..b0173021 100644 --- a/overlord/src/app/recipe/recipe-list/recipe-list.component.html +++ b/overlord/src/app/recipe/recipe-list/recipe-list.component.html @@ -5,6 +5,9 @@ save_alt + + save_alt + add_box Add