import uuid from datetime import date, datetime from decimal import Decimal from typing import List, Optional import brewman.schemas.input as schema_in import brewman.schemas.voucher as output from fastapi import APIRouter, Depends, HTTPException, Request, 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_date, get_first_day, set_date from ..db.session import SessionLocal from ..models import Account, AttendanceType, Employee from ..models.voucher import Attendance, Incentive, Journal, Voucher, VoucherType from ..schemas.auth import UserToken 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.IncentiveIn = Depends(schema_in.IncentiveIn.load_form), db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["incentive"]), ): try: item: Voucher = save(data, user, db) employees = get_employees( get_first_day(data.date_), data.date_, data.incentives, None, db ) amount = balance(data.date_, None, db) * Decimal(0.9) # 10% for Deb Dip total_points = sum(e.points * e.days_worked for e in employees) point_value = round(amount / total_points, 2) save_incentives(item, employees, point_value, 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.IncentiveIn, 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_incentives( voucher: Voucher, employees: List[schema_in.IncentiveEmployee], point_value: Decimal, db: Session, ): total_amount = 0 for item in employees: item_amount = round(item.points * item.days_worked * point_value) journal = Journal( amount=item_amount, debit=-1, account_id=item.employee_id, cost_centre_id=item.cost_centre_id, ) inc = Incentive( journal=journal, days_worked=item.days_worked, points=item.points ) voucher.journals.append(journal) voucher.incentives.append(inc) db.add(journal) db.add(inc) total_amount += item_amount sc = db.query(Account).filter(Account.id == Account.incentive_id()).first() journal = Journal( amount=total_amount, debit=1, account_id=sc.id, cost_centre_id=sc.cost_centre_id ) voucher.journals.append(journal) db.add(journal) @router.put("/{id_}") def update_route( id_: uuid.UUID, request: Request, data: schema_in.IncentiveIn = Depends(schema_in.IncentiveIn.load_form), db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["incentive"]), ): try: item: Voucher = update(id_, data, user, db) employees = get_employees( get_first_day(data.date_), data.date_, data.incentives, item.journals, db ) amount = balance(data.date_, item.id, db) * Decimal(0.9) # 10% for Deb Dip total_points = sum(e.points * e.days_worked for e in employees) point_value = round(amount / total_points, 2) update_incentives(item, employees, point_value, db) db.commit() set_date(request.session, data.date_) 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.IncentiveIn, 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.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_incentives( voucher: Voucher, employees: List[schema_in.IncentiveEmployee], point_value: Decimal, db: Session, ): total_amount = 0 for item in voucher.incentives: employee = next( e for e in employees if e.employee_id == item.journal.account_id ) item_amount = round(employee.points * employee.days_worked * point_value) item.days_worked = employee.days_worked item.points = employee.points item.journal.amount = item_amount total_amount += item_amount journal = next( j for j in voucher.journals if j.account_id == Account.incentive_id() ) journal.amount = total_amount @router.get("/{id_}", response_model=output.Voucher) def get_id( id_: uuid.UUID, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["incentive"]), ): 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, d: str = None, db: Session = Depends(get_db), user: UserToken = Security(get_user, scopes=["incentive"]), ): additional_info = {"date": d or get_date(request.session), "type": "Incentive"} return blank_voucher(additional_info, db) def get_employees( start_date: date, finish_date: date, incentives: List[schema_in.Incentive], journals: Optional[List[Journal]], db: Session, ) -> List[schema_in.IncentiveEmployee]: 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() ) check_if_employees_changed(incentives, employees, journals) 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 = 0.5 * round( sum(map(lambda x: AttendanceType.by_id(x.attendance_type).value, att)) / 0.5 ) points = next(x for x in incentives if x.employee_id == employee.id).points details.append( schema_in.IncentiveEmployee( employee_id=employee.id, cost_centre_id=employee.cost_centre_id, days_worked=att, points=points, ) ) return details def balance(date_: date, voucher_id: Optional[uuid.UUID], db: Session): amount = ( db.query(func.sum(Journal.amount * Journal.debit)) .join(Journal.voucher) .filter(Voucher.date <= date_) .filter(Voucher.type != VoucherType.by_name("Issue").id) .filter(Journal.account_id == Account.incentive_id()) ) if voucher_id is not None: amount = amount.filter(Voucher.id != voucher_id) amount = amount.scalar() return 0 if amount is None else amount * -1 def check_if_employees_changed( json: List[schema_in.Incentive], db: List[Employee], voucher: Optional[List[Journal]], ): json = set(x.employee_id for x in json) db = set(x.id for x in db) voucher = ( set(x.account_id for x in voucher if x.account_id != Account.incentive_id()) if voucher is not None else None ) if voucher is None: if len(json ^ db) != 0: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Employee missing in json data", ) else: if len(json ^ db) != 0 or len(db ^ voucher) != 0: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Employee missing in json data", )