diff --git a/payroll/filters.py b/payroll/filters.py index 2440b8920..6a27ba35b 100644 --- a/payroll/filters.py +++ b/payroll/filters.py @@ -18,6 +18,7 @@ from payroll.models.models import ( Deduction, FilingStatus, LoanAccount, + Reimbursement, ) 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 to keep the field name for group by option diff --git a/payroll/templates/payroll/loan/records.html b/payroll/templates/payroll/loan/records.html index f7bd9fa12..a90d51833 100644 --- a/payroll/templates/payroll/loan/records.html +++ b/payroll/templates/payroll/loan/records.html @@ -2,15 +2,15 @@
- Fine + {% trans "Fine" %} - Loan + {% trans "Loan" %} - Advanced Salary + {% trans "Advanced Salary" %}
diff --git a/payroll/views/component_views.py b/payroll/views/component_views.py index 5a2e5746f..20bb538c7 100644 --- a/payroll/views/component_views.py +++ b/payroll/views/component_views.py @@ -18,12 +18,20 @@ import pandas as pd from asset.models import Asset from base.models import Company 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 base.methods import get_key_instances +from base.methods import filter_own_recodes, get_key_instances from base.methods import closest_numbers +from leave.models import AvailableLeave 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 ( calculate_allowance, calculate_gross_pay, @@ -39,6 +47,7 @@ from payroll.filters import ( DeductionFilter, LoanAccountFilter, PayslipFilter, + ReimbursementFilter, ) from payroll.forms import component_forms as forms from payroll.methods.payslip_calc import ( @@ -188,8 +197,8 @@ def payroll_calculation(employee, start_date, end_date): "gross_pay": gross_pay, "contract_wage": contract_wage, "basic_pay": basic_pay, - "paid_days":paid_days, - "unpaid_days":unpaid_days, + "paid_days": paid_days, + "unpaid_days": unpaid_days, "taxable_gross_pay": taxable_gross_pay, "basic_pay_deductions": basic_pay_deductions, "gross_pay_deductions": gross_pay_deductions, @@ -940,7 +949,7 @@ def delete_loan(request): Delete loan """ 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 # loans.delete() for loan in loans: @@ -988,9 +997,150 @@ def asset_fine(request): instance.provided_date = date.today() instance.asset_id = asset instance.save() - messages.success(request,"Asset fine added") + messages.success(request, "Asset fine added") return render( request, "payroll/asset_fine/form.html", {"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("") + 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)