diff --git a/brewman/alembic/versions/0e6c5953a63f_recipe.py b/brewman/alembic/versions/0e6c5953a63f_recipe.py new file mode 100644 index 00000000..7052d8d1 --- /dev/null +++ b/brewman/alembic/versions/0e6c5953a63f_recipe.py @@ -0,0 +1,55 @@ +"""recipe + +Revision ID: 0e6c5953a63f +Revises: c39eb451a683 +Create Date: 2021-11-01 12:11:16.813756 + +""" +import sqlalchemy as sa + +from alembic import op +from sqlalchemy import column, func, table, text +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision = "0e6c5953a63f" +down_revision = "c39eb451a683" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column("recipes", "product_id", new_column_name="sku_id") + op.alter_column("recipes", "valid_to", new_column_name="valid_till", existing_type=sa.DATE(), nullable=True) + op.alter_column("recipes", "valid_from", existing_type=sa.DATE(), nullable=True) + op.drop_constraint("recipes_product_id_fkey", "recipes", type_="foreignkey") + op.create_foreign_key( + op.f("fk_recipes_sku_id_stock_keeping_units"), "recipes", "stock_keeping_units", ["sku_id"], ["id"] + ) + op.drop_column("recipes", "effective_to") + op.drop_column("recipes", "effective_from") + + recipe = table( + "recipes", + column("sku_id", postgresql.UUID(as_uuid=True)), + column("valid_from", sa.Date()), + column("valid_till", sa.Date()), + ) + + op.create_exclude_constraint( + "uq_recipes_sku_id", + "recipes", + (recipe.c.sku_id, "="), + (func.daterange(recipe.c.valid_from, recipe.c.valid_till, text("'[]'")), "&&"), + ) + # ### end Alembic commands ### + + +# delete from recipe_items where recipe_id in (select id from recipes where product_id in (select product_id from recipes group by product_id having count(*) > 1) and effective_to is not null); +# delete from recipes where id in (select id from recipes where product_id in (select product_id from recipes group by product_id having count(*) > 1) and effective_to is not null); + + +def downgrade(): + pass diff --git a/brewman/brewman/models/recipe.py b/brewman/brewman/models/recipe.py index 43643f77..9cd82135 100644 --- a/brewman/brewman/models/recipe.py +++ b/brewman/brewman/models/recipe.py @@ -1,7 +1,5 @@ import uuid -from datetime import date - from sqlalchemy import Column, Date, ForeignKey, Numeric, Unicode from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship @@ -13,7 +11,7 @@ class Recipe(Base): __tablename__ = "recipes" id = Column("id", UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - product_id = Column("product_id", UUID(as_uuid=True), ForeignKey("products.id"), nullable=False) + sku_id = Column("sku_id", UUID(as_uuid=True), ForeignKey("stock_keeping_units.id"), nullable=False) quantity = Column("quantity", Numeric(precision=15, scale=2), nullable=False) cost_price = Column("cost_price", Numeric(precision=15, scale=2), nullable=False) @@ -23,31 +21,25 @@ class Recipe(Base): valid_from = Column("valid_from", Date, nullable=True) valid_till = Column("valid_till", Date, nullable=True) - effective_from = Column("effective_from", Date, nullable=False) - effective_to = Column("effective_to", Date) - - recipe_items = relationship("RecipeItem", backref="recipe") - product = relationship("Product", back_populates="recipes") + items = relationship("RecipeItem", back_populates="recipe") + sku = relationship("StockKeepingUnit", back_populates="recipes") def __init__( self, - product_id=None, + sku_id=None, quantity=None, cost_price=None, sale_price=None, valid_from=None, valid_till=None, notes=None, - effective_from=None, id_=None, ): - self.product_id = product_id + self.sku_id = sku_id self.quantity = quantity self.cost_price = cost_price self.sale_price = sale_price self.valid_from = valid_from self.valid_till = valid_till self.notes = "" if notes is None else notes - self.effective_from = date.today() if effective_from is None else effective_from - self.effective_to = None self.id = id_ diff --git a/brewman/brewman/models/recipe_item.py b/brewman/brewman/models/recipe_item.py index f4c64ddf..35ca1e3a 100644 --- a/brewman/brewman/models/recipe_item.py +++ b/brewman/brewman/models/recipe_item.py @@ -17,11 +17,17 @@ class RecipeItem(Base): quantity = Column("quantity", Integer, nullable=False) price = Column("price", Integer, nullable=False) + recipe = relationship("Recipe", back_populates="items") product = relationship("Product") - def __init__(self, recipe_id=None, product_id=None, quantity=None, price=None, id_=None): + def __init__(self, recipe_id=None, product_id=None, quantity=None, price=None, recipe=None, id_=None): self.recipe_id = recipe_id self.product_id = product_id self.quantity = quantity self.price = price + if recipe is None: + self.recipe_id = recipe_id + else: + self.recipe_id = recipe.id + self.recipe = recipe self.id = id_ diff --git a/brewman/brewman/schemas/recipe.py b/brewman/brewman/schemas/recipe.py index bf3b5a41..17f0cc4c 100644 --- a/brewman/brewman/schemas/recipe.py +++ b/brewman/brewman/schemas/recipe.py @@ -1,36 +1,14 @@ import uuid -from datetime import date +from datetime import date, datetime from decimal import Decimal from typing import List, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, validator from . import to_camel - - -class ProductLink(BaseModel): - id_: uuid.UUID = Field(...) - name: Optional[str] - units: Optional[str] - sale_price: Optional[Decimal] - is_sold: Optional[bool] - fraction_units: Optional[str] - fraction: Optional[Decimal] - product_yield: Optional[Decimal] - - class Config: - alias_generator = to_camel - - -class RecipeItem(BaseModel): - id_: Optional[uuid.UUID] - product: ProductLink - quantity: int - price: int - - class Config: - fields = {"id_": "id"} +from .product import ProductLink +from .recipe_item import RecipeItem class RecipeIn(BaseModel): @@ -44,9 +22,6 @@ class RecipeIn(BaseModel): valid_from: Optional[date] valid_till: Optional[date] - effective_from: Optional[date] - effective_till: Optional[date] - items: List[RecipeItem] class Config: @@ -54,6 +29,22 @@ class RecipeIn(BaseModel): alias_generator = to_camel json_encoders = {date: lambda v: v.strftime("%d-%b-%Y") if v is not None else None} + @validator("valid_from", pre=True) + def parse_valid_from(cls, value): + if isinstance(value, date): + return value + if value is None: + return value + return datetime.strptime(value, "%d-%b-%Y").date() + + @validator("valid_till", pre=True) + def parse_valid_till(cls, value): + if isinstance(value, date): + return value + if value is None: + return value + return datetime.strptime(value, "%d-%b-%Y").date() + class Recipe(RecipeIn): id_: uuid.UUID @@ -65,8 +56,9 @@ class Recipe(RecipeIn): class RecipeBlank(RecipeIn): - product: Optional[ProductLink] + product: Optional[ProductLink] # type: ignore[assignment] class Config: anystr_strip_whitespace = True alias_generator = to_camel + json_encoders = {date: lambda v: v.strftime("%d-%b-%Y")} diff --git a/brewman/brewman/schemas/recipe_item.py b/brewman/brewman/schemas/recipe_item.py index 328506d3..a6a8ddc0 100644 --- a/brewman/brewman/schemas/recipe_item.py +++ b/brewman/brewman/schemas/recipe_item.py @@ -1,9 +1,9 @@ import uuid + from typing import Optional -from pydantic import BaseModel - from brewman.schemas.product import ProductLink +from pydantic import BaseModel class RecipeItem(BaseModel): @@ -13,4 +13,4 @@ class RecipeItem(BaseModel): price: int class Config: - fields = {"id_": "id"} \ No newline at end of file + fields = {"id_": "id"}