From fe5626ab0f2d8e96241de87a82fd96d635a6d846 Mon Sep 17 00:00:00 2001 From: Horilla Date: Wed, 14 Aug 2024 12:16:09 +0530 Subject: [PATCH] [ADD] PAYROLL: Python codeble federal tax feature --- payroll/forms/tax_forms.py | 20 ++++ payroll/methods/federal_tax.py | 101 ++++++++++++++++ payroll/methods/tax_calc.py | 110 ++++++++++-------- payroll/models/models.py | 3 +- .../payroll/tax/filing_status_creation.html | 46 ++++++++ .../payroll/tax/filing_status_list.html | 2 +- .../payroll/tax/filing_status_view.html | 1 + .../payroll/tax/tax_bracket_view.html | 52 +++++++++ payroll/urls/tax_urls.py | 1 + payroll/views/tax_views.py | 21 +++- 10 files changed, 303 insertions(+), 54 deletions(-) create mode 100644 payroll/methods/federal_tax.py diff --git a/payroll/forms/tax_forms.py b/payroll/forms/tax_forms.py index e5c9acfa2..5de4d5c5b 100644 --- a/payroll/forms/tax_forms.py +++ b/payroll/forms/tax_forms.py @@ -17,6 +17,7 @@ from django.utils.translation import gettext_lazy as _ from base.methods import reload_queryset from horilla import horilla_middlewares +from payroll.methods import federal_tax from payroll.models.models import FilingStatus from payroll.models.tax_models import TaxBracket @@ -95,6 +96,25 @@ class FilingStatusForm(ModelForm): fields = "__all__" exclude = ["is_active"] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + attrs: dict = self.fields["use_py"].widget.attrs + attrs[ + "onchange" + ] = """ + if($(this).is(':checked')){ + $('#oc-editor').show(); + }else{ + $('#oc-editor').hide(); + } + """ + + if self.instance.pk is None: + self.instance.python_code = federal_tax.CODE + else: + del self.fields["use_py"] + del self.fields["python_code"] + class TaxBracketForm(ModelForm): """Form for creating and updating tax bracket.""" diff --git a/payroll/methods/federal_tax.py b/payroll/methods/federal_tax.py new file mode 100644 index 000000000..493f8eb4b --- /dev/null +++ b/payroll/methods/federal_tax.py @@ -0,0 +1,101 @@ +""" +federal_tax.py + +""" + +CODE = ''' +""" +federal_tax.py +""" + +YEARLY_TAXABLE_INCOME = 189000.52 + + +def calcluate_federal_tax(yearly_income: int, **kwargs) -> float: + """ + Federal Tax calculation method + + yearly_income: The early converted 'based on' amount + + eg: yearly_income-> 189000 then taxable_amount-> 39312.0 (yearly) + """ + + def filter_brackets(brackets: list) -> list: + """ + This method to filter out the actual brackets/brackets range + """ + # brackets that contains actual bracket range, calculated_rate, and diff amount + filterd_brackets = [] + for bracket in brackets: + if bracket["max"] > bracket["min"]: + + # bracket: {'rate': 12, 'min': 11000, 'max': 44725} + + # finding diff amount and adding to the bracket + bracket["diff"] = bracket["max"] - bracket["min"] + # find bracket rate from the difference and adding to bracket + bracket["calculated_rate"] = (bracket["rate"] / 100) * bracket["diff"] + + # bracket: {'rate': 12, 'min': 11000, 'max': 44725, 'diff': 33725, 'calculated_rate': 4047.0} + + filterd_brackets.append(bracket) + continue + # returning valid filtered brackets + return filterd_brackets + # returning valid filtered brackets + return filterd_brackets + + # filter_brackets method/function will sort out the brackets + + # for example for the 189000 yearly income come in the 32% group, + # so the final the max considered as min(231250,189000) which is 189000 + brackets = [ + {"rate": 10, "min": 0, "max": min(11000, yearly_income)}, + {"rate": 12, "min": 11000, "max": min(44725, yearly_income)}, + {"rate": 22, "min": 44725, "max": min(95375, yearly_income)}, + {"rate": 24, "min": 95375, "max": min(182100, yearly_income)}, + {"rate": 32, "min": 182100, "max": min(231250, yearly_income)}, + {"rate": 35, "min": 231250, "max": min(578125, yearly_income)}, + {"rate": 37, "min": 578125, "max": max(578125, yearly_income)}, + ] + + # filtering the brackets to actual range + brackets = filter_brackets(brackets=brackets) + + # finding yearly taxable amount + taxable_amount = sum(bracket["calculated_rate"] for bracket in brackets) + + """ + use formated_result method to print the table + """ + # formated_result(brackets=brackets, taxable_amount=taxable_amount) + + # returning the taxable amount later on the yearly taxable amount- + # is converted to daily and calculate federal tax for the total days between the + # Payslip period + return taxable_amount + + +def formated_result(brackets: dict, taxable_amount: float) -> None: + """ + It will print the brackets such a formated way + """ + col_width = 7 + print("----------------------Brackets----------------------") + print( + f"|{'Rate':<{col_width}} |{'Min':<{col_width}} |{'Max':<{col_width}} |{'Taxable':<{col_width}} |{'Bracket Tax':<{col_width}} |" + ) + + for bracket in brackets: + print( + f"|{bracket['rate']:<{col_width}}% |{bracket['min']:<{col_width}} | {bracket['max']:<{col_width}} | {bracket['diff']:<{col_width}} | {round(bracket['calculated_rate'],2):<{col_width + 3}} |" + ) + + print(f"| YEARLY TAXABLE INCOME | {taxable_amount} |") + print("----------------------------------------------------") + + +month_taxable = calcluate_federal_tax(YEARLY_TAXABLE_INCOME) +print("YEARLY TAXABLE AMOUNT", month_taxable) + +''' diff --git a/payroll/methods/tax_calc.py b/payroll/methods/tax_calc.py index f050746cf..5a4afb0b5 100644 --- a/payroll/methods/tax_calc.py +++ b/payroll/methods/tax_calc.py @@ -6,6 +6,7 @@ based on their contract details and income information. """ import datetime +import logging from payroll.methods.payslip_calc import ( calculate_gross_pay, @@ -14,6 +15,8 @@ from payroll.methods.payslip_calc import ( from payroll.models.models import Contract from payroll.models.tax_models import TaxBracket +logger = logging.getLogger(__name__) + def calculate_taxable_amount(**kwargs): """Calculate the taxable amount for a given employee within a specific period. @@ -39,52 +42,63 @@ def calculate_taxable_amount(**kwargs): ).first() filing = contract.filing_status federal_tax_for_period = 0 - if filing is not None: - based = filing.based_on - num_days = (end_date - start_date).days + 1 - calculation_functions = { - "taxable_gross_pay": calculate_taxable_gross_pay, - "gross_pay": calculate_gross_pay, - } - if based in calculation_functions: - calculation_function = calculation_functions[based] - income = calculation_function(**kwargs) - income = float(income[based]) - else: - income = float(basic_pay) - year = end_date.year - check_start_date = datetime.date(year, 1, 1) - check_end_date = datetime.date(year, 12, 31) - total_days = (check_end_date - check_start_date).days + 1 - yearly_income = income / num_days * total_days - yearly_income = round(yearly_income, 2) - tax_brackets = TaxBracket.objects.filter(filing_status_id=filing).order_by( - "min_income" - ) - federal_tax = 0 - remaining_income = yearly_income - if tax_brackets.exists(): - if ( - tax_brackets.first().min_income - <= yearly_income - <= tax_brackets.first().max_income - ): - tax_rate = tax_brackets.first().tax_rate - tax_amount = yearly_income * tax_rate / 100 - federal_tax += tax_amount - elif tax_brackets.first().min_income <= yearly_income: - for tax_bracket in tax_brackets: - min_income = tax_bracket.min_income - max_income = tax_bracket.max_income - tax_rate = tax_bracket.tax_rate - if remaining_income <= 0: - break - taxable_amount = min(remaining_income, max_income - min_income) - tax_amount = taxable_amount * tax_rate / 100 - federal_tax += tax_amount - remaining_income -= taxable_amount - daily_federal_tax = federal_tax / total_days - federal_tax_for_period = daily_federal_tax * num_days - else: - federal_tax_for_period = 0 + tax_brackets = TaxBracket.objects.filter(filing_status_id=filing).order_by( + "min_income" + ) + num_days = (end_date - start_date).days + 1 + calculation_functions = { + "taxable_gross_pay": calculate_taxable_gross_pay, + "gross_pay": calculate_gross_pay, + } + based = filing.based_on + if based in calculation_functions: + calculation_function = calculation_functions[based] + income = calculation_function(**kwargs) + income = float(income[based]) + else: + income = float(basic_pay) + + year = end_date.year + check_start_date = datetime.date(year, 1, 1) + check_end_date = datetime.date(year, 12, 31) + total_days = (check_end_date - check_start_date).days + 1 + yearly_income = income / num_days * total_days + yearly_income = round(yearly_income, 2) + federal_tax = 0 + if filing is not None and not filing.use_py: + brackets = [ + { + "rate": item["tax_rate"], + "min": item["min_income"], + "max": min(item["max_income"], yearly_income), + } + for item in tax_brackets.values("tax_rate", "min_income", "max_income") + ] + filterd_brackets = [] + for bracket in brackets: + if bracket["max"] > bracket["min"]: + bracket["diff"] = bracket["max"] - bracket["min"] + bracket["calculated_rate"] = (bracket["rate"] / 100) * bracket["diff"] + filterd_brackets.append(bracket) + continue + break + print(filterd_brackets) + federal_tax = sum(bracket["calculated_rate"] for bracket in filterd_brackets) + + elif filing.use_py: + code = filing.python_code + code = code.replace("print(", "pass#print(") + code = code.replace(" formated_result(", "# formated_result(") + local_vars = {} + exec(code, {}, local_vars) + try: + federal_tax = local_vars["calcluate_federal_tax"](yearly_income) + except Exception as e: + logger.error(e) + + federal_tax_for_period = 0 + if federal_tax and (tax_brackets.exists() or filing.use_py): + daily_federal_tax = federal_tax / total_days + federal_tax_for_period = daily_federal_tax * num_days + return federal_tax_for_period diff --git a/payroll/models/models.py b/payroll/models/models.py index 0ddfa283b..4e7dbceed 100644 --- a/payroll/models/models.py +++ b/payroll/models/models.py @@ -30,7 +30,6 @@ from base.models import ( from employee.methods.duration_methods import strtime_seconds from employee.models import BonusPoint, Employee, EmployeeWorkInformation from horilla import horilla_middlewares -from horilla.methods import get_horilla_model_class from horilla.models import HorillaModel from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog @@ -96,6 +95,8 @@ class FilingStatus(HorillaModel): default="taxable_gross_pay", verbose_name=_("Based on"), ) + use_py = models.BooleanField(verbose_name="Python Code", default=False) + python_code = models.TextField(null=True) description = models.TextField( blank=True, verbose_name=_("Description"), diff --git a/payroll/templates/payroll/tax/filing_status_creation.html b/payroll/templates/payroll/tax/filing_status_creation.html index ad4fd8f8f..ed948ae54 100644 --- a/payroll/templates/payroll/tax/filing_status_creation.html +++ b/payroll/templates/payroll/tax/filing_status_creation.html @@ -46,3 +46,49 @@ + + + + diff --git a/payroll/templates/payroll/tax/filing_status_list.html b/payroll/templates/payroll/tax/filing_status_list.html index 81c8affd7..7e38064df 100644 --- a/payroll/templates/payroll/tax/filing_status_list.html +++ b/payroll/templates/payroll/tax/filing_status_list.html @@ -44,7 +44,7 @@ style="display: none" >