import uuid from typing import Optional import brewman.schemas.master as schemas from fastapi import APIRouter, Depends, HTTPException, Security, status from sqlalchemy import desc from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session, joinedload_all from ..core.security import get_current_active_user as get_user from ..db.session import SessionLocal from ..models.master import Account, Product from ..models.voucher import Batch, Inventory, Voucher, VoucherType from ..schemas.auth import UserToken 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=round(data.fraction, 5), fraction_units=data.fraction_units, product_yield=round(data.product_yield, 5), product_group_id=data.product_group.id_, account_id=Account.all_purchases(), price=round(data.price, 2), sale_price=round(data.sale_price, 2), 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 = round(data.fraction, 5) item.fraction_units = data.fraction_units item.product_yield = round(data.product_yield, 5) item.product_group_id = data.product_group.id_ item.account_id = Account.all_purchases() item.price = round(data.price, 2) item.sale_price = round(data.sale_price, 2) 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)