Files
ihrm/attendance/views/requests.py

774 lines
32 KiB
Python

"""
requests.py
This module is used to register the endpoints to the attendance requests
"""
import copy
import json
from datetime import date, datetime, time
from urllib.parse import parse_qs
from django.contrib import messages
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import render
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from attendance.filters import AttendanceFilters, AttendanceRequestReGroup
from attendance.forms import (
AttendanceRequestForm,
BulkAttendanceRequestForm,
NewRequestForm,
)
from attendance.methods.utils import (
get_diff_dict,
get_employee_last_name,
paginator_qry,
shift_schedule_today,
)
from attendance.models import Attendance, AttendanceActivity, AttendanceLateComeEarlyOut
from attendance.views.clock_in_out import early_out, late_come
from base.methods import (
choosesubordinates,
closest_numbers,
filtersubordinates,
get_key_instances,
is_reportingmanager,
)
from base.models import EmployeeShift, EmployeeShiftDay
from employee.models import Employee
from horilla.decorators import hx_request_required, login_required, manager_can_enter
from notifications.signals import notify
@login_required
def request_attendance(request):
"""
This method is used to render template to register new attendance for a normal user
"""
form = AttendanceRequestForm()
if request.method == "POST":
form = AttendanceRequestForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.save()
return render(request, "requests/attendance/form.html", {"form": form})
@login_required
def request_attendance_view(request):
"""
This method is used to view the attendances for to request
"""
requests = Attendance.objects.filter(
is_validate_request=True,
)
requests = filtersubordinates(
request=request,
perm="attendance.view_attendance",
queryset=requests,
)
requests = requests | Attendance.objects.filter(
employee_id__employee_user_id=request.user,
is_validate_request=True,
)
requests = AttendanceFilters(request.GET, requests).qs
previous_data = request.GET.urlencode()
data_dict = parse_qs(previous_data)
get_key_instances(Attendance, data_dict)
keys_to_remove = [key for key, value in data_dict.items() if value == ["unknown"]]
for key in keys_to_remove:
data_dict.pop(key)
attendances = filtersubordinates(
request=request,
perm="attendance.view_attendance",
queryset=Attendance.objects.all(),
)
attendances = attendances | Attendance.objects.filter(
employee_id__employee_user_id=request.user
)
attendances = attendances.filter(
employee_id__is_active=True,
)
attendances = AttendanceFilters(request.GET, attendances).qs
filter_obj = AttendanceFilters()
check_attendance = Attendance.objects.all()
if check_attendance.exists():
template = "requests/attendance/view-requests.html"
else:
template = "requests/attendance/requests_empty.html"
requests_ids = json.dumps(
[instance.id for instance in paginator_qry(requests, None).object_list]
)
attendances_ids = json.dumps(
[instance.id for instance in paginator_qry(attendances, None).object_list]
)
requests = requests.filter(
employee_id__is_active=True,
)
return render(
request,
template,
{
"requests": paginator_qry(requests, None),
"attendances": paginator_qry(attendances, None),
"requests_ids": requests_ids,
"attendances_ids": attendances_ids,
"f": filter_obj,
"filter_dict": data_dict,
"gp_fields": AttendanceRequestReGroup.fields,
},
)
@login_required
@hx_request_required
def request_new(request):
"""
This method is used to create new attendance requests
"""
if request.GET.get("bulk") and eval(request.GET.get("bulk")):
employee = request.user.employee_get
form = BulkAttendanceRequestForm(initial={"employee_id": employee})
if request.method == "POST":
form = BulkAttendanceRequestForm(request.POST)
form.instance.attendance_clock_in_date = request.POST.get("from_date")
form.instance.attendance_date = request.POST.get("from_date")
if form.is_valid():
instance = form.save(commit=False)
messages.success(request, _("Attendance request created"))
return HttpResponse(
render(
request,
"requests/attendance/request_new_form.html",
{"form": form},
).content.decode("utf-8")
+ "<script>location.reload();</script>"
)
return render(
request,
"requests/attendance/request_new_form.html",
{"form": form, "bulk": True},
)
form = NewRequestForm()
form = choosesubordinates(request, form, "attendance.change_attendance")
form.fields["employee_id"].queryset = form.fields[
"employee_id"
].queryset | Employee.objects.filter(employee_user_id=request.user)
form.fields["employee_id"].initial = request.user.employee_get.id
if request.method == "POST":
form = NewRequestForm(request.POST)
form = choosesubordinates(request, form, "attendance.change_attendance")
form.fields["employee_id"].queryset = form.fields[
"employee_id"
].queryset | Employee.objects.filter(employee_user_id=request.user)
if form.is_valid():
if form.new_instance is not None:
form.new_instance.save()
messages.success(request, _("New attendance request created"))
return HttpResponse(
render(
request,
"requests/attendance/request_new_form.html",
{"form": form},
).content.decode("utf-8")
+ "<script>location.reload();</script>"
)
messages.success(request, _("Update request updated"))
return HttpResponse(
render(
request,
"requests/attendance/request_new_form.html",
{"form": form},
).content.decode("utf-8")
+ "<script>location.reload();</script>"
)
return render(
request,
"requests/attendance/request_new_form.html",
{"form": form, "bulk": False},
)
@login_required
def attendance_request_changes(request, attendance_id):
"""
This method is used to store the requested changes to the instance
"""
attendance = Attendance.objects.get(id=attendance_id)
form = AttendanceRequestForm(instance=attendance)
form.fields["work_type_id"].widget.attrs.update(
{
"class": "w-100",
"style": "height:50px;border-radius:0;border:1px solid hsl(213deg,22%,84%)",
}
)
form.fields["shift_id"].widget.attrs.update(
{
"class": "w-100",
"style": "height:50px;border-radius:0;border:1px solid hsl(213deg,22%,84%)",
}
)
if request.method == "POST":
form = AttendanceRequestForm(request.POST, instance=copy.copy(attendance))
form.fields["work_type_id"].widget.attrs.update(
{
"class": "w-100",
"style": "height:50px;border-radius:0;border:1px solid hsl(213deg,22%,84%)",
}
)
form.fields["shift_id"].widget.attrs.update(
{
"class": "w-100",
"style": "height:50px;border-radius:0;border:1px solid hsl(213deg,22%,84%)",
}
)
work_type_id = form.data["work_type_id"]
shift_id = form.data["shift_id"]
if work_type_id is None or not len(work_type_id):
form.add_error("work_type_id", "This field is required")
if shift_id is None or not len(shift_id):
form.add_error("shift_id", "This field is required")
if form.is_valid():
# commit already set to False
# so the changes not affected to the db
instance = form.save()
instance.employee_id = attendance.employee_id
instance.id = attendance.id
if attendance.request_type != "create_request":
attendance.requested_data = json.dumps(instance.serialize())
attendance.request_description = instance.request_description
# set the user level validation here
attendance.is_validate_request = True
attendance.save()
else:
instance.is_validate_request_approved = False
instance.is_validate_request = True
instance.save()
messages.success(request, _("Attendance update request created."))
employee = attendance.employee_id
if attendance.employee_id.employee_work_info.reporting_manager_id:
reporting_manager = (
attendance.employee_id.employee_work_info.reporting_manager_id.employee_user_id
)
user_last_name = get_employee_last_name(attendance)
notify.send(
request.user,
recipient=reporting_manager,
verb=f"{employee.employee_first_name} {user_last_name}'s\
attendance update request for {attendance.attendance_date} is created",
verb_ar=f"تم إنشاء طلب تحديث الحضور لـ {employee.employee_first_name} \
{user_last_name }في {attendance.attendance_date}",
verb_de=f"Die Anfrage zur Aktualisierung der Anwesenheit von \
{employee.employee_first_name} {user_last_name} \
für den {attendance.attendance_date} wurde erstellt",
verb_es=f"Se ha creado la solicitud de actualización de asistencia para {employee.employee_first_name}\
{user_last_name} el {attendance.attendance_date}",
verb_fr=f"La demande de mise à jour de présence de {employee.employee_first_name}\
{user_last_name} pour le {attendance.attendance_date} a été créée",
redirect=reverse("request-attendance-view")
+ f"?id={attendance.id}",
icon="checkmark-circle-outline",
)
return HttpResponse(
render(
request, "requests/attendance/form.html", {"form": form}
).content.decode("utf-8")
+ "<script>location.reload();</script>"
)
return render(request, "requests/attendance/form.html", {"form": form})
@login_required
def validate_attendance_request(request, attendance_id):
"""
This method to validate the requested attendance
args:
attendance_id : attendance id
"""
attendance = Attendance.objects.get(id=attendance_id)
first_dict = attendance.serialize()
empty_data = {
"employee_id": None,
"attendance_date": None,
"attendance_clock_in_date": None,
"attendance_clock_in": None,
"attendance_clock_out": None,
"attendance_clock_out_date": None,
"shift_id": None,
"work_type_id": None,
"attendance_worked_hour": None,
}
if attendance.request_type == "create_request":
other_dict = first_dict
first_dict = empty_data
else:
other_dict = json.loads(attendance.requested_data)
requests_ids_json = request.GET.get("requests_ids")
previous_instance_id = next_instance_id = attendance.pk
if requests_ids_json:
previous_instance_id, next_instance_id = closest_numbers(
json.loads(requests_ids_json), attendance_id
)
return render(
request,
"requests/attendance/individual_view.html",
{
"data": get_diff_dict(first_dict, other_dict, Attendance),
"attendance": attendance,
"previous": previous_instance_id,
"next": next_instance_id,
"requests_ids": requests_ids_json,
},
)
@login_required
@manager_can_enter("attendance.change_attendance")
def approve_validate_attendance_request(request, attendance_id):
"""
This method is used to validate the attendance requests
"""
attendance = Attendance.objects.get(id=attendance_id)
prev_attendance_date = attendance.attendance_date
prev_attendance_clock_in_date = attendance.attendance_clock_in_date
prev_attendance_clock_in = attendance.attendance_clock_in
attendance.attendance_validated = True
attendance.is_validate_request_approved = True
attendance.is_validate_request = False
attendance.request_description = None
attendance.save()
if attendance.requested_data is not None:
requested_data = json.loads(attendance.requested_data)
requested_data["attendance_clock_out"] = (
None
if requested_data["attendance_clock_out"] == "None"
else requested_data["attendance_clock_out"]
)
requested_data["attendance_clock_out_date"] = (
None
if requested_data["attendance_clock_out_date"] == "None"
else requested_data["attendance_clock_out_date"]
)
Attendance.objects.filter(id=attendance_id).update(**requested_data)
# DUE TO AFFECT THE OVERTIME CALCULATION ON SAVE METHOD, SAVE THE INSTANCE ONCE MORE
attendance = Attendance.objects.get(id=attendance_id)
attendance.save()
if (
attendance.attendance_clock_out is None
or attendance.attendance_clock_out_date is None
):
attendance.attendance_validated = True
activity = AttendanceActivity.objects.filter(
employee_id=attendance.employee_id,
attendance_date=prev_attendance_date,
clock_in_date=prev_attendance_clock_in_date,
clock_in=prev_attendance_clock_in,
)
if activity:
activity.update(
employee_id=attendance.employee_id,
attendance_date=attendance.attendance_date,
clock_in_date=attendance.attendance_clock_in_date,
clock_in=attendance.attendance_clock_in,
)
else:
AttendanceActivity.objects.create(
employee_id=attendance.employee_id,
attendance_date=attendance.attendance_date,
clock_in_date=attendance.attendance_clock_in_date,
clock_in=attendance.attendance_clock_in,
)
# Create late come or early out objects
shift = attendance.shift_id
day = attendance.attendance_date.strftime("%A").lower()
day = EmployeeShiftDay.objects.get(day=day)
minimum_hour, start_time_sec, end_time_sec = shift_schedule_today(
day=day, shift=shift
)
if attendance.attendance_clock_in:
late_come(
attendance, start_time=start_time_sec, end_time=end_time_sec, shift=shift
)
if attendance.attendance_clock_out:
early_out(
attendance, start_time=start_time_sec, end_time=end_time_sec, shift=shift
)
messages.success(request, _("Attendance request has been approved"))
employee = attendance.employee_id
notify.send(
request.user,
recipient=employee.employee_user_id,
verb=f"Your attendance request for \
{attendance.attendance_date} is validated",
verb_ar=f"تم التحقق من طلب حضورك في تاريخ \
{attendance.attendance_date}",
verb_de=f"Ihr Anwesenheitsantrag für das Datum \
{attendance.attendance_date} wurde bestätigt",
verb_es=f"Se ha validado su solicitud de asistencia \
para la fecha {attendance.attendance_date}",
verb_fr=f"Votre demande de présence pour la date \
{attendance.attendance_date} est validée",
redirect=reverse("request-attendance-view") + f"?id={attendance.id}",
icon="checkmark-circle-outline",
)
if attendance.employee_id.employee_work_info.reporting_manager_id:
reporting_manager = (
attendance.employee_id.employee_work_info.reporting_manager_id.employee_user_id
)
user_last_name = get_employee_last_name(attendance)
notify.send(
request.user,
recipient=reporting_manager,
verb=f"{employee.employee_first_name} {user_last_name}'s\
attendance request for {attendance.attendance_date} is validated",
verb_ar=f"تم التحقق من طلب الحضور لـ {employee.employee_first_name} \
{user_last_name} في {attendance.attendance_date}",
verb_de=f"Die Anwesenheitsanfrage von {employee.employee_first_name} \
{user_last_name} für den {attendance.attendance_date} wurde validiert",
verb_es=f"Se ha validado la solicitud de asistencia de \
{employee.employee_first_name} {user_last_name} para el {attendance.attendance_date}",
verb_fr=f"La demande de présence de {employee.employee_first_name} \
{user_last_name} pour le {attendance.attendance_date} a été validée",
redirect=reverse("request-attendance-view") + f"?id={attendance.id}",
icon="checkmark-circle-outline",
)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
def cancel_attendance_request(request, attendance_id):
"""
This method is used to cancel attendance request
"""
try:
attendance = Attendance.objects.get(id=attendance_id)
if (
attendance.employee_id.employee_user_id == request.user
or is_reportingmanager(request)
or request.user.has_perm("attendance.change_attendance")
):
attendance.is_validate_request_approved = False
attendance.is_validate_request = False
attendance.request_description = None
attendance.requested_data = None
attendance.request_type = None
attendance.save()
if attendance.request_type == "create_request":
attendance.delete()
messages.success(request, _("The requested attendance is removed."))
else:
messages.success(request, _("Attendance request has been rejected"))
employee = attendance.employee_id
notify.send(
request.user,
recipient=employee.employee_user_id,
verb=f"Your attendance request for {attendance.attendance_date} is rejected",
verb_ar=f"تم رفض طلبك للحضور في تاريخ {attendance.attendance_date}",
verb_de=f"Ihre Anwesenheitsanfrage für {attendance.attendance_date} wurde abgelehnt",
verb_es=f"Tu solicitud de asistencia para el {attendance.attendance_date} ha sido rechazada",
verb_fr=f"Votre demande de présence pour le {attendance.attendance_date} est rejetée",
icon="close-circle-outline",
)
except (Attendance.DoesNotExist, OverflowError):
messages.error(request, _("Attendance request not found"))
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
def select_all_filter_attendance_request(request):
page_number = request.GET.get("page")
filtered = request.GET.get("filter")
filters = json.loads(filtered) if filtered else {}
if page_number == "all":
if request.user.has_perm("attendance.view_attendance"):
employee_filter = AttendanceFilters(
request.GET,
queryset=Attendance.objects.filter(is_validate_request=True),
)
else:
employee_filter = AttendanceFilters(
request.GET,
queryset=Attendance.objects.filter(
employee_id__employee_user_id=request.user, is_validate_request=True
)
| Attendance.objects.filter(
employee_id__employee_work_info__reporting_manager_id__employee_user_id=request.user,
is_validate_request=True,
),
)
# Get the filtered queryset
filtered_employees = employee_filter.qs
employee_ids = [str(emp.id) for emp in filtered_employees]
total_count = filtered_employees.count()
context = {"employee_ids": employee_ids, "total_count": total_count}
return JsonResponse(context)
@login_required
@manager_can_enter("attendance.change_attendance")
def bulk_approve_attendance_request(request):
"""
This method is used to validate the attendance requests
"""
ids = request.POST["ids"]
ids = json.loads(ids)
for attendance_id in ids:
attendance = Attendance.objects.get(id=attendance_id)
prev_attendance_date = attendance.attendance_date
prev_attendance_clock_in_date = attendance.attendance_clock_in_date
prev_attendance_clock_in = attendance.attendance_clock_in
attendance.attendance_validated = True
attendance.is_validate_request_approved = True
attendance.is_validate_request = False
attendance.request_description = None
attendance.save()
if attendance.requested_data is not None:
requested_data = json.loads(attendance.requested_data)
requested_data["attendance_clock_out"] = (
None
if requested_data["attendance_clock_out"] == "None"
else requested_data["attendance_clock_out"]
)
requested_data["attendance_clock_out_date"] = (
None
if requested_data["attendance_clock_out_date"] == "None"
else requested_data["attendance_clock_out_date"]
)
Attendance.objects.filter(id=attendance_id).update(**requested_data)
# DUE TO AFFECT THE OVERTIME CALCULATION ON SAVE METHOD, SAVE THE INSTANCE ONCE MORE
attendance = Attendance.objects.get(id=attendance_id)
attendance.save()
if (
attendance.attendance_clock_out is None
or attendance.attendance_clock_out_date is None
):
attendance.attendance_validated = True
activity = AttendanceActivity.objects.filter(
employee_id=attendance.employee_id,
attendance_date=prev_attendance_date,
clock_in_date=prev_attendance_clock_in_date,
clock_in=prev_attendance_clock_in,
)
if activity:
activity.update(
employee_id=attendance.employee_id,
attendance_date=attendance.attendance_date,
clock_in_date=attendance.attendance_clock_in_date,
clock_in=attendance.attendance_clock_in,
)
else:
AttendanceActivity.objects.create(
employee_id=attendance.employee_id,
attendance_date=attendance.attendance_date,
clock_in_date=attendance.attendance_clock_in_date,
clock_in=attendance.attendance_clock_in,
)
# Create late come or early out objects
shift = attendance.shift_id
day = attendance.attendance_date.strftime("%A").lower()
day = EmployeeShiftDay.objects.get(day=day)
minimum_hour, start_time_sec, end_time_sec = shift_schedule_today(
day=day, shift=shift
)
if attendance.attendance_clock_in:
late_come(
attendance,
start_time=start_time_sec,
end_time=end_time_sec,
shift=shift,
)
if attendance.attendance_clock_out:
early_out(
attendance,
start_time=start_time_sec,
end_time=end_time_sec,
shift=shift,
)
messages.success(request, _("Attendance request has been approved"))
employee = attendance.employee_id
notify.send(
request.user,
recipient=employee.employee_user_id,
verb=f"Your attendance request for \
{attendance.attendance_date} is validated",
verb_ar=f"تم التحقق من طلب حضورك في تاريخ \
{attendance.attendance_date}",
verb_de=f"Ihr Anwesenheitsantrag für das Datum \
{attendance.attendance_date} wurde bestätigt",
verb_es=f"Se ha validado su solicitud de asistencia \
para la fecha {attendance.attendance_date}",
verb_fr=f"Votre demande de présence pour la date \
{attendance.attendance_date} est validée",
redirect=reverse("request-attendance-view") + f"?id={attendance.id}",
icon="checkmark-circle-outline",
)
if attendance.employee_id.employee_work_info.reporting_manager_id:
reporting_manager = (
attendance.employee_id.employee_work_info.reporting_manager_id.employee_user_id
)
user_last_name = get_employee_last_name(attendance)
notify.send(
request.user,
recipient=reporting_manager,
verb=f"{employee.employee_first_name} {user_last_name}'s\
attendance request for {attendance.attendance_date} is validated",
verb_ar=f"تم التحقق من طلب الحضور لـ {employee.employee_first_name} \
{user_last_name} في {attendance.attendance_date}",
verb_de=f"Die Anwesenheitsanfrage von {employee.employee_first_name} \
{user_last_name} für den {attendance.attendance_date} wurde validiert",
verb_es=f"Se ha validado la solicitud de asistencia de \
{employee.employee_first_name} {user_last_name} para el {attendance.attendance_date}",
verb_fr=f"La demande de présence de {employee.employee_first_name} \
{user_last_name} pour le {attendance.attendance_date} a été validée",
redirect=reverse("request-attendance-view") + f"?id={attendance.id}",
icon="checkmark-circle-outline",
)
return HttpResponse("success")
@login_required
@manager_can_enter("attendance.delete_attendance")
def bulk_reject_attendance_request(request):
"""
This method is used to delete bulk attendance request
"""
ids = request.POST["ids"]
ids = json.loads(ids)
for attendance_id in ids:
try:
attendance = Attendance.objects.get(id=attendance_id)
if (
attendance.employee_id.employee_user_id == request.user
or is_reportingmanager(request)
or request.user.has_perm("attendance.change_attendance")
):
attendance.is_validate_request_approved = False
attendance.is_validate_request = False
attendance.request_description = None
attendance.requested_data = None
attendance.request_type = None
attendance.save()
if attendance.request_type == "create_request":
attendance.delete()
messages.success(request, _("The requested attendance is removed."))
else:
messages.success(
request, _("The requested attendance is rejected.")
)
employee = attendance.employee_id
notify.send(
request.user,
recipient=employee.employee_user_id,
verb=f"Your attendance request for {attendance.attendance_date} is rejected",
verb_ar=f"تم رفض طلبك للحضور في تاريخ {attendance.attendance_date}",
verb_de=f"Ihre Anwesenheitsanfrage für {attendance.attendance_date} wurde abgelehnt",
verb_es=f"Tu solicitud de asistencia para el {attendance.attendance_date} ha sido rechazada",
verb_fr=f"Votre demande de présence pour le {attendance.attendance_date} est rejetée",
icon="close-circle-outline",
)
except (Attendance.DoesNotExist, OverflowError):
messages.error(request, _("Attendance request not found"))
return HttpResponse("success")
@login_required
@manager_can_enter("attendance.change_attendance")
def edit_validate_attendance(request, attendance_id):
"""
This method is used to edit and update the validate request attendance
"""
attendance = Attendance.objects.get(id=attendance_id)
initial = attendance.serialize()
if attendance.request_type != "create_request":
initial = json.loads(attendance.requested_data)
initial["request_description"] = attendance.request_description
form = AttendanceRequestForm(initial=initial)
form.instance.id = attendance.id
hx_target = request.META.get("HTTP_HX_TARGET")
if request.method == "POST":
form = AttendanceRequestForm(request.POST, instance=copy.copy(attendance))
if form.is_valid():
instance = form.save()
instance.employee_id = attendance.employee_id
instance.id = attendance.id
if attendance.request_type != "create_request":
attendance.requested_data = json.dumps(instance.serialize())
attendance.request_description = instance.request_description
# set the user level validation here
attendance.is_validate_request = True
attendance.save()
else:
instance.is_validate_request_approved = False
instance.is_validate_request = True
instance.save()
return HttpResponse(
f"""
<script>
$('#editValidateAttendanceRequest').removeClass('oh-modal--show');
$('[data-target="#validateAttendanceRequest"][data-attendance-id={attendance.id}]').click();
$('#messages').html(
`
<div class="oh-alert-container">
<div class="oh-alert oh-alert--animated oh-alert--success">
Attendance request updated.
</div>
</div>
`
)
</script>
"""
)
return render(
request,
"requests/attendance/update_form.html",
{"form": form, "hx_target": hx_target},
)
@login_required
@hx_request_required
def get_employee_shift(request):
"""
method used to get employee shift
"""
employee_id = request.GET.get("employee_id")
shift = None
if employee_id:
employee = Employee.objects.get(id=employee_id)
shift = employee.get_shift
form = NewRequestForm()
if request.GET.get("bulk") and eval(request.GET.get("bulk")):
form = BulkAttendanceRequestForm()
form.fields["shift_id"].queryset = EmployeeShift.objects.all()
form.fields["shift_id"].widget.attrs["hx-trigger"] = "load,change"
form.fields["shift_id"].initial = shift
shift_id = render_to_string(
"requests/attendance/form_field.html",
{
"field": form["shift_id"],
"shift": shift,
},
)
return HttpResponse(f"{shift_id}")