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", )