[ADD] PAYROLL: Advance salary/Loan in payroll

This commit is contained in:
Horilla
2024-01-10 09:48:59 +05:30
parent 5bc42d9e13
commit 73bbd7414e
14 changed files with 535 additions and 30 deletions

View File

@@ -10,6 +10,7 @@ from payroll.models.models import (
FilingStatus,
Payslip,
WorkRecord,
LoanAccount,
)
from payroll.models.tax_models import (
PayrollSettings,
@@ -25,3 +26,4 @@ admin.site.register(Allowance)
admin.site.register(Deduction)
admin.site.register(Payslip)
admin.site.register(PayrollSettings)
admin.site.register(LoanAccount)

View File

@@ -12,7 +12,13 @@ from django import forms
from employee.models import Employee
from horilla.filters import filter_by_name
from base.filters import FilterSet
from payroll.models.models import Allowance, Contract, Deduction, FilingStatus
from payroll.models.models import (
Allowance,
Contract,
Deduction,
FilingStatus,
LoanAccount,
)
from payroll.models.models import Payslip
@@ -277,7 +283,7 @@ class PayslipFilter(FilterSet):
"deduction__gte",
"net_pay__lte",
"net_pay__gte",
"sent_to_employee"
"sent_to_employee",
]
def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
@@ -285,19 +291,47 @@ class PayslipFilter(FilterSet):
for field in self.form.fields.keys():
self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}"
class LoanAccountFilter(FilterSet):
"""
LoanAccountFilter
"""
search = django_filters.CharFilter(field_name="title", lookup_expr="icontains")
search_employee = django_filters.CharFilter(method=filter_by_name)
provided_date = django_filters.DateFilter(
widget=forms.DateInput(attrs={"type": "date"}),
field_name="provided_date",
)
class Meta:
model = LoanAccount
fields = [
"search",
"search_employee",
"provided_date",
"settled",
"employee_id",
"employee_id__employee_work_info__department_id",
"employee_id__employee_work_info__job_position_id",
"employee_id__employee_work_info__reporting_manager_id",
]
class ContractReGroup:
"""
Class to keep the field name for group by option
"""
fields = [
("","select"),
("employee_id","Employee"),
("employee_id.employee_work_info.job_position_id","Job Position"),
("employee_id.employee_work_info.department_id","Department"),
("contract_status","Status"),
("employee_id.employee_work_info.shift_id","Shift"),
("employee_id.employee_work_info.work_type_id","Work Type"),
("employee_id.employee_work_info.job_role_id","Job Role"),
("employee_id.employee_work_info.reporting_manager_id","Reporting Manager"),
("employee_id.employee_work_info.company_id","Company"),
]
("", "select"),
("employee_id", "Employee"),
("employee_id.employee_work_info.job_position_id", "Job Position"),
("employee_id.employee_work_info.department_id", "Department"),
("contract_status", "Status"),
("employee_id.employee_work_info.shift_id", "Shift"),
("employee_id.employee_work_info.work_type_id", "Work Type"),
("employee_id.employee_work_info.job_role_id", "Job Role"),
("employee_id.employee_work_info.reporting_manager_id", "Reporting Manager"),
("employee_id.employee_work_info.company_id", "Company"),
]

View File

