brewman/brewman/brewman/routers/purchase_return.py

290 lines
9.5 KiB
Python

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)