[UPDT] ATTENDANCE: Added condition checking for biometric app presence and its settings
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
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}"
|
||||
|
||||
33
base/templates/base/install_biometric_attendance.html
Normal file
33
base/templates/base/install_biometric_attendance.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends 'settings.html' %} {% load i18n %} {% block settings %}
|
||||
|
||||
<div class="oh-inner-sidebar-content mb-4">
|
||||
<div class="oh-inner-sidebar-content__header d-flex justify-content-between align-items-center">
|
||||
<h2 class="oh-inner-sidebar-content__title">{% trans "Biometric Attendance" %}</h2>
|
||||
</div>
|
||||
<div class="oh-switch ms-3">{% trans "Activate Biometric Attendance" %} :
|
||||
<input type="checkbox" name="is_installed" data-widget="style-widget" class="style-widget oh-switch__checkbox"
|
||||
{% if biometric.is_installed %} checked title="{% trans 'Activated' %}" {% else %} title="{% trans 'Activate' %}" {% endif %}
|
||||
id="is_installed">
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#is_installed').on('change',function(){
|
||||
var boolean = $(this).is(":checked")
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/settings/activate-biometric-attendance",
|
||||
data: {
|
||||
is_installed: boolean,
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload();
|
||||
} else {
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock settings %}
|
||||
10
base/urls.py
10
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",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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("<script>window.location.reload();</script>")
|
||||
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"})
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ sidebar_urls = [
|
||||
"resignation-requests-view",
|
||||
"action-type",
|
||||
"general-settings",
|
||||
"view-biometric-devices",
|
||||
]
|
||||
remove_urls = [
|
||||
"objective-detailed-view",
|
||||
|
||||
BIN
static/images/ui/devices.png
Normal file
BIN
static/images/ui/devices.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
202
templates/animation.html
Normal file
202
templates/animation.html
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
{% load i18n %} {% load static %}
|
||||
<style>
|
||||
@keyframes biometricPulse {
|
||||
from {
|
||||
transform: translateX(-10%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
#mask-biometric-animation {
|
||||
animation: biometricPulse 2s linear 0.1s infinite;
|
||||
}
|
||||
.hr-biometric {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
.hr-biometric-container {
|
||||
position: absolute; /* or position: relative; */
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.hr-biometric-message{
|
||||
color: #303030;
|
||||
display: block;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
<div class="hr-biometric-container">
|
||||
|
||||
<!-- SVG CODE -->
|
||||
<svg
|
||||
class="hr-biometric"
|
||||
width="1102"
|
||||
height="339"
|
||||
viewBox="0 0 1102 339"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<rect
|
||||
x="671.5"
|
||||
y="5.5"
|
||||
width="425"
|
||||
height="328"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="694.5"
|
||||
y="28.5"
|
||||
width="185"
|
||||
height="72"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="693.5"
|
||||
y="128.5"
|
||||
width="39"
|
||||
height="39"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="694.5"
|
||||
y="195.5"
|
||||
width="39"
|
||||
height="39"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="694.5"
|
||||
y="262.5"
|
||||
width="39"
|
||||
height="39"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="765.5"
|
||||
y="128.5"
|
||||
width="39"
|
||||
height="39"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="766.5"
|
||||
y="195.5"
|
||||
width="39"
|
||||
height="39"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="766.5"
|
||||
y="262.5"
|
||||
width="39"
|
||||
height="39"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="839.5"
|
||||
y="128.5"
|
||||
width="39"
|
||||
height="39"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="840.5"
|
||||
y="195.5"
|
||||
width="39"
|
||||
height="39"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="840.5"
|
||||
y="262.5"
|
||||
width="39"
|
||||
height="39"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<rect
|
||||
x="908.5"
|
||||
y="131.5"
|
||||
width="155"
|
||||
height="170"
|
||||
fill="white"
|
||||
stroke="#999999"
|
||||
stroke-width="11" />
|
||||
<line
|
||||
x1="936"
|
||||
y1="100.5"
|
||||
x2="1036"
|
||||
y2="100.5"
|
||||
stroke="#999999"
|
||||
stroke-width="7" />
|
||||
<line
|
||||
x1="936"
|
||||
y1="32.5"
|
||||
x2="1036"
|
||||
y2="32.5"
|
||||
stroke="#999999"
|
||||
stroke-width="7" />
|
||||
<line
|
||||
x1="920"
|
||||
y1="83.5"
|
||||
x2="1053"
|
||||
y2="83.5"
|
||||
stroke="#999999"
|
||||
stroke-width="7" />
|
||||
<line
|
||||
x1="919"
|
||||
y1="49.5"
|
||||
x2="1052"
|
||||
y2="49.5"
|
||||
stroke="#999999"
|
||||
stroke-width="7" />
|
||||
<line
|
||||
x1="912.023"
|
||||
y1="66.5001"
|
||||
x2="1061.02"
|
||||
y2="67.5001"
|
||||
stroke="#999999"
|
||||
stroke-width="7" />
|
||||
<mask
|
||||
id="mask0_703_126"
|
||||
style="mask-type: alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="42"
|
||||
width="606"
|
||||
height="262">
|
||||
<path
|
||||
d="M0.5 221.5H225.5L322 135.5V301.5L407.5 60.5L434.5 221.5H606"
|
||||
stroke="white"
|
||||
stroke-width="9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_703_126)">
|
||||
<rect
|
||||
x="-561"
|
||||
y="-262"
|
||||
width="669"
|
||||
height="686"
|
||||
id="mask-biometric-animation"
|
||||
fill="#E54F38" />
|
||||
</g>
|
||||
</svg>
|
||||
<!-- END OF SVG CODE -->
|
||||
<span class="hr-biometric-message">{% trans "Trying to connect..." %}</span>
|
||||
</div>
|
||||
@@ -113,7 +113,16 @@
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if biometric_app_exists %}
|
||||
<div class="oh-input-group">
|
||||
<a
|
||||
id="condition"
|
||||
href="{% url 'enable-biometric-attendance' %}"
|
||||
class="oh-inner-sidebar__link oh-dropdown__link"
|
||||
>{% trans "Biometric Attendance" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -428,6 +428,17 @@
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if biometric_app_exists and is_installed %}
|
||||
{% if perms.attendance.view_attendance or request.user|is_reportingmanager %}
|
||||
<li class="oh-sidebar__submenu-item">
|
||||
<a
|
||||
class="oh-sidebar__submenu-link"
|
||||
href="{% url 'view-biometric-devices' %}"
|
||||
>{% trans "Biometric Devices" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li class="oh-sidebar__submenu-item">
|
||||
<a
|
||||
class="oh-sidebar__submenu-link"
|
||||
|
||||
Reference in New Issue
Block a user