@@ -15,7 +15,7 @@ from employee.models import Employee
from employee.filters import EmployeeFilter
from payroll.models import tax_models as models
from payroll.widgets import component_widgets as widget
from payroll.models.models import Allowance, Contract
from payroll.models.models import Allowance, Contract, LoanAccount, Payslip
import payroll.models.models
from base.methods import reload_queryset
@@ -315,9 +315,10 @@ class BonusForm(Form):
"""
Bonus Creating Form
"""
title = forms.CharField(max_length=100)
date = forms.DateField(widget=forms.DateInput())
employee_id = forms.IntegerField(label="Employee",widget=forms.HiddenInput())
employee_id = forms.IntegerField(label="Employee", widget=forms.HiddenInput())
amount = forms.DecimalField(label="Amount")
def save(self, commit=True):
@@ -346,3 +347,39 @@ class BonusForm(Form):
self.fields["date"].widget = forms.DateInput(
attrs={"type": "date", "class": "oh-input w-100"}
)
class LoanAccountForm(ModelForm):
"""
LoanAccountForm
"""
verbose_name = "Loan / Advanced Sarlary"
class Meta:
model = LoanAccount
fields = "__all__"
widgets = {
"provided_date": forms.DateTimeInput(attrs={"type": "date"}),
"installment_start_date": forms.DateTimeInput(attrs={"type": "date"}),
}
def as_p(self):
"""
Render the form fields as HTML table rows with Bootstrap styling.
"""
context = {"form": self}
table_html = render_to_string("common_form.html", context)
return table_html
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.verbose_name = self.instance.title
fields_to_exclude = ["employee_id", "installment_start_date"]
if Payslip.objects.filter(installment_ids__in=list(self.instance.deduction_ids.values_list("id",flat=True))).exists():
fields_to_exclude = fields_to_exclude + ["loan_amount", "installments"]
self.initial["provided_date"] = str(self.instance.provided_date)
for field in fields_to_exclude:
if field in self.fields:
del self.fields[field]

View File

@@ -596,6 +596,5 @@ def save_payslip(**kwargs):
instance.net_pay = round(kwargs["net_pay"], 2)
instance.pay_head_data = kwargs["pay_data"]
instance.save()
instance.installment_ids.set(kwargs["installments"])
return instance

View File

@@ -8,7 +8,7 @@ import operator
import contextlib
from attendance.models import Attendance
from payroll.models import models
from payroll.models.models import Contract, Allowance
from payroll.models.models import Contract, Allowance, LoanAccount
from payroll.methods.limits import compute_limit
operator_mapping = {
@@ -366,6 +366,9 @@ def calculate_pre_tax_deduction(*_args, **kwargs):
.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 = []
@@ -414,7 +417,7 @@ def calculate_pre_tax_deduction(*_args, **kwargs):
"amount": amount,
}
serialized_deductions.append(serialized_deduction)
return {"pretax_deductions": serialized_deductions}
return {"pretax_deductions": serialized_deductions, "installments": installments}
def calculate_post_tax_deduction(*_args, **kwargs):
@@ -453,6 +456,9 @@ def calculate_post_tax_deduction(*_args, **kwargs):
.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 = []
@@ -512,6 +518,7 @@ def calculate_post_tax_deduction(*_args, **kwargs):
return {
"post_tax_deductions": serialized_deductions,
"net_pay_deduction": serialized_net_pay_deductions,
"installments":installments,
}

View File

