[UPDT] PAYROLL: Reimbursement condition updation

This commit is contained in:
Horilla
2024-01-12 21:07:25 +05:30
parent a0fb09145e
commit b1d03c7ca5
10 changed files with 742 additions and 11 deletions

View File

@@ -11,6 +11,7 @@ from payroll.models.models import (
Payslip,
WorkRecord,
LoanAccount,
Reimbursement
)
from payroll.models.tax_models import (
PayrollSettings,
@@ -27,3 +28,4 @@ admin.site.register(Deduction)
admin.site.register(Payslip)
admin.site.register(PayrollSettings)
admin.site.register(LoanAccount)
admin.site.register(Reimbursement)

View File

@@ -2,20 +2,30 @@
These forms provide a convenient way to handle data input, validation, and customization
of form fields and widgets for the corresponding models in the payroll management system.
"""
from typing import Any
import uuid
import datetime
from django import forms
from django.utils.translation import gettext_lazy as _
from django.template.loader import render_to_string
from base import thread_local_middleware
from horilla_widgets.forms import HorillaForm
from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField
from horilla_widgets.widgets.select_widgets import HorillaMultiSelectWidget
from base.forms import Form, ModelForm
from employee.models import Employee
from employee.filters import EmployeeFilter
from leave.models import AvailableLeave, LeaveType
from payroll.models import tax_models as models
from payroll.widgets import component_widgets as widget
from payroll.models.models import Allowance, Contract, LoanAccount, Payslip
from payroll.models.models import (
Allowance,
Contract,
LoanAccount,
Payslip,
Reimbursement,
ReimbursementMultipleAttachment,
)
import payroll.models.models
from base.methods import reload_queryset
@@ -378,7 +388,11 @@ class LoanAccountForm(ModelForm):
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():
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:
@@ -388,14 +402,133 @@ class LoanAccountForm(ModelForm):
class AssetFineForm(LoanAccountForm):
verbose_name = "Asset Fine"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['loan_amount'].label = 'Fine Amount'
fields_to_exclude = ["employee_id", "provided_date","type"]
self.fields["loan_amount"].label = "Fine Amount"
fields_to_exclude = ["employee_id", "provided_date", "type"]
for field in fields_to_exclude:
if field in self.fields:
del self.fields[field]
if field in self.fields:
del self.fields[field]
pass
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = [single_file_clean(data, initial)]
return result[0]
class ReimbursementForm(ModelForm):
"""
ReimbursementForm
"""
verbose_name = "Reimbursement / Encashment"
class Meta:
model = Reimbursement
fields = "__all__"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
exclude_fields = []
if not self.instance.pk:
self.initial["allowance_on"] = str(datetime.date.today())
request = getattr(thread_local_middleware._thread_locals, "request", None)
if request:
employee = (
request.user.employee_get
if self.instance.pk is None
else self.instance.employee_id
)
self.initial["employee_id"] = employee.id
assigned_leaves = LeaveType.objects.filter(
employee_available_leave__employee_id=employee,
employee_available_leave__total_leave_days__gte=1,
)
self.assigned_leaves = AvailableLeave.objects.filter(
leave_type_id__in=assigned_leaves, employee_id=employee
)
self.fields["leave_type_id"].queryset = assigned_leaves
self.fields["leave_type_id"].empty_label = None
self.fields["employee_id"].empty_label = None
type_attr = self.fields["type"].widget.attrs
type_attr["onchange"] = "toggleReimbursmentType($(this))"
self.fields["type"].widget.attrs.update(type_attr)
employee_attr = self.fields["employee_id"].widget.attrs
employee_attr["onchange"] = "getAssignedLeave($(this).val())"
self.fields["employee_id"].widget.attrs.update(employee_attr)
self.fields["allowance_on"].widget = forms.DateInput(
attrs={"type": "date", "class": "oh-input w-100"}
)
self.fields["attachment"] = MultipleFileField(label="Attachements")
# deleting fields based on type
type = None
if self.data and not self.instance.pk:
type = self.data["type"]
elif self.instance is not None:
type = self.instance.type
if not request.user.has_perm("payroll.add_reimbursement"):
exclude_fields.append("employee_id")
if type == "reimbursement" and self.instance.pk:
exclude_fields = exclude_fields + [
"leave_type_id",
"cfd_to_encash",
"ad_to_encash",
]
elif self.instance.pk or self.data.get("type") == "leave_encashment":
exclude_fields = exclude_fields + [
"attachment",
"amount",
]
if self.instance.pk:
exclude_fields = exclude_fields + ["type", "employee_id"]
for field in exclude_fields:
if field in self.fields:
del self.fields[field]
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 save(self, commit: bool = ...) -> Any:
attachemnt = []
multiple_attachment_ids = []
attachemnts = None
if self.files.getlist("attachment"):
attachemnts = self.files.getlist("attachment")
self.instance.attachemnt = attachemnts[0]
multiple_attachment_ids = []
for attachemnt in attachemnts:
file_instance = ReimbursementMultipleAttachment()
file_instance.attachment = attachemnt
file_instance.save()
multiple_attachment_ids.append(file_instance.pk)
instance = super().save(commit)
instance.other_attachments.add(*multiple_attachment_ids)
return instance, attachemnts

View File

@@ -2,8 +2,10 @@
models.py
Used to register models
"""
from collections.abc import Iterable
from datetime import date, datetime, timedelta
import threading
from typing import Any
from django import forms
from django.db import models
from django.dispatch import receiver
@@ -14,6 +16,7 @@ from django.utils.translation import gettext_lazy as _
from django.db.models.signals import pre_save, pre_delete
from django.http import QueryDict
from asset.models import Asset
from base import thread_local_middleware
from employee.models import EmployeeWorkInformation
from employee.models import Employee, Department, JobPosition
from base.models import Company, EmployeeShift, WorkType, JobRole
@@ -25,7 +28,7 @@ from attendance.models import (
Attendance,
strtime_seconds,
)
from leave.models import LeaveRequest
from leave.models import LeaveRequest, LeaveType
# Create your models here.
@@ -1360,7 +1363,7 @@ class LoanAccount(models.Model):
@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
Post save metod for loan account
"""
installments = []
if created and instance.asset_id is None and instance.type != "fine":
@@ -1405,3 +1408,152 @@ def create_installments(sender, instance, created, **kwargs):
installment.save()
installments.append(installment)
instance.deduction_ids.set(installments)
class ReimbursementMultipleAttachment(models.Model):
"""
ReimbursementMultipleAttachement Model
"""
attachment = models.FileField(upload_to="payroll/reimbursements")
class Reimbursement(models.Model):
"""
Reimbursement Model
"""
reimbursement_types = [
("reimbursement", "Reimbursement"),
("leave_encashment", "Leave Encashment"),
]
status_types = [
("requested", "Requested"),
("approved", "Approved"),
("canceled", "Canceled"),
]
title = models.CharField(max_length=50)
type = models.CharField(
choices=reimbursement_types, max_length=16, default="reimbursement"
)
employee_id = models.ForeignKey(
Employee, on_delete=models.PROTECT, verbose_name="Employee"
)
allowance_on = models.DateField()
attachment = models.FileField(upload_to="payroll/reimbursements", null=True)
other_attachments = models.ManyToManyField(
ReimbursementMultipleAttachment, blank=True, editable=False
)
leave_type_id = models.ForeignKey(
LeaveType, on_delete=models.PROTECT, null=True, verbose_name="Leave type"
)
ad_to_encash = models.FloatField(
default=0, help_text="Available Days to encash", verbose_name="Available days"
)
cfd_to_encash = models.FloatField(
default=0,
help_text="Carry Forward Days to encash",
verbose_name="Carry forward days",
)
amount = models.FloatField(default=0)
status = models.CharField(
max_length=10, choices=status_types, default="requested", editable=False
)
approved_by = models.ForeignKey(
Employee,
on_delete=models.SET_NULL,
null=True,
related_name="approved_by",
editable=False,
)
description = models.TextField(null=True)
allowance_id = models.ForeignKey(
Allowance, on_delete=models.SET_NULL, null=True, editable=False
)
created_at = models.DateTimeField(auto_now_add=True)
is_active=models.BooleanField(default=True,editable=False)
def save(self, *args, **kwargs) -> None:
request = getattr(thread_local_middleware._thread_locals, "request", None)
# Setting the created use if the used dont have the permission
has_perm = request.user.has_perm("payroll.add_reimbursement")
if not has_perm:
self.employee_id = request.user.employee_get
if self.type == "reimbursement" and self.attachment is None:
raise ValidationError({"attachment": "This field is required"})
elif self.type == "leave_encashment" and self.leave_type_id is None:
raise ValidationError({"leave_type_id": "This field is required"})
self.cfd_to_encash = max((round(self.cfd_to_encash * 2) / 2), 0)
self.ad_to_encash = max((round(self.ad_to_encash * 2) / 2), 0)
assigned_leave = self.leave_type_id.employee_available_leave.filter(
employee_id=self.employee_id
).first()
if self.status != "approved" or self.allowance_id is None:
super().save(*args, **kwargs)
if self.status == "approved" and self.allowance_id is None:
if self.type == "reimbursement":
proceed = True
else:
proceed = False
if assigned_leave:
available_days = assigned_leave.available_days
carryforward_days = assigned_leave.carryforward_days
if (
available_days >= self.ad_to_encash
and carryforward_days >= self.cfd_to_encash
):
proceed = True
assigned_leave.available_days = (
available_days - self.ad_to_encash
)
assigned_leave.carryforward_days = (
carryforward_days - self.cfd_to_encash
)
assigned_leave.save()
else:
request = getattr(
thread_local_middleware._thread_locals, "request", None
)
if request:
messages.info(
request,
"The employee don't have that much leaves to encash in CFD / Available days",
)
# if self.ad
if proceed:
reimbursement = Allowance()
reimbursement.one_time_date = self.allowance_on
reimbursement.title = self.title
reimbursement.only_show_under_employee = True
reimbursement.include_active_employees = False
reimbursement.amount = self.amount
reimbursement.save()
reimbursement.include_active_employees = False
reimbursement.specific_employees.add(self.employee_id)
reimbursement.save()
self.allowance_id = reimbursement
if request:
self.approved_by = request.user.employee_get
else:
self.status = "requested"
super().save(*args, **kwargs)
elif self.status == "canceled" and self.allowance_id is not None:
cfd_days = self.cfd_to_encash
available_days = self.ad_to_encash
if assigned_leave:
assigned_leave.available_days = (
assigned_leave.available_days + available_days
)
assigned_leave.carryforward_days = (
assigned_leave.carryforward_days + cfd_days
)
assigned_leave.save()
self.allowance_id.delete()
def delete(self, *args, **kwargs):
if self.allowance_id:
self.allowance_id.delete()
return super().delete(*args, **kwargs)

View File

@@ -0,0 +1,16 @@
{% load i18n %}
<h3 class="oh-faq-card__title">{% trans 'Attachments' %}</h3>
<span class="oh-card__footer--border-top pt-1 mb-3" style="display: block;"></span>
{% for attachment in reimbursement.other_attachments.all %}
<div id="attachmentContainer{{forloop.counter}}">
<div class="oh-btn-group" style="width: 10%;">
<button onclick="window.open('{{ protocol }}://{{ host }}{{ attachment.attachment.url }}', '_blank')" class="oh-btn oh-btn--light text-dark"><ion-icon name="eye-outline"></ion-icon></button>
<a href="{% url "delete-attachments" reimbursement.id %}?ids={{attachment.id}}" onclick="return confirm('Do you want to delete this attachment?')" class="oh-btn oh-btn--light"><ion-icon name="trash"></ion-icon></a>
</div>
<iframe style="width: 100%;height: 200px;" height="100" src="{{ protocol }}://{{ host }}{{ attachment.attachment.url }}" frameborder="0">adsf</iframe>
</div>
{% endfor %}
<div class="d-flex flex-row-reverse">
<button onclick="$('.oh-modal--show').toggleClass('oh-modal--show');" class="oh-btn oh-btn--secondary mt-2 mr-0 pl-4 pr-5 oh-btn--w-100-resp">{% trans 'Close' %}</button>
</div>

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>{% trans "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 "Reimbursement" %}</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 "Status" %}</label>
{{f.form.status}}
</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 "Type" %}</label>
{{f.form.type}}
</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>
</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,40 @@
{% load i18n %}
<form hx-post="{% url "create-reimbursement" %}?instance_id={{form.instance.id}}" hx-target="#reimbursementModalBody"
hx-encoding="multipart/form-data">
{{form.as_p}}
</form>
<table class="oh-table oh-table--sortable" {% if not form.instance.id or form.instance.type == "reimbursement" %}
id="availableTable" style="display: none;" {% endif %}>
<thead>
<tr>
<th>{% trans "Leave Type" %}</th>
<th>{% trans "Available Days" %}</th>
<th title="{% trans " Carry Forward Days" %}">{% trans "CFD" %}</th>
</tr>
</thead>
<tbody {% if not form.instance.id %} id="availableTableBody" {% endif %}>
{% for available_leave in form.assigned_leaves %}
<tr class="toggle-highlight">
<td>{{available_leave.leave_type_id}}</td>
<td>{{available_leave.available_days}}</td>
<td>{{available_leave.carryforward_days}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
toggleReimbursmentType($("#reimbursementModalBody [name=type]").first())
removeHighlight()
var attachemntLabelElement = $("#reimbursementModalBody [for=id_attachment]");
var attachemntlElement = $("#reimbursementModalBody #id_attachment");
var attachemntParentlElement = $("#reimbursementModalBody [for=id_attachment]").parent();
// Detach both elements from the DOM
attachemntLabelElement.detach();
attachemntlElement.detach();
attachemntParentlElement.html("")
// Append them in the desired order
attachemntParentlElement.append(attachemntLabelElement);
attachemntParentlElement.append(attachemntlElement);
</script>

View File

@@ -0,0 +1,32 @@
{% load i18n %}
<section class="oh-wrapper oh-main__topbar" style="padding-bottom: 1rem;">
<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 'Reimbursements' %}</h1>
</div>
<form hx-get="{% url 'search-reimbursement' %}" hx-target="#reimbursementContainer">
<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>
<ul class="oh-view-types ml-2" style="margin-bottom: 0;">
<li class="oh-view-type" data-view="list" onclick="event.stopPropagation()">
<a class="oh-btn oh-btn--view" title="List"><ion-icon name="list-outline" role="img" class="md hydrated" aria-label="list outline"></ion-icon></a>
</li>
<li class="oh-view-type" data-view="card">
<a class="oh-btn oh-btn--view oh-btn--view-active" title="Card"><ion-icon name="grid-outline" role="img" class="md hydrated" aria-label="grid outline"></ion-icon></a>
</li>
</ul>
{% include 'payroll/reimbursement/filter.html' %}
<div class="oh-main__titlebar-button-container">
<div class="oh-main__titlebar-button-container">
<a hx-get="{% url 'create-reimbursement' %}" hx-target="#reimbursementModalBody" data-toggle="oh-modal-toggle" data-target="#reimbursementModal" 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,174 @@
{% load i18n %}
{% include "filter_tags.html" %}
<div class="d-flex flex-row-reverse oh-wrapper">
<span class="mb-3" onclick="$('[name=status]').val('canceled').change(); $('.filterButton').click()" style="cursor: pointer; margin-right: 15px;">
<span class="oh-dot oh-dot--small me-1" style="background-color:red"></span>
{% trans "Canceled" %}
</span>
<span class="mb-3" onclick="$('[name=status]').val('requested').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>
{% trans "Requested" %}
</span>
<span class="mb-3" onclick="$('[name=status]').val('approved').change();$('.filterButton').click()" style="cursor: pointer; margin-right: 15px;">
<span class="oh-dot oh-dot--small me-1" style="background-color:yellowgreen"></span>
{% trans "Approved" %}
</span>
<span class="mb-3" onclick="$('[name=type]').val('leave_encashment').change();$('.filterButton').click()" style="cursor: pointer; margin-right: 15px;">
<span class="oh-dot oh-dot--small me-1" style="background-color:#cd8232"></span>
{% trans "Encashment" %}
</span>
<span class="mb-3" onclick="$('[name=type]').val('reimbursement').change();$('.filterButton').click()" style="cursor: pointer; margin-right: 15px;">
<span class="oh-dot oh-dot--small me-1" style="background-color:#32a1cd"></span>
{% trans "Reimbursement" %}
</span>
</div>
<div class="oh-wrapper oh-faq-cards" >
{% for req in requests %}
<div class="oh-faq-card" id="requestCard{{ req.id }}">
<div class="d-flex justify-content-between">
<div style="margin-bottom: 10px;">
<div class="loan-type" style="display: inline;">{{ req.get_type_display }}</div>
<div class="loan-type" style="display: inline; {% if req.status == "approved" %}background:#9acd3245; {% elif req.status == "canceled" %} background:#ff45002e;{% endif %}">{{ req.get_status_display }}</div>
</div>
<div>
{% if req.status != "approved" %}
<a hx-get="{% url 'create-reimbursement' %}?instance_id={{ req.id }}" hx-target="#reimbursementModalBody"
class="mr-2" data-toggle="oh-modal-toggle" data-target="#reimbursementModal"><ion-icon
class="text-dark md hydrated" name="create-outline" role="img" aria-label="create outline"></ion-icon></a>
{% endif %}
{% if perms.payroll.delete_reimbursement %}
<a href="{% url "delete-reimbursement" %}?ids={{req.id}}" onclick="return confirm('Do you want to delete this record?')"><ion-icon class="text-danger md hydrated"
name="trash-outline" role="img" aria-label="trash outline"></ion-icon></a>
{% endif %}
</div>
</div>
<div class="oh-timeoff-modal__profile-content">
<div class="oh-profile mb-2">
<div class="oh-profile__avatar">
<img src="{{ req.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">{{ req.employee_id }}</span>
<span class="oh-timeoff-modal__user m-0" style="font-size: 12px; color: #4d4a4a">
{{req.employee_id.get_department }} / {{ req.employee_id.get_job_position }}</span>
</div>
</div>
</div>
<h3 class="oh-faq-card__title">{{ req.title }}</h3>
<p class="oh-faq-card__desc">{{ req.description }}.</p>
<span class="oh-card__footer--border-top pt-1" style="display: block;">
<i style="color: hsl(0,0%,45%);">{% trans 'Allowance on' %}</i> {{ req.allowance_on }}.
{% if req.type == 'reimbursement' %}
<i><a data-target="#reimbursementAttachementModal" data-toggle="oh-modal-toggle" hx-target="#reimbursementAttachementModalBody" hx-get="{% url "reimbursement-attachments" req.id %}" class="text-danger"
title="Attachments" rel="noopener noreferrer">{% trans 'View Attachments' %}</a></i>
{% endif %}
</span>
{% if perms.payroll.change_reimbursement %}
<form action="{% url 'approve-reimbursements' %}">
<input type="hidden" name="ids" value="{{ req.id }}" />
<input type="hidden" name="status" />
{% if req.type == 'reimbursement' %}
<p class="oh-faq-card__desc" style="height: 150px;">
<iframe id="iframe_pdf" src="{{ req.attachment.url }}" style="width: 100%" frameborder="0"></iframe>
</p>
{% else %}
<p class="oh-faq-card__desc" style="max-height: 180px;">
<i>
{% trans 'Requsted for total' %} <span class="text-danger"> {{ req.ad_to_encash|add:req.cfd_to_encash }} </span>
{% trans 'days' %}
{% trans 'days to encash.' %}
</i>
<input style="display: block;padding: 10px;margin-top: 10px;" type="number" required default="0" min="0" placeholder="Amount"
name="amount" class="mt-2"{% if req.status == "approved" %}disabled{% endif %} {% if req.amount %} value="{{req.amount}}"{% endif %} />
</p>
{% endif %}
<div class="oh-btn-group">
<button type="button"
onclick="$('#requestCard{{ req.id }} [name=amount]').attr('required',false);reimbursementConfirm('Do You really want to cancel the request?','#requestCard{{ req.id }}');$('#requestCard{{ req.id }} [name=status]').val('canceled')"
class="oh-btn oh-btn--primary oh-btn--block w-100">
<ion-icon name="close" class="mr-1"></ion-icon>
{% trans 'Cancel' %}
</button>
{% if req.status != "approved" %}
<button type="button"
onclick="reimbursementConfirm('Do You really want to approve the request?','#requestCard{{ req.id }}',true);$('#requestCard{{ req.id }} [name=status]').val('approved')"
class="oh-btn oh-btn--secondary oh-btn--block w-100">
<ion-icon name="checkmark" class="mr-1"></ion-icon>
{% trans 'Approve' %}
</button>
{% else %}
<button type="button" class="oh-btn oh-btn--secondary oh-btn--disabled oh-btn--block w-100">
<ion-icon name="checkmark" class="mr-1"></ion-icon>
{% trans 'Approved' %}
</button>
{% endif %}
</div>
<input type="submit" hidden id="requestCard{{ req.id }}Button">
</form>
{% endif %}
</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' %} {{ requests.number }} {% trans 'of' %} {{ requests.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="{{ requests.number }}" hx-get="{% url 'search-reimbursement' %}?{{ pd }}" hx-target="#reimbursementContainer" min="1" />
<span class="oh-pagination__label">{% trans 'of' %} {{ requests.paginator.num_pages }}</span>
</div>
<ul class="oh-pagination__items">
{% if requests.has_previous %}
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target="#reimbursementContainer" hx-get="{% url 'search-reimbursement' %}?{{ pd }}&page=1" class="oh-pagination__link">{% trans 'First' %}</a>
</li>
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target="#reimbursementContainer" hx-get="{% url 'search-reimbursement' %}?{{ pd }}&page={{ requests.previous_page_number }}" class="oh-pagination__link">{% trans 'Previous' %}</a>
</li>
{% endif %}
{% if requests.has_next %}
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target="#reimbursementContainer" hx-get="{% url 'search-reimbursement' %}?{{ pd }}&page={{ requests.next_page_number }}" class="oh-pagination__link">{% trans 'Next' %}</a>
</li>
<li class="oh-pagination__item oh-pagination__item--wide">
<a hx-target="#reimbursementContainer" hx-get="{% url 'search-reimbursement' %}?{{ pd }}&page={{ requests.paginator.num_pages }}" class="oh-pagination__link">{% trans 'Last' %}</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
<script>
function reimbursementConfirm(params, target, approve = false) {
Swal.fire({
text: params,
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#008000',
cancelButtonColor: '#d33',
confirmButtonText: "Confirm",
cancelButtonText: "Close"
}).then((result) => {
if (result.isConfirmed) {
if (approve) {
$(`${target} [name=amount]`).attr("required", true)
}
$(target + "Button").click()
if (event.target.tagName.toLowerCase() === 'form') {
event.target.submit()
} else if (event.target.tagName.toLowerCase() === 'a') {
window.location.href = event.target.href
}
} else {
}
})
}
</script>

View File

@@ -0,0 +1,91 @@
{% extends 'index.html' %}
{% block content %}
{% include 'payroll/reimbursement/nav.html' %}
<style>
.toggle-highlight {
background-color: gold;
transition: background-color 0.5s ease;
}
.loan-type {
background: #73bbe12b;
font-size: 0.8rem;
padding: 4px 8px;
border-radius: 10px;
margin-bottom: 6px;
font-weight: 600;
color: #357579;
}
</style>
<div id="reimbursementContainer">
{% include 'payroll/reimbursement/request_cards.html' %}
</div>
<div class="oh-modal" id="reimbursementModal" 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="reimbursementModalBody"></div>
</div>
</div>
<div class="oh-modal" id="reimbursementAttachementModal" 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="reimbursementAttachementModalBody"></div>
</div>
</div>
<script>
function toggleReimbursmentType(element) {
if (element.val() == 'reimbursement') {
$('#reimbursementModalBody [name=attachment]').parent().show()
$('#reimbursementModalBody [name=attachment]').attr("required",true)
$('#reimbursementModalBody [name=leave_type_id]').parent().hide().attr("required",false)
$('#reimbursementModalBody [name=cfd_to_encash]').parent().hide().attr("required",false)
$('#reimbursementModalBody [name=ad_to_encash]').parent().hide().attr("required",false)
$('#reimbursementModalBody [name=amount]').parent().show().attr("required",true)
$('#reimbursementModalBody #availableTable').hide().attr("required",false)
} else if(element.val() == 'leave_encashment') {
$('#reimbursementModalBody [name=attachment]').parent().hide()
$('#reimbursementModalBody [name=attachment]').attr("required",false)
$('#reimbursementModalBody [name=leave_type_id]').parent().show().attr("required",true)
$('#reimbursementModalBody [name=cfd_to_encash]').parent().show().attr("required",true)
$('#reimbursementModalBody [name=ad_to_encash]').parent().show().attr("required",true)
$('#reimbursementModalBody [name=amount]').parent().hide().attr("required",false)
$('#reimbursementModalBody #availableTable').show().attr("required",true)
}
}
function getAssignedLeave(employeeId) {
$.ajax({
type: "get",
url: "{% url "get-assigned-leaves" %}",
data: { "employeeId": employeeId },
dataType: "json",
success: function (response) {
console.log("===========");
console.log(response)
console.log("===========");
let rows = ""
for (let index = 0; index < response.length; index++) {
const element = response[index];
rows = rows + `<tr class="toggle-highlight"><td>${element.leave_type_id__name
}</td><td>${element.available_days}</td><td>${element.carryforward_days}</td></tr>`
}
$("#availableTableBody").html($(rows))
removeHighlight()
}
});
}
function removeHighlight() {
setTimeout(function() {
$(".toggle-highlight").removeClass("toggle-highlight")
}, 200);
}
</script>
{% endblock %}

View File

@@ -86,4 +86,40 @@ urlpatterns = [
component_views.asset_fine,
name="asset-fine",
),
path(
"view-reimbursement",
component_views.view_reimbursement,
name="view-reimbursement",
),
path(
"create-reimbursement",
component_views.create_reimbursement,
name="create-reimbursement",
),
path(
"search-reimbursement",
component_views.search_reimbursement,
name="search-reimbursement",
),
path(
"get-assigned-leaves/",
component_views.get_assigned_leaves,
name="get-assigned-leaves",
),
path(
"approve-reimbursements",
component_views.approve_reimbursements,
name="approve-reimbursements",
),
path(
"delete-reimbursements",
component_views.delete_reimbursements,
name="delete-reimbursement",
),
path(
"reimbursement-attachements/<int:instance_id>/",
component_views.reimbursement_attachments,
name="reimbursement-attachments",
),
path("delete-attachments/<int:_reimbursement_id>/",component_views.delete_attachments,name="delete-attachments")
]