diff --git a/base/context_processors.py b/base/context_processors.py index 8df1b6697..08f04b2c5 100644 --- a/base/context_processors.py +++ b/base/context_processors.py @@ -164,3 +164,10 @@ def get_initial_prefix(request): instance_id = settings.id prefix = settings.badge_id_prefix return {"get_initial_prefix": prefix, "prefix_instance_id": instance_id} + + +def biometric_app_exists(request): + from django.conf import settings + + biometric_app_exists = "biometric" in settings.INSTALLED_APPS + return {"biometric_app_exists": biometric_app_exists} diff --git a/base/models.py b/base/models.py index ab32e7481..4d8bf840d 100644 --- a/base/models.py +++ b/base/models.py @@ -981,32 +981,27 @@ class DynamicEmailConfiguration(HorillaModel): default=False, verbose_name=_("Primary Mail Server") ) - host = models.CharField( - null=True, max_length=256, verbose_name=_("Email Host") - ) + host = models.CharField(null=True, max_length=256, verbose_name=_("Email Host")) - port = models.SmallIntegerField( null=True, verbose_name=_("Email Port")) + port = models.SmallIntegerField(null=True, verbose_name=_("Email Port")) from_email = models.EmailField( - null=True, max_length=256, verbose_name=_("Default From Email") + null=True, max_length=256, verbose_name=_("Default From Email") ) username = models.CharField( - null=True, max_length=256, verbose_name=_("Email Host Username"), ) display_name = models.CharField( - null=True, max_length=256, verbose_name=_("Display Name"), ) password = models.CharField( - null=True, max_length=256, verbose_name=_("Email Authentication Password"), @@ -1019,7 +1014,7 @@ class DynamicEmailConfiguration(HorillaModel): fail_silently = models.BooleanField(default=False, verbose_name=_("Fail Silently")) timeout = models.SmallIntegerField( - null=True, verbose_name=_("Email Send Timeout (seconds)") + null=True, verbose_name=_("Email Send Timeout (seconds)") ) company_id = models.OneToOneField( Company, on_delete=models.CASCADE, null=True, blank=True @@ -1034,7 +1029,7 @@ class DynamicEmailConfiguration(HorillaModel): ) ) if not self.company_id and not self.is_primary: - raise ValidationError({"company_id": _("This field is required")}) + raise ValidationError({"company_id": _("This field is required")}) def __str__(self): return self.username @@ -1359,10 +1354,19 @@ class DriverViewed(models.Model): class DashboardEmployeeCharts(HorillaModel): from employee.models import Employee - + employee = models.ForeignKey(Employee, on_delete=models.CASCADE) charts = models.JSONField(default=list, blank=True, null=True) def __str__(self): return f"{self.employee} - charts" - \ No newline at end of file + + +class BiometricAttendance(models.Model): + is_installed = models.BooleanField(default=False) + company_id = models.ForeignKey( + Company, null=True, editable=False, on_delete=models.PROTECT,related_name="biometric_enabled_company" + ) + + def __str__(self): + return f"{self.is_installed}" diff --git a/base/templates/base/install_biometric_attendance.html b/base/templates/base/install_biometric_attendance.html new file mode 100644 index 000000000..f05f8a246 --- /dev/null +++ b/base/templates/base/install_biometric_attendance.html @@ -0,0 +1,33 @@ +{% extends 'settings.html' %} {% load i18n %} {% block settings %} + +
+ +{% endblock settings %} diff --git a/base/urls.py b/base/urls.py index 2f4cf1e74..f1aed64fa 100644 --- a/base/urls.py +++ b/base/urls.py @@ -896,4 +896,14 @@ urlpatterns = [ path("driver-viewed", views.driver_viewed_status, name="driver-viewed"), path("employee-charts", views.employee_charts, name="employee-charts"), path("employee-chart-show", views.employee_chart_show, name="employee-chart-show"), + path( + "settings/enable-biometric-attendance/", + views.enable_biometric_attendance_view, + name="enable-biometric-attendance", + ), + path( + "settings/activate-biometric-attendance", + views.activate_biometric_attendance, + name="activate-biometric-attendance", + ), ] diff --git a/base/views.py b/base/views.py index e5cf40d5d..2b69f6858 100644 --- a/base/views.py +++ b/base/views.py @@ -25,6 +25,7 @@ from attendance.forms import AttendanceValidationConditionForm from attendance.methods.group_by import group_by_queryset from attendance.models import AttendanceValidationCondition, GraceTime from django.views.decorators.csrf import csrf_exempt +from django.conf import settings from horilla_audit.forms import HistoryTrackingFieldsForm from horilla_audit.models import AccountBlockUnblock, AuditTag, HistoryTrackingFields from notifications.models import Notification @@ -112,6 +113,7 @@ from base.models import ( WorkTypeRequest, Tags, WorkTypeRequestComment, + BiometricAttendance, ) from base.filters import ( RotatingShiftRequestReGroup, @@ -5499,3 +5501,35 @@ def employee_chart_show(request): return HttpResponse("") context = {"dashboard_charts": charts, "employee_chart": employee_charts.charts} return render(request, "dashboard_chart_form.html", context) + +def enable_biometric_attendance_view(request): + biometric = BiometricAttendance.objects.first() + return render( + request, + "base/install_biometric_attendance.html", + {"biometric": biometric}, + ) + + +def activate_biometric_attendance(request): + if request.method == "GET": + is_installed = request.GET.get("is_installed") + instance = BiometricAttendance.objects.first() + if not instance: + instance = BiometricAttendance.objects.create() + if is_installed == "true": + instance.is_installed = True + messages.success( + request, + _("The biometric attendance feature has been activated successfully."), + ) + else: + instance.is_installed = False + messages.info( + request, + _( + "The biometric attendance feature has been deactivated successfully." + ), + ) + instance.save() + return JsonResponse({"message": "Success"}) \ No newline at end of file diff --git a/horilla/decorators.py b/horilla/decorators.py index fe61a34e4..7e6159226 100755 --- a/horilla/decorators.py +++ b/horilla/decorators.py @@ -1,16 +1,16 @@ import logging, os from urllib.parse import urlencode -from django.http import HttpResponse, HttpResponseRedirect, Http404 -from django.core.exceptions import ObjectDoesNotExist +from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import redirect from django.urls import reverse +from django.utils.translation import gettext as _ from base.models import MultipleApprovalManagers from employee.models import Employee, EmployeeWorkInformation from django.contrib import messages from django.shortcuts import render from horilla.settings import TEMPLATES, BASE_DIR from horilla import settings -from leave.models import LeaveRequestConditionApproval +from base.models import BiometricAttendance logger = logging.getLogger(__name__) @@ -101,14 +101,14 @@ def duplicate_permission(function): is_manager = EmployeeWorkInformation.objects.filter( reporting_manager_id=employee ).exists() - + app_label = kwargs["model"]._meta.app_label model_name = kwargs["model"]._meta.model_name obj_id = kwargs["obj_id"] - object_instance = kwargs["model"].objects.filter(pk=obj_id).first() + object_instance = kwargs["model"].objects.filter(pk=obj_id).first() try: if object_instance.employee_id == employee: - return function(request, *args, **kwargs) + return function(request, *args, **kwargs) except: pass permission = f"{app_label}.add_{model_name}" @@ -239,3 +239,20 @@ def owner_can_enter(function, perm: str, model: object, manager_access=False): return render(request, "no_perm.html") return _function + + +def install_required(function): + def _function(request, *args, **kwargs): + object = BiometricAttendance.objects.all().first() + if object.is_installed: + return function(request, *args, **kwargs) + else: + messages.info( + request, + _( + "Please activate the biometric attendance feature in the settings menu." + ), + ) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + + return _function diff --git a/horilla/horilla_apps.py b/horilla/horilla_apps.py index dfc78e08c..1aadeebb3 100644 --- a/horilla/horilla_apps.py +++ b/horilla/horilla_apps.py @@ -3,7 +3,6 @@ horilla_apps This module is used to register horilla addons """ - from horilla.settings import INSTALLED_APPS from horilla import settings @@ -15,4 +14,4 @@ INSTALLED_APPS.append("haystack") INSTALLED_APPS.append("helpdesk") INSTALLED_APPS.append("offboarding") -setattr(settings, "EMAIL_BACKEND", "base.backends.ConfiguredEmailBackend") +setattr(settings,"EMAIL_BACKEND","base.backends.ConfiguredEmailBackend") \ No newline at end of file diff --git a/horilla/horilla_context_processors.py b/horilla/horilla_context_processors.py index e69de29bb..edf714ec1 100644 --- a/horilla/horilla_context_processors.py +++ b/horilla/horilla_context_processors.py @@ -0,0 +1,33 @@ +""" +horilla_context_process.py + +This module is used to register context processors without effecting the horilla/settings.py module +""" +from horilla.settings import TEMPLATES + +TEMPLATES[0]["OPTIONS"]["context_processors"].append( + "base.context_processors.get_companies", +) +TEMPLATES[0]["OPTIONS"]["context_processors"].append( + "base.context_processors.resignation_request_enabled", +) +TEMPLATES[0]["OPTIONS"]["context_processors"].append( + "base.context_processors.timerunner_enabled", +) +TEMPLATES[0]["OPTIONS"]["context_processors"].append( + "base.context_processors.intial_notice_period", +) +TEMPLATES[0]["OPTIONS"]["context_processors"].append( + "base.context_processors.check_candidate_self_tracking", +) +TEMPLATES[0]["OPTIONS"]["context_processors"].append( + "base.context_processors.check_candidate_self_tracking_rating", +) + +TEMPLATES[0]["OPTIONS"]["context_processors"].append( + "base.context_processors.get_initial_prefix", +) +TEMPLATES[0]["OPTIONS"]["context_processors"].append( + "base.context_processors.biometric_app_exists", +) + diff --git a/horilla_crumbs/context_processors.py b/horilla_crumbs/context_processors.py index c374e7b4f..0b96ec11f 100644 --- a/horilla_crumbs/context_processors.py +++ b/horilla_crumbs/context_processors.py @@ -103,6 +103,7 @@ sidebar_urls = [ "resignation-requests-view", "action-type", "general-settings", + "view-biometric-devices", ] remove_urls = [ "objective-detailed-view", diff --git a/static/images/ui/devices.png b/static/images/ui/devices.png new file mode 100644 index 000000000..45725d0e0 Binary files /dev/null and b/static/images/ui/devices.png differ diff --git a/static/images/ui/no_vacnacy.png b/static/images/ui/no_vacnacy.png deleted file mode 100644 index 2a52ec934..000000000 Binary files a/static/images/ui/no_vacnacy.png and /dev/null differ diff --git a/templates/animation.html b/templates/animation.html new file mode 100644 index 000000000..51d5522c0 --- /dev/null +++ b/templates/animation.html @@ -0,0 +1,202 @@ + +{% load i18n %} {% load static %} + +