brewman/brewman/brewman/routers/voucher.py

450 lines
16 KiB
Python

import uuid
from datetime import date, datetime
from decimal import Decimal
from typing import Optional
import brewman.schemas.voucher as output
from fastapi import APIRouter, Depends, HTTPException, Security, status
from sqlalchemy import func, or_
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_first_day
from ..db.session import SessionLocal
from ..models.master import Account, AccountBase, AttendanceType, CostCentre, Employee
from ..models.voucher import (
Attendance,
DbImage,
Inventory,
Journal,
Voucher,
VoucherType,
)
from ..routers import get_lock_info
from ..schemas.auth import UserToken
router = APIRouter()
# Dependency
def get_db() -> Session:
try:
db = SessionLocal()
yield db
finally:
db.close()
@router.post("/post-voucher/{id_}", response_model=output.Voucher)
def post_voucher(
id_: uuid.UUID,
db: Session = Depends(get_db),
user: UserToken = Security(get_user, scopes=["post-vouchers"]),
):
try:
voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
check_voucher_lock_info(voucher.date, voucher.date, db)
voucher.posted = True
voucher.poster_id = user.id_
db.commit()
return voucher_info(voucher, 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 check_delete_permissions(voucher: Voucher, user: UserToken):
voucher_type = VoucherType.by_id(voucher.type).name.replace(" ", "-").lower()
if voucher_type in ["payment", "receipt"]:
voucher_type = "journal"
if voucher.posted and "edit-posted-vouchers" not in user.permissions:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not allowed to edit posted vouchers",
)
elif (
voucher.user_id != user.id_
and "edit-other-user's-vouchers" not in user.permissions
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not allowed to edit other user's vouchers",
)
elif voucher_type not in user.permissions:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"You are not allowed ({VoucherType.by_id(voucher.type).name}) vouchers",
)
@router.delete("/delete/{id_}")
def delete_voucher(
id_: uuid.UUID,
db: Session = Depends(get_db),
user: UserToken = Security(get_user),
):
voucher: Voucher = db.query(Voucher).filter(Voucher.id == id_).first()
images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all()
check_delete_permissions(voucher, user)
check_voucher_lock_info(None, voucher.date, db)
json_voucher = voucher_info(voucher, db)
batches_to_delete = []
if voucher.type == VoucherType.by_name("Issue").id:
for item in voucher.journals:
if item.debit == 1:
destination = item.cost_centre_id
else:
source = item.cost_centre_id
if source == CostCentre.cost_centre_purchase():
batch_consumed = True
elif destination == CostCentre.cost_centre_purchase():
batch_consumed = False
else:
batch_consumed = None
if batch_consumed is None:
pass
elif batch_consumed:
for item in voucher.inventories:
item.batch.quantity_remaining += item.quantity
else:
for item in voucher.inventories:
if item.batch.quantity_remaining < item.quantity:
raise HTTPException(
status_code=status.HTTP_423_LOCKED,
detail=f"Only {item.batch.quantity_remaining} of {item.product.name} remaining.\n So it cannot be deleted",
)
item.batch.quantity_remaining -= item.quantity
elif voucher.type == VoucherType.by_name("Purchase").id:
for item in voucher.inventories:
uses = (
db.query(func.count(Inventory.id))
.filter(Inventory.batch_id == item.batch.id)
.filter(Inventory.id != item.id)
.scalar()
)
if uses > 0:
raise HTTPException(
status_code=status.HTTP_423_LOCKED,
detail=f"{item.product.name} has been issued and cannot be deleted",
)
batches_to_delete.append(item.batch)
elif voucher.type == VoucherType.by_name("Purchase Return").id:
for item in voucher.inventories:
item.batch.quantity_remaining += item.quantity
for b in batches_to_delete:
db.delete(b)
db.delete(voucher)
for image in images:
db.delete(image)
db.commit()
return blank_voucher(info=json_voucher, db=db)
def voucher_info(voucher, db):
json_voucher = {
"id": voucher.id,
"date": voucher.date.strftime("%d-%b-%Y"),
"isStarred": voucher.is_starred,
"type": VoucherType.by_id(voucher.type).name,
"posted": voucher.posted,
"narration": voucher.narration,
"journals": [],
"inventories": [],
"employeeBenefits": [],
"incentives": [],
"incentive": 0,
"files": [],
"creationDate": voucher.creation_date.strftime("%d-%b-%Y %H:%M"),
"lastEditDate": voucher.last_edit_date.strftime("%d-%b-%Y %H:%M"),
"user": {"id": voucher.user.id, "name": voucher.user.name},
"poster": voucher.poster.name if voucher.posted else "",
}
if voucher.reconcile_date is not None:
json_voucher["reconcileDate"] = voucher.reconcile_date.strftime("%d-%b-%Y")
if voucher.type == 2: # "Purchase"
item = [j for j in voucher.journals if j.debit == -1][0]
json_voucher["vendor"] = {"id": item.account.id, "name": item.account.name}
elif voucher.type == 3: # "Issue"
item = [j for j in voucher.journals if j.debit == -1][0]
json_voucher["source"] = {"id": item.cost_centre_id, "name": ""}
item = [j for j in voucher.journals if j.debit == 1][0]
json_voucher["destination"] = {"id": item.cost_centre_id, "name": ""}
else:
for item in voucher.journals:
json_voucher["journals"].append(
{
"id": item.id,
"debit": item.debit,
"amount": item.amount,
"account": {"id": item.account.id, "name": item.account.name},
"costCentre": {"id": item.cost_centre_id},
}
)
for item in voucher.employee_benefits:
json_voucher["employeeBenefits"].append(
{
"grossSalary": item.gross_salary,
"daysWorked": item.days_worked,
"esiEmployee": item.esi_ee,
"pfEmployee": item.pf_ee,
"esiEmployer": item.esi_er,
"pfEmployer": item.pf_er,
"employee": {
"id": item.journal.account.id,
"name": item.journal.account.name,
"designation": item.journal.account.designation,
"costCentre": {
"id": item.journal.account.cost_centre.id,
"name": item.journal.account.cost_centre.name,
},
},
}
)
for item in voucher.incentives:
employee = (
db.query(Employee).filter(Employee.id == item.journal.account_id).first()
)
json_voucher["incentives"].append(
{
"employeeId": item.journal.account_id,
"name": item.journal.account.name,
"designation": employee.designation,
"department": item.journal.account.cost_centre.name,
"daysWorked": item.days_worked,
"points": item.points,
}
)
if len(json_voucher["incentives"]) > 0:
json_voucher["incentive"] = next(
x.amount for x in voucher.journals if x.account_id == Account.incentive_id()
)
for item in voucher.inventories:
text = f"{item.product.name} ({item.product.units}) {item.batch.quantity_remaining:.2f}@{item.batch.rate:.2f} from {item.batch.name.strftime('%d-%b-%Y')}"
json_voucher["inventories"].append(
{
"id": item.id,
"quantity": item.quantity,
"rate": item.rate,
"tax": item.tax,
"discount": item.discount,
"amount": item.amount,
"product": {
"id": item.product.id,
"name": item.product.full_name,
"units": item.product.units,
"price": item.rate,
},
"batch": {
"id": item.batch.id,
"name": text,
"quantityRemaining": item.batch.quantity_remaining,
"tax": item.batch.tax,
"discount": item.batch.discount,
"rate": item.batch.rate,
"product": {
"id": item.batch.product.id,
"name": item.batch.product.full_name,
},
},
}
)
images = db.query(DbImage).filter(DbImage.resource_id == voucher.id).all()
for image in images:
json_voucher["files"].append(
{
"id": image.id,
"resized": f"/db-image/{image.id}/resized",
"thumbnail": f"/db-image/{image.id}/thumbnail",
}
)
return json_voucher
def blank_voucher(info, db):
if "type" not in info:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Voucher Type is null",
)
type_ = info["type"]
if "date" not in info:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Date cannot be null",
)
json_voucher = {
"type": type_,
"date": info["date"],
"isStarred": False,
"posted": False,
"narration": "",
"journals": [],
"inventories": [],
"employeeBenefits": [],
}
if type_ == "Journal":
pass
elif type_ == "Payment":
account = None
if info and "account" in info and info["account"]:
account = (
db.query(AccountBase)
.filter(AccountBase.id == uuid.UUID(info["account"]))
.first()
)
if account is not None:
account = {"id": account.id, "name": account.name}
else:
account = AccountBase.cash_in_hand()
json_voucher["journals"].append({"account": account, "amount": 0, "debit": -1})
elif type_ == "Receipt":
account = None
if info and "account" in info and info["account"]:
account = (
db.query(AccountBase)
.filter(AccountBase.id == uuid.UUID(info["account"]))
.first()
)
if account is not None:
account = {"id": account.id, "name": account.name}
else:
account = AccountBase.cash_in_hand()
json_voucher["journals"].append({"account": account, "amount": 0, "debit": 1})
elif type_ == "Purchase":
json_voucher["vendor"] = AccountBase.local_purchase()
elif type_ == "Purchase Return":
json_voucher["journals"].append(
{"account": AccountBase.local_purchase(), "amount": 0, "debit": 1}
)
elif type_ == "Issue":
if "source" in info:
json_voucher["source"] = {"id": info["source"]}
else:
json_voucher["source"] = {"id": CostCentre.cost_centre_purchase()}
if "destination" in info:
json_voucher["destination"] = {"id": info["destination"]}
else:
json_voucher["destination"] = {"id": CostCentre.cost_centre_kitchen()}
elif type_ == "Employee Benefit":
json_voucher["employeeBenefits"] = []
elif type_ == "Incentive":
json_voucher["incentives"], json_voucher["incentive"] = incentive_employees(
info["date"], db
)
else:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f'Voucher of type "{type_}" does not exist.',
)
json_voucher["files"] = []
return json_voucher
def incentive_employees(date_, db: Session):
date_ = datetime.strptime(date_, "%d-%b-%Y")
start_date = get_first_day(date_)
finish_date = date_
details = []
employees = (
db.query(Employee)
.filter(Employee.joining_date <= finish_date)
.filter(or_(Employee.is_active, Employee.leaving_date >= start_date))
.order_by(Employee.cost_centre_id)
.order_by(Employee.designation)
.order_by(Employee.name)
.all()
)
for employee in employees:
att = (
db.query(Attendance)
.filter(Attendance.employee_id == employee.id)
.filter(Attendance.date >= start_date)
.filter(Attendance.date <= finish_date)
.filter(Attendance.is_valid == True)
.all()
)
att = sum(map(lambda x: AttendanceType.by_id(x.attendance_type).value, att))
details.append(
{
"employeeId": employee.id,
"name": employee.name,
"designation": employee.designation,
"department": employee.cost_centre.name,
"daysWorked": att,
"points": employee.points,
}
)
amount = (
db.query(func.sum(Journal.amount * Journal.debit))
.join(Journal.voucher)
.filter(Voucher.date < finish_date)
.filter(Voucher.type != VoucherType.by_name("Issue").id)
.filter(Journal.account_id == Account.incentive_id())
.scalar()
)
amount = 0 if amount is None else amount * Decimal(0.9) * -1
return details, amount
def check_voucher_lock_info(voucher_date: Optional[date], data_date: date, db: Session):
start, finish = get_lock_info(db)
if (
start is not None
and start > data_date
or voucher_date is not None
and start > voucher_date
):
raise HTTPException(
status_code=status.HTTP_423_LOCKED,
detail=f"Vouchers before {start.strftime('%d-%b-%Y')} have been locked.",
)
elif (
finish is not None
and finish < data_date
or voucher_date is not None
and finish < voucher_date
):
raise HTTPException(
status_code=status.HTTP_423_LOCKED,
detail=f"Vouchers after {finish.strftime('%d-%b-%Y')} have been locked.",
)
# elif start is not None and (start > data.date_ or start > item.date):
# raise HTTPException(
# status_code=status.HTTP_403_FORBIDDEN,
# detail=f"Vouchers before {start.strftime('%d-%b-%Y')} have been locked.",
# )
# elif finish is not None and (finish < data.date_ or finish < item.date):
# raise HTTPException(
# status_code=status.HTTP_403_FORBIDDEN,
# detail=f"Vouchers after {finish.strftime('%d-%b-%Y')} have been locked.",
# )
def check_voucher_edit_allowed(voucher: Voucher, user: UserToken):
if voucher.posted and "edit-posted-vouchers" not in user.permissions:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not allowed to edit posted vouchers",
)
elif (
voucher.user_id != user.id_
and "edit-other-user's-vouchers" not in user.permissions
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You are not allowed to edit other user's vouchers",
)