Files
ihrm/leave/cbv/leave_requests.py

589 lines
22 KiB
Python

"""
This page handles the cbv of leave requests page
"""
import ast
import contextlib
from typing import Any
from django.contrib import messages
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import resolve, reverse, reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from base.cbv.penalty import ViewPenaltyList
from base.decorators import manager_can_enter
from base.filters import PenaltyFilter
from base.methods import choosesubordinates, filtersubordinates, is_reportingmanager
from base.models import PenaltyAccounts
from horilla_views.cbv_methods import login_required
from horilla_views.generic.cbv.views import (
HorillaDetailedView,
HorillaFormView,
HorillaListView,
HorillaNavView,
TemplateView,
)
from leave.filters import LeaveRequestFilter
from leave.forms import LeaveRequestCreationForm, LeaveRequestExportForm
from leave.methods import filter_conditional_leave_request
from leave.models import AvailableLeave, LeaveRequest, LeaveType
from leave.threading import LeaveMailSendThread
from leave.views import multiple_approvals_check
from notifications.signals import notify
@method_decorator(login_required, name="dispatch")
@method_decorator(manager_can_enter("leave.view_leaverequest"), name="dispatch")
class LeaveRequestsView(TemplateView):
"""
for leave requests page view
"""
template_name = "cbv/leave_requests/leave_requests_home.html"
@method_decorator(login_required, name="dispatch")
@method_decorator(manager_can_enter("leave.view_leaverequest"), name="dispatch")
class LeaveRequestsListView(HorillaListView):
"""
Lits view of the page
"""
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.search_url = reverse("request-filter")
self.view_id = "leaveRequest"
if self.request.user.has_perm("leave.change_leaverequest"):
self.bulk_update_fields = ["status"]
def handle_bulk_submission(self, request):
instance_ids = request.POST.getlist("instance_ids")
if (
instance_ids
and isinstance(instance_ids[0], str)
and instance_ids[0].startswith("[")
):
instance_ids = ast.literal_eval(instance_ids[0])
instance_ids = [int(i) for i in instance_ids if str(i).isdigit()]
filtered_ids = []
for request_id in instance_ids:
leave_request = LeaveRequest.objects.get(id=request_id)
if leave_request.employee_id.id != request.user.employee_get.id:
filtered_ids.append(request_id)
if request.user.is_superuser:
filtered_ids = instance_ids
formatted_ids = [str(filtered_ids)]
request.POST = request.POST.copy()
request.POST.setlist("instance_ids", formatted_ids)
return super().handle_bulk_submission(request)
def get_queryset(self):
queryset = super().get_queryset()
data = queryset
queryset = filter_conditional_leave_request(self.request)
data = filtersubordinates(self.request, data, "leave.view_leaverequest")
return data
filter_class = LeaveRequestFilter
model = LeaveRequest
records_per_page = 10
columns = [
(_("Employee"), "leave_requests_custom_emp_col"),
(_("Leave Type"), "leave_type_id"),
(_("Start Date"), "start_date"),
(_("End Date"), "end_date"),
(_("Requested Days"), "requested_days"),
(_("Leave Clash"), "leave_clash_col"),
(_("Status"), "custom_status_col"),
(_("Comment"), "comment_sidebar"),
(_("Penalities"), "penality_col"),
]
row_attrs = """
{is_attendance_request_cancelled},
hx-get='{leave_requests_detail_view}?instance_ids={ordered_ids}'
hx-target="#genericModalBody"
data-target="#genericModal"
data-toggle="oh-modal-toggle"
"""
sortby_mapping = [
("Employee", "employee_id__get_full_name", "employee_id__get_avatar"),
("Leave Type", "leave_type_id"),
("Start Date", "start_date"),
("End Date", "end_date"),
("Requested Days", "requested_days"),
("Status", "status"),
]
action_method = "confirmation_col"
option_method = "actions_col"
header_attrs = {
"leave_requests_custom_emp_col": """
style="width:200px !important;"
""",
"option": """
style="width:200px !important;"
""",
}
row_status_indications = [
(
"rejected--dot",
_("Rejected"),
"""
onclick="
$('#applyFilter').closest('form').find('[name=status]').val('rejected');
$('[name=canceled]').val('unknown').change();
$('[name=approved]').val('unknown').change();
$('[name=requested]').val('unknown').change();
$('#applyFilter').click();
"
""",
),
(
"cancelled--dot",
_("Cancelled"),
"""
onclick="
$('#applyFilter').closest('form').find('[name=status]').val('cancelled');
$('[name=rejected]').val('unknown').change();
$('[name=approved]').val('unknown').change();
$('[name=requested]').val('unknown').change();
$('#applyFilter').click();
"
""",
),
(
"approved--dot",
_("Approved"),
"""
onclick="
$('#applyFilter').closest('form').find('[name=status]').val('approved');
$('[name=rejected]').val('unknown').change();
$('[name=canceled]').val('unknown').change();
$('[name=requested]').val('unknown').change();
$('#applyFilter').click();
"
""",
),
(
"requested--dot",
_("Requested"),
"""
onclick="
$('#applyFilter').closest('form').find('[name=status]').val('requested');
$('[name=rejected]').val('unknown').change();
$('[name=canceled]').val('unknown').change();
$('[name=approved]').val('unknown').change();
$('#applyFilter').click();
"
""",
),
]
row_status_class = (
"rejected-{status} cancelled-{status} requested-{status} approved-{status}"
)
@method_decorator(login_required, name="dispatch")
@method_decorator(manager_can_enter("leave.view_leaverequest"), name="dispatch")
class LeaveRequestsNavView(HorillaNavView):
"""
nav bar
"""
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.search_url = reverse("request-filter")
self.search_in = [
("leave_type_id", "Leave Type"),
("status", "Status"),
("employe_id__employee_work_info__department_id", "Department"),
("employee_id__employee_work_info__job_position_id", "Job Position"),
("employee_id__employee_work_info__company_id", "Company"),
]
self.actions = [
{
"action": _("Bulk Approve"),
"attrs": """
onclick="
bulkApproveLeaveRequests();
"
style="cursor: pointer;"
""",
},
{
"action": _("Export"),
"attrs": f"""
data-toggle = "oh-modal-toggle"
data-target = "#genericModal"
hx-target="#genericModalBody"
hx-get ="{reverse('leave-requests-nav-export')}"
style="cursor: pointer;"
""",
},
{
"action": _("Delete"),
"attrs": """
onclick="
bulkDeleteLeaveRequests();
"
data-action ="delete"
style="cursor: pointer; color:red !important"
""",
},
]
if self.request.user.has_perm("leave.add_leaverequest") or is_reportingmanager(
self.request
):
self.create_attrs = f"""
hx-get="{reverse_lazy("request-creation")}"
hx-target="#genericModalBody"
data-target="#genericModal"
data-toggle="oh-modal-toggle"
"""
nav_title = _("Leave Requests")
filter_instance = LeaveRequestFilter()
filter_body_template = "cbv/leave_requests/filter.html"
filter_form_context_name = "form"
search_swap_target = "#listContainer"
group_by_fields = [
("employee_id", _("Employee")),
("leave_type_id", _("Leave Type")),
("start_date", _("Start Date")),
("end_date", _("End Date")),
("status", _("Status")),
("requested_days", _("Requested Days")),
(
"employee_id__employee_work_info__reporting_manager_id",
_("Reporting Manager"),
),
("employee_id__employee_work_info__department_id", _("Department")),
("employee_id__employee_work_info__job_position_id", _("Job Position")),
("employee_id__employee_work_info__employee_type_id", _("Employement Type")),
("employee_id__employee_work_info__company_id", _("Company")),
]
@method_decorator(login_required, name="dispatch")
@method_decorator(manager_can_enter("leave.view_leaverequest"), name="dispatch")
class LeaveRequestsExportNav(TemplateView):
"""
for bulk export
"""
template_name = "cbv/leave_requests/leave_requests_export.html"
def get_context_data(self, **kwargs: Any):
"""
get data for export
"""
data = LeaveRequest.objects.all()
export_form = LeaveRequestExportForm
export_filter = LeaveRequestFilter(queryset=data)
context = super().get_context_data(**kwargs)
context["export_form"] = export_form
context["export_filter"] = export_filter
return context
@method_decorator(login_required, name="dispatch")
@method_decorator(manager_can_enter("leave.view_leaverequest"), name="dispatch")
class LeaveRequestsDetailView(HorillaDetailedView):
"""
detail view of page
"""
model = LeaveRequest
title = _("Details")
header = {
"title": "leave_requests_detail_subtitle",
"subtitle": "my_leave_request_detail_subtitle",
"avatar": "employee_id__get_avatar",
}
body = [
(_("Leave Type"), "leave_type_id"),
(_("Days"), "requested_days"),
(_("Start Date"), "start_date"),
(_("End Date"), "end_date"),
(_("Created Date"), "requested_date"),
(_("Created By"), "created_by"),
(_("Description"), "description"),
(_("View attachment"), "attachment_action", True),
]
cols = {
"description": 12,
}
action_method = "leave_requests_detail_view_actions"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
body = list(self.body)
if self.instance.multiple_approvals:
insert_index = 7
body.insert(
insert_index,
(_("Multiple Approvals"), "multiple_approval_action", True),
)
if self.instance.reject_reason:
insert_index = 8
body.insert(
insert_index, (_("Reason for Rejection"), "rejected_action", True)
)
self.cols["rejected_action"] = 12
context["body"] = body
return context
@method_decorator(login_required, name="dispatch")
@method_decorator(manager_can_enter("leave.add_leaverequest"), name="dispatch")
class LeaveRequestFormView(HorillaFormView):
"""
form view
"""
model = LeaveRequest
form_class = LeaveRequestCreationForm
template_name = "cbv/leave_requests/form/inherit.html"
new_display_title = _("Leave Request")
def get_initial(self) -> dict:
initial = super().get_initial()
leave_type_id = self.kwargs.get("type_id")
employee_id = self.kwargs.get("emp_id")
if leave_type_id and employee_id:
initial["leave_type_id"] = leave_type_id
initial["employee_id"] = employee_id
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request:
employee = self.request.user.employee_get
if employee:
available_leaves = employee.available_leave.all()
assigned_leave_types = LeaveType.objects.filter(
id__in=available_leaves.values_list("leave_type_id", flat=True)
)
self.form.fields["leave_type_id"].queryset = assigned_leave_types
if self.form.instance.pk:
leave_request = LeaveRequest.objects.get(id=self.form.instance.pk)
leave_type_id = leave_request.leave_type_id
employee = leave_request.employee_id
self.form_class(instance=leave_request)
if employee:
available_leaves = employee.available_leave.all()
assigned_leave_types = LeaveType.objects.filter(
id__in=available_leaves.values_list("leave_type_id", flat=True)
)
if leave_type_id not in assigned_leave_types.values_list(
"id", flat=True
):
assigned_leave_types = (
assigned_leave_types
| LeaveType.objects.filter(id=leave_type_id.id)
)
self.form.fields["leave_type_id"].queryset = assigned_leave_types
# form = self.form_class(instance = self.form.instance)
self.form = choosesubordinates(
self.request, self.form, "leave.add_leaverequest"
)
if self.form.instance.pk:
self.form_class.verbose_name = _("Leave Request")
context["form"] = self.form
context["view_id"] = "leaverequest"
return context
def form_invalid(self, form: Any) -> HttpResponse:
if self.form.instance.pk:
self.form_class.verbose_name = _("Leave Request")
if not form.is_valid():
errors = form.errors.as_data()
return render(
self.request, self.template_name, {"form": form, "errors": errors}
)
return super().form_invalid(form)
def form_valid(self, form: LeaveRequestCreationForm) -> HttpResponse:
form = self.form_class(
self.request.POST, self.request.FILES, instance=self.form.instance
)
if form.is_valid():
if form.instance.pk:
leave_request = form.save(commit=False)
save = True
if save:
leave_request.save()
messages.success(
self.request, _("Leave request is updated successfully")
)
with contextlib.suppress(Exception):
notify.send(
self.request.user.employee_get,
recipient=leave_request.employee_id.employee_work_info.reporting_manager_id.employee_user_id,
verb=f"Leave request updated for {leave_request.employee_id}.",
verb_ar=f"تم تحديث طلب الإجازة لـ {leave_request.employee_id}.",
verb_de=f"Urlaubsantrag aktualisiert für {leave_request.employee_id}.",
verb_es=f"Solicitud de permiso actualizada para {leave_request.employee_id}.",
verb_fr=f"Demande de congé mise à jour pour {leave_request.employee_id}.",
icon="people-circle",
redirect=reverse("request-view")
+ f"?id={leave_request.id}",
)
else:
leave_request = form.save(commit=False)
save = True
if leave_request.leave_type_id.require_approval == "no":
employee_id = leave_request.employee_id
leave_type_id = leave_request.leave_type_id
available_leave = AvailableLeave.objects.get(
leave_type_id=leave_type_id, employee_id=employee_id
)
leave_request.created_by = self.request.user.employee_get
leave_request.save()
if leave_request.requested_days > available_leave.available_days:
leave = (
leave_request.requested_days
- available_leave.available_days
)
leave_request.approved_available_days = (
available_leave.available_days
)
available_leave.available_days = 0
available_leave.carryforward_days = (
available_leave.carryforward_days - leave
)
leave_request.approved_carryforward_days = leave
else:
available_leave.available_days = (
available_leave.available_days
- leave_request.requested_days
)
leave_request.approved_available_days = (
leave_request.requested_days
)
leave_request.status = "approved"
available_leave.save()
if save:
leave_request.created_by = self.request.user.employee_get
leave_request.save()
if multiple_approvals_check(leave_request.id):
conditional_requests = multiple_approvals_check(
leave_request.id
)
managers = []
for manager in conditional_requests["managers"]:
managers.append(manager.employee_user_id)
with contextlib.suppress(Exception):
notify.send(
self.request.user.employee_get,
recipient=managers[0],
verb="You have a new leave request to validate.",
verb_ar="لديك طلب إجازة جديد يجب التحقق منه.",
verb_de="Sie haben eine neue Urlaubsanfrage zur Validierung.",
verb_es="Tiene una nueva solicitud de permiso que debe validar.",
verb_fr="Vous avez une nouvelle demande de congé à valider.",
icon="people-circle",
redirect=f"/leave/request-view?id={leave_request.id}",
)
mail_thread = LeaveMailSendThread(
self.request, leave_request, type="request"
)
mail_thread.start()
messages.success(
self.request, _("Leave request created successfully")
)
with contextlib.suppress(Exception):
notify.send(
self.request.user.employee_get,
recipient=leave_request.employee_id.employee_work_info.reporting_manager_id.employee_user_id,
verb=f"New leave request created for {leave_request.employee_id}.",
verb_ar=f"تم إنشاء طلب إجازة جديد لـ {leave_request.employee_id}.",
verb_de=f"Neuer Urlaubsantrag erstellt für {leave_request.employee_id}.",
verb_es=f"Nueva solicitud de permiso creada para {leave_request.employee_id}.",
verb_fr=f"Nouvelle demande de congé créée pour {leave_request.employee_id}.",
icon="people-circle",
redirect=reverse("request-view")
+ f"?id={leave_request.id}",
)
leave_requests = LeaveRequest.objects.all()
if len(leave_requests) == 1:
return HttpResponse("")
return self.HttpResponse("")
return super().form_valid(form)
@method_decorator(login_required, name="dispatch")
class LeaveClashListView(LeaveRequestsListView):
"""
list view of leave clash col
"""
def get_queryset(self):
queryset = HorillaListView.get_queryset(self)
pk = self.kwargs.get("pk")
record = LeaveRequest.objects.get(id=pk)
if record.status != "rejected" or record.status != "cancelled":
queryset = (
queryset.filter(
Q(
employee_id__employee_work_info__department_id=record.employee_id.employee_work_info.department_id
)
| Q(
employee_id__employee_work_info__job_position_id=record.employee_id.employee_work_info.job_position_id
),
start_date__lte=record.end_date,
end_date__gte=record.start_date,
)
.exclude(id=pk)
.exclude(Q(status="cancelled") | Q(status="rejected"))
)
return queryset.distinct()
columns = [
col
for col in LeaveRequestsListView.columns
if col[1] not in ["leave_clash_col", "penality_col", "actions_col"]
] + [(_("Clased Due To"), "clashed_due_to")]
row_status_class = ""
row_status_indications = None
bulk_select_option = None
row_attrs = ""
ViewPenaltyList.columns.extend(
[
(_("Leave Type"), "leave_type_id"),
(_("Minus Days"), "minus_leaves"),
(_("Deducted FromCFD"), "get_deduct_from_carry_forward"),
]
)