From 9383cf84d477f77a7d4a7a1f5f626aaf03d9d267 Mon Sep 17 00:00:00 2001 From: tanshu Date: Sun, 10 May 2020 16:05:39 +0530 Subject: [PATCH] Done: Product Group Done: Product --- brewman/models/master.py | 36 ++- brewman/routers/account.py | 6 +- brewman/routers/cost_centre.py | 9 +- brewman/routers/product.py | 396 ++++++++++++++----------------- brewman/routers/product_group.py | 210 ++++++++++------ brewman/schemas/master.py | 68 ++++-- 6 files changed, 394 insertions(+), 331 deletions(-) diff --git a/brewman/models/master.py b/brewman/models/master.py index f0f0ed16..69539170 100644 --- a/brewman/models/master.py +++ b/brewman/models/master.py @@ -111,6 +111,18 @@ class Product(Base): return False, "Product has entries" return True, "" + @classmethod + def query(cls, term, is_purchased=None, active=None, db=None): + query_ = db.query(Product) + if active is not None: + query_ = query_.filter(Product.is_active == active) + if is_purchased is not None: + query_ = query_.filter(Product.is_purchased == is_purchased) + if term is not None: + for item in term.split(): + if item.strip() != "": + query_ = query_.filter(Product.name.ilike("%" + item + "%")) + @classmethod def suspense(cls): return uuid.UUID("aa79a643-9ddc-4790-ac7f-a41f9efb4c15") @@ -303,20 +315,20 @@ class AccountBase(Base): self.is_fixture = is_fixture @classmethod - def list(cls, type, name, reconcilable=None, active=None, dbsession=None): - query = dbsession.query(cls) - if type is not None: - if not isinstance(type, int): - type = int(type) - query = query.filter(cls.type == type) + def query(cls, q, type_, reconcilable=None, active=None, db=None): + query_ = db.query(cls) + if type_ is not None: + if not isinstance(type_, int): + type_ = int(type_) + query_ = query_.filter(cls.type == type_) if reconcilable is not None: - query = query.filter(cls.is_reconcilable == reconcilable) + query_ = query_.filter(cls.is_reconcilable == reconcilable) if active is not None: - query = query.filter(cls.is_active == active) - if name is not None: - for item in name.split(): - query = query.filter(cls.name.ilike("%" + item + "%")) - return query.order_by(cls.name) + query_ = query_.filter(cls.is_active == active) + if q is not None: + for item in q.split(): + query_ = query_.filter(cls.name.ilike("%" + item + "%")) + return query_.order_by(cls.name) def create(self, dbsession): code = ( diff --git a/brewman/routers/account.py b/brewman/routers/account.py index 4c539f0d..b9186d69 100644 --- a/brewman/routers/account.py +++ b/brewman/routers/account.py @@ -151,7 +151,7 @@ async def show_term( count = c list_ = [] - for index, item in enumerate(AccountBase.list(t, q, r, a, db)): + for index, item in enumerate(AccountBase.query(q, t, r, a, db)): list_.append({"id": item.id, "name": item.name}) if count is not None and index == count - 1: break @@ -195,7 +195,7 @@ def balance(id_: uuid.UUID, date, db: Session): return 0 if bal is None else bal -def account_info(id_, db): +def account_info(id_: uuid.UUID, db: Session): if id_ is None: account = { "code": "(Auto)", @@ -224,7 +224,7 @@ def account_info(id_, db): return account -def delete_with_data(account, db): +def delete_with_data(account: Account, db: Session): suspense_account = ( db.query(Account).filter(Account.id == Account.suspense()).first() ) diff --git a/brewman/routers/cost_centre.py b/brewman/routers/cost_centre.py index eba76223..c8cf5f49 100644 --- a/brewman/routers/cost_centre.py +++ b/brewman/routers/cost_centre.py @@ -1,6 +1,7 @@ import traceback import uuid from typing import List, Optional + from fastapi import APIRouter, HTTPException, status, Depends, Security from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session @@ -28,10 +29,16 @@ def save( user: User = Security(get_user, scopes=["cost-centres"]), ): try: - item = CostCentre(data.name) + item = CostCentre(name=data.name) db.add(item) db.commit() return cost_centre_info(item, db) + except SQLAlchemyError as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) except Exception: db.rollback() raise HTTPException( diff --git a/brewman/routers/product.py b/brewman/routers/product.py index 6da78537..64dc6f2b 100644 --- a/brewman/routers/product.py +++ b/brewman/routers/product.py @@ -1,245 +1,203 @@ +import traceback import uuid -from decimal import Decimal, InvalidOperation +from typing import Optional +from fastapi import APIRouter, HTTPException, status, Depends, Security from sqlalchemy import desc -from sqlalchemy.orm import joinedload_all +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import joinedload_all, Session -from brewman.models.master import Product, Account -from brewman.models.validation_exception import ValidationError -from brewman.models.voucher import Voucher, Batch, Inventory, VoucherType - -from fastapi import APIRouter +import brewman.schemas.master as schemas +from ..core.security import User, get_current_active_user as get_user +from ..db.session import SessionLocal +from ..models.master import Product, Account +from ..models.voucher import Voucher, Batch, Inventory, VoucherType router = APIRouter() -@router.post("/new") # "Products" -def save(request): - json = request.json_body - - name = json.get("name", "").strip() - if name == "": - raise ValidationError("Name cannot be blank") - - units = json.get("units", "").strip() - +# Dependency +def get_db(): try: - fraction = Decimal(json.get("fraction", 0)) - if fraction <= 0: - raise ValidationError("Fraction must be a decimal > 0") - except (ValueError, InvalidOperation): - raise ValidationError("Fraction must be a decimal > 0") + db = SessionLocal() + yield db + finally: + db.close() - fraction_units = json.get("fractionUnits", "").strip() +@router.post("/", response_model=schemas.Product) +def save( + data: schemas.ProductIn, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["products"]), +): try: - product_yield = Decimal(json.get("productYield", 1)) - if product_yield <= 0 or product_yield > 1: - raise ValidationError("Yield must be a decimal > 0 <= 1") - except (ValueError, InvalidOperation): - raise ValidationError("Yield must be a decimal > 0 <= 1") - - product_group = json.get("productGroup", None) - if product_group is None: - raise ValidationError("please choose a product group") - product_group_id = uuid.UUID(product_group["id"]) - try: - price = Decimal(json.get("price", 0)) - if price < 0: - raise ValidationError("Price must be a decimal >= 0") - except (ValueError, InvalidOperation): - raise ValidationError("Price must be a decimal >= 0") - try: - sale_price = Decimal(json.get("salePrice", 0)) - if price < 0: - raise ValidationError("Sale Price must be a decimal >= 0") - except (ValueError, InvalidOperation): - raise ValidationError("Price must be a decimal >= 0") - is_active = json.get("isActive", True) - is_purchased = json.get("isPurchased", True) - is_sold = json.get("isSold", True) - item = Product( - 0, - name, - units, - fraction, - fraction_units, - product_yield, - product_group_id, - Account.all_purchases(), - price, - sale_price, - is_active, - is_purchased, - is_sold, - ).create(request.dbsession) - transaction.commit() - return product_info(item.id, request.dbsession) - - -@router.put("/{id}") # "Products" -def update(request): - item = ( - request.dbsession.query(Product) - .filter(Product.id == uuid.UUID(request.matchdict["id"])) - .first() - ) - if item.is_fixture: - raise ValidationError( - "{0} is a fixture and cannot be edited or deleted.".format(item.full_name) + item = Product( + name=data.name, + units=data.units, + fraction=data.fraction, + fraction_units=data.fraction_units, + product_yield=data.product_yield, + product_group_id=data.product_group.id_, + account_id=Account.all_purchases(), + price=data.price, + sale_price=data.sale_price, + is_active=data.is_active, + is_purchased=data.is_purchased, + is_sold=data.is_sold + ).create(db) + db.commit() + return product_info(item.id, db) + except SQLAlchemyError as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), ) - item.name = request.json_body["name"].strip() - item.units = request.json_body["units"].strip() - try: - item.fraction = Decimal(request.json_body["fraction"]) - if item.fraction <= 0: - raise ValidationError("Fraction must be a decimal > 0") - except (ValueError, InvalidOperation): - raise ValidationError("Fraction must be a decimal > 0") - item.fraction_units = request.json_body["fractionUnits"] - - try: - item.product_yield = Decimal(request.json_body["productYield"]) - if item.product_yield <= 0 or item.product_yield > 1: - raise ValidationError("Yield must be a decimal > 0 <= 1") - except (ValueError, InvalidOperation): - raise ValidationError("Yield must be a decimal > 0 <= 1") - - item.product_group_id = uuid.UUID(request.json_body["productGroup"]["id"]) - item.account_id = Account.all_purchases() - try: - item.price = Decimal(request.json_body["price"]) - if item.price < 0: - raise ValidationError("Price must be a decimal >= 0") - except (ValueError, InvalidOperation): - raise ValidationError("Price must be a decimal >= 0") - try: - item.sale_price = Decimal(request.json_body["salePrice"]) - if item.sale_price < 0: - raise ValidationError("Sale Price must be a decimal >= 0") - except (ValueError, InvalidOperation): - raise ValidationError("Sale Price must be a decimal >= 0") - item.is_active = request.json_body["isActive"] - item.is_fixture = request.json_body["isFixture"] - item.is_purchased = request.json_body["isPurchased"] - item.is_sold = request.json_body["isSold"] - transaction.commit() - return product_info(item.id, request.dbsession) -@router.delete("/{id}") # "Products" -def delete(request): - product = ( - request.dbsession.query(Product) - .filter(Product.id == uuid.UUID(request.matchdict["id"])) - .first() - ) - can_delete, reason = product.can_delete(request.has_permission("Advanced Delete")) +@router.put("/{id_}", response_model=schemas.Product) +def update( + id_: uuid.UUID, + data: schemas.ProductIn, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["products"]), +): + try: + item: Product = db.query(Product).filter(Product.id == id_).first() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + item.name = data.name + item.units = data.units + item.fraction = data.fraction + item.fraction_units = data.fraction_units + item.product_yield = data.product_yield + item.product_group_id = data.product_group.id_ + item.account_id = Account.all_purchases() + item.price = data.price + item.sale_price = data.sale_price + item.is_active = data.is_active + item.is_purchased = data.is_purchased + item.is_sold = data.is_sold + db.commit() + return product_info(item.id, db) + except SQLAlchemyError as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), + ) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), + ) + + +@router.delete("/{id_}") +def delete( + id_: uuid.UUID, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["products"]), +): + product: Product = db.query(Product).filter(Product.id == id_).first() + can_delete, reason = product.can_delete("Advanced Delete" in user.permissions) if can_delete: - delete_with_data(product, request) - transaction.commit() - return product_info(None, request.dbsession) + delete_with_data(product, db) + db.commit() + return product_info(None, db) else: - transaction.abort() - response = Response("Cannot delete product because {0}".format(reason)) - response.status_int = 500 - return response + db.abort() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Cannot delete account because {reason}", + ) -@router.get("/{id}") # "Products" -def show_id(request): - return product_info(uuid.UUID(request.matchdict.get("id", None)), request.dbsession) +@router.get("/") # "Products" +def show_blank( + db: Session = Depends(get_db), user: User = Security(get_user, scopes=["products"]) +): + return product_info(None, db) -@router.get("/new") # "Products" -def show_blank(request): - return product_info(None, request.dbsession) +@router.get("/list") +def show_list(db: Session = Depends(get_db), user: User = Depends(get_user)): + return [ + { + "id": item.id, + "code": item.code, + "name": item.name, + "units": item.units, + "costPrice": item.price, + "salePrice": item.sale_price, + "productGroup": item.product_group.name, + "isActive": item.is_active, + "fraction": item.fraction, + "fractionUnits": item.fraction_units, + "isPurchased": item.is_purchased, + "isSold": item.is_sold, + "productYield": item.product_yield, + "isFixture": item.is_fixture, + } + for item in db.query(Product).order_by(desc(Product.is_active)).order_by(Product.product_group_id).order_by(Product.name).all() + ] -@router.get("/", ) # "Authenticated" -def show_list(l: bool): - list_ = ( - request.dbsession.query(Product) - .order_by(desc(Product.is_active)) - .order_by(Product.product_group_id) - .order_by(Product.name) - .all() - ) - products = [] - for item in list_: - products.append( +@router.get("/query") +async def show_term( + t: str = None, + a: bool = None, + c: int = None, + p: bool = None, + e: bool = False, + db: Session = Depends(get_db), + current_user: User = Depends(get_user), +): + count = c + extended = e + list_ = [] + for index, item in enumerate(Product.query(t, p, a, db)): + list_.append( { "id": item.id, - "code": item.code, - "name": item.name, + "name": item.full_name, + "price": item.price, "units": item.units, - "costPrice": item.price, - "salePrice": item.sale_price, - "productGroup": item.product_group.name, - "isActive": item.is_active, "fraction": item.fraction, "fractionUnits": item.fraction_units, - "isPurchased": item.is_purchased, - "isSold": item.is_sold, "productYield": item.product_yield, - "isFixture": item.is_fixture, + "isSold": item.is_sold, + "salePrice": item.sale_price, + } if extended else { + "id": item.id, "name": item.full_name, "price": item.price } ) - return products + if count is not None and index == count - 1: + break + return sorted(list_, key=lambda k: k["name"]) -@router.get("/") # "Authenticated" -async def show_term(t: str): - term = request.GET.get("t", None) - term = term.strip() if term is not None and term.strip() != "" else None - active = request.GET.get("a", None) - active = active if active is not None else None - count = request.GET.get("c", None) - count = None if count is None or count == "" else int(count) - is_purchased = request.GET.get("p", None) - is_purchased = is_purchased if is_purchased is not None else None - extended = request.GET.get("e", False) - - def add_list(query): - local_results = [] - for index, item in enumerate(query): - if extended: - product = { - "id": item.id, - "name": item.full_name, - "price": item.price, - "units": item.units, - "fraction": item.fraction, - "fractionUnits": item.fraction_units, - "productYield": item.product_yield, - "isSold": item.is_sold, - "salePrice": item.sale_price, - } - else: - product = {"id": item.id, "name": item.full_name, "price": item.price} - local_results.append(product) - if count is not None and index == count - 1: - break - return local_results - - result_list = [] - - query = request.dbsession.query(Product) - if active is not None: - query = query.filter(Product.is_active == active) - if is_purchased is not None: - query = query.filter(Product.is_purchased == is_purchased) - if term is not None: - for item in term.split(): - if item.strip() != "": - query = query.filter(Product.name.ilike("%" + item + "%")) - result_list += add_list(query) - - return sorted(result_list, key=lambda k: k["name"]) +@router.get("/{id_}") +def show_id( + id_: uuid.UUID, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["accounts"]), +): + return product_info(id_, db) -def product_info(id_, dbsession): +def product_info(id_: Optional[uuid.UUID], db: Session): if id_ is None: product = { "code": "(Auto)", @@ -249,7 +207,7 @@ def product_info(id_, dbsession): "isSold": False, } else: - product = dbsession.query(Product).filter(Product.id == id_).first() + product = db.query(Product).filter(Product.id == id_).first() product = { "id": product.id, "code": product.code, @@ -270,17 +228,17 @@ def product_info(id_, dbsession): return product -def delete_with_data(product, request): +def delete_with_data(product: Product, db: Session): suspense_product = ( - request.dbsession.query(Product) + db.query(Product) .filter(Product.id == Product.suspense()) .first() ) suspense_batch = ( - request.dbsession.query(Batch).filter(Batch.id == Batch.suspense()).first() + db.query(Batch).filter(Batch.id == Batch.suspense()).first() ) query = ( - request.dbsession.query(Voucher) + db.query(Voucher) .options(joinedload_all(Voucher.inventories, Inventory.product, innerjoin=True)) .filter(Voucher.inventories.any(Inventory.product_id == product.id)) .all() @@ -296,7 +254,7 @@ def delete_with_data(product, request): else: others = True if not others and voucher.type == VoucherType.by_id("Issue"): - request.dbsession.delete(voucher) + db.delete(voucher) else: if sus_inv is None: prod_inv.product = suspense_product @@ -305,15 +263,11 @@ def delete_with_data(product, request): prod_inv.tax = 0 prod_inv.discount = 0 prod_inv.batch = suspense_batch - voucher.narration += "\nSuspense \u20B9{0:,.2f} is {1}".format( - prod_inv.amount, product.name - ) + voucher.narration += f"\nSuspense \u20B9{prod_inv.amount:,.2f} is {product.name}" else: sus_inv.quantity += prod_inv.amount - request.dbsession.delete(prod_inv) - voucher.narration += "\nDeleted \u20B9{0:,.2f} of {1}".format( - prod_inv.amount, product.name - ) + db.delete(prod_inv) + voucher.narration += f"\nDeleted \u20B9{prod_inv.amount:,.2f} of {product.name}" for batch in product.batches: - request.dbsession.delete(batch) - request.dbsession.delete(product) + db.delete(batch) + db.delete(product) diff --git a/brewman/routers/product_group.py b/brewman/routers/product_group.py index 1af24157..5adc4f71 100644 --- a/brewman/routers/product_group.py +++ b/brewman/routers/product_group.py @@ -1,90 +1,150 @@ +import traceback import uuid +from typing import List, Optional +from fastapi import APIRouter, HTTPException, status, Depends, Security +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import Session + +import brewman.schemas.master as schemas +from ..core.security import User, get_current_active_user as get_user +from ..db.session import SessionLocal from brewman.models.master import ProductGroup -from brewman.models.validation_exception import ValidationError -from fastapi import APIRouter - router = APIRouter() -@router.post("/new") # "Product Groups" -def save(request): - item = ProductGroup(request.json_body["name"]) - request.dbsession.add(item) - transaction.commit() - return product_group_info(item.id, request.dbsession) +# Dependency +def get_db(): + try: + db = SessionLocal() + yield db + finally: + db.close() -@router.put("/{id}") # "Product Groups" -def update(request): - item = ( - request.dbsession.query(ProductGroup) - .filter(ProductGroup.id == uuid.UUID(request.matchdict["id"])) - .first() - ) - if item.is_fixture: - raise ValidationError( - "{0} is a fixture and cannot be edited or deleted.".format(item.name) +@router.post("/", response_model=schemas.ProductGroup) +def save( + data: schemas.ProductGroupIn, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["product-groups"]), +): + try: + item = ProductGroup(name=data.name) + db.add(item) + db.commit() + return product_group_info(item, db) + except SQLAlchemyError as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), ) - item.name = request.json_body["name"] - transaction.commit() - return product_group_info(item.id, request.dbsession) -@router.delete("/{id}") # "Product Groups" -def delete(request): - item = ( - request.dbsession.query(ProductGroup) - .filter(ProductGroup.id == uuid.UUID(request.matchdict["id"])) - .first() - ) +@router.put("/{id_}", response_model=schemas.ProductGroup) +def update( + id_: uuid.UUID, + data: schemas.ProductGroupIn, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["product-groups"]), +): + try: + item = db.query(ProductGroup).filter(ProductGroup.id == id_).first() + if item.is_fixture: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + item.name = data.name + db.commit() + return product_group_info(item, db) + except SQLAlchemyError as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=str(e), + ) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), + ) + +@router.delete("/{id_}") +def delete( + id_: uuid.UUID, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["product-groups"]), +): + try: + item = db.query(ProductGroup).filter(ProductGroup.id == id_).first() + + if item is None: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Product Group not found", + ) + elif item.is_fixture: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"{item.name} is a fixture and cannot be edited or deleted.", + ) + else: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Product Group deletion not implemented", + ) + except Exception: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=traceback.format_exc(), + ) + + +@router.get("/") +def show_blank( + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["product-groups"]), +): + return product_group_info(None, db) + + +@router.get("/list", response_model=List[schemas.ProductGroup]) +async def show_list(db: Session = Depends(get_db), user: User = Depends(get_user)): + return[ + {"id": item.id, "name": item.name, "isFixture": item.is_fixture} + for item in db.query(ProductGroup).order_by(ProductGroup.name).all() + ] + + +@router.get("/{id_}", response_model=schemas.ProductGroup) +def show_id( + id_: uuid.UUID, + db: Session = Depends(get_db), + user: User = Security(get_user, scopes=["product-groups"]), +): + item = db.query(ProductGroup).filter(ProductGroup.id == id_).first() + return product_group_info(item, db) + + +def product_group_info(item: Optional[ProductGroup], db): if item is None: - response = Response("Product Group not Found") - response.status_int = 500 - return response - elif item.is_fixture: - raise ValidationError( - "{0} is a fixture and cannot be edited or deleted.".format(item.name) - ) - else: - response = Response("Product Group deletion not implemented") - response.status_int = 500 - return response - - -@router.get("/{id}") # "Product Groups" -def show_id(request): - return product_group_info( - uuid.UUID(request.matchdict.get("id", None)), request.dbsession - ) - - -@router.get("/new") # "Product Groups" -def show_blank(request): - return product_group_info(None, request.dbsession) - - -@router.get("/") # "Authenticated" -async def show_list(l: bool): - list_ = request.dbsession.query(ProductGroup).order_by(ProductGroup.name).all() - product_groups = [] - for item in list_: - product_groups.append( - {"id": item.id, "name": item.name, "isFixture": item.is_fixture} - ) - return product_groups - - -def product_group_info(id_, dbsession): - if id_ is None: - return {"name": ''} - else: - product_group = ( - dbsession.query(ProductGroup).filter(ProductGroup.id == id_).first() - ) return { - "id": product_group.id, - "name": product_group.name, - "isFixture": product_group.is_fixture, + "name": "", + "isFixture": False, + } + else: + return { + "id": item.id, + "name": item.name, + "isFixture": item.is_fixture, } diff --git a/brewman/schemas/master.py b/brewman/schemas/master.py index e86c52aa..50d49afa 100644 --- a/brewman/schemas/master.py +++ b/brewman/schemas/master.py @@ -11,23 +11,59 @@ def to_camel(string: str) -> str: return ''.join([first] + [word.capitalize() for word in others]) -class Product(BaseModel): +class AccountLink(BaseModel): + id_: uuid.UUID = Field(...) + + class Config: + fields = {'id_': 'id'} + + +class ProductGroupIn(BaseModel): + name: str = Field(..., min_length=1) + + +class ProductGroup(ProductGroupIn): id_: uuid.UUID - code: int - name: str - units: str - fraction: Decimal - fraction_units: str - product_yield: Decimal - product_group_id: uuid.UUID - account_id: uuid.UUID - price: Decimal - sale_price: Decimal - is_active: bool is_fixture: bool + + class Config: + fields = {'id_': 'id'} + anystr_strip_whitespace = True + alias_generator = to_camel + + +class ProductGroupLink(BaseModel): + id_: uuid.UUID = Field(...) + + class Config: + fields = {'id_': 'id'} + + +class ProductIn(BaseModel): + name: str = Field(..., min_length=1) + units: str + fraction: Decimal = Field(ge=0, multiple_of=0.00001, default=0) + fraction_units: str + product_yield: Decimal = Field(ge=0, le=1, multiple_of=0.00001, default=1) + product_group: ProductGroupLink = Field(...) + account_id: AccountLink = Field(...) + price: Decimal = Field(ge=0, multiple_of=0.01, default=0) + sale_price: Decimal = Field(ge=0, multiple_of=0.01, default=0) + is_active: bool is_purchased: bool is_sold: bool + class Config: + fields = {'id_': 'id'} + anystr_strip_whitespace = True + alias_generator = to_camel + + +class Product(ProductIn): + id_: uuid.UUID + code: int + is_fixture: bool + class Recipe(BaseModel): id_: uuid.UUID @@ -53,14 +89,8 @@ class RecipeItem(BaseModel): price: int -class ProductGroup(BaseModel): - id_: uuid.UUID - name: str - is_fixture: bool - - class CostCentreLink(BaseModel): - id_: uuid.UUID + id_: uuid.UUID = Field(...) class Config: fields = {'id_': 'id'}