277 lines
8.5 KiB
Python
277 lines
8.5 KiB
Python
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)
|