brewman/brewman/routers/voucher.py

402 lines
15 KiB
Python

import uuid
from typing import Optional
from datetime import datetime, date
from decimal import Decimal
from fastapi import (
APIRouter,
HTTPException,
status,
Depends,
Security,
)
from sqlalchemy import func, or_
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from brewman.models.master import (
AccountBase,
CostCentre,
Employee,
AttendanceType,
Account,
)
from brewman.models.voucher import (
VoucherType,
Inventory,
DbImage,
Attendance,
Journal,
)
from brewman.routers import get_lock_info
from brewman.core.session import get_first_day
from ..schemas.auth import UserToken
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.voucher import Voucher
import brewman.schemas.voucher as output
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", "purchase"]:
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.batch.quantity_remaining:
raise ValueError(
f"Quantity remaining for {item.product.name} is less than issued, hence 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 ValueError(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 ValueError("Voucher Type is null")
type_ = info["type"]
if "date" not in info:
raise ValueError("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 ValueError(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",
)