import uuid from datetime import datetime from decimal import Decimal from typing import List import brewman.schemas.input as schema_in import brewman.schemas.voucher as output from fastapi import APIRouter, Depends, File, HTTPException, Request, Security, status from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from ..core.security import get_current_active_user as get_user from ..core.session import get_date, set_date from ..db.session import SessionLocal from ..models import AccountBase from ..models.voucher import Batch, Inventory, Journal, Voucher, VoucherType from ..schemas.auth import UserToken from .db_image import save_files, update_files from .voucher import ( blank_voucher, check_voucher_edit_allowed, check_voucher_lock_info, voucher_info, ) router = APIRouter() # Dependency def get_db() -> Session: try: db = SessionLocal() yield db finally: db.close() @router.post("", response_model=output.Voucher) def save_route( request: Request, data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["purchase-return"]), ): try: item: Voucher = save(data, user, db) save_inventories(item, data.inventories, db) save_journals(item, data.vendor, db) save_files(item.id, i, t, db) db.commit() set_date(data.date_.strftime("%d-%b-%Y"), request.session) info = voucher_info(item, db) return info except SQLAlchemyError as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e), ) except Exception: db.rollback() raise def save(data: schema_in.PurchaseIn, user: UserToken, db: Session) -> Voucher: check_voucher_lock_info(None, data.date_, db) voucher = Voucher( date=data.date_, narration=data.narration, is_starred=data.is_starred, user_id=user.id_, type_=VoucherType.by_name(data.type_), ) db.add(voucher) return voucher def save_inventories( voucher: Voucher, inventories: List[schema_in.Inventory], db: Session ): for item in inventories: batch = db.query(Batch).filter(Batch.id == item.batch.id_).first() if item.quantity > batch.quantity_remaining: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"Maximum quantity is {batch.quantity_remaining}.", ) if batch.name > voucher.date: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"Return date cannot be before {batch.product.name.strftime('%d-%b-%Y')}", ) batch.quantity_remaining -= item.quantity item = Inventory( product=batch.product, quantity=item.quantity, rate=batch.rate, tax=batch.tax, discount=batch.discount, batch=batch, ) voucher.inventories.append(item) db.add(item) def save_journals(voucher: Voucher, ven: schema_in.AccountLink, db: Session): vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() journals = {} amount = 0 for item in voucher.inventories: account = item.product.account amount += round(item.amount, 2) if account.id in journals: journals[account.id].amount += round(item.amount, 2) else: journals[account.id] = Journal( debit=-1, cost_centre_id=account.cost_centre_id, account_id=account.id, amount=round(item.amount, 2), ) journals[vendor.id] = Journal( debit=1, cost_centre_id=vendor.cost_centre_id, account_id=vendor.id, amount=amount, ) for item in journals.values(): voucher.journals.append(item) db.add(item) @router.put("/{id_}", response_model=output.Voucher) def update_route( id_: uuid.UUID, request: Request, data: schema_in.PurchaseIn = Depends(schema_in.PurchaseIn.load_form), db: Session = Depends(get_db), i: List[bytes] = File(None), t: List[bytes] = File(None), user: UserToken = Security(get_user, scopes=["purchase-return"]), ): try: item: Voucher = update(id_, data, user, db) update_inventory(item, data.inventories, db) update_journals(item, data.vendor, db) # journals_valid(voucher) update_files(item.id, data.files, i, t, db) db.commit() set_date(data.date_.strftime("%d-%b-%Y"), request.session) return voucher_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 def update( id_: uuid.UUID, data: schema_in.PurchaseIn, user: UserToken, db: Session ) -> Voucher: voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() check_voucher_lock_info(voucher.date, data.date_, db) check_voucher_edit_allowed(voucher, user) voucher.date = data.date_ voucher.is_starred = data.is_starred voucher.narration = data.narration voucher.user_id = user.id_ voucher.posted = False voucher.last_edit_date = datetime.utcnow() return voucher def update_inventory( voucher: Voucher, new_inventories: List[schema_in.Inventory], db: Session ): for it in range(len(voucher.inventories), 0, -1): item = voucher.inventories[it - 1] found = False for j in range(len(new_inventories), 0, -1): new_inventory = new_inventories[j - 1] if new_inventory.id_ == item.id: found = True if item.product_id != new_inventory.product.id_: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Product cannot be changed", ) old_quantity = round(Decimal(item.quantity), 2) quantity_remaining = round(Decimal(item.batch.quantity_remaining), 2) if new_inventory.quantity - old_quantity > quantity_remaining: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"{old_quantity + quantity_remaining} is the maximum for {item.product.full_name}.", ) if item.batch.name > voucher.date: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"Voucher cannot be before {item.product.name.strftime('%d-%b-%Y')}", ) item.batch.quantity_remaining -= new_inventory.quantity - old_quantity item.quantity = new_inventory.quantity new_inventories.remove(new_inventory) break if not found: item.batch.quantity_remaining += item.quantity voucher.inventories.remove(item) db.delete(item) save_inventories(voucher, new_inventories, db) def update_journals(voucher: Voucher, ven: schema_in.AccountLink, db): vendor = db.query(AccountBase).filter(AccountBase.id == ven.id_).first() journals = {} amount = 0 for item in voucher.inventories: account = item.product.account amount += item.amount if account.id in journals: journals[account.id].amount += item.amount else: journals[account.id] = Journal( debit=-1, cost_centre_id=account.cost_centre_id, account_id=account.id, amount=item.amount, ) journals[vendor.id] = Journal( debit=1, cost_centre_id=vendor.cost_centre_id, account_id=vendor.id, amount=amount, ) for i in range(len(voucher.journals), 0, -1): item = voucher.journals[i - 1] if item.account_id in journals: item.debit = journals[item.account_id].debit item.amount = round(journals[item.account_id].amount, 2) item.cost_centre_id = journals[item.account_id].cost_centre_id del journals[item.account_id] else: db.delete(item) voucher.journals.remove(item) for item in journals.values(): item.amount = round(item.amount, 2) voucher.journals.append(item) db.add(item) @router.get("/{id_}", response_model=output.Voucher) def get_id( id_: uuid.UUID, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["purchase-return"]), ): try: item: Voucher = db.query(Voucher).filter(Voucher.id == id_).first() return voucher_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 @router.get("", response_model=output.Voucher) def show_blank( request: Request, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["purchase-return"]), ): additional_info = {"date": get_date(request.session), "type": "Purchase Return"} return blank_voucher(additional_info, db)