diff --git a/attendance/models.py b/attendance/models.py index 3d39ad164..31a32ea4b 100644 --- a/attendance/models.py +++ b/attendance/models.py @@ -901,6 +901,13 @@ class AttendanceGeneralSetting(HorillaModel): """ time_runner = models.BooleanField(default=True) + enable_check_in = models.BooleanField( + default=True, + verbose_name=_("Enable Check in/Check out"), + help_text=_( + "Enabling this feature allows employees to record their attendance using the Check-In/Check-Out button." + ), + ) company_id = models.ForeignKey(Company, on_delete=models.CASCADE, null=True) diff --git a/attendance/templates/attendance/components/in_out_component.html b/attendance/templates/attendance/components/in_out_component.html index ce85c097d..c5455d336 100644 --- a/attendance/templates/attendance/components/in_out_component.html +++ b/attendance/templates/attendance/components/in_out_component.html @@ -1,35 +1,39 @@ {% load horillafilters i18n %} {% if request.user.employee_get %} {% with get_forecasted_at_work=request.user.employee_get.get_forecasted_at_work %} - {% if request.session.selected_company == "all" or request.user.employee_get.employee_work_info.company_id.id == request.session.selected_company|default:"-1"|add:"0" %} - - {% if request.user|is_clocked_in and get_forecasted_at_work.has_attendance %} - + + {% else %} + + {% if enabled_timerunner %} + + {% endif %} + {% endif %} {% endif %} - - - {% else %} - - {% if enabled_timerunner %} - - {% endif %} - {% endif %} {% endif %} {% endwith %} {% endif %} diff --git a/attendance/templates/attendance/settings/check_in_check_out_enable_form.html b/attendance/templates/attendance/settings/check_in_check_out_enable_form.html new file mode 100644 index 000000000..2c3319942 --- /dev/null +++ b/attendance/templates/attendance/settings/check_in_check_out_enable_form.html @@ -0,0 +1,67 @@ +{% extends 'settings.html' %}{% block settings %}{% load static %}{% load widget_tweaks %}{% load i18n %} + + +
+
+

{% trans 'Enable Check In/Check out' %}

+ {% comment %} {% if not condition and perms.attendance.add_attendancevalidationcondition%} + + {% endif %} {% endcomment %} +
+
+
+
+
+
+ {% trans 'Company' %} +
+
+ {% trans 'Check in/Check out' %} +
+
+
+
+ {% for att_setting in attendance_settings %} +
+
+ {% if att_setting.company_id %} + {{att_setting.company_id}} + {% else %} + {% trans "All company" %} + {% endif %} +
+
+
+ {% if perms.attendance.change_attendancegeneralsetting %} + + {% else %} + + {% endif %} + +
+
+
+ {% endfor %} +
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/attendance/urls.py b/attendance/urls.py index 4307c8fd4..6d392df82 100644 --- a/attendance/urls.py +++ b/attendance/urls.py @@ -465,6 +465,16 @@ urlpatterns = [ views.enable_disable_tracking_late_come_early_out, name="enable-disable-tracking-late-come-early-out", ), + path( + "check-in-check-out-setting/", + views.check_in_check_out_setting, + name="check-in-check-out-setting", + ), + path( + "enable-disable-check-in", + views.enable_disable_check_in, + name="enable-disable-check-in", + ), path( "grace-settings-view/", views.grace_time_view, diff --git a/attendance/views/clock_in_out.py b/attendance/views/clock_in_out.py index 6a21ab9a5..2a9f4be71 100644 --- a/attendance/views/clock_in_out.py +++ b/attendance/views/clock_in_out.py @@ -10,6 +10,7 @@ import logging logger = logging.getLogger(__name__) from datetime import date, datetime, timedelta +from django.contrib import messages from django.db.models import Q from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ @@ -25,6 +26,7 @@ from attendance.methods.utils import ( from attendance.models import ( Attendance, AttendanceActivity, + AttendanceGeneralSetting, AttendanceLateComeEarlyOut, GraceTime, ) @@ -33,7 +35,7 @@ from base.context_processors import ( enable_late_come_early_out_tracking, timerunner_enabled, ) -from base.models import AttendanceAllowedIP, EmployeeShiftDay +from base.models import AttendanceAllowedIP, Company, EmployeeShiftDay from horilla.decorators import hx_request_required, login_required from horilla.horilla_middlewares import _thread_locals @@ -196,134 +198,144 @@ def clock_in(request): """ This method is used to mark the attendance once per a day and multiple attendance activities. """ - allowed_attendance_ips = AttendanceAllowedIP.objects.first() + # check wether check in/check out feature is enabled + selected_company = request.session.get("selected_company") + company = Company.objects.filter(id=selected_company).first() + attendance_general_settings = AttendanceGeneralSetting.objects.filter( + company_id=company + ).first() + if attendance_general_settings and attendance_general_settings.enable_check_in: + allowed_attendance_ips = AttendanceAllowedIP.objects.first() - # 'not request.__dict__.get("datetime")' used to check if the request is from a biometric device - if ( - not request.__dict__.get("datetime") - and allowed_attendance_ips - and allowed_attendance_ips.is_enabled - ): + # 'not request.__dict__.get("datetime")' used to check if the request is from a biometric device + if ( + not request.__dict__.get("datetime") + and allowed_attendance_ips + and allowed_attendance_ips.is_enabled + ): - x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") - ip = request.META.get("REMOTE_ADDR") - if x_forwarded_for: - ip = x_forwarded_for.split(",")[0] + x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") + ip = request.META.get("REMOTE_ADDR") + if x_forwarded_for: + ip = x_forwarded_for.split(",")[0] - allowed_ips = allowed_attendance_ips.additional_data.get("allowed_ips", []) - ip_allowed = False - for allowed_ip in allowed_ips: - try: - if ipaddress.ip_address(ip) in ipaddress.ip_network( - allowed_ip, strict=False - ): - ip_allowed = True - break - except ValueError: - continue + allowed_ips = allowed_attendance_ips.additional_data.get("allowed_ips", []) + ip_allowed = False + for allowed_ip in allowed_ips: + try: + if ipaddress.ip_address(ip) in ipaddress.ip_network( + allowed_ip, strict=False + ): + ip_allowed = True + break + except ValueError: + continue - if not ip_allowed: - return HttpResponse(_("You cannot mark attendance from this network")) + if not ip_allowed: + return HttpResponse(_("You cannot mark attendance from this network")) - employee, work_info = employee_exists(request) - datetime_now = datetime.now() - if request.__dict__.get("datetime"): - datetime_now = request.datetime - if employee and work_info is not None: - shift = work_info.shift_id - date_today = date.today() - if request.__dict__.get("date"): - date_today = request.date - attendance_date = date_today - day = date_today.strftime("%A").lower() - day = EmployeeShiftDay.objects.get(day=day) - now = datetime.now().strftime("%H:%M") - if request.__dict__.get("time"): - now = request.time.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. + employee, work_info = employee_exists(request) + datetime_now = datetime.now() + if request.__dict__.get("datetime"): + datetime_now = request.datetime + if employee and work_info is not None: + shift = work_info.shift_id + date_today = date.today() + if request.__dict__.get("date"): + date_today = request.date + attendance_date = date_today + day = date_today.strftime("%A").lower() + day = EmployeeShiftDay.objects.get(day=day) + now = datetime.now().strftime("%H:%M") + if request.__dict__.get("time"): + now = request.time.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 + 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 + 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 + attendance = 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, + in_datetime=datetime_now, + ) + script = "" + hidden_label = "" + time_runner_enabled = timerunner_enabled(request)["enabled_timerunner"] + mouse_in = "" + mouse_out = "" + if time_runner_enabled: + script = """ + + """.format( + at_work_seconds_forecasted=employee.get_forecasted_at_work()[ + "forecasted_at_work_seconds" + ] ) - attendance_date = date_yesterday - day = day_yesterday - attendance = 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, - in_datetime=datetime_now, - ) - script = "" - hidden_label = "" - time_runner_enabled = timerunner_enabled(request)["enabled_timerunner"] - mouse_in = "" - mouse_out = "" - if time_runner_enabled: - script = """ - - """.format( - at_work_seconds_forecasted=employee.get_forecasted_at_work()[ - "forecasted_at_work_seconds" - ] - ) - hidden_label = """ - style="display:none" - """ - mouse_in = """ onmouseenter = "$(this).find('span').show();$(this).find('.time-runner').hide();" """ - mouse_out = """ onmouseleave = "$(this).find('span').hide();$(this).find('.time-runner').show();" """ + hidden_label = """ + style="display:none" + """ + mouse_in = """ onmouseenter = "$(this).find('span').show();$(this).find('.time-runner').hide();" """ + mouse_out = """ onmouseleave = "$(this).find('span').hide();$(this).find('.time-runner').show();" """ + return HttpResponse( + """ + + {script} + """.format( + check_out=_("Check-Out"), + script=script, + hidden_label=hidden_label, + mouse_in=mouse_in, + mouse_out=mouse_out, + ) + ) return HttpResponse( - """ - - {script} - """.format( - check_out=_("Check-Out"), - script=script, - hidden_label=hidden_label, - mouse_in=mouse_in, - mouse_out=mouse_out, + _( + "You Don't have work information filled or your employee detail neither entered " ) ) - return HttpResponse( - _( - "You Don't have work information filled or your employee detail neither entered " - ) - ) + else: + messages.error(request, _("Check in/Check out feature is not enabled.")) + return HttpResponse("") def clock_out_attendance_and_activity(employee, date_today, now, out_datetime=None): @@ -458,103 +470,113 @@ def clock_out(request): """ This method is used to set the out date and time for attendance and attendance activity """ - datetime_now = datetime.now() - if request.__dict__.get("datetime"): - datetime_now = request.datetime - employee, work_info = employee_exists(request) - shift = work_info.shift_id - date_today = date.today() - if request.__dict__.get("date"): - date_today = request.date - 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") - if request.__dict__.get("time"): - now = request.time.strftime("%H:%M") - minimum_hour, start_time_sec, end_time_sec = shift_schedule_today( - day=day, shift=shift - ) - attendance = clock_out_attendance_and_activity( - employee=employee, date_today=date_today, now=now, out_datetime=datetime_now - ) - if attendance: - early_out_instance = attendance.late_come_early_out.filter(type="early_out") - is_night_shift = attendance.is_night_shift() - next_date = attendance.attendance_date + timedelta(days=1) - if not early_out_instance.exists(): - if is_night_shift: - now_sec = strtime_seconds(now) - mid_sec = strtime_seconds("12:00") + # check wether check in/check out feature is enabled + selected_company = request.session.get("selected_company") + company = Company.objects.filter(id=selected_company).first() + attendance_general_settings = AttendanceGeneralSetting.objects.filter( + company_id=company + ).first() + if attendance_general_settings and attendance_general_settings.enable_check_in: + datetime_now = datetime.now() + if request.__dict__.get("datetime"): + datetime_now = request.datetime + employee, work_info = employee_exists(request) + shift = work_info.shift_id + date_today = date.today() + if request.__dict__.get("date"): + date_today = request.date + 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") + if request.__dict__.get("time"): + now = request.time.strftime("%H:%M") + minimum_hour, start_time_sec, end_time_sec = shift_schedule_today( + day=day, shift=shift + ) + attendance = clock_out_attendance_and_activity( + employee=employee, date_today=date_today, now=now, out_datetime=datetime_now + ) + if attendance: + early_out_instance = attendance.late_come_early_out.filter(type="early_out") + is_night_shift = attendance.is_night_shift() + next_date = attendance.attendance_date + timedelta(days=1) + if not early_out_instance.exists(): + if is_night_shift: + now_sec = strtime_seconds(now) + mid_sec = strtime_seconds("12:00") - if (attendance.attendance_date == date_today) or ( - # check is next day mid - mid_sec >= now_sec - and date_today == next_date - ): + if (attendance.attendance_date == date_today) or ( + # check is next day mid + mid_sec >= now_sec + and date_today == next_date + ): + early_out( + attendance=attendance, + start_time=start_time_sec, + end_time=end_time_sec, + shift=shift, + ) + elif attendance.attendance_date == date_today: early_out( attendance=attendance, start_time=start_time_sec, end_time=end_time_sec, shift=shift, ) - elif attendance.attendance_date == date_today: - early_out( - attendance=attendance, - start_time=start_time_sec, - end_time=end_time_sec, - shift=shift, - ) - script = "" - hidden_label = "" - time_runner_enabled = timerunner_enabled(request)["enabled_timerunner"] - mouse_in = "" - mouse_out = "" - if time_runner_enabled: - script = """ - - """.format( - at_work_seconds_forecasted=employee.get_forecasted_at_work()[ - "forecasted_at_work_seconds" - ], - ) - hidden_label = """ - style="display:none" - """ - mouse_in = """ onmouseenter="$(this).find('div.at-work-seconds').hide();$(this).find('span').show();" """ - mouse_out = """onmouseleave="$(this).find('div.at-work-seconds').show();$(this).find('span').hide();" """ - return HttpResponse( - """ - - {script} + script = "" + hidden_label = "" + time_runner_enabled = timerunner_enabled(request)["enabled_timerunner"] + mouse_in = "" + mouse_out = "" + if time_runner_enabled: + script = """ + """.format( - check_in=_("Check-In"), - script=script, - hidden_label=hidden_label, - mouse_in=mouse_in, - mouse_out=mouse_out, + at_work_seconds_forecasted=employee.get_forecasted_at_work()[ + "forecasted_at_work_seconds" + ], + ) + hidden_label = """ + style="display:none" + """ + mouse_in = """ onmouseenter="$(this).find('div.at-work-seconds').hide();$(this).find('span').show();" """ + mouse_out = """onmouseleave="$(this).find('div.at-work-seconds').show();$(this).find('span').hide();" """ + return HttpResponse( + """ + + {script} + """.format( + check_in=_("Check-In"), + script=script, + hidden_label=hidden_label, + mouse_in=mouse_in, + mouse_out=mouse_out, + ) ) - ) + else: + messages.error(request, _("Check in/Check out feature is not enabled.")) + return HttpResponse("") diff --git a/attendance/views/views.py b/attendance/views/views.py index 942246e62..713a4f743 100644 --- a/attendance/views/views.py +++ b/attendance/views/views.py @@ -2494,6 +2494,42 @@ def enable_disable_tracking_late_come_early_out(request): return HttpResponse("") +@login_required +def check_in_check_out_setting(request): + """ + Check in check out setting + """ + attendance_settings = AttendanceGeneralSetting.objects.all() + return render( + request, + "attendance/settings/check_in_check_out_enable_form.html", + {"attendance_settings": attendance_settings}, + ) + + +@login_required +def enable_disable_check_in(request): + """ + Enables or disables check in check out. + """ + if request.method == "POST": + if request.POST.get("isChecked") and request.POST.get("isChecked") == "false": + enable = False + else: + enable = True + setting_id = request.POST.get("setting_Id") + attendance_gen_setting = AttendanceGeneralSetting.objects.filter( + id=setting_id + ).first() + attendance_gen_setting.enable_check_in = enable + attendance_gen_setting.save() + message = _("enabled") if enable else _("disabled") + messages.success( + request, _("Check in/Check out {} successfully").format(message) + ) + return HttpResponse("") + + @login_required @permission_required("attendance.view_attendancevalidationcondition") def grace_time_view(request): diff --git a/base/templates/announcement/announcements_list.html b/base/templates/announcement/announcements_list.html index 167256a6e..b60488811 100644 --- a/base/templates/announcement/announcements_list.html +++ b/base/templates/announcement/announcements_list.html @@ -41,4 +41,4 @@ {% endif %}
- \ No newline at end of file + diff --git a/base/templatetags/horillafilters.py b/base/templatetags/horillafilters.py index fb5ae188d..bbff108cd 100644 --- a/base/templatetags/horillafilters.py +++ b/base/templatetags/horillafilters.py @@ -16,7 +16,7 @@ from django.template import TemplateSyntaxError from django.template.defaultfilters import register from django.utils.translation import gettext as _ -from base.models import EmployeeShiftSchedule +from base.models import Company, EmployeeShiftSchedule from employee.methods.duration_methods import strtime_seconds from horilla.horilla_middlewares import _thread_locals from horilla.methods import get_horilla_model_class @@ -307,3 +307,32 @@ def currency_symbol_position(amount): currency_symbol = f"{currency} {amount}" return currency_symbol + + +@register.filter(name="is_check_in_enabled") +def is_check_in_enabled(request): + """ + This method checks whether the check-in/check-out feature is enabled. + """ + from attendance.models import AttendanceGeneralSetting + + # from base.models import Company # Assuming Company is the correct model for `selected_company` + selected_company = request.session.get("selected_company") + if not selected_company: + return False # Safeguard if session key is missing + + # Fetch the settings based on the selected company + if selected_company == "all": + attendance_settings, created = AttendanceGeneralSetting.objects.get_or_create( + company_id=None + ) + else: + company = Company.objects.filter(id=selected_company).first() + if not company: + return False # Return False if the company doesn't exist + attendance_settings, created = AttendanceGeneralSetting.objects.get_or_create( + company_id=company + ) + + # Check if check-in is enabled + return bool(attendance_settings and attendance_settings.enable_check_in) diff --git a/templates/settings.html b/templates/settings.html index 55e427f48..ed15325b4 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -367,6 +367,16 @@ > {% endif %} +
+ {% if perms.attendance.view_attendancevalidationcondition %} + {% trans "Check In/Check Out" %} + {% endif %} +
{% if perms.attendance.view_attendancevalidationcondition %}