[UPDT] PAYROLL: Reimbursement condition updation
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
16
payroll/templates/payroll/reimbursement/attachments.html
Normal file
16
payroll/templates/payroll/reimbursement/attachments.html
Normal 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>
|
||||
55
payroll/templates/payroll/reimbursement/filter.html
Normal file
55
payroll/templates/payroll/reimbursement/filter.html
Normal 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>
|
||||
40
payroll/templates/payroll/reimbursement/form.html
Normal file
40
payroll/templates/payroll/reimbursement/form.html
Normal 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>
|
||||
32
payroll/templates/payroll/reimbursement/nav.html
Normal file
32
payroll/templates/payroll/reimbursement/nav.html
Normal 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>
|
||||
174
payroll/templates/payroll/reimbursement/request_cards.html
Normal file
174
payroll/templates/payroll/reimbursement/request_cards.html
Normal 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>
|
||||
@@ -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 %}
|
||||
@@ -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")
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user