[ADD] PAYROLL: Python codeble federal tax feature
This commit is contained in:
@@ -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."""
|
||||
|
||||
101
payroll/methods/federal_tax.py
Normal file
101
payroll/methods/federal_tax.py
Normal file
@@ -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)
|
||||
|
||||
'''
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -46,3 +46,49 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<iframe
|
||||
style="display: none;"
|
||||
frameBorder="0"
|
||||
height="450px"
|
||||
id="oc-editor"
|
||||
src="https://onecompiler.com/embed/python?codeChangeEvent=true&hideNew=true&hideNewFileOption=true&hideStdin=true&hideTitle=true&listenToEvents=true&availableLanguages=false&theme=dark"
|
||||
width="100%"
|
||||
>
|
||||
</iframe>
|
||||
<script>
|
||||
$("#objectCreateModal #objectCreateModalTarget").css("max-width","90%")
|
||||
console.log($("#objectCreateModal #objectCreateModalTarget"))
|
||||
var iFrame = document.getElementById('oc-editor');
|
||||
|
||||
// Make sure the iframe is fully loaded before sending the postMessage
|
||||
iFrame.onload = function() {
|
||||
// Populate the code in the OneCompiler editor
|
||||
iFrame.contentWindow.postMessage({
|
||||
eventType: 'populateCode',
|
||||
language: 'python',
|
||||
files: [
|
||||
{
|
||||
"name": "federal_tax.py",
|
||||
"content": `{{form.instance.python_code|safe}}`
|
||||
}
|
||||
]
|
||||
}, "*");
|
||||
|
||||
// Trigger the code to run
|
||||
iFrame.contentWindow.postMessage({
|
||||
eventType: 'triggerRun'
|
||||
}, "*");
|
||||
};
|
||||
window.onmessage = function (e) {
|
||||
if (e.data && e.data.language) {
|
||||
code = e.data.files[0].content
|
||||
$("[name=python_code]").html(code);
|
||||
// handle the e.data which contains the code object
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
$("[name=python_code]").parent().hide();
|
||||
$("[name=use_py]").change();
|
||||
</script>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
style="display: none"
|
||||
>
|
||||
<ul class="oh-dropdown__items">
|
||||
{% if perms.payroll.add_taxbracket %}
|
||||
{% if perms.payroll.add_taxbracket and not filing_status.use_py %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
class="oh-dropdown__link oh-dropdown__link"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends 'index.html' %} {% block content %} {% load static %} {% load i18n %}
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not filing_status.use_py %}
|
||||
<div class="oh-sticky-table__table">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
@@ -75,3 +76,54 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<iframe
|
||||
frameBorder="0"
|
||||
height="450px"
|
||||
id="oc-editor{{filing_status.pk}}"
|
||||
src="https://onecompiler.com/embed/python?codeChangeEvent=true&hideNew=true&hideNewFileOption=true&hideStdin=true&hideTitle=true&listenToEvents=true&availableLanguages=false&theme=dark"
|
||||
width="100%"
|
||||
>
|
||||
</iframe>
|
||||
<script>
|
||||
var iFrame = document.getElementById('oc-editor{{filing_status.pk}}');
|
||||
|
||||
// Make sure the iframe is fully loaded before sending the postMessage
|
||||
iFrame.onload = function() {
|
||||
// Populate the code in the OneCompiler editor
|
||||
iFrame.contentWindow.postMessage({
|
||||
eventType: 'populateCode',
|
||||
language: 'python',
|
||||
files: [
|
||||
{
|
||||
"name": "federal_tax.py",
|
||||
"content": `{{filing_status.python_code|safe}}`
|
||||
}
|
||||
]
|
||||
}, "*");
|
||||
|
||||
// Trigger the code to run
|
||||
iFrame.contentWindow.postMessage({
|
||||
eventType: 'triggerRun'
|
||||
}, "*");
|
||||
};
|
||||
window.onmessage = function (e) {
|
||||
if (e.data && e.data.language) {
|
||||
code = e.data.files[0].content
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: "{% url 'update-py-code' filing_status.pk %}",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
code:code,
|
||||
},
|
||||
success: function (response) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// handle the e.data which contains the code object
|
||||
}
|
||||
};
|
||||
|
||||
</script>{% endif %}
|
||||
|
||||
@@ -50,4 +50,5 @@ urlpatterns = [
|
||||
tax_views.delete_tax_bracket,
|
||||
name="tax-bracket-delete",
|
||||
),
|
||||
path("update-py-code/<int:pk>/", tax_views.update_py_code, name="update-py-code"),
|
||||
]
|
||||
|
||||
@@ -14,7 +14,7 @@ import math
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -166,12 +166,11 @@ def tax_bracket_list(request, filing_status_id):
|
||||
The rendered "tax_bracket_view.html" template with the tax brackets for the
|
||||
specified filing status.
|
||||
"""
|
||||
filing_status = FilingStatus.objects.get(id=filing_status_id)
|
||||
tax_brackets = TaxBracket.objects.filter(
|
||||
filing_status_id=filing_status_id
|
||||
).order_by("max_income")
|
||||
context = {
|
||||
"tax_brackets": tax_brackets,
|
||||
}
|
||||
context = {"tax_brackets": tax_brackets, "filing_status": filing_status}
|
||||
return render(request, "payroll/tax/tax_bracket_view.html", context)
|
||||
|
||||
|
||||
@@ -273,3 +272,17 @@ def delete_tax_bracket(request, tax_bracket_id):
|
||||
if filing_status_id
|
||||
else HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("payroll.change_taxbracket")
|
||||
def update_py_code(request, pk):
|
||||
"""
|
||||
Ajax method to update python code of filing status
|
||||
"""
|
||||
code = request.POST["code"]
|
||||
filing = FilingStatus.objects.get(pk=pk)
|
||||
if not filing.python_code == code:
|
||||
filing.python_code = code
|
||||
filing.save()
|
||||
return JsonResponse({"message": "success"})
|
||||
|
||||
Reference in New Issue
Block a user