Files
ihrm/attendance/views.py
Horilla 2fee7c18bb [IMP] Remove inter module dependency (#274)
This commit introduces significant changes to the architecture of the Horilla HRMS system by decoupling interdependent modules. The following modifications were made:

1. **Module Independence**: Each module has been refactored to eliminate reliance on other modules, promoting a more modular and maintainable codebase.
2. **Refactored Imports and Dependencies**: Adjusted import statements and dependency injections to support independent module operation.
3. **Compatibility and Functionality**: Ensured that all modules are compatible with existing systems and maintain their intended functionality both independently and when integrated with other modules.

These changes enhance the modularity, maintainability, and scalability of the Horilla HRMS, allowing developers to work on individual modules without affecting the entire system. Future development and deployment will be more efficient and less prone to issues arising from tightly coupled code.

**NOTE**
For existing Horilla users, if you face any issues during the migrations, please run the following command and try again the migrations.

- `python3 manage.py makemigrations`
- `python3 manage.py migrate base`
- `python3 manage.py migrate`





* [IMP] ASSET: Asset module dependency removal from other Horilla apps

* [IMP] ATTENDANCE: Attendance module dependency removal from other Horilla apps

* [IMP] BASE: Base module dependency removal from other Horilla apps

* [IMP] EMPLOYEE: Employee module dependency removal from other Horilla apps

* [IMP] HELPDESK: Helpdesk module dependency removal from other Horilla apps

* [IMP] HORILLA AUDIT: Horilla Audit module dependency removal from other Horilla apps

* [IMP] HORILLA CRUMBS: Horilla Crumbs module dependency removal from other Horilla apps

* [IMP] HORILLA AUTOMATIONS: Horilla Automations module dependency removal from other Horilla apps

* [IMP] HORILLA VIEWS: Horilla Views module dependency removal from other Horilla apps

* [IMP] LEAVE: Leave module dependency removal from other Horilla apps

* [IMP] OFFBOARDING: Offboarding module dependency removal from other Horilla apps

* [IMP] ONBOARDING: Onboarding module dependency removal from other Horilla apps

* [IMP] PMS: PMS module dependency removal from other Horilla apps

* [IMP] PAYROLL: Payroll module dependency removal from other Horilla apps

* [IMP] RECRUITMENT: Recruitment module dependency removal from other Horilla apps

* [IMP] HORILLA: Dependency removal updates

* [IMP] TEMPLATES: Dependency removal updates

* [IMP] STATIC: Dependency removal updates

* [IMP] HORILLA DOCUMENTS: Horilla Documents module dependency removal from other Horilla apps

* [ADD] HORILLA: methods.py

* [UPDT] HORILLA: Settings.py

* [FIX] EMPLOYEE: About tab issue

* Update horilla_settings.py

* Remove dummy db init password
2024-08-05 14:22:44 +05:30

1346 lines
47 KiB
Python

"""
views.py
This module contains the view functions for handling HTTP requests and rendering
responses in your application.
Each view function corresponds to a specific URL route and performs the necessary
actions to handle the request, process data, and generate a response.
This module is part of the recruitment project and is intended to
provide the main entry points for interacting with the application's functionality.
"""
import contextlib
import json
from datetime import date, datetime, timedelta
from django.contrib import messages
from django.core.paginator import Paginator
from django.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from attendance.filters import (
AttendanceActivityFilter,
AttendanceActivityReGroup,
AttendanceFilters,
AttendanceOverTimeFilter,
AttendanceOvertimeReGroup,
AttendanceReGroup,
LateComeEarlyOutFilter,
LateComeEarlyOutReGroup,
)
from attendance.forms import (
AttendanceForm,
AttendanceOverTimeForm,
AttendanceUpdateForm,
AttendanceValidationConditionForm,
)
from attendance.methods.utils import (
activity_datetime,
employee_exists,
format_time,
is_reportingmanger,
overtime_calculation,
shift_schedule_today,
strtime_seconds,
)
from attendance.models import (
Attendance,
AttendanceActivity,
AttendanceLateComeEarlyOut,
AttendanceOverTime,
AttendanceValidationCondition,
)
from base.methods import choosesubordinates, filtersubordinates, sortby
from base.models import Department, EmployeeShiftDay, EmployeeShiftSchedule
from employee.models import Employee
from horilla.decorators import (
hx_request_required,
login_required,
manager_can_enter,
permission_required,
)
from notifications.signals import notify
# Create your views here.
def late_come_create(attendance):
"""
used to create late come report
args:
attendance : attendance object
"""
late_come_obj = AttendanceLateComeEarlyOut()
late_come_obj.type = "late_come"
late_come_obj.attendance_id = attendance
late_come_obj.employee_id = attendance.employee_id
late_come_obj.save()
return late_come_obj
def late_come(attendance, start_time, end_time):
"""
this method is used to mark the late check-in attendance after the shift starts
args:
attendance : attendance obj
start_time : attendance day shift start time
end_time : attendance day shift end time
"""
now_sec = strtime_seconds(datetime.now().strftime("%H:%M"))
mid_day_sec = strtime_seconds("12:00")
if start_time > end_time and start_time != end_time:
# night shift
if now_sec < mid_day_sec:
# Here attendance or attendance activity for new day night shift
late_come_create(attendance)
elif now_sec > start_time:
# Here attendance or attendance activity for previous day night shift
late_come_create(attendance)
elif start_time < now_sec:
late_come_create(attendance)
return True
def early_out_create(attendance):
"""
Used to create early out report
args:
attendance : attendance obj
"""
late_come_obj = AttendanceLateComeEarlyOut()
late_come_obj.type = "early_out"
late_come_obj.attendance_id = attendance
late_come_obj.employee_id = attendance.employee_id
late_come_obj.save()
return late_come_obj
def early_out(attendance, start_time, end_time):
"""
This method is used to mark the early check-out attendance before the shift ends
args:
attendance : attendance obj
start_time : attendance day shift start time
start_end : attendance day shift end time
"""
now_sec = strtime_seconds(datetime.now().strftime("%H:%M"))
mid_day_sec = strtime_seconds("12:00")
if start_time > end_time:
# Early out condition for night shift
if now_sec < mid_day_sec:
if now_sec < end_time:
# Early out condition for general shift
early_out_create(attendance)
else:
early_out_create(attendance)
return
if end_time > now_sec:
early_out_create(attendance)
return
def attendance_validate(attendance):
"""
This method is is used to check condition for at work in AttendanceValidationCondition
model instance it return true if at work is smaller than condition
args:
attendance : attendance object
"""
conditions = AttendanceValidationCondition.objects.all()
# Set the default condition for 'at work' to 9:00 AM
condition_for_at_work = strtime_seconds("09:00")
if conditions.exists():
condition_for_at_work = strtime_seconds(conditions[0].validation_at_work)
at_work = strtime_seconds(attendance.attendance_worked_hour)
return condition_for_at_work >= at_work
@login_required
@manager_can_enter("attendance.add_attendance")
def attendance_create(request):
"""
This method is used to render attendance create form and save if it is valid
"""
form = AttendanceForm()
form = choosesubordinates(request, form, "attendance.add_attendance")
if request.method == "POST":
form = AttendanceForm(request.POST)
form = choosesubordinates(request, form, "attendance.add_attendance")
if form.is_valid():
form.save()
messages.success(request, _("Attendance added."))
response = render(
request, "attendance/attendance/form.html", {"form": form}
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(request, "attendance/attendance/form.html", {"form": form})
def paginator_qry(qryset, page_number):
"""
This method is used to paginate queryset
"""
paginator = Paginator(qryset, 50)
qryset = paginator.get_page(page_number)
return qryset
@login_required
@manager_can_enter("attendance.view_attendance")
def attendance_view(request):
"""
This method is used to view attendances.
"""
previous_data = request.environ["QUERY_STRING"]
form = AttendanceForm()
condition = AttendanceValidationCondition.objects.first()
minot = strtime_seconds("00:30")
if condition is not None:
minot = strtime_seconds(condition.minimum_overtime_to_approve)
validate_attendances = Attendance.objects.filter(attendance_validated=False)
attendances = Attendance.objects.filter(attendance_validated=True)
ot_attendances = Attendance.objects.filter(
attendance_overtime_approve=False,
overtime_second__gte=minot,
attendance_validated=True,
)
filter_obj = AttendanceFilters(queryset=Attendance.objects.all())
attendances = filtersubordinates(request, attendances, "attendance.view_attendance")
validate_attendances = filtersubordinates(
request, validate_attendances, "attendance.view_attendance"
)
ot_attendances = filtersubordinates(
request, ot_attendances, "attendance.view_attendance"
)
return render(
request,
"attendance/attendance/attendance_view.html",
{
"form": form,
"validate_attendances": paginator_qry(
validate_attendances, request.GET.get("vpage")
),
"attendances": paginator_qry(attendances, request.GET.get("page")),
"overtime_attendances": paginator_qry(
ot_attendances, request.GET.get("opage")
),
"f": filter_obj,
"pd": previous_data,
"gp_fields": AttendanceReGroup.fields,
},
)
@login_required
@manager_can_enter("attendance.view_attendance")
def attendance_search(request):
"""
This method is used to search attendance by employee
"""
previous_data = request.environ["QUERY_STRING"]
field = request.GET.get("field")
minot = strtime_seconds("00:30")
condition = AttendanceValidationCondition.objects.first()
if condition is not None:
minot = strtime_seconds(condition.minimum_overtime_to_approve)
validate_attendances = Attendance.objects.filter(attendance_validated=False)
attendances = Attendance.objects.filter(attendance_validated=True)
ot_attendances = Attendance.objects.filter(
attendance_overtime_approve=False,
overtime_second__gte=minot,
attendance_validated=True,
)
validate_attendances = AttendanceFilters(request.GET, validate_attendances).qs
attendances = AttendanceFilters(request.GET, attendances).qs
ot_attendances = AttendanceFilters(request.GET, ot_attendances).qs
template = "attendance/attendance/tab_content.html"
if field != "" and field is not None:
field_copy = field.replace(".", "__")
attendances = attendances.order_by(field_copy)
validate_attendances = validate_attendances.order_by(field_copy)
ot_attendances = ot_attendances.order_by(field_copy)
template = "attendance/attendance/group_by.html"
attendances = filtersubordinates(request, attendances, "attendance.view_attendance")
validate_attendances = filtersubordinates(
request, validate_attendances, "attendance.view_attendance"
)
ot_attendances = filtersubordinates(
request, ot_attendances, "attendance.view_attendance"
)
attendances = sortby(request, attendances, "sortby")
validate_attendances = sortby(request, validate_attendances, "sortby")
ot_attendances = sortby(request, ot_attendances, "sortby")
return render(
request,
template,
{
"validate_attendances": paginator_qry(
validate_attendances, request.GET.get("vpage")
),
"attendances": paginator_qry(attendances, request.GET.get("page")),
"overtime_attendances": paginator_qry(
ot_attendances, request.GET.get("opage")
),
"pd": previous_data,
"field": field,
},
)
@login_required
@manager_can_enter("attendance.change_attendance")
def attendance_update(request, obj_id):
"""
This method render form to update attendance and save if the form is valid
args:
obj_id : attendance id
"""
attendance = Attendance.objects.get(id=obj_id)
form = AttendanceUpdateForm(
instance=attendance,
)
form = choosesubordinates(request, form, "attendance.change_attendance")
if request.method == "POST":
form = AttendanceUpdateForm(request.POST, instance=attendance)
form = choosesubordinates(request, form, "attendance.change_attendance")
if form.is_valid():
form.save()
messages.success(request, _("Attendance Updated."))
response = render(
request, "attendance/attendance/update_form.html", {"form": form}
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(
request,
"attendance/attendance/update_form.html",
{
"form": form,
},
)
@login_required
@permission_required("attendance.delete_attendance")
@require_http_methods(["POST"])
def attendance_delete(request, obj_id):
"""
This method is used to delete attendance.
args:
obj_id : attendance id
"""
attendance = Attendance.objects.get(id=obj_id)
month = attendance.attendance_date
month = month.strftime("%B").lower()
overtime = attendance.employee_id.employee_overtime.filter(month=month).last()
if overtime is not None:
if attendance.attendance_overtime_approve:
# Subtract overtime of this attendance
total_overtime = strtime_seconds(overtime.overtime)
attendance_overtime_seconds = strtime_seconds(
attendance.attendance_overtime
)
if total_overtime > attendance_overtime_seconds:
total_overtime = total_overtime - attendance_overtime_seconds
else:
total_overtime = attendance_overtime_seconds - total_overtime
overtime.overtime = format_time(total_overtime)
overtime.save()
try:
attendance.delete()
messages.success(request, _("Attendance deleted."))
except Exception as error:
messages.error(request, error)
messages.error(request, _("You cannot delete this attendance"))
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@require_http_methods(["POST"])
@permission_required("attendance.delete_attendance")
def attendance_bulk_delete(request):
"""
This method is used to delete bulk of attendances
"""
ids = request.POST["ids"]
ids = json.loads(ids)
for attendance_id in ids:
try:
attendance = Attendance.objects.get(id=attendance_id)
month = attendance.attendance_date
month = month.strftime("%B").lower()
overtime = attendance.employee_id.employee_overtime.filter(
month=month
).last()
if overtime is not None:
if attendance.attendance_overtime_approve:
# Subtract overtime of this attendance
total_overtime = strtime_seconds(overtime.overtime)
attendance_overtime_seconds = strtime_seconds(
attendance.attendance_overtime
)
if total_overtime > attendance_overtime_seconds:
total_overtime = total_overtime - attendance_overtime_seconds
else:
total_overtime = attendance_overtime_seconds - total_overtime
overtime.overtime = format_time(total_overtime)
overtime.save()
try:
attendance.delete()
messages.success(request, _("Attendance Deleted"))
except:
messages.error(
request,
_(
"You cannot delete this %(attendance)s"
% {"attendance": attendance}
),
)
except:
pass
return JsonResponse({"message": "Success"})
@login_required
def view_my_attendance(request):
"""
This method is used to view self attendances of employee
"""
user = request.user
try:
employee = user.employee_get
except:
return redirect("/employee/employee-profile")
employee = user.employee_get
employee_attendances = employee.employee_attendances.all()
filter = AttendanceFilters()
return render(
request,
"attendance/own_attendance/view_own_attendances.html",
{
"attendances": paginator_qry(employee_attendances, request.GET.get("page")),
"f": filter,
},
)
@login_required
@hx_request_required
def filter_own_attendance(request):
"""
This method is used to filter own attendances
"""
attendances = Attendance.objects.filter(employee_id=request.user.employee_get)
attendances = AttendanceFilters(request.GET, queryset=attendances).qs
return render(
request,
"attendance/own_attendance/attendances.html",
{"attendances": paginator_qry(attendances, request.GET.get("page"))},
)
@login_required
def own_attendance_sort(request):
"""
This method is used to sort out attendances
"""
attendances = Attendance.objects.filter(employee_id=request.user.employee_get)
previous_data = request.environ["QUERY_STRING"]
attendances = sortby(request, attendances, "orderby")
return render(
request,
"attendance/own_attendance/attendances.html",
{
"attendances": paginator_qry(attendances, request.GET.get("page")),
"pd": previous_data,
},
)
@login_required
@manager_can_enter("attendance.add_attendanceovertime")
def attendance_overtime_create(request):
"""
This method is used to render overtime creating form and save if the form is valid
"""
form = AttendanceOverTimeForm()
form = choosesubordinates(request, form, "attendance.add_attendanceovertime")
if request.method == "POST":
form = AttendanceOverTimeForm(request.POST)
form = choosesubordinates(request, form, "attendance.add_attendanceovertime")
if form.is_valid():
form.save()
messages.success(request, _("Attendance account added."))
response = render(
request, "attendance/attendance_account/form.html", {"form": form}
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(request, "attendance/attendance_account/form.html", {"form": form})
@login_required
@manager_can_enter("attendance.view_attendanceovertime")
def attendance_overtime_view(request):
"""
This method is used to view attendance account or overtime account.
"""
previous_data = request.environ["QUERY_STRING"]
accounts = AttendanceOverTime.objects.all()
accounts = filtersubordinates(
request, accounts, "attendance.view_attendanceovertime"
)
form = AttendanceOverTimeForm()
form = choosesubordinates(request, form, "attendance.add_attendanceovertime")
filter_obj = AttendanceOverTimeFilter()
return render(
request,
"attendance/attendance_account/attendance_overtime_view.html",
{
"accounts": paginator_qry(accounts, request.GET.get("page")),
"form": form,
"pd": previous_data,
"f": filter_obj,
"gp_fields": AttendanceOvertimeReGroup.fields,
},
)
@login_required
@manager_can_enter("attendance.view_attendanceovertime")
def attendance_overtime_search(request):
"""
This method is used to search attendance overtime account by employee.
"""
field = request.GET.get("field")
previous_data = request.environ["QUERY_STRING"]
accounts = AttendanceOverTimeFilter(request.GET).qs
form = AttendanceOverTimeForm()
template = "attendance/attendance_account/overtime_list.html"
if field != "" and field is not None:
field_copy = field.replace(".", "__")
accounts = accounts.order_by(field_copy)
template = "attendance/attendance_account/group_by.html"
accounts = sortby(request, accounts, "sortby")
accounts = filtersubordinates(
request, accounts, "attendance.view_attendanceovertime"
)
return render(
request,
template,
{
"accounts": paginator_qry(accounts, request.GET.get("page")),
"form": form,
"pd": previous_data,
"field": field,
},
)
@login_required
@manager_can_enter("attendance.change_attendanceovertime")
@hx_request_required
def attendance_overtime_update(request, obj_id):
"""
This method is used to update attendance overtime and save if the forms is valid
args:
obj_id : attendance overtime id
"""
overtime = AttendanceOverTime.objects.get(id=obj_id)
form = AttendanceOverTimeForm(instance=overtime)
form = choosesubordinates(request, form, "attendance.change_attendanceovertime")
if request.method == "POST":
form = AttendanceOverTimeForm(request.POST, instance=overtime)
form = choosesubordinates(request, form, "attendance.change_attendanceovertime")
if form.is_valid():
form.save()
messages.success(request, _("Attendance account updated successfully."))
response = render(
request,
"attendance/attendance_account/update_form.html",
{"form": form},
)
return HttpResponse(
response.content.decode("utf-8") + "<script>location.reload();</script>"
)
return render(
request, "attendance/attendance_account/update_form.html", {"form": form}
)
@login_required
@permission_required("attendance.delete_AttendanceOverTime")
@require_http_methods(["POST"])
def attendance_overtime_delete(request, obj_id):
"""
This method is used to delete attendance overtime
args:
obj_id : attendance overtime id
"""
try:
AttendanceOverTime.objects.get(id=obj_id).delete()
messages.success(request, _("OT account deleted."))
except Exception as e:
messages.error(request, e)
messages.error(request, _("You cannot delete this attendance OT"))
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
@permission_required("attendance.view_attendanceactivity")
def attendance_activity_view(request):
"""
This method will render a template to view all attendance activities
"""
attendance_activities = AttendanceActivity.objects.all()
previous_data = request.environ["QUERY_STRING"]
filter_obj = AttendanceActivityFilter()
return render(
request,
"attendance/attendance_activity/attendance_activity_view.html",
{
"data": paginator_qry(attendance_activities, request.GET.get("page")),
"pd": previous_data,
"f": filter_obj,
"gp_fields": AttendanceActivityReGroup.fields,
},
)
@login_required
@permission_required("attendance.view_attendanceactivity")
def attendance_activity_search(request):
"""
This method is used to search attendance activity
"""
previous_data = request.environ["QUERY_STRING"]
field = request.GET.get("field")
attendance_activities = AttendanceActivityFilter(
request.GET,
).qs
template = "attendance/attendance_activity/activity_list.html"
if field != "" and field is not None:
field_copy = field.replace(".", "__")
attendance_activities = attendance_activities.order_by(field_copy)
template = "attendance/attendance_activity/group_by.html"
attendance_activities = filtersubordinates(
request, attendance_activities, "attendance.view_attendanceactivity"
)
attendance_activities = sortby(request, attendance_activities, "orderby")
return render(
request,
template,
{
"data": paginator_qry(attendance_activities, request.GET.get("page")),
"pd": previous_data,
"field": field,
},
)
@login_required
@permission_required("attendance.delete_attendanceactivity")
@require_http_methods(["POST", "DELTE"])
def attendance_activity_delete(request, obj_id):
"""
This method is used to delete attendance activity
args:
obj_id : attendance activity id
"""
try:
AttendanceActivity.objects.get(id=obj_id).delete()
messages.success(request, _("Attendance activity deleted"))
except Exception as e:
messages.error(request, e)
messages.error(request, _("You cannot delete this activity"))
return redirect("/attendance/attendance-activity-view")
def clock_in_attendance_and_activity(
employee,
date_today,
attendance_date,
day,
now,
shift,
minimum_hour,
start_time,
end_time,
):
"""
This method is used to create attendance activity or attendance when an employee clocks-in
args:
employee : employee instance
date_today : date
attendance_date : the date that attendance for
day : shift day
now : current time
shift : shift object
minimum_hour : minimum hour in shift schedule
start_time : start time in shift schedule
end_time : end time in shift schedule
"""
# attendance activity create
AttendanceActivity(
employee_id=employee,
attendance_date=attendance_date,
clock_in_date=date_today,
shift_day=day,
clock_in=now,
).save()
# create attendance if not exist
attendance = Attendance.objects.filter(
employee_id=employee, attendance_date=attendance_date
)
if not attendance.exists():
attendance = Attendance()
attendance.employee_id = employee
attendance.shift_id = shift
attendance.work_type_id = attendance.employee_id.employee_work_info.work_type_id
attendance.attendance_date = attendance_date
attendance.attendance_day = day
attendance.attendance_clock_in = now
attendance.attendance_clock_in_date = date_today
attendance.minimum_hour = minimum_hour
attendance.save()
# check here late come or not
late_come(attendance=attendance, start_time=start_time, end_time=end_time)
else:
attendance = attendance[0]
attendance.attendance_clock_out = None
attendance.attendance_clock_out_date = None
attendance.save()
# delete if the attendance marked the early out
early_out_instance = attendance.late_come_early_out.filter(type="early_out")
if early_out_instance.exists():
early_out_instance[0].delete()
return attendance
@login_required
def clock_in(request):
"""
This method is used to mark the attendance once per a day and multiple attendance activities.
"""
employee, work_info = employee_exists(request)
if employee and work_info is not None:
shift = work_info.shift_id
date_today = date.today()
attendance_date = date_today
day = date_today.strftime("%A").lower()
day = EmployeeShiftDay.objects.get(day=day)
now = datetime.now().strftime("%H:%M")
now_sec = strtime_seconds(now)
mid_day_sec = strtime_seconds("12:00")
minimum_hour, start_time_sec, end_time_sec = shift_schedule_today(
day=day, shift=shift
)
if start_time_sec > end_time_sec:
# night shift
# ------------------
# Night shift in Horilla consider a 24 hours from noon to next day noon,
# the shift day taken today if the attendance clocked in after 12 O clock.
if mid_day_sec > now_sec:
# Here you need to create attendance for yesterday
date_yesterday = date_today - timedelta(days=1)
day_yesterday = date_yesterday.strftime("%A").lower()
day_yesterday = EmployeeShiftDay.objects.get(day=day_yesterday)
minimum_hour, start_time_sec, end_time_sec = shift_schedule_today(
day=day_yesterday, shift=shift
)
attendance_date = date_yesterday
day = day_yesterday
clock_in_attendance_and_activity(
employee=employee,
date_today=date_today,
attendance_date=attendance_date,
day=day,
now=now,
shift=shift,
minimum_hour=minimum_hour,
start_time=start_time_sec,
end_time=end_time_sec,
)
return HttpResponse(
"""
<button class="oh-btn oh-btn--warning-outline "
hx-get="/attendance/clock-out"
hx-target='#attendance-activity-container'
hx-swap='innerHTML'><ion-icon class="oh-navbar__clock-icon mr-2
text-warning"
name="exit-outline"></ion-icon>
<span class="hr-check-in-out-text">{check_out}</span>
</button>
""".format(
check_out=_("Check-Out")
)
)
return HttpResponse(
"You Don't have work information filled or your employee detail neither entered "
)
def clock_out_attendance_and_activity(employee, date_today, now):
"""
Clock out the attendance and activity
args:
employee : employee instance
date_today : today date
now : now
"""
attendance_activities = AttendanceActivity.objects.filter(
employee_id=employee
).order_by("attendance_date", "id")
if attendance_activities.exists():
attendance_activity = attendance_activities.last()
attendance_activity.clock_out = now
attendance_activity.clock_out_date = date_today
attendance_activity.save()
attendance_activities = attendance_activities.filter(~Q(clock_out=None)).filter(
attendance_date=attendance_activity.attendance_date
)
# Here calculate the total durations between the attendance activities
duration = 0
for attendance_activity in attendance_activities:
in_datetime, out_datetime = activity_datetime(attendance_activity)
difference = out_datetime - in_datetime
days_second = difference.days * 24 * 3600
seconds = difference.seconds
total_seconds = days_second + seconds
duration = duration + total_seconds
duration = format_time(duration)
# update clock out of attendance
attendance = Attendance.objects.filter(employee_id=employee).order_by(
"-attendance_date", "-id"
)[0]
attendance.attendance_clock_out = now
attendance.attendance_clock_out_date = date_today
attendance.attendance_worked_hour = duration
attendance.save()
# Overtime calculation
attendance.attendance_overtime = overtime_calculation(attendance)
# Validate the attendance as per the condition
attendance.attendance_validated = attendance_validate(attendance)
attendance.save()
return
@login_required
def clock_out(request):
"""
This method is used to set the out date and time for attendance and attendance activity
"""
employee, work_info = employee_exists(request)
shift = work_info.shift_id
date_today = date.today()
day = date_today.strftime("%A").lower()
day = EmployeeShiftDay.objects.get(day=day)
attendance = (
Attendance.objects.filter(employee_id=employee)
.order_by("id", "attendance_date")
.last()
)
if attendance is not None:
day = attendance.attendance_day
now = datetime.now().strftime("%H:%M")
minimum_hour, start_time_sec, end_time_sec = shift_schedule_today(
day=day, shift=shift
)
early_out_instance = attendance.late_come_early_out.filter(type="early_out")
if not early_out_instance.exists():
early_out(
attendance=attendance, start_time=start_time_sec, end_time=end_time_sec
)
clock_out_attendance_and_activity(employee=employee, date_today=date_today, now=now)
return HttpResponse(
"""
<button class="oh-btn oh-btn--success-outline "
hx-get="/attendance/clock-in"
hx-target='#attendance-activity-container'
hx-swap='innerHTML'>
<ion-icon class="oh-navbar__clock-icon mr-2 text-success"
name="enter-outline"></ion-icon>
<span class="hr-check-in-out-text">{check_in}</span>
</button>
""".format(
check_in=_("Check-In")
)
)
@login_required
@manager_can_enter("attendance.view_attendancelatecomeearlyout")
def late_come_early_out_view(request):
"""
This method render template to view all late come early out entries
"""
reports = AttendanceLateComeEarlyOut.objects.all()
reports = filtersubordinates(
request, reports, "attendance.view_attendancelatecomeearlyout"
)
filter_obj = LateComeEarlyOutFilter()
return render(
request,
"attendance/late_come_early_out/reports.html",
{
"data": paginator_qry(reports, request.GET.get("page")),
"f": filter_obj,
"gp_fields": LateComeEarlyOutReGroup.fields,
},
)
@login_required
@manager_can_enter("attendance.view_attendancelatecomeearlyout")
def late_come_early_out_search(request):
"""
This method is used to search late come early out by employee.
Also include filter and pagination.
"""
field = request.GET.get("field")
previous_data = request.environ["QUERY_STRING"]
reports = LateComeEarlyOutFilter(
request.GET,
).qs
template = "attendance/late_come_early_out/report_list.html"
if field != "" and field is not None:
template = "attendance/late_come_early_out/group_by.html"
field_copy = field.replace(".", "__")
reports = reports.order_by(field_copy)
reports = filtersubordinates(
request, reports, "attendance.view_attendancelatecomeearlyout"
)
reports = sortby(request, reports, "sortby")
return render(
request,
template,
{
"data": paginator_qry(reports, request.GET.get("page")),
"pd": previous_data,
"field": field,
},
)
@login_required
@permission_required("attendance.delete_attendancelatecomeearlyout")
@require_http_methods(["POST"])
def late_come_early_out_delete(request, obj_id):
"""
This method is used to delete the late come early out instance
args:
obj_id : late come early out instance id
"""
try:
AttendanceLateComeEarlyOut.objects.get(id=obj_id).delete()
messages.success(request, _("Late-in early-out deleted"))
except Exception as e:
messages.error(request, e)
messages.error(request, _("You cannot delete this Late-in early-out"))
return redirect("/attendance/late-come-early-out-view")
@login_required
@permission_required("attendance.add_attendancevalidationcondition")
def validation_condition_create(request):
"""
This method render a form to create attendance validation conditions,
and create if the form is valid.
"""
form = AttendanceValidationConditionForm()
condition = AttendanceValidationCondition.objects.first()
if request.method == "POST":
form = AttendanceValidationConditionForm(request.POST)
if form.is_valid():
form.save()
return render(
request,
"attendance/break_point/condition.html",
{"form": form, "condition": condition},
)
@login_required
@permission_required("attendance.change_attendancevalidationcondition")
def validation_condition_update(request, obj_id):
"""
This method is used to update validation condition
Args:
obj_id : validation condition instance id
"""
condition = AttendanceValidationCondition.objects.get(id=obj_id)
form = AttendanceValidationConditionForm(instance=condition)
if request.method == "POST":
form = AttendanceValidationConditionForm(request.POST, instance=condition)
if form.is_valid():
form.save()
return render(
request,
"attendance/break_point/condition.html",
{"form": form, "condition": condition},
)
@login_required
@permission_required("attendance.change_attendancevalidationcondition")
@require_http_methods(["POST"])
def validation_condition_delete(request, obj_id):
"""
This method is used to delete created validation condition
args:
obj_id : validation condition id
"""
try:
AttendanceValidationCondition.objects.get(id=obj_id).delete()
messages.success(request, _("validation condition deleted."))
except Exception as e:
messages.error(request, e)
messages.error(request, _("You cannot delete this validation condition."))
return redirect("/attendance/validation-condition-view")
@login_required
@require_http_methods(["POST"])
@manager_can_enter("attendance.change_attendance")
def validate_bulk_attendance(request):
"""
This method is used to validate bulk of attendances
"""
ids = request.POST["ids"]
ids = json.loads(ids)
for attendance_id in ids:
attendance = Attendance.objects.get(id=attendance_id)
attendance.attendance_validated = True
attendance.save()
messages.success(request, _("Attendance validated."))
notify.send(
request.user.employee_get,
recipient=attendance.employee_id.employee_user_id,
verb=f"Your attendance for the date {attendance.attendance_date} is validated",
verb_ar=f"تم التحقق من حضورك في تاريخ {attendance.attendance_date}",
verb_de=f"Ihre Anwesenheit für das Datum {attendance.attendance_date} wurde bestätigt",
verb_es=f"Se ha validado su asistencia para la fecha {attendance.attendance_date}",
verb_fr=f"Votre présence pour la date {attendance.attendance_date} est validée",
redirect=reverse("view-my-attendance"),
icon="checkmark",
)
return JsonResponse({"message": f"{attendance.employee_id} success"})
@login_required
def validate_this_attendance(request, obj_id):
"""
This method is used to validate attendance
args:
id : attendance id
"""
attendance = Attendance.objects.get(id=obj_id)
if is_reportingmanger(request, attendance) or request.user.has_perm(
"attendance.change_attendance"
):
attendance = Attendance.objects.get(id=obj_id)
attendance.attendance_validated = True
attendance.save()
messages.success(request, _("Attendance validated."))
notify.send(
request.user.employee_get,
recipient=attendance.employee_id.employee_user_id,
verb=f"Your attendance for the date {attendance.attendance_date} is validated",
verb_ar=f"تم تحقيق حضورك في تاريخ {attendance.attendance_date}",
verb_de=f"Deine Anwesenheit für das Datum {attendance.attendance_date} ist bestätigt.",
verb_es=f"Se valida tu asistencia para la fecha {attendance.attendance_date}.",
verb_fr=f"Votre présence pour la date {attendance.attendance_date} est validée.",
redirect=reverse("view-my-attendance"),
icon="checkmark",
)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
return HttpResponse("You Dont Have Permission")
@login_required
def revalidate_this_attendance(request, obj_id):
"""
This method is used to not validate the attendance.
args:
id : attendance id
"""
attendance = Attendance.objects.get(id=obj_id)
if is_reportingmanger(request, attendance) or request.user.has_perm(
"attendance.change_attendance"
):
attendance.attendance_validated = False
attendance.save()
with contextlib.suppress(Exception):
notify.send(
request.user.employee_get,
recipient=(
attendance.employee_id.employee_work_info.reporting_manager_id.employee_user_id
),
verb=f"{attendance.employee_id} requested revalidation for \
{attendance.attendance_date} attendance",
verb_ar=f"{attendance.employee_id} طلب إعادة التحقق من حضور تاريخ {attendance.attendance_date}",
verb_de=f"{attendance.employee_id} beantragte eine Neubewertung der Teilnahme am {attendance.attendance_date}",
verb_es=f"{attendance.employee_id} solicitó la validación nuevamente para la asistencia del {attendance.attendance_date}",
verb_fr=f"{attendance.employee_id} a demandé une revalidation pour la présence du {attendance.attendance_date}",
redirect=reverse("view-my-attendance"),
icon="refresh",
)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
return HttpResponse("You Cannot Request for others attendance")
@login_required
@manager_can_enter("attendance.change_attendance")
def approve_overtime(request, obj_id):
"""
This method is used to approve attendance overtime
args:
obj_id : attendance id
"""
attendance = Attendance.objects.get(id=obj_id)
attendance.attendance_overtime_approve = True
attendance.save()
with contextlib.suppress(Exception):
notify.send(
request.user.employee_get,
recipient=attendance.employee_id.employee_user_id,
verb=f"Your {attendance.attendance_date}'s attendance overtime approved.",
verb_ar=f"تمت الموافقة على إضافة ساعات العمل الإضافية لتاريخ {attendance.attendance_date}.",
verb_de=f"Die Überstunden für den {attendance.attendance_date} wurden genehmigt.",
verb_es=f"Se ha aprobado el tiempo extra de asistencia para el {attendance.attendance_date}.",
verb_fr=f"Les heures supplémentaires pour la date {attendance.attendance_date} ont été approuvées.",
redirect=reverse("attendance-overtime-view"),
icon="checkmark",
)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
@manager_can_enter("attendance.change_attendance")
def approve_bulk_overtime(request):
"""
This method is used to approve bulk of attendance
"""
ids = request.POST["ids"]
ids = json.loads(ids)
for attendance_id in ids:
attendance = Attendance.objects.get(id=attendance_id)
attendance.attendance_overtime_approve = True
attendance.save()
messages.success(request, _("Overtime approved"))
notify.send(
request.user.employee_get,
recipient=attendance.employee_id.employee_user_id,
verb=f"Overtime approved for {attendance.attendance_date}'s attendance",
verb_ar=f"تمت الموافقة على العمل الإضافي لحضور تاريخ {attendance.attendance_date}",
verb_de=f"Überstunden für die Anwesenheit am {attendance.attendance_date} genehmigt",
verb_es=f"Horas extra aprobadas para la asistencia del {attendance.attendance_date}",
verb_fr=f"Heures supplémentaires approuvées pour la présence du {attendance.attendance_date}",
redirect=reverse("attendance-overtime-view"),
icon="checkmark",
)
return JsonResponse({"message": "Success"})
def find_on_time(request, today, week_day, department=None):
"""
This method is used to find count for on time attendances
"""
on_time = 0
attendances = Attendance.objects.filter(attendance_date=today)
attendances = filtersubordinates(request, attendances, "attendance.view_attendance")
if department is not None:
attendances = attendances.filter(
employee_id__employee_work_info__department_id=department
)
excepted_attendances = 0
for attendance in attendances:
shift = attendance.shift_id
schedules_today = shift.employeeshiftschedule_set.filter(day__day=week_day)
if schedules_today.first() is not None:
excepted_attendances = excepted_attendances + 1
late_come_obj = attendance.late_come_early_out.filter(
type="late_come"
).first()
if late_come_obj is None:
on_time = on_time + 1
return on_time
def find_late_come(today, department=None):
"""
This method is used to find count of late comers
"""
late_come_obj = AttendanceLateComeEarlyOut.objects.filter(
type="late_come", attendance_id__attendance_date=today
)
if department is not None:
late_come_obj = late_come_obj.filter(
employee_id__employee_work_info__department_id=department
)
return len(late_come_obj)
def find_expected_attendances(week_day):
"""
This method is used to find count of expected attendances for the week day
"""
schedules_today = EmployeeShiftSchedule.objects.filter(day__day=week_day)
expected_attendances = 0
for schedule in schedules_today:
shift = schedule.shift_id
expected_attendances = expected_attendances + len(
shift.employeeworkinformation_set.all()
)
return expected_attendances
def find_early_out(today, department=None):
"""
This method is used to find early out attendances and it returns query set
"""
if department is not None:
early_out_obj = AttendanceLateComeEarlyOut.objects.filter(
type="early_out",
employee_id__employee_work_info__department_id=department,
attendance_id__attendance_date=today,
)
else:
early_out_obj = AttendanceLateComeEarlyOut.objects.filter(
type="early_out", attendance_id__attendance_date=today
)
return early_out_obj
@login_required
def dashboard(request):
"""
This method is used to render individual dashboard for attendance module
"""
employees = Employee.objects.filter(
is_active=True,
).filter(~Q(employee_work_info__shift_id=None))
total_employees = len(employees)
today = datetime.today()
week_day = today.strftime("%A").lower()
on_time = find_on_time(request, today=today, week_day=week_day)
late_come_obj = find_late_come(today=today)
marked_attendances = late_come_obj + on_time
expected_attendances = find_expected_attendances(week_day=week_day)
on_time_ratio = 0
late_come_ratio = 0
marked_attendances_ratio = 0
if expected_attendances != 0:
on_time_ratio = f"{(on_time / expected_attendances) * 100:.1f}"
late_come_ratio = f"{(late_come_obj / expected_attendances) * 100:.1f}"
marked_attendances_ratio = (
f"{(marked_attendances / expected_attendances) * 100:.1f}"
)
early_outs = AttendanceLateComeEarlyOut.objects.filter(
type="early_out", attendance_id__attendance_date=today
)
return render(
request,
"attendance/dashboard/dashboard.html",
{
"total_employees": total_employees,
"on_time": on_time,
"on_time_ratio": on_time_ratio,
"late_come": late_come_obj,
"late_come_ratio": late_come_ratio,
"expected_attendances": expected_attendances,
"marked_attendances": marked_attendances,
"marked_attendances_ratio": marked_attendances_ratio,
"on_break": early_outs,
},
)
def generate_data_set(request, dept):
"""
This method is used to generate all the dashboard data
"""
today = datetime.today()
week_day = today.strftime("%A").lower()
# below method will find all the on-time attendance corresponding to the
# employee shift and shift schedule.
on_time = find_on_time(request, today=today, week_day=week_day, department=dept)
# below method will find all the late-come attendance corresponding to the
# employee shift and schedule.
late_come_obj = find_late_come(today=today, department=dept)
# below method will find all the early-out attendance corresponding to the
# employee shift and shift schedule
early_out_obj = find_early_out(department=dept, today=today)
data = {
"label": dept.department,
"data": [on_time, late_come_obj, len(early_out_obj)],
}
return data
@login_required
def dashboard_attendance(request):
"""
This method is used to render json response of dashboard data
Returns:
JsonResponse: returns data set as json
"""
labels = [
_("On Time"),
_("Late Come"),
_("On Break"),
]
data_set = []
departments = Department.objects.all()
for dept in departments:
data_set.append(generate_data_set(request, dept))
return JsonResponse({"dataSet": data_set, "labels": labels})