@@ -3,9 +3,12 @@ models.py
Used to register models
"""
from datetime import date, datetime, timedelta
import threading
from django import forms
from django.db import models
from django.dispatch import receiver
from django.contrib import messages
from django.db.models.signals import post_save
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.db.models.signals import pre_save, pre_delete
@@ -21,10 +24,10 @@ from attendance.models import (
Attendance,
strtime_seconds,
)
from leave.models import LeaveRequest
# Create your models here.
@@ -810,7 +813,8 @@ class Allowance(models.Model):
company_id = models.ForeignKey(
Company, null=True, editable=False, on_delete=models.PROTECT
)
only_show_under_employee = models.BooleanField(default=False,editable=False)
only_show_under_employee = models.BooleanField(default=False, editable=False)
is_loan = models.BooleanField(default=False,editable=False)
objects = HorillaCompanyManager()
class Meta:
@@ -1102,9 +1106,15 @@ class Deduction(models.Model):
company_id = models.ForeignKey(
Company, null=True, editable=False, on_delete=models.PROTECT
)
only_show_under_employee = models.BooleanField(default=False,editable=False)
only_show_under_employee = models.BooleanField(default=False, editable=False)
objects = HorillaCompanyManager()
is_installment = models.BooleanField(default=False,editable=False)
def installment_payslip(self):
payslip = Payslip.objects.filter(installment_ids=self).first()
return payslip
def clean(self):
super().clean()
@@ -1203,7 +1213,7 @@ class Payslip(models.Model):
)
sent_to_employee = models.BooleanField(null=True, default=False)
objects = HorillaCompanyManager("employee_id__employee_work_info__company_id")
installment_ids = models.ManyToManyField(Deduction,editable=False)
def __str__(self) -> str:
return f"Payslip for {self.employee_id} - Period: {self.start_date} to {self.end_date}"
@@ -1276,3 +1286,105 @@ class Payslip(models.Model):
ordering = [
"-end_date",
]
class LoanAccount(models.Model):
"""
This modal is used to store the loan Account details
"""
title = models.CharField(max_length=20)
employee_id = models.ForeignKey(
Employee, on_delete=models.PROTECT, verbose_name=_("Employee")
)
loan_amount = models.FloatField(default=0)
provided_date = models.DateField()
allowance_id = models.ForeignKey(
Allowance, on_delete=models.SET_NULL, editable=False, null=True
)
description = models.TextField(null=True)
deduction_ids = models.ManyToManyField(Deduction, editable=False)
is_fixed = models.BooleanField(default=True, editable=False)
rate = models.FloatField(default=0, editable=False)
installments = models.IntegerField(verbose_name=_("Total installments"))
installment_start_date = models.DateField(
help_text="From the start date deduction will apply"
)
apply_on = models.CharField(default="end_of_month", max_length=10, editable=False)
settled = models.BooleanField(default=False)
def get_installments(self):
loan_amount = self.loan_amount
total_installments = self.installments
installment_amount = loan_amount / total_installments
installment_start_date = self.installment_start_date
installment_schedule = {}
installment_date = installment_start_date
for i in range(total_installments):
installment_schedule[str(installment_date)] = installment_amount
installment_date = installment_date + timedelta(days=30 * (i + 1))
return installment_schedule
def delete(self, *args, **kwargs):
self.deduction_ids.all().delete()
self.allowance_id.delete()
if not Payslip.objects.filter(installment_ids__in=list(self.deduction_ids.values_list("id",flat=True))).exists():
super().delete(*args, **kwargs)
return
def installment_ratio(self):
total_installments = self.installments
installment_paid = Payslip.objects.filter(installment_ids__in = self.deduction_ids.all() ).count()
if not installment_paid:
return 0
return (installment_paid/total_installments)*100
@receiver(post_save, sender=LoanAccount)
def create_installments(sender, instance, created, **kwargs):
"""
This is post save method, used to create initial stage for the recruitment
"""
installments = []
if created:
loan = Allowance()
loan.amount = instance.loan_amount
loan.title = instance.title
loan.include_active_employees = False
loan.amount = instance.loan_amount
loan.only_show_under_employee = True
loan.is_fixed = True
loan.one_time_date = instance.provided_date
loan.is_loan = True
loan.save()
loan.include_active_employees = False
loan.specific_employees.add(instance.employee_id)
loan.save()
instance.allowance_id = loan
# Here create the instance...
super(LoanAccount, instance).save()
else:
deductions = instance.deduction_ids.values_list("id", flat=True)
# Re create deduction only when existing installment not exists in payslip
if not Payslip.objects.filter(installment_ids__in=deductions).exists():
Deduction.objects.filter(id__in=deductions).delete()
# Installment deductions
for installment_date, installment_amount in instance.get_installments().items():
installment = Deduction()
installment.title = instance.title
installment.include_active_employees = False
installment.amount = installment_amount
installment.is_fixed = True
installment.one_time_date = installment_date
installment.only_show_under_employee = True
installment.is_installment = True
installment.save()
installment.include_active_employees = False
installment.specific_employees.add(instance.employee_id)
installment.save()
installments.append(installment)
instance.deduction_ids.set(installments)

View File

@@ -0,0 +1,55 @@
{% load i18n %}
<div class="oh-main__titlebar-button-container">
<div class="oh-dropdown" x-data="{open: false}">
<button class="oh-btn ml-2" @click="open = !open">
<ion-icon name="filter" class="mr-1"></ion-icon>Filter
</button>
<div
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
x-show="open"
@click.outside="open = false"
>
<div class="oh-dropdown__filter-body">
<div class="oh-accordion">
<div class="oh-accordion-header">{% trans "Loan Filter" %}</div>
<div class="oh-accordion-body">
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label class="oh-label">{% trans "Employee" %}</label>
{{f.form.employee_id}}
</div>
<div class="oh-input-group">
<label class="oh-label">{% trans "Provided date" %}</label>
{{f.form.provided_date}}
</div>
<div class="oh-input-group">
<label class="oh-label">{% trans "Reporting manager" %}</label>
{{f.form.employee_id__employee_work_info__reporting_manager_id}}
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-6">
<div class="oh-input-group">
<label class="oh-label">{% trans "Department" %}</label>
{{f.form.employee_id__employee_work_info__department_id}}
</div>
<div class="oh-input-group">
<label class="oh-label">{% trans "Job position" %}</label>
{{f.form.employee_id__employee_work_info__job_position_id}}
</div>
<div class="oh-input-group">
<label class="oh-label">{% trans "Settled" %}</label>
{{f.form.settled}}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="oh-dropdown__filter-footer">
<button class="oh-btn oh-btn--secondary oh-btn--small w-100 filterButton">Filter</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,3 @@
<form hx-post="{% url "create-loan" %}?instance_id={{instance_id}}" hx-target="#loanModalBody">
{{form.as_p}}
</form>

View File

@@ -0,0 +1,45 @@
{% load i18n %}
<div class="oh-timeoff-modal__profile-content">
<div class="oh-profile mb-2">
<div class="oh-profile__avatar">
<img src="{{ loan.employee_id.get_avatar }}" class="oh-profile__image me-2" />
</div>
<div class="oh-timeoff-modal__profile-info">
<span class="oh-timeoff-modal__user m-0 fw-bold">{{ loan.employee_id }}</span>
<span class="oh-timeoff-modal__user m-0" style="font-size: 12px; color: #4d4a4a">{{ loan.employee_id.get_department }} /{{ loan.employee_id.get_job_position }}</span>
<span class="oh-timeoff-modal__user m-0" style="font-size: 12px; color: #4d4a4a">{{ record.provided_date }}</span>
</div>
</div>
</div>
<h3 class="oh-faq-card__title">
{{loan.title}}
</h3>
<div class="oh-sticky-table__table mt-3">
<div class="oh-sticky-table__thead">
<div class="oh-sticky-table__tr">
<div class="oh-sticky-table__th" align="center" style="width: 50px;">{% trans "S/n" %}</div>
<div class="oh-sticky-table__th" align="center" style="width: 80px;">{% trans "One Time Date" %}</div>
<div class="oh-sticky-table__th" align="center" style="width: 80px;">{% trans "Amount" %}</div>
<div class="oh-sticky-table__th" align="center" style="width: 60px;">{% trans "Status" %}</div>
</div>
</div>
<div class="oh-sticky-table__tbody">
{% for deduction in installments %}
<div class="oh-sticky-table__tr">
<div class="oh-sticky-table__td" align="center">{{ forloop.counter }}</div>
<div class="oh-sticky-table__td dateformat_changer" align="center">{{ deduction.one_time_date }}</div>
<div class="oh-sticky-table__td" align="center">{{ deduction.amount|floatformat:2 }}</div>
<div class="oh-sticky-table__td" align="center">
{% if deduction.installment_payslip %}
<span title="Installment paid, Click to view">
<a href="{% url "view-created-payslip" deduction.installment_payslip.id %}">
</a>
</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>

View File

@@ -0,0 +1,24 @@
{% load i18n %}
<section class="oh-wrapper oh-main__topbar">
<div class="oh-main__titlebar oh-main__titlebar--left oh-d-flex-column--resp oh-mb-3--small">
<h1 class="oh-main__titlebar-title fw-bold">{% trans 'Loan / Advanced Salary' %}</h1>
</div>
<form hx-get="{% url 'search-loan' %}" hx-target="#loanContainer">
<div class="oh-main__titlebar oh-main__titlebar--right oh-d-flex-column--resp oh-mb-3--small">
<div class="oh-input-group oh-input__search-group mr-4">
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left md hydrated" role="img" aria-label="search outline"></ion-icon>
<input type="text" id="pipelineSearch" placeholder="Search" style="margin-right:10px" onkeyup="$('.filterButton').click()" name="search" class="oh-input oh-input__icon mr-3" aria-label="Search Input" />
</div>
{% include 'payroll/loan/filter.html' %}
<div class="oh-main__titlebar-button-container">
<div class="oh-main__titlebar-button-container">
<a hx-get="{% url 'create-loan' %}" hx-target="#loanModalBody" data-toggle="oh-modal-toggle" data-target="#loanModal" class="oh-btn oh-btn--secondary">
<ion-icon name="add-outline"></ion-icon>
{% trans 'Create' %}
</a>
</div>
</div>
</div>
</form>
</section>

View File

@@ -0,0 +1,63 @@
{% load i18n %}
<div class="oh-wrapper oh-faq-cards">
{% include 'filter_tags.html' %}
{% for record in records %}
<div class="oh-faq-card">
<div class="text-danger d-flex flex-row-reverse">
<a href="{% url 'delete-loan' %}?ids={{ record.id }}" onclick="return confirm('Do you want to delete this record?')"><ion-icon class="text-danger" name="trash-outline"></ion-icon></a>
<a class="mr-2" hx-get="{% url 'create-loan' %}?instance_id={{ record.id }}" hx-target="#loanModalBody" data-toggle="oh-modal-toggle" data-target="#loanModal"><ion-icon class="text-dark" name="create-outline"></ion-icon></a>
</div>
<div class="oh-timeoff-modal__profile-content">
<div class="oh-profile mb-2">
<div class="oh-profile__avatar">
<img src="{{ record.employee_id.get_avatar }}" class="oh-profile__image me-2" alt="Mary Magdalene" />
</div>
<div class="oh-timeoff-modal__profile-info">
<span class="oh-timeoff-modal__user m-0 fw-bold">{{ record.employee_id }}</span>
<span class="oh-timeoff-modal__user m-0" style="font-size: 12px; color: #4d4a4a">{{ record.employee_id.get_department }} /{{ record.employee_id.get_job_position }}</span>
<span class="oh-timeoff-modal__user m-0" style="font-size: 12px; color: #4d4a4a">{{ record.provided_date }}</span>
</div>
</div>
</div>
<h3 class="oh-faq-card__title">{{ record.title }}</h3>
<div class="oh-recuritment__progress-bar">
<div class="oh-progress-bar__state" role="progressbar" aria-valuemax="100" style="width:{{ record.installment_ratio }}%"></div>
</div>
<p class="oh-faq-card__desc">{{ record.description }}.</p>
<a hx-get="{% url 'view-installments' %}?loan_id={{ record.id }}" hx-target="#viewInstallmentModalBody" data-target="#viewInstallmentModal" data-toggle="oh-modal-toggle" class="oh-btn oh-btn--secondary oh-btn--block">{% trans 'Installmetns' %}</a>
</div>
{% endfor %}
</div>
<div class="oh-wrapper w-100">
<div class="oh-pagination">
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal">{% trans 'Page' %} {{ records.number }} {% trans 'of' %} {{ records.paginator.num_pages }}.</span>
<nav class="oh-pagination__nav">
<div class="oh-pagination__input-container me-3">
<span class="oh-pagination__label me-1">{% trans 'Page' %}</span>
<input type="number" name="page" class="oh-pagination__input" value="{{ records.number }}" hx-get="{% url 'search-loan' %}?{{ pd }}" hx-target="#loanContainer" min="1" />
<span class="oh-pagination__label">{% trans 'of' %} {{ records.paginator.num_pages }}</span>
</div>
<ul class="oh-pagination__items">
{% if records.has_previous %}
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target="#loanContainer" hx-get="{% url 'search-loan' %}?{{ pd }}&page=1" class="oh-pagination__link">{% trans 'First' %}</a>
</li>
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target="#loanContainer" hx-get="{% url 'search-loan' %}?{{ pd }}&page={{ records.previous_page_number }}" class="oh-pagination__link">{% trans 'Previous' %}</a>
</li>
{% endif %}
{% if records.has_next %}
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target="#loanContainer" hx-get="{% url 'search-loan' %}?{{ pd }}&page={{ records.next_page_number }}" class="oh-pagination__link">{% trans 'Next' %}</a>
</li>
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target="#loanContainer" hx-get="{% url 'search-loan' %}?{{ pd }}&page={{ records.paginator.num_pages }}" class="oh-pagination__link">{% trans 'Last' %}</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>

View File

@@ -0,0 +1,26 @@
{% extends 'index.html' %}
{% block content %}
{% load i18n %}
{% include 'payroll/loan/nav.html' %}
<div id="loanContainer">
{% include 'payroll/loan/records.html' %}
</div>
<div class="oh-modal" id="loanModal" role="dialog" aria-hidden="true">
<div class="oh-modal__dialog" style="max-width: 550px">
<div class="oh-modal__dialog-header">
<button type="button" class="oh-modal__close" aria-label="Close"><ion-icon name="close-outline"></ion-icon></button>
</div>
<div class="oh-modal__dialog-body" id="loanModalBody"></div>
</div>
</div>
<div class="oh-modal" id="viewInstallmentModal" role="dialog" aria-hidden="true">
<div class="oh-modal__dialog" style="max-width: 550px">
<div class="oh-modal__dialog-header">
<button type="button" class="oh-modal__close" aria-label="Close"><ion-icon name="close-outline"></ion-icon></button>
</div>
<div class="oh-modal__dialog-body" id="viewInstallmentModalBody"></div>
</div>
</div>
{% endblock %}

View File

@@ -71,6 +71,11 @@ urlpatterns = [
name="hx-create-allowance",
),
path("send-slip",component_views.send_slip,name="send-slip"),
path("add-bonus/",component_views.add_bonus,name="add-bonus")
path("add-bonus/",component_views.add_bonus,name="add-bonus"),
path("view-loan/",component_views.view_loans,name="view-loan"),
path("create-loan/",component_views.create_loan,name="create-loan"),
path("view-installments/",component_views.view_installments,name="view-installments"),
path("delete-loan/",component_views.delete_loan,name="delete-loan"),
path("search-loan/",component_views.search_loan,name="search-loan"),
]

View File

@@ -22,7 +22,7 @@ from horilla.settings import EMAIL_HOST_USER
from base.methods import get_key_instances
from base.methods import closest_numbers
import payroll.models.models
from payroll.models.models import Allowance, Deduction, Payslip
from payroll.models.models import Allowance, Deduction, LoanAccount, Payslip
from payroll.methods.payslip_calc import (
calculate_allowance,
calculate_gross_pay,
@@ -33,7 +33,12 @@ from payroll.methods.payslip_calc import (
calculate_pre_tax_deduction,
calculate_tax_deduction,
)
from payroll.filters import AllowanceFilter, DeductionFilter, PayslipFilter
from payroll.filters import (
AllowanceFilter,
DeductionFilter,
LoanAccountFilter,
PayslipFilter,
)
from payroll.forms import component_forms as forms
from payroll.methods.payslip_calc import (
calculate_net_pay_deduction,
@@ -118,6 +123,10 @@ def payroll_calculation(employee, start_date, end_date):
pretax_deductions = calculate_pre_tax_deduction(**kwargs)
post_tax_deductions = calculate_post_tax_deduction(**kwargs)
installments = (
pretax_deductions["installments"] | post_tax_deductions["installments"]
)
taxable_gross_pay = calculate_taxable_gross_pay(**kwargs)
tax_deductions = calculate_tax_deduction(**kwargs)
federal_tax = calculate_taxable_amount(**kwargs)
@@ -197,6 +206,7 @@ def payroll_calculation(employee, start_date, end_date):
json_data = json.dumps(data_to_json)
payslip_data["json_data"] = json_data
payslip_data["installments"] = installments
return payslip_data
@@ -497,6 +507,7 @@ def generate_payslip(request):
data["deduction"] = payslip["total_deductions"]
data["net_pay"] = payslip["net_pay"]
data["pay_data"] = json.loads(payslip["json_data"])
data["installments"] = payslip["installments"]
instance = save_payslip(**data)
instances.append(instance)
messages.success(request, f"{employees.count()} payslip saved as draft")
@@ -558,6 +569,7 @@ def create_payslip(request):
data["deduction"] = payslip_data["total_deductions"]
data["net_pay"] = payslip_data["net_pay"]
data["pay_data"] = json.loads(payslip_data["json_data"])
data["installments"] = payslip_data["installments"]
payslip_data["instance"] = save_payslip(**data)
form = forms.PayslipForm()
messages.success(request, _("Payslip Saved"))
@@ -849,9 +861,6 @@ def send_slip(request):
@login_required
@permission_required("payroll.add_allowance")
def add_bonus(request):
print("========================================")
print(request.GET)
print("========================================")
employee_id = request.GET["employee_id"]
form = forms.BonusForm(initial={"employee_id": employee_id})
if request.method == "POST":
@@ -860,4 +869,88 @@ def add_bonus(request):
form.save()
messages.success(request, "Bonus Added")
return HttpResponse("<script>window.location.reload()</script>")
return render(request, "payroll/bonus/form.html", {"form": form,"employee_id":employee_id})
return render(
request, "payroll/bonus/form.html", {"form": form, "employee_id": employee_id}
)
@login_required
@permission_required("payroll.view_loanaccount")
def view_loans(request):
"""
This method is used to render template to disply all the loan records
"""
records = LoanAccount.objects.all()
filter_instance = LoanAccountFilter()
return render(
request,
"payroll/loan/view_loan.html",
{"records": paginator_qry(records, request.GET.get("page")), "f": filter_instance},
)
@login_required
@permission_required("payroll.add_loanaccount")
def create_loan(request):
"""
This method is used to create and update the loan instance
"""
instance_id = eval(str(request.GET.get("instance_id")))
instance = LoanAccount.objects.filter(id=instance_id).first()
form = forms.LoanAccountForm(instance=instance)
if request.method == "POST":
form = forms.LoanAccountForm(request.POST, instance=instance)
if form.is_valid():
form.save()
messages.success(request, "Loan created/updated")
return HttpResponse("<script>window.location.reload()</script>")
return render(
request, "payroll/loan/form.html", {"form": form, "instance_id": instance_id}
)
@login_required
@permission_required("payroll.view_loanaccount")
def view_installments(request):
"""
View install ments
"""
loan_id = request.GET["loan_id"]
loan = LoanAccount.objects.get(id=loan_id)
installments = loan.deduction_ids.all()
return render(
request,
"payroll/loan/installments.html",
{"installments": installments, "loan": loan},
)
@login_required
@permission_required("payroll.delete_loanaccount")
def delete_loan(request):
"""
Delete loan
"""
ids = request.GET.getlist("ids")
loans = LoanAccount.objects.filter(id__in=ids)
# This 👇 would'nt trigger the delete method in the model
# loans.delete()
for loan in loans:
loan.delete()
messages.success(request, "Loan account deleted")
return redirect(view_loans)
@login_required
@permission_required("payroll.view_loanaccount")
def search_loan(request):
"""
Search loan method
"""
records = LoanAccountFilter(request.GET).qs
data_dict = parse_qs(request.GET.urlencode())
return render(
request,
"payroll/loan/records.html",
{"records": paginator_qry(records, request.GET.get("page")), "filter_dict": data_dict,"pd":request.GET.urlencode()},
)