import uuid from typing import Optional from fastapi import APIRouter, HTTPException, status, Depends, Security from sqlalchemy import desc from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import joinedload_all, Session from ..schemas.auth import UserToken import brewman.schemas.master as schemas from ..core.security import 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() # Dependency def get_db(): try: db = SessionLocal() yield db finally: db.close() @router.post("/", response_model=schemas.Product) def save( data: schemas.ProductIn, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["products"]), ): try: 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 @router.put("/{id_}", response_model=schemas.Product) def update( id_: uuid.UUID, data: schemas.ProductIn, db: Session = Depends(get_db), user: UserToken = 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_423_LOCKED, 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 @router.delete("/{id_}") def delete( id_: uuid.UUID, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["products"]), ): item: Product = db.query(Product).filter(Product.id == id_).first() can_delete, reason = item.can_delete("advanced-delete" in user.permissions) if can_delete: delete_with_data(item, db) db.commit() return product_info(None, db) else: db.abort() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Cannot delete account because {reason}", ) @router.get("/") def show_blank( db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["products"]), ): return product_info(None, db) @router.get("/list") def show_list(db: Session = Depends(get_db), user: UserToken = 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("/query") async def show_term( q: str = None, a: bool = None, c: int = None, p: bool = None, e: bool = False, db: Session = Depends(get_db), current_user: UserToken = Depends(get_user), ): count = c extended = e list_ = [] for index, item in enumerate(Product.query(q, p, a, db)): list_.append( { "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, } if extended else {"id": item.id, "name": item.full_name, "price": item.price} ) if count is not None and index == count - 1: break return sorted(list_, key=lambda k: k["name"]) @router.get("/{id_}") def show_id( id_: uuid.UUID, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["accounts"]), ): return product_info(id_, db) def product_info(id_: Optional[uuid.UUID], db: Session): if id_ is None: product = { "code": "(Auto)", "productGroup": {}, "isActive": True, "isPurchased": True, "isSold": False, } else: product = db.query(Product).filter(Product.id == id_).first() product = { "id": product.id, "code": product.code, "name": product.name, "units": product.units, "fraction": product.fraction, "fractionUnits": product.fraction_units, "productYield": product.product_yield, "price": product.price, "salePrice": product.sale_price, "isActive": product.is_active, "isFixture": product.is_fixture, "isPurchased": product.is_purchased, "isSold": product.is_sold, "productGroup": {"id": product.product_group_id}, "account": {"id": product.account_id}, } return product def delete_with_data(product: Product, db: Session): suspense_product = db.query(Product).filter(Product.id == Product.suspense()).first() suspense_batch = db.query(Batch).filter(Batch.id == Batch.suspense()).first() query = ( db.query(Voucher) .options(joinedload_all(Voucher.inventories, Inventory.product, innerjoin=True)) .filter(Voucher.inventories.any(Inventory.product_id == product.id)) .all() ) for voucher in query: others, sus_inv, prod_inv = False, None, None for inventory in voucher.inventories: if inventory.product_id == product.id: prod_inv = inventory elif inventory.product_id == Product.suspense(): sus_inv = inventory else: others = True if not others and voucher.type == VoucherType.by_id("Issue"): db.delete(voucher) else: if sus_inv is None: prod_inv.product = suspense_product prod_inv.quantity = prod_inv.amount prod_inv.rate = 1 prod_inv.tax = 0 prod_inv.discount = 0 prod_inv.batch = suspense_batch voucher.narration += f"\nSuspense \u20B9{prod_inv.amount:,.2f} is {product.name}" else: sus_inv.quantity += prod_inv.amount db.delete(prod_inv) voucher.narration += f"\nDeleted \u20B9{prod_inv.amount:,.2f} of {product.name}" for batch in product.batches: db.delete(batch) db.delete(product)