From cd6a5e129fd36de90a467398ff928ab08d55d9eb Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Mon, 25 Dec 2023 10:57:44 +0530 Subject: [PATCH] Feature: Recording the nutritional and ice cream related values in the database --- brewman/alembic/versions/2438cd581f00_nut.py | 77 +++++++++++++++++++ brewman/brewman/models/product.py | 45 ++++++++++- brewman/brewman/models/product_group.py | 14 +++- brewman/brewman/routers/product.py | 54 +++++++++++++ brewman/brewman/routers/product_group.py | 6 +- brewman/brewman/schemas/product.py | 18 +++++ brewman/brewman/schemas/product_group.py | 3 + overlord/src/app/core/product-group.ts | 4 + overlord/src/app/core/product.ts | 31 ++++++++ .../product-group-detail.component.html | 4 + .../product-group-detail.component.ts | 8 ++ .../app/product-group/product-group.module.ts | 2 + .../product-detail.component.html | 63 ++++++++++++++- .../product-detail.component.ts | 70 ++++++++++++++++- 14 files changed, 391 insertions(+), 8 deletions(-) create mode 100644 brewman/alembic/versions/2438cd581f00_nut.py diff --git a/brewman/alembic/versions/2438cd581f00_nut.py b/brewman/alembic/versions/2438cd581f00_nut.py new file mode 100644 index 00000000..14e54562 --- /dev/null +++ b/brewman/alembic/versions/2438cd581f00_nut.py @@ -0,0 +1,77 @@ +"""nut + +Revision ID: 2438cd581f00 +Revises: ba0fff092981 +Create Date: 2023-10-16 16:26:45.922654 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "2438cd581f00" +down_revision = "ba0fff092981" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "products", sa.Column("protein", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column( + "products", sa.Column("carbohydrate", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column( + "products", sa.Column("total_sugar", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column( + "products", sa.Column("added_sugar", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column( + "products", sa.Column("total_fat", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column( + "products", sa.Column("saturated_fat", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column( + "products", sa.Column("trans_fat", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column( + "products", sa.Column("cholestrol", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column( + "products", sa.Column("sodium", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column("products", sa.Column("msnf", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False)) + op.add_column( + "products", sa.Column("other_solids", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column( + "products", sa.Column("total_solids", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False) + ) + op.add_column("products", sa.Column("water", sa.Numeric(precision=15, scale=5), server_default="0", nullable=False)) + + op.add_column("product_groups", sa.Column("nutritional", sa.Boolean(), server_default=False, nullable=False)) + op.add_column("product_groups", sa.Column("ice_cream", sa.Boolean(), server_default=False, nullable=False)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("products", "water") + op.drop_column("products", "total_solids") + op.drop_column("products", "other_solids") + op.drop_column("products", "msnf") + op.drop_column("products", "sodium") + op.drop_column("products", "cholestrol") + op.drop_column("products", "trans_fat") + op.drop_column("products", "saturated_fat") + op.drop_column("products", "total_fat") + op.drop_column("products", "added_sugar") + op.drop_column("products", "total_sugar") + op.drop_column("products", "carbohydrate") + op.drop_column("products", "protein") + # ### end Alembic commands ### diff --git a/brewman/brewman/models/product.py b/brewman/brewman/models/product.py index 4b8a9b54..3cca8a4e 100644 --- a/brewman/brewman/models/product.py +++ b/brewman/brewman/models/product.py @@ -1,8 +1,9 @@ import uuid +from decimal import Decimal from typing import TYPE_CHECKING -from sqlalchemy import Boolean, ForeignKey, Integer, Text, Unicode, Uuid +from sqlalchemy import Boolean, ForeignKey, Integer, Numeric, Text, Unicode, Uuid from sqlalchemy.orm import Mapped, mapped_column, relationship from ..db.base_class import reg @@ -38,6 +39,20 @@ class Product: product_group: Mapped["ProductGroup"] = relationship("ProductGroup", back_populates="products") account: Mapped["Account"] = relationship("Account", back_populates="products") + 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) + added_sugar: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + total_fat: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + saturated_fat: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + trans_fat: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + cholestrol: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + sodium: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + msnf: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + other_solids: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + total_solids: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + water: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=5), nullable=False) + def __init__( self, name: str, @@ -47,6 +62,19 @@ class Product: is_active: bool, is_purchased: bool, is_sold: bool, + protein: Decimal = 0, + carbohydrate: Decimal = 0, + total_sugar: Decimal = 0, + added_sugar: Decimal = 0, + total_fat: Decimal = 0, + saturated_fat: Decimal = 0, + trans_fat: Decimal = 0, + cholestrol: Decimal = 0, + sodium: Decimal = 0, + msnf: Decimal = 0, + other_solids: Decimal = 0, + total_solids: Decimal = 0, + water: Decimal = 0, code: int | None = None, id_: uuid.UUID | None = None, is_fixture: bool | None = False, @@ -60,6 +88,21 @@ class Product: self.is_active = is_active self.is_purchased = is_purchased self.is_sold = is_sold + + self.protein = protein + self.carbohydrate = carbohydrate + self.total_sugar = total_sugar + self.added_sugar = added_sugar + self.total_fat = total_fat + self.saturated_fat = saturated_fat + self.trans_fat = trans_fat + self.cholestrol = cholestrol + self.sodium = sodium + self.msnf = msnf + self.other_solids = other_solids + self.total_solids = total_solids + self.water = water + if id_ is not None: self.id = id_ if is_fixture is not None: diff --git a/brewman/brewman/models/product_group.py b/brewman/brewman/models/product_group.py index 312749e4..4d7ec14b 100644 --- a/brewman/brewman/models/product_group.py +++ b/brewman/brewman/models/product_group.py @@ -18,12 +18,24 @@ class ProductGroup: id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, insert_default=uuid.uuid4) name: Mapped[str] = mapped_column(Unicode, unique=True, nullable=False) + nutritional: Mapped[bool] = mapped_column(Boolean, nullable=False) + ice_cream: Mapped[bool] = mapped_column(Boolean, nullable=False) + is_fixture: Mapped[bool] = mapped_column(Boolean, nullable=False) is_fixture: Mapped[bool] = mapped_column(Boolean, nullable=False) products: Mapped[list["Product"]] = relationship("Product", back_populates="product_group") - def __init__(self, name: str, id_: uuid.UUID | None = None, is_fixture: bool | None = False) -> None: + def __init__( + self, + name: str, + nutritional: bool = False, + ice_cream: bool = False, + id_: uuid.UUID | None = None, + is_fixture: bool | None = False, + ) -> None: self.name = name + self.nutritional = nutritional + self.ice_cream = ice_cream if id_ is not None: self.id = id_ if is_fixture is not None: diff --git a/brewman/brewman/routers/product.py b/brewman/brewman/routers/product.py index 894995b4..86435df6 100644 --- a/brewman/brewman/routers/product.py +++ b/brewman/brewman/routers/product.py @@ -40,6 +40,19 @@ def save( is_active=data.is_active, is_purchased=data.is_purchased, is_sold=data.is_sold, + protein=data.protein, + carbohydrate=data.carbohydrate, + total_sugar=data.total_sugar, + added_sugar=data.added_sugar, + total_fat=data.total_fat, + saturated_fat=data.saturated_fat, + trans_fat=data.trans_fat, + cholestrol=data.cholestrol, + sodium=data.sodium, + msnf=data.msnf, + other_solids=data.other_solids, + total_solids=data.total_solids, + water=data.water, ) item.code = db.execute(select(func.coalesce(func.max(Product.code), 0) + 1)).scalar_one() db.add(item) @@ -89,6 +102,21 @@ def update_route( item.is_active = data.is_active item.is_purchased = data.is_purchased item.is_sold = data.is_sold + + item.protein = data.protein + item.carbohydrate = data.carbohydrate + item.total_sugar = data.total_sugar + item.added_sugar = data.added_sugar + item.total_fat = data.total_fat + item.saturated_fat = data.saturated_fat + item.trans_fat = data.trans_fat + item.cholestrol = data.cholestrol + item.sodium = data.sodium + item.msnf = data.msnf + item.other_solids = data.other_solids + item.total_solids = data.total_solids + item.water = data.water + if not len(data.skus): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -294,6 +322,19 @@ 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), + protein=product.protein, + carbohydrate=product.carbohydrate, + total_sugar=product.total_sugar, + added_sugar=product.added_sugar, + total_fat=product.total_fat, + saturated_fat=product.saturated_fat, + trans_fat=product.trans_fat, + cholestrol=product.cholestrol, + sodium=product.sodium, + msnf=product.msnf, + other_solids=product.other_solids, + total_solids=product.total_solids, + water=product.water, ) @@ -306,6 +347,19 @@ def product_blank() -> schemas.ProductBlank: is_purchased=True, is_sold=False, is_fixture=False, + protein=0, + carbohydrate=0, + total_sugar=0, + added_sugar=0, + total_fat=0, + saturated_fat=0, + trans_fat=0, + cholestrol=0, + sodium=0, + msnf=0, + other_solids=0, + total_solids=0, + water=0, ) diff --git a/brewman/brewman/routers/product_group.py b/brewman/brewman/routers/product_group.py index 2f8a024a..55b0241a 100644 --- a/brewman/brewman/routers/product_group.py +++ b/brewman/brewman/routers/product_group.py @@ -22,7 +22,7 @@ def save( ) -> schemas.ProductGroup: try: with SessionFuture() as db: - item = ProductGroup(name=data.name) + item = ProductGroup(name=data.name, nutritional=data.nutritional, ice_cream=data.ice_cream) db.add(item) db.commit() return product_group_info(item) @@ -48,6 +48,8 @@ def update_route( detail=f"{item.name} is a fixture and cannot be edited or deleted.", ) item.name = data.name + item.nutritional = data.nutritional + item.ice_cream = data.ice_cream db.commit() return product_group_info(item) except SQLAlchemyError as e: @@ -112,6 +114,8 @@ def product_group_info(item: ProductGroup) -> schemas.ProductGroup: return schemas.ProductGroup( id_=item.id, name=item.name, + nutritional=item.nutritional, + ice_cream=item.ice_cream, is_fixture=item.is_fixture, ) diff --git a/brewman/brewman/schemas/product.py b/brewman/brewman/schemas/product.py index e4c81252..6787ec6e 100644 --- a/brewman/brewman/schemas/product.py +++ b/brewman/brewman/schemas/product.py @@ -1,5 +1,7 @@ import uuid +from decimal import Decimal + from pydantic import BaseModel, ConfigDict, Field from . import to_camel @@ -21,6 +23,22 @@ class ProductIn(BaseModel): is_active: bool is_purchased: bool is_sold: bool + + 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 + model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) diff --git a/brewman/brewman/schemas/product_group.py b/brewman/brewman/schemas/product_group.py index ee59add8..5a7af849 100644 --- a/brewman/brewman/schemas/product_group.py +++ b/brewman/brewman/schemas/product_group.py @@ -7,6 +7,9 @@ from . import to_camel class ProductGroupIn(BaseModel): name: str = Field(..., min_length=1) + nutritional: bool + ice_cream: bool + model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True) class ProductGroup(ProductGroupIn): diff --git a/overlord/src/app/core/product-group.ts b/overlord/src/app/core/product-group.ts index 1067ddbb..5e0d7310 100644 --- a/overlord/src/app/core/product-group.ts +++ b/overlord/src/app/core/product-group.ts @@ -1,10 +1,14 @@ export class ProductGroup { id: string | undefined; name: string; + nutritional: boolean; + iceCream: boolean; isFixture: boolean; public constructor(init?: Partial) { this.name = ''; + this.nutritional = false; + this.iceCream = false; this.isFixture = false; Object.assign(this, init); } diff --git a/overlord/src/app/core/product.ts b/overlord/src/app/core/product.ts index 59db1501..3127fc47 100644 --- a/overlord/src/app/core/product.ts +++ b/overlord/src/app/core/product.ts @@ -30,6 +30,21 @@ export class Product { isSold: boolean; productGroup?: ProductGroup; + protein: number; + carbohydrate: number; + totalSugar: number; + addedSugar: number; + totalFat: number; + saturatedFat: number; + transFat: number; + cholestrol: number; + sodium: number; + + msnf: number; + otherSolids: number; + totalSolids: number; + water: number; + public constructor(init?: Partial) { this.code = 0; this.name = ''; @@ -39,6 +54,22 @@ export class Product { this.isFixture = false; this.isPurchased = true; this.isSold = false; + + this.protein = 0; + this.carbohydrate = 0; + this.totalSugar = 0; + this.addedSugar = 0; + this.totalFat = 0; + this.saturatedFat = 0; + this.transFat = 0; + this.cholestrol = 0; + this.sodium = 0; + + this.msnf = 0; + this.otherSolids = 0; + this.totalSolids = 0; + this.water = 0; + Object.assign(this, init); } } diff --git a/overlord/src/app/product-group/product-group-detail/product-group-detail.component.html b/overlord/src/app/product-group/product-group-detail/product-group-detail.component.html index 65e9a081..1b795268 100644 --- a/overlord/src/app/product-group/product-group-detail/product-group-detail.component.html +++ b/overlord/src/app/product-group/product-group-detail/product-group-detail.component.html @@ -10,6 +10,10 @@ +
+ Nutritional Information? + Ice Cream? +
diff --git a/overlord/src/app/product-group/product-group-detail/product-group-detail.component.ts b/overlord/src/app/product-group/product-group-detail/product-group-detail.component.ts index 5a902844..0c113367 100644 --- a/overlord/src/app/product-group/product-group-detail/product-group-detail.component.ts +++ b/overlord/src/app/product-group/product-group-detail/product-group-detail.component.ts @@ -15,6 +15,8 @@ export class ProductGroupDetailComponent implements OnInit, AfterViewInit { @ViewChild('nameElement', { static: true }) nameElement?: ElementRef; form: FormGroup<{ name: FormControl; + nutritional: FormControl; + iceCream: FormControl; }>; item: ProductGroup = new ProductGroup(); @@ -27,6 +29,8 @@ export class ProductGroupDetailComponent implements OnInit, AfterViewInit { ) { this.form = new FormGroup({ name: new FormControl(null), + nutritional: new FormControl(false, { nonNullable: true }), + iceCream: new FormControl(false, { nonNullable: true }), }); } @@ -42,6 +46,8 @@ export class ProductGroupDetailComponent implements OnInit, AfterViewInit { this.item = item; this.form.setValue({ name: this.item.name, + nutritional: this.item.nutritional, + iceCream: this.item.iceCream, }); } @@ -68,6 +74,8 @@ export class ProductGroupDetailComponent implements OnInit, AfterViewInit { getItem(): ProductGroup { const formModel = this.form.value; this.item.name = formModel.name ?? ''; + this.item.nutritional = formModel.nutritional ?? false; + this.item.iceCream = formModel.iceCream ?? false; return this.item; } } diff --git a/overlord/src/app/product-group/product-group.module.ts b/overlord/src/app/product-group/product-group.module.ts index 95a31445..bcbd5724 100644 --- a/overlord/src/app/product-group/product-group.module.ts +++ b/overlord/src/app/product-group/product-group.module.ts @@ -4,6 +4,7 @@ import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatPaginatorModule } from '@angular/material/paginator'; @@ -23,6 +24,7 @@ import { ProductGroupRoutingModule } from './product-group-routing.module'; MatPaginatorModule, MatSortModule, MatCardModule, + MatCheckboxModule, MatProgressSpinnerModule, MatInputModule, MatButtonModule, 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 69d6e2d3..642f8b40 100644 --- a/overlord/src/app/product/product-detail/product-detail.component.html +++ b/overlord/src/app/product/product-detail/product-detail.component.html @@ -28,13 +28,74 @@
Product Type - + {{ pg.name }}
+

Nutritional Information

+
+ + Protein + + + + Carbohydrate + + + + Total Sugar + + + + Added Sugar + + + + Total Fat + + + + Saturated Fat + + + + Trans Fat + + + + Cholestrol + + + + Sodium + + +
+

Ice Cream Information

+
+ + MSNF + + + + Other Solids + + + + Total Solids + + + + Water + + +

Stock Keeping Units

; isActive: FormControl; productGroup: FormControl; + + protein: FormControl; + carbohydrate: FormControl; + totalSugar: FormControl; + addedSugar: FormControl; + totalFat: FormControl; + saturatedFat: FormControl; + transFat: FormControl; + cholestrol: FormControl; + sodium: FormControl; + + msnf: FormControl; + otherSolids: FormControl; + totalSolids: FormControl; + water: FormControl; }>; productGroups: ProductGroup[] = []; @@ -66,6 +81,21 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { isSold: new FormControl(true, { nonNullable: true }), isActive: new FormControl(true, { nonNullable: true }), productGroup: new FormControl(null), + + protein: new FormControl(0, { nonNullable: true }), + carbohydrate: new FormControl(0, { nonNullable: true }), + totalSugar: new FormControl(0, { nonNullable: true }), + addedSugar: new FormControl(0, { nonNullable: true }), + totalFat: new FormControl(0, { nonNullable: true }), + saturatedFat: new FormControl(0, { nonNullable: true }), + transFat: new FormControl(0, { nonNullable: true }), + cholestrol: new FormControl(0, { nonNullable: true }), + sodium: new FormControl(0, { nonNullable: true }), + + msnf: new FormControl(0, { nonNullable: true }), + otherSolids: new FormControl(0, { nonNullable: true }), + totalSolids: new FormControl(0, { nonNullable: true }), + water: new FormControl(0, { nonNullable: true }), }); } @@ -81,6 +111,7 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { } showItem(item: Product) { + item.productGroup = this.productGroups.find((x) => x.id === item.productGroup?.id); this.item = item; this.form.setValue({ code: this.item.code || '(Auto)', @@ -97,6 +128,21 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { isSold: this.item.isSold, isActive: this.item.isActive, productGroup: this.item.productGroup ? this.item.productGroup.id ?? '' : '', + + protein: this.item.protein, + carbohydrate: this.item.carbohydrate, + totalSugar: this.item.totalSugar, + addedSugar: this.item.addedSugar, + totalFat: this.item.totalFat, + saturatedFat: this.item.saturatedFat, + transFat: this.item.transFat, + cholestrol: this.item.cholestrol, + sodium: this.item.sodium, + + msnf: this.item.msnf, + otherSolids: this.item.otherSolids, + totalSolids: this.item.totalSolids, + water: this.item.water, }); } @@ -108,6 +154,10 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { }, 0); } + updatePG(id: string) { + this.item.productGroup = this.productGroups.find((x) => x.id == id); + } + addRow() { const formValue = this.form.value.addRow; if (formValue === undefined) { @@ -220,10 +270,22 @@ export class ProductDetailComponent implements OnInit, AfterViewInit { this.item.isPurchased = formModel.isPurchased ?? true; this.item.isSold = formModel.isSold ?? false; this.item.isActive = formModel.isActive ?? true; - if (this.item.productGroup === null || this.item.productGroup === undefined) { - this.item.productGroup = new ProductGroup(); - } - this.item.productGroup.id = formModel.productGroup ?? ''; + + this.item.protein = formModel.protein ?? 0; + this.item.carbohydrate = formModel.carbohydrate ?? 0; + this.item.totalSugar = formModel.totalSugar ?? 0; + this.item.addedSugar = formModel.addedSugar ?? 0; + this.item.totalFat = formModel.totalFat ?? 0; + this.item.saturatedFat = formModel.saturatedFat ?? 0; + this.item.transFat = formModel.transFat ?? 0; + this.item.cholestrol = formModel.cholestrol ?? 0; + this.item.sodium = formModel.sodium ?? 0; + + this.item.msnf = formModel.msnf ?? 0; + this.item.otherSolids = formModel.otherSolids ?? 0; + this.item.totalSolids = formModel.totalSolids ?? 0; + this.item.water = formModel.water ?? 0; + return this.item; } }