1044 lines
37 KiB
Python
1044 lines
37 KiB
Python
"""
|
|
This module contains various functions for calculating payroll-related information for employees.
|
|
It includes functions for calculating gross pay, taxable gross pay, allowances, tax deductions,
|
|
pre-tax deductions, and post-tax deductions.
|
|
|
|
"""
|
|
|
|
import contextlib
|
|
import operator
|
|
|
|
from django.apps import apps
|
|
|
|
# from attendance.models import Attendance
|
|
from horilla.methods import get_horilla_model_class
|
|
from payroll.methods.limits import compute_limit
|
|
from payroll.models import models
|
|
from payroll.models.models import (
|
|
Allowance,
|
|
Contract,
|
|
Deduction,
|
|
LoanAccount,
|
|
MultipleCondition,
|
|
)
|
|
|
|
|
|
def return_none(a, b):
|
|
return None
|
|
|
|
|
|
operator_mapping = {
|
|
"equal": operator.eq,
|
|
"notequal": operator.ne,
|
|
"lt": operator.lt,
|
|
"gt": operator.gt,
|
|
"le": operator.le,
|
|
"ge": operator.ge,
|
|
"icontains": operator.contains,
|
|
"range": return_none,
|
|
}
|
|
filter_mapping = {
|
|
"work_type_id": {
|
|
"filter": lambda employee, allowance, start_date, end_date: {
|
|
"employee_id": employee,
|
|
"work_type_id__id": allowance.work_type_id.id,
|
|
"attendance_date__range": (start_date, end_date),
|
|
"attendance_validated": True,
|
|
}
|
|
},
|
|
"shift_id": {
|
|
"filter": lambda employee, allowance, start_date, end_date: {
|
|
"employee_id": employee,
|
|
"shift_id__id": allowance.shift_id.id,
|
|
"attendance_date__range": (start_date, end_date),
|
|
"attendance_validated": True,
|
|
}
|
|
},
|
|
"overtime": {
|
|
"filter": lambda employee, allowance, start_date, end_date: {
|
|
"employee_id": employee,
|
|
"attendance_date__range": (start_date, end_date),
|
|
"attendance_overtime_approve": True,
|
|
"attendance_validated": True,
|
|
}
|
|
},
|
|
"attendance": {
|
|
"filter": lambda employee, allowance, start_date, end_date: {
|
|
"employee_id": employee,
|
|
"attendance_date__range": (start_date, end_date),
|
|
"attendance_validated": True,
|
|
}
|
|
},
|
|
}
|
|
|
|
|
|
tets = {
|
|
"net_pay": 35140.905000000006,
|
|
"employee": 1,
|
|
"allowances": [
|
|
{
|
|
"allowance_id": 5,
|
|
"title": "Low Basic Pay Assistance",
|
|
"is_taxable": True,
|
|
"amount": 0,
|
|
},
|
|
{
|
|
"allowance_id": 13,
|
|
"title": "Bonus point Redeem for Adam Luis ",
|
|
"is_taxable": True,
|
|
"amount": 75.0,
|
|
},
|
|
{
|
|
"allowance_id": 17,
|
|
"title": "Motorcycle",
|
|
"is_taxable": True,
|
|
"amount": 5000.0,
|
|
},
|
|
{
|
|
"allowance_id": 2,
|
|
"title": "Meal Allowance",
|
|
"is_taxable": False,
|
|
"amount": 800.0,
|
|
},
|
|
],
|
|
"gross_pay": 39284.09090909091,
|
|
"contract_wage": 35000.0,
|
|
"basic_pay": 33409.09090909091,
|
|
"paid_days": 21.0,
|
|
"unpaid_days": 1.0,
|
|
"taxable_gross_pay": {"taxable_gross_pay": 35848.47727272727},
|
|
"basic_pay_deductions": [],
|
|
"gross_pay_deductions": [],
|
|
"pretax_deductions": [
|
|
{
|
|
"deduction_id": 1,
|
|
"title": "Social Security (FICA)",
|
|
"is_pretax": True,
|
|
"amount": 2435.6136363636365,
|
|
"employer_contribution_rate": 6.2,
|
|
},
|
|
{
|
|
"deduction_id": 62,
|
|
"title": "Late Come penalty",
|
|
"is_pretax": True,
|
|
"amount": 200.0,
|
|
"employer_contribution_rate": 0.0,
|
|
},
|
|
],
|
|
"post_tax_deductions": [
|
|
{
|
|
"deduction_id": 2,
|
|
"title": "Medicare tax",
|
|
"is_pretax": False,
|
|
"amount": 484.43181818181824,
|
|
"employer_contribution_rate": 1.45,
|
|
},
|
|
{
|
|
"deduction_id": 55,
|
|
"title": "ESI",
|
|
"is_pretax": False,
|
|
"amount": 0,
|
|
"employer_contribution_rate": 3.25,
|
|
},
|
|
{
|
|
"deduction_id": 73,
|
|
"title": "Test",
|
|
"is_pretax": False,
|
|
"amount": 0.0,
|
|
"employer_contribution_rate": 0.0,
|
|
},
|
|
],
|
|
"tax_deductions": [
|
|
{
|
|
"deduction_id": 75,
|
|
"title": "test tax netpay",
|
|
"is_tax": True,
|
|
"amount": 668.1818181818182,
|
|
"employer_contribution_rate": 3.0,
|
|
}
|
|
],
|
|
"net_deductions": [
|
|
{
|
|
"deduction_id": 74,
|
|
"title": "Test Netpay",
|
|
"is_pretax": False,
|
|
"amount": 354.9586363636364,
|
|
"employer_contribution_rate": 2.0,
|
|
}
|
|
],
|
|
"total_deductions": 3788.227272727273,
|
|
"loss_of_pay": 1590.909090909091,
|
|
"federal_tax": 0,
|
|
"start_date": "2024-02-01",
|
|
"end_date": "2024-02-29",
|
|
"range": "Feb 01 2024 - Feb 29 2024",
|
|
}
|
|
|
|
|
|
def dynamic_attr(obj, attribute_path):
|
|
"""
|
|
Retrieves the value of a nested attribute from a related object dynamically.
|
|
|
|
Args:
|
|
obj: The base object from which to start accessing attributes.
|
|
attribute_path (str): The path of the nested attribute to retrieve, using
|
|
double underscores ('__') to indicate relationship traversal.
|
|
|
|
Returns:
|
|
The value of the nested attribute if it exists, or None if it doesn't exist.
|
|
"""
|
|
attributes = attribute_path.split("__")
|
|
|
|
for attr in attributes:
|
|
with contextlib.suppress(Exception):
|
|
if isinstance(obj.first(), Contract):
|
|
obj = obj.filter(is_active=True).first()
|
|
|
|
obj = getattr(obj, attr, None)
|
|
if obj is None:
|
|
break
|
|
return obj
|
|
|
|
|
|
def calculate_gross_pay(*_args, **kwargs):
|
|
"""
|
|
Calculate the gross pay for an employee within a given date range.
|
|
|
|
Args:
|
|
employee: The employee object for whom to calculate the gross pay.
|
|
start_date: The start date of the period for which to calculate the gross pay.
|
|
end_date: The end date of the period for which to calculate the gross pay.
|
|
|
|
Returns:
|
|
A dictionary containing the gross pay as the "gross_pay" key.
|
|
|
|
"""
|
|
basic_pay = kwargs["basic_pay"]
|
|
total_allowance = kwargs["total_allowance"]
|
|
# basic_pay = compute_salary_on_period(employee, start_date, end_date)["basic_pay"]
|
|
gross_pay = total_allowance + basic_pay
|
|
return {
|
|
"gross_pay": gross_pay,
|
|
"basic_pay": basic_pay,
|
|
}
|
|
|
|
|
|
def calculate_taxable_gross_pay(*_args, **kwargs):
|
|
"""
|
|
Calculate the taxable gross pay for an employee within a given date range.
|
|
|
|
Args:
|
|
employee: The employee object for whom to calculate the taxable gross pay.
|
|
start_date: The start date of the period for which to calculate the taxable gross pay.
|
|
end_date: The end date of the period for which to calculate the taxable gross pay.
|
|
|
|
Returns:
|
|
A dictionary containing the taxable gross pay as the "taxable_gross_pay" key.
|
|
|
|
"""
|
|
allowances = kwargs["allowances"]
|
|
gross_pay = calculate_gross_pay(**kwargs)
|
|
gross_pay = gross_pay["gross_pay"]
|
|
pre_tax_deductions = calculate_pre_tax_deduction(**kwargs)
|
|
non_taxable_allowance_total = sum(
|
|
allowance["amount"]
|
|
for allowance in allowances["allowances"]
|
|
if not allowance["is_taxable"]
|
|
)
|
|
pretax_deduction_total = sum(
|
|
deduction["amount"]
|
|
for deduction in pre_tax_deductions["pretax_deductions"]
|
|
if deduction["is_pretax"]
|
|
)
|
|
taxable_gross_pay = gross_pay - non_taxable_allowance_total - pretax_deduction_total
|
|
return {
|
|
"taxable_gross_pay": taxable_gross_pay,
|
|
}
|
|
|
|
|
|
def calculate_allowance(**kwargs):
|
|
"""
|
|
Calculate the allowances for an employee within the specified payroll period.
|
|
|
|
Args:
|
|
employee (Employee): The employee object for which to calculate the allowances.
|
|
start_date (datetime.date): The start date of the payroll period.
|
|
end_date (datetime.date): The end date of the payroll period.
|
|
|
|
"""
|
|
employee = kwargs["employee"]
|
|
start_date = kwargs["start_date"]
|
|
end_date = kwargs["end_date"]
|
|
basic_pay = kwargs["basic_pay"]
|
|
day_dict = kwargs["day_dict"]
|
|
specific_allowances = Allowance.objects.filter(specific_employees=employee)
|
|
conditional_allowances = Allowance.objects.filter(is_condition_based=True).exclude(
|
|
exclude_employees=employee
|
|
)
|
|
active_employees = Allowance.objects.filter(include_active_employees=True).exclude(
|
|
exclude_employees=employee
|
|
)
|
|
|
|
allowances = specific_allowances | conditional_allowances | active_employees
|
|
|
|
allowances = (
|
|
allowances.exclude(one_time_date__lt=start_date)
|
|
.exclude(one_time_date__gt=end_date)
|
|
.distinct()
|
|
)
|
|
|
|
employee_allowances = []
|
|
tax_allowances = []
|
|
no_tax_allowances = []
|
|
tax_allowances_amt = []
|
|
no_tax_allowances_amt = []
|
|
# Append allowances based on condition, or unconditionally to employee
|
|
for allowance in allowances:
|
|
if allowance.is_condition_based:
|
|
conditions = list(
|
|
allowance.other_conditions.values_list("field", "condition", "value")
|
|
)
|
|
condition_field = allowance.field
|
|
condition_operator = allowance.condition
|
|
condition_value = allowance.value.lower().replace(" ", "_")
|
|
conditions.append((condition_field, condition_operator, condition_value))
|
|
applicable = True
|
|
for condition in conditions:
|
|
val = dynamic_attr(employee, condition[0])
|
|
if val is not None:
|
|
operator_func = operator_mapping.get(condition[1])
|
|
condition_value = type(val)(condition[2])
|
|
if operator_func(val, condition_value):
|
|
applicable = applicable * True
|
|
continue
|
|
else:
|
|
applicable = False
|
|
break
|
|
else:
|
|
applicable = False
|
|
break
|
|
if applicable:
|
|
employee_allowances.append(allowance)
|
|
else:
|
|
if allowance.based_on in filter_mapping:
|
|
filter_params = filter_mapping[allowance.based_on]["filter"](
|
|
employee, allowance, start_date, end_date
|
|
)
|
|
if apps.is_installed("attendance"):
|
|
Attendance = get_horilla_model_class(
|
|
app_label="attendance", model="attendance"
|
|
)
|
|
if Attendance.objects.filter(**filter_params):
|
|
employee_allowances.append(allowance)
|
|
else:
|
|
employee_allowances.append(allowance)
|
|
# Filter and append taxable allowance and not taxable allowance
|
|
for allowance in employee_allowances:
|
|
if allowance.is_taxable:
|
|
tax_allowances.append(allowance)
|
|
else:
|
|
no_tax_allowances.append(allowance)
|
|
# Find and append the amount of tax_allowances
|
|
for allowance in tax_allowances:
|
|
if allowance.is_fixed:
|
|
amount = allowance.amount
|
|
kwargs["amount"] = amount
|
|
kwargs["component"] = allowance
|
|
|
|
amount = if_condition_on(**kwargs)
|
|
tax_allowances_amt.append(amount)
|
|
else:
|
|
calculation_function = calculation_mapping.get(allowance.based_on)
|
|
amount = calculation_function(
|
|
**{
|
|
"employee": employee,
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"component": allowance,
|
|
"allowances": None,
|
|
"total_allowance": None,
|
|
"basic_pay": basic_pay,
|
|
"day_dict": day_dict,
|
|
},
|
|
)
|
|
kwargs["amount"] = amount
|
|
kwargs["component"] = allowance
|
|
amount = if_condition_on(**kwargs)
|
|
tax_allowances_amt.append(amount)
|
|
# Find and append the amount of not tax_allowances
|
|
for allowance in no_tax_allowances:
|
|
if allowance.is_fixed:
|
|
amount = allowance.amount
|
|
kwargs["amount"] = amount
|
|
kwargs["component"] = allowance
|
|
amount = if_condition_on(**kwargs)
|
|
no_tax_allowances_amt.append(amount)
|
|
|
|
else:
|
|
calculation_function = calculation_mapping.get(allowance.based_on)
|
|
amount = calculation_function(
|
|
**{
|
|
"employee": employee,
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"component": allowance,
|
|
"day_dict": day_dict,
|
|
"basic_pay": basic_pay,
|
|
}
|
|
)
|
|
kwargs["amount"] = amount
|
|
kwargs["component"] = allowance
|
|
amount = if_condition_on(**kwargs)
|
|
no_tax_allowances_amt.append(amount)
|
|
serialized_allowances = []
|
|
|
|
# Serialize taxable allowances
|
|
for allowance, amount in zip(tax_allowances, tax_allowances_amt):
|
|
serialized_allowance = {
|
|
"allowance_id": allowance.id,
|
|
"title": allowance.title,
|
|
"is_taxable": allowance.is_taxable,
|
|
"amount": amount,
|
|
}
|
|
serialized_allowances.append(serialized_allowance)
|
|
|
|
# Serialize no-taxable allowances
|
|
for allowance, amount in zip(no_tax_allowances, no_tax_allowances_amt):
|
|
serialized_allowance = {
|
|
"allowance_id": allowance.id,
|
|
"title": allowance.title,
|
|
"is_taxable": allowance.is_taxable,
|
|
"amount": amount,
|
|
}
|
|
serialized_allowances.append(serialized_allowance)
|
|
return {"allowances": serialized_allowances}
|
|
|
|
|
|
def calculate_tax_deduction(*_args, **kwargs):
|
|
"""
|
|
Calculates the tax deductions for the specified employee within the given date range.
|
|
|
|
Args:
|
|
employee (Employee): The employee for whom the tax deductions are being calculated.
|
|
start_date (date): The start date of the tax deduction period.
|
|
end_date (date): The end date of the tax deduction period.
|
|
allowances (dict): Dictionary containing the calculated allowances.
|
|
total_allowance (float): The total amount of allowances.
|
|
basic_pay (float): The basic pay amount.
|
|
day_dict (dict): Dictionary containing working day details.
|
|
|
|
Returns:
|
|
dict: A dictionary containing the serialized tax deductions.
|
|
"""
|
|
employee = kwargs["employee"]
|
|
start_date = kwargs["start_date"]
|
|
end_date = kwargs["end_date"]
|
|
specific_deductions = models.Deduction.objects.filter(
|
|
specific_employees=employee, is_pretax=False, is_tax=True
|
|
)
|
|
active_employee_deduction = models.Deduction.objects.filter(
|
|
include_active_employees=True, is_pretax=False, is_tax=True
|
|
).exclude(exclude_employees=employee)
|
|
deductions = specific_deductions | active_employee_deduction
|
|
deductions = (
|
|
deductions.exclude(one_time_date__lt=start_date)
|
|
.exclude(one_time_date__gt=end_date)
|
|
.exclude(update_compensation__isnull=False)
|
|
)
|
|
deductions_amt = []
|
|
serialized_deductions = []
|
|
for deduction in deductions:
|
|
calculation_function = calculation_mapping.get(deduction.based_on)
|
|
amount = calculation_function(
|
|
**{
|
|
"employee": employee,
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"component": deduction,
|
|
"allowances": kwargs["allowances"],
|
|
"total_allowance": kwargs["total_allowance"],
|
|
"basic_pay": kwargs["basic_pay"],
|
|
"day_dict": kwargs["day_dict"],
|
|
}
|
|
)
|
|
kwargs["amount"] = amount
|
|
kwargs["component"] = deduction
|
|
amount = if_condition_on(**kwargs)
|
|
deductions_amt.append(amount)
|
|
for deduction, amount in zip(deductions, deductions_amt):
|
|
serialized_deduction = {
|
|
"deduction_id": deduction.id,
|
|
"title": deduction.title,
|
|
"is_tax": deduction.is_tax,
|
|
"amount": amount,
|
|
"employer_contribution_rate": deduction.employer_rate,
|
|
}
|
|
serialized_deductions.append(serialized_deduction)
|
|
return {"tax_deductions": serialized_deductions}
|
|
|
|
|
|
def calculate_pre_tax_deduction(*_args, **kwargs):
|
|
"""
|
|
This function retrieves pre-tax deductions applicable to the employee and calculates
|
|
their amounts
|
|
|
|
Args:
|
|
employee: The employee object for whom to calculate the pre-tax deductions.
|
|
start_date: The start date of the period for which to calculate the pre-tax deductions.
|
|
end_date: The end date of the period for which to calculate the pre-tax deductions.
|
|
|
|
Returns:
|
|
A dictionary containing the pre-tax deductions as the "pretax_deductions" key.
|
|
|
|
"""
|
|
employee = kwargs["employee"]
|
|
start_date = kwargs["start_date"]
|
|
end_date = kwargs["end_date"]
|
|
|
|
specific_deductions = models.Deduction.objects.filter(
|
|
specific_employees=employee, is_pretax=True, is_tax=False
|
|
)
|
|
conditional_deduction = models.Deduction.objects.filter(
|
|
is_condition_based=True, is_pretax=True, is_tax=False
|
|
).exclude(exclude_employees=employee)
|
|
active_employee_deduction = models.Deduction.objects.filter(
|
|
include_active_employees=True, is_pretax=True, is_tax=False
|
|
).exclude(exclude_employees=employee)
|
|
|
|
deductions = specific_deductions | conditional_deduction | active_employee_deduction
|
|
deductions = (
|
|
deductions.exclude(one_time_date__lt=start_date)
|
|
.exclude(one_time_date__gt=end_date)
|
|
.exclude(update_compensation__isnull=False)
|
|
)
|
|
# Installment deductions
|
|
installments = deductions.filter(is_installment=True)
|
|
|
|
pre_tax_deductions = []
|
|
pre_tax_deductions_amt = []
|
|
serialized_deductions = []
|
|
|
|
for deduction in deductions:
|
|
if deduction.is_condition_based:
|
|
conditions = list(
|
|
deduction.other_conditions.values_list("field", "condition", "value")
|
|
)
|
|
condition_field = deduction.field
|
|
condition_operator = deduction.condition
|
|
condition_value = deduction.value.lower().replace(" ", "_")
|
|
conditions.append((condition_field, condition_operator, condition_value))
|
|
operator_func = operator_mapping.get(condition_operator)
|
|
applicable = True
|
|
for condition in conditions:
|
|
val = dynamic_attr(employee, condition[0])
|
|
if val is not None:
|
|
operator_func = operator_mapping.get(condition[1])
|
|
condition_value = type(val)(condition[2])
|
|
if operator_func(val, condition_value):
|
|
applicable = applicable * True
|
|
continue
|
|
else:
|
|
applicable = False
|
|
break
|
|
else:
|
|
applicable = False
|
|
break
|
|
if applicable:
|
|
pre_tax_deductions.append(deduction)
|
|
else:
|
|
pre_tax_deductions.append(deduction)
|
|
|
|
for deduction in pre_tax_deductions:
|
|
if deduction.is_fixed:
|
|
kwargs["amount"] = deduction.amount
|
|
kwargs["component"] = deduction
|
|
pre_tax_deductions_amt.append(if_condition_on(**kwargs))
|
|
else:
|
|
calculation_function = calculation_mapping.get(deduction.based_on)
|
|
amount = calculation_function(
|
|
**{
|
|
"employee": employee,
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"component": deduction,
|
|
"allowances": kwargs["allowances"],
|
|
"total_allowance": kwargs["total_allowance"],
|
|
"basic_pay": kwargs["basic_pay"],
|
|
"day_dict": kwargs["day_dict"],
|
|
}
|
|
)
|
|
kwargs["amount"] = amount
|
|
kwargs["component"] = deduction
|
|
pre_tax_deductions_amt.append(if_condition_on(**kwargs))
|
|
for deduction, amount in zip(pre_tax_deductions, pre_tax_deductions_amt):
|
|
serialized_deduction = {
|
|
"deduction_id": deduction.id,
|
|
"title": deduction.title,
|
|
"is_pretax": deduction.is_pretax,
|
|
"amount": amount,
|
|
"employer_contribution_rate": deduction.employer_rate,
|
|
}
|
|
serialized_deductions.append(serialized_deduction)
|
|
return {"pretax_deductions": serialized_deductions, "installments": installments}
|
|
|
|
|
|
def calculate_post_tax_deduction(*_args, **kwargs):
|
|
"""
|
|
This function retrieves post-tax deductions applicable to the employee and calculates
|
|
their amounts
|
|
|
|
Args:
|
|
employee: The employee object for whom to calculate the pre-tax deductions.
|
|
start_date: The start date of the period for which to calculate the pre-tax deductions.
|
|
end_date: The end date of the period for which to calculate the pre-tax deductions.
|
|
|
|
Returns:
|
|
A dictionary containing the pre-tax deductions as the "post_tax_deductions" key.
|
|
|
|
"""
|
|
employee = kwargs["employee"]
|
|
start_date = kwargs["start_date"]
|
|
end_date = kwargs["end_date"]
|
|
allowances = kwargs["allowances"]
|
|
total_allowance = kwargs["total_allowance"]
|
|
basic_pay = kwargs["basic_pay"]
|
|
day_dict = kwargs["day_dict"]
|
|
specific_deductions = models.Deduction.objects.filter(
|
|
specific_employees=employee, is_pretax=False, is_tax=False
|
|
)
|
|
conditional_deduction = models.Deduction.objects.filter(
|
|
is_condition_based=True, is_pretax=False, is_tax=False
|
|
).exclude(exclude_employees=employee)
|
|
active_employee_deduction = models.Deduction.objects.filter(
|
|
include_active_employees=True, is_pretax=False, is_tax=False
|
|
).exclude(exclude_employees=employee)
|
|
deductions = specific_deductions | conditional_deduction | active_employee_deduction
|
|
deductions = (
|
|
deductions.exclude(one_time_date__lt=start_date)
|
|
.exclude(one_time_date__gt=end_date)
|
|
.exclude(update_compensation__isnull=False)
|
|
)
|
|
# Installment deductions
|
|
installments = deductions.filter(is_installment=True)
|
|
|
|
post_tax_deductions = []
|
|
post_tax_deductions_amt = []
|
|
serialized_deductions = []
|
|
serialized_net_pay_deductions = []
|
|
|
|
for deduction in deductions:
|
|
if deduction.is_condition_based:
|
|
condition_field = deduction.field
|
|
condition_operator = deduction.condition
|
|
condition_value = deduction.value.lower().replace(" ", "_")
|
|
employee_value = dynamic_attr(employee, condition_field)
|
|
operator_func = operator_mapping.get(condition_operator)
|
|
if employee_value is not None:
|
|
condition_value = type(employee_value)(condition_value)
|
|
if operator_func(employee_value, condition_value):
|
|
post_tax_deductions.append(deduction)
|
|
else:
|
|
post_tax_deductions.append(deduction)
|
|
for deduction in post_tax_deductions:
|
|
if deduction.is_fixed:
|
|
amount = deduction.amount
|
|
kwargs["amount"] = amount
|
|
kwargs["component"] = deduction
|
|
amount = if_condition_on(**kwargs)
|
|
post_tax_deductions_amt.append(amount)
|
|
else:
|
|
if deduction.based_on != "net_pay":
|
|
calculation_function = calculation_mapping.get(deduction.based_on)
|
|
amount = calculation_function(
|
|
**{
|
|
"employee": employee,
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"component": deduction,
|
|
"allowances": allowances,
|
|
"total_allowance": total_allowance,
|
|
"basic_pay": basic_pay,
|
|
"day_dict": day_dict,
|
|
}
|
|
)
|
|
kwargs["amount"] = amount
|
|
kwargs["component"] = deduction
|
|
amount = if_condition_on(**kwargs)
|
|
post_tax_deductions_amt.append(amount)
|
|
|
|
for deduction, amount in zip(post_tax_deductions, post_tax_deductions_amt):
|
|
serialized_deduction = {
|
|
"deduction_id": deduction.id,
|
|
"title": deduction.title,
|
|
"is_pretax": deduction.is_pretax,
|
|
"amount": amount,
|
|
"employer_contribution_rate": deduction.employer_rate,
|
|
}
|
|
serialized_deductions.append(serialized_deduction)
|
|
for deduction in post_tax_deductions:
|
|
if deduction.based_on == "net_pay":
|
|
serialized_net_pay_deduction = {"deduction": deduction}
|
|
serialized_net_pay_deductions.append(serialized_net_pay_deduction)
|
|
return {
|
|
"post_tax_deductions": serialized_deductions,
|
|
"net_pay_deduction": serialized_net_pay_deductions,
|
|
"installments": installments,
|
|
}
|
|
|
|
|
|
def calculate_net_pay_deduction(net_pay, net_pay_deductions, **kwargs):
|
|
"""
|
|
Calculates the deductions based on the net pay amount.
|
|
|
|
Args:
|
|
net_pay (float): The net pay amount.
|
|
net_pay_deductions (list): List of net pay deductions.
|
|
day_dict (dict): Dictionary containing working day details.
|
|
|
|
Returns:
|
|
dict: A dictionary containing the serialized deductions and deduction amount.
|
|
"""
|
|
day_dict = kwargs["day_dict"]
|
|
serialized_net_pay_deductions = []
|
|
deductions = [item["deduction"] for item in net_pay_deductions]
|
|
deduction_amt = []
|
|
for deduction in deductions:
|
|
amount = calculate_based_on_net_pay(deduction, net_pay, day_dict)
|
|
kwargs["amount"] = amount
|
|
kwargs["component"] = deduction
|
|
amount = if_condition_on(**kwargs)
|
|
deduction_amt.append(amount)
|
|
net_deduction = 0
|
|
for deduction, amount in zip(deductions, deduction_amt):
|
|
serialized_deduction = {
|
|
"deduction_id": deduction.id,
|
|
"title": deduction.title,
|
|
"is_pretax": deduction.is_pretax,
|
|
"amount": amount,
|
|
"employer_contribution_rate": deduction.employer_rate,
|
|
}
|
|
net_deduction = amount + net_deduction
|
|
serialized_net_pay_deductions.append(serialized_deduction)
|
|
return {
|
|
"net_pay_deductions": serialized_net_pay_deductions,
|
|
"net_deduction": net_deduction,
|
|
}
|
|
|
|
|
|
def if_condition_on(*_args, **kwargs):
|
|
"""
|
|
This method is used to check the allowance or deduction through the the conditions
|
|
|
|
Args:
|
|
employee (obj): Employee instance
|
|
amount (float): calculated amount of the component
|
|
component (obj): Allowance or Deduction instance
|
|
start_date (obj): Start date of the period
|
|
end_date (obj): End date of the period
|
|
|
|
Returns:
|
|
_type_: _description_
|
|
"""
|
|
component = kwargs["component"]
|
|
basic_pay = kwargs["basic_pay"]
|
|
amount = kwargs["amount"]
|
|
gross_pay = 0
|
|
amount = float(amount)
|
|
if not isinstance(component, Allowance):
|
|
gross_pay = calculate_gross_pay(
|
|
**kwargs,
|
|
)["gross_pay"]
|
|
condition_value = basic_pay if component.if_choice == "basic_pay" else gross_pay
|
|
if component.if_condition == "range":
|
|
if not component.start_range <= condition_value <= component.end_range:
|
|
amount = 0
|
|
else:
|
|
operator_func = operator_mapping.get(component.if_condition)
|
|
if not operator_func(condition_value, component.if_amount):
|
|
amount = 0
|
|
return amount
|
|
|
|
|
|
def calculate_based_on_basic_pay(*_args, **kwargs):
|
|
"""
|
|
Calculate the amount of an allowance or deduction based on the employee's
|
|
basic pay with rate provided in the allowance or deduction object
|
|
|
|
Args:
|
|
employee (Employee): The employee object for whom to calculate the amount.
|
|
start_date (datetime.date): The start date of the period for which to calculate the amount.
|
|
end_date (datetime.date): The end date of the period for which to calculate the amount.
|
|
component (Component): The allowance or deduction object that defines the rate or percentage
|
|
to apply.
|
|
|
|
Returns:
|
|
The calculated allowance or deduction amount based on the employee's basic pay.
|
|
|
|
"""
|
|
component = kwargs["component"]
|
|
basic_pay = kwargs["basic_pay"]
|
|
day_dict = kwargs["day_dict"]
|
|
rate = component.rate
|
|
amount = basic_pay * rate / 100
|
|
amount = compute_limit(component, amount, day_dict)
|
|
|
|
return amount
|
|
|
|
|
|
def calculate_based_on_gross_pay(*_args, **kwargs):
|
|
"""
|
|
Calculate the amount of an allowance or deduction based on the employee's gross pay with rate
|
|
provided in the allowance or deduction object
|
|
|
|
Args:
|
|
employee (Employee): The employee object for whom to calculate the amount.
|
|
start_date (datetime.date): The start date of the period for which to calculate the amount.
|
|
end_date (datetime.date): The end date of the period for which to calculate the amount.
|
|
component (Component): The allowance or deduction object that defines the rate or percentage
|
|
to apply.
|
|
|
|
Returns:+-
|
|
The calculated allowance or deduction amount based on the employee's gross pay.
|
|
|
|
"""
|
|
|
|
component = kwargs["component"]
|
|
gross_pay = calculate_gross_pay(**kwargs)
|
|
rate = component.rate
|
|
amount = gross_pay["gross_pay"] * rate / 100
|
|
return amount
|
|
|
|
|
|
def calculate_based_on_taxable_gross_pay(*_args, **kwargs):
|
|
"""
|
|
Calculate the amount of an allowance or deduction based on the employee's taxable gross pay with
|
|
rate provided in the allowance or deduction object
|
|
|
|
Args:
|
|
employee (Employee): The employee object for whom to calculate the amount.
|
|
start_date (datetime.date): The start date of the period for which to calculate the amount.
|
|
end_date (datetime.date): The end date of the period for which to calculate the amount.
|
|
component (Component): The allowance or deduction object that defines the rate or percentage
|
|
to apply.
|
|
|
|
Returns:
|
|
The calculated component amount based on the employee's taxable gross pay.
|
|
|
|
"""
|
|
component = kwargs["component"]
|
|
taxable_gross_pay = calculate_taxable_gross_pay(**kwargs)
|
|
taxable_gross_pay = taxable_gross_pay["taxable_gross_pay"]
|
|
rate = component.rate
|
|
amount = taxable_gross_pay * rate / 100
|
|
return amount
|
|
|
|
|
|
def calculate_based_on_net_pay(component, net_pay, day_dict):
|
|
"""
|
|
Calculates the amount of an allowance or deduction based on the net pay of an employee.
|
|
|
|
Args:
|
|
component (Allowance or Deduction): The allowance or deduction object.
|
|
net_pay (float): The net pay of the employee.
|
|
day_dict (dict): Dictionary containing working day details.
|
|
|
|
Returns:
|
|
float: The calculated amount of the component based on the net pay.
|
|
"""
|
|
rate = float(component.rate)
|
|
amount = net_pay * rate / 100
|
|
amount = compute_limit(component, amount, day_dict)
|
|
|
|
amount = compute_limit(component, amount, day_dict)
|
|
return amount
|
|
|
|
|
|
def calculate_based_on_attendance(*_args, **kwargs):
|
|
"""
|
|
Calculates the amount of an allowance or deduction based on the attendance of an employee.
|
|
|
|
Args:
|
|
employee (Employee): The employee for whom the attendance is being calculated.
|
|
start_date (date): The start date of the attendance period.
|
|
end_date (date): The end date of the attendance period.
|
|
component (Allowance or Deduction): The allowance or deduction object.
|
|
day_dict (dict): Dictionary containing working day details.
|
|
|
|
Returns:
|
|
float: The calculated amount of the component based on the attendance.
|
|
"""
|
|
|
|
if not apps.is_installed("attendance"):
|
|
return 0
|
|
|
|
Attendance = get_horilla_model_class(app_label="attendance", model="attendance")
|
|
employee = kwargs["employee"]
|
|
start_date = kwargs["start_date"]
|
|
end_date = kwargs["end_date"]
|
|
component = kwargs["component"]
|
|
day_dict = kwargs["day_dict"]
|
|
|
|
count = Attendance.objects.filter(
|
|
employee_id=employee,
|
|
attendance_date__range=(start_date, end_date),
|
|
attendance_validated=True,
|
|
).count()
|
|
amount = count * component.per_attendance_fixed_amount
|
|
|
|
amount = compute_limit(component, amount, day_dict)
|
|
|
|
return amount
|
|
|
|
|
|
def calculate_based_on_shift(*_args, **kwargs):
|
|
"""
|
|
Calculates the amount of an allowance or deduction based on the employee's shift attendance.
|
|
|
|
Args:
|
|
employee (Employee): The employee for whom the shift attendance is being calculated.
|
|
start_date (date): The start date of the attendance period.
|
|
end_date (date): The end date of the attendance period.
|
|
component (Allowance or Deduction): The allowance or deduction object.
|
|
day_dict (dict): Dictionary containing working day details.
|
|
|
|
Returns:
|
|
float: The calculated amount of the component based on the shift attendance.
|
|
"""
|
|
if not apps.is_installed("attendance"):
|
|
return 0
|
|
|
|
Attendance = get_horilla_model_class(app_label="attendance", model="attendance")
|
|
employee = kwargs["employee"]
|
|
start_date = kwargs["start_date"]
|
|
end_date = kwargs["end_date"]
|
|
component = kwargs["component"]
|
|
day_dict = kwargs["day_dict"]
|
|
|
|
shift_id = component.shift_id.id
|
|
count = Attendance.objects.filter(
|
|
employee_id=employee,
|
|
shift_id=shift_id,
|
|
attendance_date__range=(start_date, end_date),
|
|
attendance_validated=True,
|
|
).count()
|
|
amount = count * component.shift_per_attendance_amount
|
|
|
|
amount = compute_limit(component, amount, day_dict)
|
|
return amount
|
|
|
|
|
|
def calculate_based_on_overtime(*_args, **kwargs):
|
|
"""
|
|
Calculates the amount of an allowance or deduction based on employee's overtime hours.
|
|
|
|
Args:
|
|
employee (Employee): The employee for whom the overtime is being calculated.
|
|
start_date (date): The start date of the overtime period.
|
|
end_date (date): The end date of the overtime period.
|
|
component (Allowance or Deduction): The allowance or deduction object.
|
|
day_dict (dict): Dictionary containing working day details.
|
|
|
|
Returns:
|
|
float: The calculated amount of the allowance or deduction based on the overtime hours.
|
|
"""
|
|
if not apps.is_installed("attendance"):
|
|
return 0
|
|
|
|
Attendance = get_horilla_model_class(app_label="attendance", model="attendance")
|
|
employee = kwargs["employee"]
|
|
start_date = kwargs["start_date"]
|
|
end_date = kwargs["end_date"]
|
|
component = kwargs["component"]
|
|
day_dict = kwargs["day_dict"]
|
|
|
|
attendances = Attendance.objects.filter(
|
|
employee_id=employee,
|
|
attendance_date__range=(start_date, end_date),
|
|
attendance_overtime_approve=True,
|
|
)
|
|
overtime = sum(attendance.overtime_second for attendance in attendances)
|
|
amount_per_hour = component.amount_per_one_hr
|
|
amount_per_second = amount_per_hour / (60 * 60)
|
|
amount = overtime * amount_per_second
|
|
amount = round(amount, 2)
|
|
|
|
amount = compute_limit(component, amount, day_dict)
|
|
|
|
return amount
|
|
|
|
|
|
def calculate_based_on_work_type(*_args, **kwargs):
|
|
"""
|
|
Calculates the amount of an allowance or deduction based on the employee's
|
|
attendance with a specific work type.
|
|
|
|
Args:
|
|
employee (Employee): The employee for whom the attendance is being considered.
|
|
start_date (date): The start date of the attendance period.
|
|
end_date (date): The end date of the attendance period.
|
|
component (Allowance or Deduction): The allowance or deduction object.
|
|
day_dict (dict): Dictionary containing working day details.
|
|
|
|
Returns:
|
|
float: The calculated amount of the allowance or deduction based on the
|
|
attendance with the specified work type.
|
|
"""
|
|
if not apps.is_installed("attendance"):
|
|
return 0
|
|
|
|
Attendance = get_horilla_model_class(app_label="attendance", model="attendance")
|
|
employee = kwargs["employee"]
|
|
start_date = kwargs["start_date"]
|
|
end_date = kwargs["end_date"]
|
|
component = kwargs["component"]
|
|
day_dict = kwargs["day_dict"]
|
|
|
|
work_type_id = component.work_type_id.id
|
|
count = Attendance.objects.filter(
|
|
employee_id=employee,
|
|
work_type_id=work_type_id,
|
|
attendance_date__range=(start_date, end_date),
|
|
attendance_validated=True,
|
|
).count()
|
|
amount = count * component.work_type_per_attendance_amount
|
|
|
|
amount = compute_limit(component, amount, day_dict)
|
|
|
|
return amount
|
|
|
|
|
|
def calculate_based_on_children(*_args, **kwargs):
|
|
"""
|
|
Calculates the amount of an allowance or deduction based on the attendance of an employee.
|
|
|
|
Args:
|
|
employee (Employee): The employee for whom the attendance is being calculated.
|
|
start_date (date): The start date of the attendance period.
|
|
end_date (date): The end date of the attendance period.
|
|
component (Allowance or Deduction): The allowance or deduction object.
|
|
day_dict (dict): Dictionary containing working day details.
|
|
|
|
Returns:
|
|
float: The calculated amount of the component based on the attendance.
|
|
"""
|
|
employee = kwargs["employee"]
|
|
component = kwargs["component"]
|
|
day_dict = kwargs["day_dict"]
|
|
count = employee.children
|
|
amount = count * component.per_children_fixed_amount
|
|
amount = compute_limit(component, amount, day_dict)
|
|
return amount
|
|
|
|
|
|
calculation_mapping = {
|
|
"basic_pay": calculate_based_on_basic_pay,
|
|
"gross_pay": calculate_based_on_gross_pay,
|
|
"taxable_gross_pay": calculate_based_on_taxable_gross_pay,
|
|
"net_pay": calculate_based_on_net_pay,
|
|
"attendance": calculate_based_on_attendance,
|
|
"shift_id": calculate_based_on_shift,
|
|
"overtime": calculate_based_on_overtime,
|
|
"work_type_id": calculate_based_on_work_type,
|
|
"children": calculate_based_on_children,
|
|
}
|