diff --git a/payroll/context_processors.py b/payroll/context_processors.py
index 69c4ec8d1..3a8fbfdff 100644
--- a/payroll/context_processors.py
+++ b/payroll/context_processors.py
@@ -3,7 +3,9 @@ context_processor.py
This module is used to register context processor`
"""
+from employee.models import Employee
from payroll.models import tax_models as models
+from payroll.models.models import Deduction
def default_currency(request):
@@ -24,3 +26,21 @@ def host(request):
"""
protocol = "https" if request.is_secure() else "http"
return {"host": request.get_host(), "protocol": protocol}
+
+
+def get_deductions(request):
+ """
+ This method used to return the deduction
+ """
+ deductions = Deduction.objects.filter(
+ only_show_under_employee=False, employer_rate__gt=0
+ )
+ return {"get_deductions": deductions}
+
+
+def get_active_employees(request):
+ """
+ This method used to return the deduction
+ """
+ employees = Employee.objects.filter(is_active=True)
+ return {"get_active_employees": employees}
diff --git a/payroll/forms/component_forms.py b/payroll/forms/component_forms.py
index 3f824ecc9..652129084 100644
--- a/payroll/forms/component_forms.py
+++ b/payroll/forms/component_forms.py
@@ -204,6 +204,9 @@ class PayslipForm(ModelForm):
for contract in active_contracts
if contract.employee_id.is_active
]
+ if self.instance.pk is None:
+ self.initial["start_date"] = datetime.date.today().replace(day=1)
+ self.initial["end_date"] = datetime.date.today()
class Meta:
"""
@@ -281,6 +284,8 @@ class GeneratePayslipForm(HorillaForm):
self.fields["start_date"].widget.attrs.update({"class": "oh-input w-100"})
self.fields["group_name"].widget.attrs.update({"class": "oh-input w-100"})
self.fields["end_date"].widget.attrs.update({"class": "oh-input w-100"})
+ self.initial["start_date"] = datetime.date.today().replace(day=1)
+ self.initial["end_date"] = datetime.date.today()
class Meta:
"""
@@ -420,7 +425,9 @@ class PayslipDeductionForm(ModelForm):
"""
Bonus Creating Form
"""
+
verbose_name = _("Deduction")
+
class Meta:
model = Deduction
fields = [
@@ -450,7 +457,7 @@ class PayslipDeductionForm(ModelForm):
context = {"form": self}
table_html = render_to_string("one_time_deduction.html", context)
return table_html
-
+
class LoanAccountForm(ModelForm):
"""
diff --git a/payroll/forms/forms.py b/payroll/forms/forms.py
index 200a2f5c3..db96dc537 100644
--- a/payroll/forms/forms.py
+++ b/payroll/forms/forms.py
@@ -1,6 +1,7 @@
"""
forms.py
"""
+
from django import forms
from django.forms import widgets
from django.utils.translation import gettext_lazy as trans
@@ -65,7 +66,7 @@ class ContractForm(ModelForm):
verbose_name = trans("Contract")
contract_start_date = forms.DateField()
- contract_end_date = forms.DateField()
+ contract_end_date = forms.DateField(required=False)
class Meta:
"""
@@ -97,6 +98,20 @@ class ContractForm(ModelForm):
"placeholder": "Select a date",
}
)
+ self.fields["contract_status"].widget.attrs.update(
+ {
+ "class": "oh-select",
+ }
+ )
+ if self.instance and self.instance.pk:
+ dynamic_url = self.get_dynamic_hx_post_url(self.instance)
+ self.fields["contract_status"].widget.attrs.update(
+ {
+ "hx-target": "#contractFormTarget",
+ "hx-post": dynamic_url,
+ "hx-swap": "outerHTML",
+ }
+ )
first = PayrollGeneralSetting.objects.first()
if first and self.instance.pk is None:
self.initial["notice_period_in_month"] = first.notice_period
@@ -109,6 +124,9 @@ class ContractForm(ModelForm):
table_html = render_to_string("contract_form.html", context)
return table_html
+ def get_dynamic_hx_post_url(self, instance):
+ return f"/payroll/update-contract-status/{instance.pk}"
+
class WorkRecordForm(ModelForm):
"""
diff --git a/payroll/settings.py b/payroll/settings.py
index 5f7c90d0b..2952898e5 100644
--- a/payroll/settings.py
+++ b/payroll/settings.py
@@ -9,6 +9,12 @@ from horilla.settings import TEMPLATES
TEMPLATES[0]["OPTIONS"]["context_processors"].append(
"payroll.context_processors.default_currency",
)
+TEMPLATES[0]["OPTIONS"]["context_processors"].append(
+ "payroll.context_processors.get_deductions",
+)
+TEMPLATES[0]["OPTIONS"]["context_processors"].append(
+ "payroll.context_processors.get_active_employees",
+)
TEMPLATES[0]["OPTIONS"]["context_processors"].append(
"payroll.context_processors.host",
)
diff --git a/payroll/templates/contract_form.html b/payroll/templates/contract_form.html
index 3358ff2c8..07ec4ac7c 100644
--- a/payroll/templates/contract_form.html
+++ b/payroll/templates/contract_form.html
@@ -8,6 +8,9 @@
{{ form.verbose_name }}
+
@@ -16,6 +19,7 @@
{% for field in form.visible_fields %}
+ {% if field.name != 'contract_status' %}
@@ -31,6 +35,7 @@
{% else %} {{ field|add_class:"form-control" }} {% endif %}
{{field.errors}}
+ {% endif %}
{% endfor %}
diff --git a/payroll/templates/payroll/common/form.html b/payroll/templates/payroll/common/form.html
index beb2015c3..acc07a6eb 100644
--- a/payroll/templates/payroll/common/form.html
+++ b/payroll/templates/payroll/common/form.html
@@ -1,99 +1,99 @@
{% extends 'index.html' %} {% block content %} {% load static %}
+
+{% endblock content %}
diff --git a/payroll/templates/payroll/dashboard.html b/payroll/templates/payroll/dashboard.html
index d4d5151f0..51205ffdb 100644
--- a/payroll/templates/payroll/dashboard.html
+++ b/payroll/templates/payroll/dashboard.html
@@ -171,6 +171,40 @@
+
{% trans "Contracts ending " %}
diff --git a/payroll/templates/payroll/dashboard/contribution.html b/payroll/templates/payroll/dashboard/contribution.html
new file mode 100644
index 000000000..73303270d
--- /dev/null
+++ b/payroll/templates/payroll/dashboard/contribution.html
@@ -0,0 +1,39 @@
+{% load i18n static %}
+
+
+
+
+
+ {% trans 'Deduction' %}
+
+
+ {% trans 'Employee Contribution' %}
+
+
+ {% trans 'Employer Contribution' %}
+
+
+
+
+ {% for deduction in contribution_deductions %}
+
+
+
+
+

+
+
{{ deduction.title }}
+
+
+
{{ currency }} {{ deduction.employee_contribution }}
+
{{ currency }} {{ deduction.employer_contribution }}
+
+ {% endfor %}
+ {% if not contribution_deductions %}
+
+

+
+ {% endif %}
+
+
+
diff --git a/payroll/urls/component_urls.py b/payroll/urls/component_urls.py
index f41c998a3..a5a9ec6e5 100644
--- a/payroll/urls/component_urls.py
+++ b/payroll/urls/component_urls.py
@@ -127,4 +127,5 @@ urlpatterns = [
component_views.delete_attachments,
name="delete-attachments",
),
+ path("get-contribution-report",component_views.get_contribution_report,name="get-contribution-report")
]
diff --git a/payroll/urls/urls.py b/payroll/urls/urls.py
index 4912bfcb4..c04e3529c 100644
--- a/payroll/urls/urls.py
+++ b/payroll/urls/urls.py
@@ -3,6 +3,7 @@ urls.py
This module is used to map url pattern or request path with view functions
"""
+
from django.urls import path, include
from payroll.views import views
from payroll.models.models import Contract, Payslip
@@ -18,6 +19,11 @@ urlpatterns = [
name="update-contract",
kwargs={"model": Contract},
),
+ path(
+ "update-contract-status/
",
+ views.contract_status_update,
+ name="update-contract-status",
+ ),
path(
"delete-contract/",
views.contract_delete,
@@ -126,7 +132,6 @@ urlpatterns = [
views.payslip_select_filter,
name="payslip-select-filter",
),
-
path(
"payroll-request-add-comment//",
views.create_payrollrequest_comment,
@@ -142,5 +147,9 @@ urlpatterns = [
views.delete_payrollrequest_comment,
name="payroll-request-delete-comment",
),
- path("initial-notice-period",views.initial_notice_period,name="initial-notice-period")
+ path(
+ "initial-notice-period",
+ views.initial_notice_period,
+ name="initial-notice-period",
+ ),
]
diff --git a/payroll/views/component_views.py b/payroll/views/component_views.py
index 295f37471..17fb54da4 100644
--- a/payroll/views/component_views.py
+++ b/payroll/views/component_views.py
@@ -4,6 +4,7 @@ component_views.py
This module is used to write methods to the component_urls patterns respectively
"""
from collections import defaultdict
+from itertools import groupby
import json
import operator
from datetime import date, datetime
@@ -930,7 +931,6 @@ def add_deduction(request):
initial={"employee_id": employee_id, "one_time_date": instance.start_date},
)
if form.is_valid():
-
# Save the form to create the Deduction instance
deduction_instance = form.save(commit=False)
deduction_instance.only_show_under_employee = True
@@ -940,7 +940,7 @@ def add_deduction(request):
deduction_instance.specific_employees.set([employee_id])
deduction_instance.include_active_employees = False
deduction_instance.save()
-
+
# Now create new payslip by deleting existing payslip
new_post_data = QueryDict(mutable=True)
new_post_data.update(
@@ -1269,3 +1269,66 @@ def delete_attachments(request, _reimbursement_id):
ReimbursementMultipleAttachment.objects.filter(id__in=ids).delete()
messages.success(request, "Attachment deleted")
return redirect(view_reimbursement)
+
+
+@login_required
+@permission_required("payroll.view_payslip")
+def get_contribution_report(request):
+ """
+ This method is used to get the contribution report
+ """
+ employee_id = request.GET["employee_id"]
+ deudction_id = request.GET.get("deduction_id")
+ pay_heads = Payslip.objects.filter(employee_id__id=employee_id).values_list(
+ "pay_head_data", flat=True
+ )
+ contribution_deductions = []
+ deductions = []
+ for head in pay_heads:
+ for deduction in head["gross_pay_deductions"]:
+ if deduction.get("deduction_id"):
+ deductions.append(deduction)
+ for deduction in head["basic_pay_deductions"]:
+ if deduction.get("deduction_id"):
+ deductions.append(deduction)
+ for deduction in head["pretax_deductions"]:
+ if deduction.get("deduction_id"):
+ deductions.append(deduction)
+ for deduction in head["post_tax_deductions"]:
+ if deduction.get("deduction_id"):
+ deductions.append(deduction)
+ for deduction in head["tax_deductions"]:
+ if deduction.get("deduction_id"):
+ deductions.append(deduction)
+ for deduction in head["net_deductions"]:
+ deductions.append(deduction)
+
+ deductions.sort(key=lambda x: x["deduction_id"])
+ grouped_deductions = {
+ key: list(group)
+ for key, group in groupby(deductions, key=lambda x: x["deduction_id"])
+ }
+
+ for deduction_id, group in grouped_deductions.items():
+ title = group[0]["title"]
+ employee_contribution = sum(item["amount"] for item in group)
+ employer_contribution = sum(
+ item["employer_contribution_amount"] for item in group
+ )
+ total_contribution = employee_contribution + employer_contribution
+
+ contribution_deductions.append(
+ {
+ "deduction_id": deduction_id,
+ "title": title,
+ "employee_contribution": employee_contribution,
+ "employer_contribution": employer_contribution,
+ "total_contribution": total_contribution,
+ }
+ )
+
+ return render(
+ request,
+ "payroll/dashboard/contribution.html",
+ {"contribution_deductions": contribution_deductions},
+ )
diff --git a/payroll/views/views.py b/payroll/views/views.py
index 3c1739bc9..302b1a7a8 100644
--- a/payroll/views/views.py
+++ b/payroll/views/views.py
@@ -3,6 +3,7 @@ views.py
This module is used to define the method for the path in the urls
"""
+
from collections import defaultdict
from urllib.parse import parse_qs
import pandas as pd
@@ -20,8 +21,19 @@ from base.methods import export_data, generate_colors, get_key_instances
from employee.models import Employee, EmployeeWorkInformation
from base.methods import closest_numbers
from base.methods import generate_pdf
-from payroll.models.models import PayrollGeneralSetting, Payslip, Reimbursement, ReimbursementrequestComment, WorkRecord, Contract
-from payroll.forms.forms import ContractForm, ReimbursementrequestCommentForm, WorkRecordForm
+from payroll.models.models import (
+ PayrollGeneralSetting,
+ Payslip,
+ Reimbursement,
+ ReimbursementrequestComment,
+ WorkRecord,
+ Contract,
+)
+from payroll.forms.forms import (
+ ContractForm,
+ ReimbursementrequestCommentForm,
+ WorkRecordForm,
+)
from payroll.models.tax_models import PayrollSettings
from payroll.forms.component_forms import ContractExportFieldForm, PayrollSettingsForm
from payroll.methods.methods import save_payslip
@@ -94,6 +106,20 @@ def contract_update(request, contract_id, **kwargs):
)
+def contract_status_update(request, contract_id):
+ if request.method == "POST":
+ contract = Contract.objects.get(id=contract_id)
+ contract_form = ContractForm(request.POST, request.FILES, instance=contract)
+ if contract_form.is_valid():
+ contract_form.save()
+ messages.success(request, _("Contract status updated"))
+ else:
+ for errors in contract_form.errors.values():
+ for error in errors:
+ messages.error(request, error)
+ return HttpResponse("")
+
+
@login_required
@permission_required("payroll.delete_contract")
def contract_delete(request, contract_id):
@@ -424,24 +450,26 @@ def contract_info_initial(request):
employee_id = request.GET["employee_id"]
work_info = EmployeeWorkInformation.objects.filter(employee_id=employee_id).first()
response_data = {
- "department": work_info.department_id.id
- if work_info.department_id is not None
- else "",
- "job_position": work_info.job_position_id.id
- if work_info.job_position_id is not None
- else "",
- "job_role": work_info.job_role_id.id
- if work_info.job_role_id is not None
- else "",
+ "department": (
+ work_info.department_id.id if work_info.department_id is not None else ""
+ ),
+ "job_position": (
+ work_info.job_position_id.id
+ if work_info.job_position_id is not None
+ else ""
+ ),
+ "job_role": (
+ work_info.job_role_id.id if work_info.job_role_id is not None else ""
+ ),
"shift": work_info.shift_id.id if work_info.shift_id is not None else "",
- "work_type": work_info.work_type_id.id
- if work_info.work_type_id is not None
- else "",
+ "work_type": (
+ work_info.work_type_id.id if work_info.work_type_id is not None else ""
+ ),
"wage": work_info.basic_salary,
"contract_start_date": work_info.date_joining if work_info.date_joining else "",
- "contract_end_date": work_info.contract_end_date
- if work_info.contract_end_date
- else "",
+ "contract_end_date": (
+ work_info.contract_end_date if work_info.contract_end_date else ""
+ ),
}
return JsonResponse(response_data)
@@ -641,15 +669,19 @@ def contract_ending(request):
date = request.GET.get("period")
month = date.split("-")[1]
year = date.split("-")[0]
-
- if request.GET.get("initialLoad") == 'true':
- if month == '12':
- month =0
+
+ if request.GET.get("initialLoad") == "true":
+ if month == "12":
+ month = 0
year = int(year) + 1
- contract_end = Contract.objects.filter(contract_end_date__month=int(month)+1,contract_end_date__year=int(year))
+ contract_end = Contract.objects.filter(
+ contract_end_date__month=int(month) + 1, contract_end_date__year=int(year)
+ )
else:
- contract_end = Contract.objects.filter(contract_end_date__month=int(month),contract_end_date__year=int(year))
+ contract_end = Contract.objects.filter(
+ contract_end_date__month=int(month), contract_end_date__year=int(year)
+ )
ending_contract = []
for contract in contract_end:
@@ -851,9 +883,11 @@ def payslip_export(request):
df_table3 = df_table3.rename(
columns={
- "contract_ending": f"Contract Ending {start_date} to {end_date}"
- if start_date and end_date
- else f"Contract Ending",
+ "contract_ending": (
+ f"Contract Ending {start_date} to {end_date}"
+ if start_date and end_date
+ else f"Contract Ending"
+ ),
}
)
@@ -918,9 +952,11 @@ def payslip_export(request):
0,
0,
max_columns - 1,
- f"Payroll details {start_date} to {end_date}"
- if start_date and end_date
- else f"Payroll details",
+ (
+ f"Payroll details {start_date} to {end_date}"
+ if start_date and end_date
+ else f"Payroll details"
+ ),
heading_format,
)
@@ -1202,19 +1238,25 @@ def create_payrollrequest_comment(request, payroll_id):
"""
payroll = Reimbursement.objects.filter(id=payroll_id).first()
emp = request.user.employee_get
- form = ReimbursementrequestCommentForm(initial={'employee_id':emp.id, 'request_id':payroll_id})
+ form = ReimbursementrequestCommentForm(
+ initial={"employee_id": emp.id, "request_id": payroll_id}
+ )
if request.method == "POST":
- form = ReimbursementrequestCommentForm(request.POST )
+ form = ReimbursementrequestCommentForm(request.POST)
if form.is_valid():
form.instance.employee_id = emp
form.instance.request_id = payroll
form.save()
- form = ReimbursementrequestCommentForm(initial={'employee_id':emp.id, 'request_id':payroll_id})
+ form = ReimbursementrequestCommentForm(
+ initial={"employee_id": emp.id, "request_id": payroll_id}
+ )
messages.success(request, _("Comment added successfully!"))
-
+
if request.user.employee_get.id == payroll.employee_id.id:
- rec = payroll.employee_id.employee_work_info.reporting_manager_id.employee_user_id
+ rec = (
+ payroll.employee_id.employee_work_info.reporting_manager_id.employee_user_id
+ )
notify.send(
request.user.employee_get,
recipient=rec,
@@ -1226,7 +1268,10 @@ def create_payrollrequest_comment(request, payroll_id):
redirect="/payroll/view-reimbursement",
icon="chatbox-ellipses",
)
- elif request.user.employee_get.id == payroll.employee_id.employee_work_info.reporting_manager_id.id:
+ elif (
+ request.user.employee_get.id
+ == payroll.employee_id.employee_work_info.reporting_manager_id.id
+ ):
rec = payroll.employee_id.employee_user_id
notify.send(
request.user.employee_get,
@@ -1240,7 +1285,10 @@ def create_payrollrequest_comment(request, payroll_id):
icon="chatbox-ellipses",
)
else:
- rec = [payroll.employee_id.employee_user_id, payroll.employee_id.employee_work_info.reporting_manager_id.employee_user_id]
+ rec = [
+ payroll.employee_id.employee_user_id,
+ payroll.employee_id.employee_work_info.reporting_manager_id.employee_user_id,
+ ]
notify.send(
request.user.employee_get,
recipient=rec,
@@ -1252,14 +1300,12 @@ def create_payrollrequest_comment(request, payroll_id):
redirect="/payroll/view-reimbursement",
icon="chatbox-ellipses",
)
-
+
return HttpResponse("")
return render(
request,
"payroll/reimbursement/reimbursement_request_comment_form.html",
- {
- "form": form, "request_id":payroll_id
- },
+ {"form": form, "request_id": payroll_id},
)
@@ -1268,7 +1314,9 @@ def view_payrollrequest_comment(request, payroll_id):
"""
This method is used to show Reimbursement request comments
"""
- comments = ReimbursementrequestComment.objects.filter(request_id=payroll_id).order_by('-created_at')
+ comments = ReimbursementrequestComment.objects.filter(
+ request_id=payroll_id
+ ).order_by("-created_at")
no_comments = False
if not comments.exists():
no_comments = True
@@ -1276,7 +1324,7 @@ def view_payrollrequest_comment(request, payroll_id):
return render(
request,
"payroll/reimbursement/comment_view.html",
- {"comments": comments, 'no_comments': no_comments }
+ {"comments": comments, "no_comments": no_comments},
)
@@ -1300,7 +1348,7 @@ def initial_notice_period(request):
notice_period = eval(request.GET["notice_period"])
settings = PayrollGeneralSetting.objects.first()
settings = settings if settings else PayrollGeneralSetting()
- settings.notice_period = max(notice_period,0)
+ settings.notice_period = max(notice_period, 0)
settings.save()
- messages.success(request,"Initial notice period updated")
+ messages.success(request, "Initial notice period updated")
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))