[UPDT] PAYROLL: User level acccess for reimbursement and encashment feature

This commit is contained in:
Horilla
2024-01-12 21:28:44 +05:30
parent b4fcd7e5ee
commit 367e67d9b6
3 changed files with 181 additions and 10 deletions

View File

@@ -18,6 +18,7 @@ from payroll.models.models import (
Deduction, Deduction,
FilingStatus, FilingStatus,
LoanAccount, LoanAccount,
Reimbursement,
) )
from payroll.models.models import Payslip from payroll.models.models import Payslip
@@ -319,6 +320,26 @@ class LoanAccountFilter(FilterSet):
] ]
class ReimbursementFilter(FilterSet):
"""
ReimbursementFilter
"""
search = django_filters.CharFilter(field_name="title", lookup_expr="icontains")
class Meta:
model = Reimbursement
fields = [
"status",
"type",
"employee_id",
"approved_by",
"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 ContractReGroup:
""" """
Class to keep the field name for group by option Class to keep the field name for group by option

View File

@@ -2,15 +2,15 @@
<div class="d-flex flex-row-reverse oh-wrapper"> <div class="d-flex flex-row-reverse oh-wrapper">
<span class="mb-3" onclick="$('[name=type]').val('fine').change(); $('.filterButton').click()" style="cursor: pointer; margin-right: 15px;"> <span class="mb-3" onclick="$('[name=type]').val('fine').change(); $('.filterButton').click()" style="cursor: pointer; margin-right: 15px;">
<span class="oh-dot oh-dot--small me-1" style="background-color:red"></span> <span class="oh-dot oh-dot--small me-1" style="background-color:red"></span>
Fine {% trans "Fine" %}
</span> </span>
<span class="mb-3" onclick="$('[name=type]').val('loan').change(); $('.filterButton').click()" style="cursor: pointer; margin-right: 15px;"> <span class="mb-3" onclick="$('[name=type]').val('loan').change(); $('.filterButton').click()" style="cursor: pointer; margin-right: 15px;">
<span class="oh-dot oh-dot--small me-1" style="background-color:rgba(128, 128, 128, 0.482)"></span> <span class="oh-dot oh-dot--small me-1" style="background-color:rgba(128, 128, 128, 0.482)"></span>
Loan {% trans "Loan" %}
</span> </span>
<span class="mb-3" onclick="$('[name=type]').val('advanced_salary').change();$('.filterButton').click()" style="cursor: pointer; margin-right: 15px;"> <span class="mb-3" onclick="$('[name=type]').val('advanced_salary').change();$('.filterButton').click()" style="cursor: pointer; margin-right: 15px;">
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span> <span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
Advanced Salary {% trans "Advanced Salary" %}
</span> </span>
</div> </div>
<div class="oh-wrapper oh-faq-cards"> <div class="oh-wrapper oh-faq-cards">

View File

@@ -18,12 +18,20 @@ import pandas as pd
from asset.models import Asset from asset.models import Asset
from base.models import Company from base.models import Company
from employee.models import Employee, EmployeeWorkInformation from employee.models import Employee, EmployeeWorkInformation
from horilla.decorators import login_required, permission_required from horilla.decorators import login_required, owner_can_enter, permission_required
from horilla.settings import EMAIL_HOST_USER from horilla.settings import EMAIL_HOST_USER
from base.methods import get_key_instances from base.methods import filter_own_recodes, get_key_instances
from base.methods import closest_numbers from base.methods import closest_numbers
from leave.models import AvailableLeave
import payroll.models.models import payroll.models.models
from payroll.models.models import Allowance, Deduction, LoanAccount, Payslip from payroll.models.models import (
Allowance,
Deduction,
LoanAccount,
Payslip,
Reimbursement,
ReimbursementMultipleAttachment,
)
from payroll.methods.payslip_calc import ( from payroll.methods.payslip_calc import (
calculate_allowance, calculate_allowance,
calculate_gross_pay, calculate_gross_pay,
@@ -39,6 +47,7 @@ from payroll.filters import (
DeductionFilter, DeductionFilter,
LoanAccountFilter, LoanAccountFilter,
PayslipFilter, PayslipFilter,
ReimbursementFilter,
) )
from payroll.forms import component_forms as forms from payroll.forms import component_forms as forms
from payroll.methods.payslip_calc import ( from payroll.methods.payslip_calc import (
@@ -188,8 +197,8 @@ def payroll_calculation(employee, start_date, end_date):
"gross_pay": gross_pay, "gross_pay": gross_pay,
"contract_wage": contract_wage, "contract_wage": contract_wage,
"basic_pay": basic_pay, "basic_pay": basic_pay,
"paid_days":paid_days, "paid_days": paid_days,
"unpaid_days":unpaid_days, "unpaid_days": unpaid_days,
"taxable_gross_pay": taxable_gross_pay, "taxable_gross_pay": taxable_gross_pay,
"basic_pay_deductions": basic_pay_deductions, "basic_pay_deductions": basic_pay_deductions,
"gross_pay_deductions": gross_pay_deductions, "gross_pay_deductions": gross_pay_deductions,
@@ -940,7 +949,7 @@ def delete_loan(request):
Delete loan Delete loan
""" """
ids = request.GET.getlist("ids") ids = request.GET.getlist("ids")
loans = LoanAccount.objects.filter(id__in=ids,settled=False) loans = LoanAccount.objects.filter(id__in=ids, settled=False)
# This 👇 would'nt trigger the delete method in the model # This 👇 would'nt trigger the delete method in the model
# loans.delete() # loans.delete()
for loan in loans: for loan in loans:
@@ -988,9 +997,150 @@ def asset_fine(request):
instance.provided_date = date.today() instance.provided_date = date.today()
instance.asset_id = asset instance.asset_id = asset
instance.save() instance.save()
messages.success(request,"Asset fine added") messages.success(request, "Asset fine added")
return render( return render(
request, request,
"payroll/asset_fine/form.html", "payroll/asset_fine/form.html",
{"form": form, "asset_id": asset_id, "employee_id": employee_id}, {"form": form, "asset_id": asset_id, "employee_id": employee_id},
) )
@login_required
def view_reimbursement(request):
"""
This method is used to render template to view reimbursements
"""
filter_object = ReimbursementFilter({"status": "requested"})
requests = filter_own_recodes(
request, filter_object.qs, "payroll.view_reimbursement"
)
data_dict = {"status": ["requested"]}
return render(
request,
"payroll/reimbursement/view_reimbursement.html",
{
"requests": paginator_qry(requests, request.GET.get("page")),
"f": filter_object,
"pd":request.GET.urlencode(),
"filter_dict": data_dict,
},
)
@login_required
def create_reimbursement(request):
"""
This method is used to create reimbursement
"""
instance_id = eval(str(request.GET.get("instance_id")))
instance = None
if instance_id:
instance = Reimbursement.objects.filter(id=instance_id).first()
form = forms.ReimbursementForm(instance=instance)
if request.method == "POST":
form = forms.ReimbursementForm(request.POST, request.FILES, instance=instance)
if form.is_valid():
form.save()
messages.success(request, "Reimbursent saved successfully")
return HttpResponse("<script>window.location.reload()</script>")
return render(request, "payroll/reimbursement/form.html", {"form": form})
@login_required
def search_reimbursement(request):
"""
This method is used to search/filter reimbursement
"""
requests = ReimbursementFilter(request.GET).qs
requests = filter_own_recodes(request, requests, "payroll.view_reimbursement")
data_dict = parse_qs(request.GET.urlencode())
return render(
request,
"payroll/reimbursement/request_cards.html",
{
"requests": paginator_qry(requests, request.GET.get("page")),
"filter_dict": data_dict,
"pd": request.GET.urlencode(),
},
)
@login_required
def get_assigned_leaves(request):
"""
This method is used to return assigned leaves of the employee
in Json
"""
assigned_leaves = (
AvailableLeave.objects.filter(
employee_id__id=request.GET["employeeId"], total_leave_days__gte=1
)
.values("leave_type_id__name", "available_days", "carryforward_days")
.distinct()
)
return JsonResponse(list(assigned_leaves), safe=False)
@login_required
@permission_required("payroll.change_reimbursement")
def approve_reimbursements(request):
"""
This method is used to approve or reject the reimbursement request
"""
ids = request.GET.getlist("ids")
status = request.GET["status"]
amount = eval(request.GET.get("amount")) if request.GET.get("amount") else 0
amount = max(0, amount)
reimbursements = Reimbursement.objects.filter(id__in=ids)
if status and len(status):
for reimbursement in reimbursements:
if reimbursement.type == "leave_encashment":
reimbursement.amount = amount
reimbursement.status = status
reimbursement.save()
messages.success(
request, f"Request {reimbursement.get_status_display()} succesfully"
)
return redirect(view_reimbursement)
@login_required
@permission_required("payroll.delete_reimbursement")
def delete_reimbursements(request):
"""
This method is used to delete the reimbursements
"""
ids = request.GET.getlist("ids")
reimbursements = Reimbursement.objects.filter(id__in=ids)
for reimbursement in reimbursements:
reimbursement.delete()
messages.success(request, "Reimbursements deleted")
return redirect(view_reimbursement)
@login_required
@owner_can_enter("payroll.view_reimbursement", Reimbursement, True)
def reimbursement_attachments(request, instance_id):
"""
This method is used to render all the attachements under the reimbursement object
"""
reimbursement = Reimbursement.objects.get(id=instance_id)
return render(
request,
"payroll/reimbursement/attachments.html",
{"reimbursement": reimbursement},
)
@login_required
@owner_can_enter("payroll.delete_reimbursement", Reimbursement, True)
def delete_attachments(request, _reimbursement_id):
"""
This mehtod is used to delete the attachements
"""
ids = request.GET.getlist("ids")
ReimbursementMultipleAttachment.objects.filter(id__in=ids).delete()
messages.success(request, "Attachment deleted")
return redirect(view_reimbursement)