diff --git a/asset/apps.py b/asset/apps.py index 9c0c19427..ab042f3bc 100644 --- a/asset/apps.py +++ b/asset/apps.py @@ -18,3 +18,13 @@ class AssetConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "asset" + + def ready(self): + from django.urls import include, path + + from horilla.urls import urlpatterns + + urlpatterns.append( + path("asset/", include("asset.urls")), + ) + super().ready() diff --git a/asset/forms.py b/asset/forms.py index 9faa18f57..652ebb33c 100644 --- a/asset/forms.py +++ b/asset/forms.py @@ -411,6 +411,9 @@ class AssetReturnForm(ModelForm): } def __init__(self, *args, **kwargs): + """ + Initializes the AssetReturnForm with initial values and custom field settings. + """ super(AssetReturnForm, self).__init__(*args, **kwargs) self.fields["return_date"].initial = date.today() diff --git a/asset/models.py b/asset/models.py index eeb871d36..fcd31a8c6 100644 --- a/asset/models.py +++ b/asset/models.py @@ -210,7 +210,9 @@ class AssetRequest(HorillaModel): null=False, blank=False, ) - asset_category_id = models.ForeignKey(AssetCategory, on_delete=models.PROTECT) + asset_category_id = models.ForeignKey( + AssetCategory, on_delete=models.PROTECT, verbose_name=_("Asset Category") + ) asset_request_date = models.DateField(auto_now_add=True) description = models.TextField(null=True, blank=True, max_length=255) asset_request_status = models.CharField( diff --git a/asset/static/src/asset/dashboard.js b/asset/static/src/asset/dashboard.js index 6d6452ed7..c8179c278 100644 --- a/asset/static/src/asset/dashboard.js +++ b/asset/static/src/asset/dashboard.js @@ -1,3 +1,4 @@ +staticUrl = $("#statiUrl").attr("data-url"); $(document).ready(function() { function available_asset_chart(dataSet) { var Asset_available_chart = document.getElementById("assetAvailableChart"); @@ -83,7 +84,7 @@ function emptyAssetAvialabeChart(assetAvailableChartChart,args,options){ var noDataImage = new Image(); noDataImage.src = assetAvailableChartChart.data.emptyImageSrc ? assetAvailableChartChart.data.emptyImageSrc - : "/static/images/ui/joiningchart.png"; + : staticUrl +"images/ui/joiningchart.png"; message = assetAvailableChartChart.data.message ? assetAvailableChartChart.data.message diff --git a/asset/templates/asset/asset_information.html b/asset/templates/asset/asset_information.html index 74954c715..542da05b9 100644 --- a/asset/templates/asset/asset_information.html +++ b/asset/templates/asset/asset_information.html @@ -117,4 +117,4 @@ - + diff --git a/asset/templates/asset/asset_return_form.html b/asset/templates/asset/asset_return_form.html index b7d80cec0..0a8a2c2b9 100644 --- a/asset/templates/asset/asset_return_form.html +++ b/asset/templates/asset/asset_return_form.html @@ -1,60 +1,89 @@ -{% load i18n %} +{% load i18n %} {% load horillafilters %}
- - -
{% trans "Asset Return Form" %}
-
+ + +
{% trans "Asset Return Form" %}
+
-
- {% csrf_token %} -
-
- - {{asset_return_form.return_status}} -
-
- - {{asset_return_form.return_date}} - {{asset_return_form.return_date.errors}} -
-
- - {{asset_return_form.return_condition}} -
-
- - {{asset_return_form.return_images}} -
-
- - {% if perms.payroll.add_loanaccount %} - - {% endif %} - -
-
-
+
+ {% csrf_token %} +
+
+ + {{asset_return_form.return_status}} +
+
+ + {{asset_return_form.return_date}} + {{asset_return_form.return_date.errors}} +
+
+ + {{asset_return_form.return_condition}} +
+
+ + {{asset_return_form.return_images}} +
+
+ + {% if "payroll"|app_installed %} + {% if perms.payroll.add_loanaccount %} + + {% endif %} + {% endif %} + +
+
+
diff --git a/asset/templates/asset_history/group_by.html b/asset/templates/asset_history/group_by.html index 3105ed6e6..810d800d7 100644 --- a/asset/templates/asset_history/group_by.html +++ b/asset/templates/asset_history/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %}
{% for asset_history_list in asset_assignments %} diff --git a/asset/templates/request_allocation/group_by.html b/asset/templates/request_allocation/group_by.html index 2ed210612..d81ab5cd9 100644 --- a/asset/templates/request_allocation/group_by.html +++ b/asset/templates/request_allocation/group_by.html @@ -1,5 +1,5 @@ {% load i18n %} -{% include 'filter_tags.html' %}{% load attendancefilters %} +{% include 'filter_tags.html' %}{% load horillafilters %}
diff --git a/asset/urls.py b/asset/urls.py index c87b71efc..0bb091029 100644 --- a/asset/urls.py +++ b/asset/urls.py @@ -178,4 +178,20 @@ urlpatterns = [ views.asset_history_search, name="asset-history-search", ), + path("asset-tab/", views.asset_tab, name="asset-tab"), + path( + "profile-asset-tab/", + views.profile_asset_tab, + name="profile-asset-tab", + ), + path( + "asset-request-tab/", + views.asset_request_tab, + name="asset-request-tab", + ), + path( + "dashboard-asset-request-approve", + views.dashboard_asset_request_approve, + name="dashboard-asset-request-approve", + ), ] diff --git a/asset/views.py b/asset/views.py index 8933ac589..594032a07 100644 --- a/asset/views.py +++ b/asset/views.py @@ -55,11 +55,13 @@ from base.methods import ( ) from base.models import Company from base.views import paginator_qry -from employee.models import EmployeeWorkInformation +from employee.models import Employee, EmployeeWorkInformation +from horilla import settings from horilla.decorators import ( hx_request_required, login_required, manager_can_enter, + owner_can_enter, permission_required, ) from horilla.group_by import group_by_queryset @@ -1422,7 +1424,7 @@ def asset_available_chart(request): "labels": labels, "dataset": dataset, "message": _("Oops!! No Asset found..."), - "emptyImageSrc": "/static/images/ui/asset.png", + "emptyImageSrc": f"/{settings.STATIC_URL}images/ui/asset.png", } return JsonResponse(response) @@ -1452,7 +1454,7 @@ def asset_category_chart(request): "labels": labels, "dataset": dataset, "message": _("Oops!! No Asset found..."), - "emptyImageSrc": "/static/images/ui/asset.png", + "emptyImageSrc": f"/{settings.STATIC_URL}images/ui/asset.png", } return JsonResponse(response) @@ -1568,3 +1570,97 @@ def asset_history_search(request): "requests_ids": requests_ids, }, ) + + +@login_required +@owner_can_enter("asset.view_asset", Employee) +def asset_tab(request, emp_id): + """ + This function is used to view asset tab of an employee in employee individual view. + + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. + + Returns: return asset-tab template + + """ + employee = Employee.objects.get(id=emp_id) + assets_requests = employee.requested_employee.all() + assets = employee.allocated_employee.all() + assets_ids = ( + json.dumps([instance.id for instance in assets]) if assets else json.dumps([]) + ) + context = { + "assets": assets, + "requests": assets_requests, + "assets_ids": assets_ids, + "employee": emp_id, + } + return render(request, "tabs/asset-tab.html", context=context) + + +@login_required +@hx_request_required +def profile_asset_tab(request, emp_id): + """ + This function is used to view asset tab of an employee in employee profile view. + + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. + + Returns: return profile-asset-tab template + + """ + employee = Employee.objects.get(id=emp_id) + assets = employee.allocated_employee.all() + assets_ids = json.dumps([instance.id for instance in assets]) + context = { + "assets": assets, + "assets_ids": assets_ids, + } + return render(request, "tabs/profile-asset-tab.html", context=context) + + +@login_required +@hx_request_required +def asset_request_tab(request, emp_id): + """ + This function is used to view asset request tab of an employee in employee individual view. + + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. + + Returns: return asset-request-tab template + + """ + employee = Employee.objects.get(id=emp_id) + assets_requests = employee.requested_employee.all() + context = {"asset_requests": assets_requests, "emp_id": emp_id} + return render(request, "tabs/asset-request-tab.html", context=context) + + +@login_required +def dashboard_asset_request_approve(request): + + asset_requests = AssetRequest.objects.filter( + asset_request_status="Requested", requested_employee_id__is_active=True + ) + asset_requests = filtersubordinates( + request, + asset_requests, + "asset.change_assetrequest", + field="requested_employee_id", + ) + requests_ids = json.dumps([instance.id for instance in asset_requests]) + + return render( + request, + "request_and_approve/asset_requests_approve.html", + { + "asset_requests": asset_requests, + "requests_ids": requests_ids, + }, + ) diff --git a/attendance/admin.py b/attendance/admin.py index 32114ecab..67b561010 100644 --- a/attendance/admin.py +++ b/attendance/admin.py @@ -4,6 +4,7 @@ admin.py This page is used to register attendance models with admins site. """ +from django.apps import apps from django.contrib import admin from .models import ( @@ -14,7 +15,7 @@ from .models import ( AttendanceRequestComment, AttendanceValidationCondition, GraceTime, - PenaltyAccount, + WorkRecords, ) # Register your models here. @@ -23,6 +24,6 @@ admin.site.register(AttendanceActivity) admin.site.register(AttendanceOverTime) admin.site.register(AttendanceLateComeEarlyOut) admin.site.register(AttendanceValidationCondition) -admin.site.register(PenaltyAccount) admin.site.register(GraceTime) admin.site.register(AttendanceRequestComment) +admin.site.register(WorkRecords) diff --git a/attendance/apps.py b/attendance/apps.py index ed1c78cee..5608eecc0 100644 --- a/attendance/apps.py +++ b/attendance/apps.py @@ -4,3 +4,13 @@ from django.apps import AppConfig class AttendanceConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "attendance" + + def ready(self): + from django.urls import include, path + + from horilla.urls import urlpatterns + + urlpatterns.append( + path("attendance/", include("attendance.urls")), + ) + super().ready() diff --git a/attendance/filters.py b/attendance/filters.py index 1c4354bce..fc135987a 100644 --- a/attendance/filters.py +++ b/attendance/filters.py @@ -9,6 +9,7 @@ import uuid import django_filters from django import forms +from django.apps import apps from django.db.models import OuterRef, Subquery from django.forms import DateTimeInput from django.utils.translation import gettext_lazy as _ @@ -18,7 +19,6 @@ from attendance.models import ( AttendanceActivity, AttendanceLateComeEarlyOut, AttendanceOverTime, - PenaltyAccount, strtime_seconds, ) from base.filters import FilterSet @@ -246,16 +246,6 @@ class LateComeEarlyOutFilter(FilterSet): self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}" -class PenaltyFilter(FilterSet): - """ - PenaltyFilter - """ - - class Meta: - model = PenaltyAccount - fields = "__all__" - - class AttendanceActivityFilter(FilterSet): """ Filter set class for AttendanceActivity model diff --git a/attendance/forms.py b/attendance/forms.py index ec865e019..b0266ce7b 100644 --- a/attendance/forms.py +++ b/attendance/forms.py @@ -30,7 +30,9 @@ from collections import OrderedDict from typing import Any, Dict from django import forms +from django.apps import apps from django.core.exceptions import ValidationError +from django.db.models.query import QuerySet from django.forms import DateTimeInput from django.template.loader import render_to_string from django.utils.html import format_html @@ -46,22 +48,19 @@ from attendance.models import ( AttendanceRequestFile, AttendanceValidationCondition, GraceTime, - PenaltyAccount, + WorkRecords, attendance_date_validate, strtime_seconds, validate_time_format, ) from base.forms import MultipleFileField -from base.methods import reload_queryset +from base.methods import get_working_days, reload_queryset from base.models import Company, EmployeeShift from employee.filters import EmployeeFilter from employee.models import Employee from horilla import horilla_middlewares from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField from horilla_widgets.widgets.select_widgets import HorillaMultiSelectWidget -from leave.filters import LeaveRequestFilter -from leave.models import LeaveType -from payroll.methods.methods import get_working_days logger = logging.getLogger(__name__) @@ -756,27 +755,6 @@ class AttendanceExportForm(forms.Form): ) -class PenaltyAccountForm(ModelForm): - """ - PenaltyAccountForm - """ - - class Meta: - model = PenaltyAccount - fields = "__all__" - exclude = ["is_active"] - - def __init__(self, *args, **kwargs): - employee = kwargs.pop("employee", None) - super().__init__(*args, **kwargs) - if employee: - available_leaves = employee.available_leave.all() - assigned_leave_types = LeaveType.objects.filter( - id__in=available_leaves.values_list("leave_type_id", flat=True) - ) - self.fields["leave_type_id"].queryset = assigned_leave_types - - class LateComeEarlyOutExportForm(forms.Form): model_fields = AttendanceLateComeEarlyOut._meta.get_fields() field_choices_1 = [ @@ -889,15 +867,20 @@ def get_date_list(employee_id, from_date, to_date): attendance_dates = [] if len(working_date_list) > 0: # filter through approved leave of employee - approved_leave_dates_filtered = LeaveRequestFilter( - data={ - "from_date": working_date_list[0], - "to_date": working_date_list[-1], - "employee_id": employee_id, - "status": "approved", - } - ) - approved_leave_dates_filtered = approved_leave_dates_filtered.qs + if apps.is_installed("leave"): + from leave.filters import LeaveRequestFilter + + approved_leave_dates_filtered = LeaveRequestFilter( + data={ + "from_date": working_date_list[0], + "to_date": working_date_list[-1], + "employee_id": employee_id, + "status": "approved", + } + ) + approved_leave_dates_filtered = approved_leave_dates_filtered.qs + else: + approved_leave_dates_filtered = QuerySet().none() approved_leave_dates = [] # Extract the list of approved leave dates if len(approved_leave_dates_filtered) > 0: @@ -1078,3 +1061,17 @@ class BulkAttendanceRequestForm(ModelForm): instance.save() return instance + + +class WorkRecordsForm(ModelForm): + """ + WorkRecordForm + """ + + class Meta: + """ + Meta class for additional options + """ + + fields = "__all__" + model = WorkRecords diff --git a/attendance/methods/utils.py b/attendance/methods/utils.py new file mode 100644 index 000000000..ec06ca1c8 --- /dev/null +++ b/attendance/methods/utils.py @@ -0,0 +1,476 @@ +""" +utils.py + +This module is used write custom methods +""" + +import calendar +from datetime import datetime, timedelta + +from django.db import models +from django.db.models import Q, Sum +from django.http import HttpResponse +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ +from django.core.paginator import Paginator + +from base.models import WEEK_DAYS, CompanyLeaves, Holidays +from base.methods import get_pagination + +MONTH_MAPPING = { + "january": 1, + "february": 2, + "march": 3, + "april": 4, + "may": 5, + "june": 6, + "july": 7, + "august": 8, + "september": 9, + "october": 10, + "november": 11, + "december": 12, +} + + +def format_time(seconds): + """ + this method is used to formate seconds to H:M and return it + args: + seconds : seconds + """ + + hour = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + seconds = int((seconds % 3600) % 60) + return f"{hour:02d}:{minutes:02d}" + + +def strtime_seconds(time): + """ + this method is used reconvert time in H:M formate string back to seconds and return it + args: + time : time in H:M format + """ + + ftr = [3600, 60, 1] + return sum(a * b for a, b in zip(ftr, map(int, time.split(":")))) + + +def get_diff_obj(first_instance, other_instance, exclude_fields=None): + """ + Compare the fields of two instances and identify the changes. + + Args: + first_instance: The first instance to compare. + other_instance: The second instance to compare. + exclude_fields: A list of field names to exclude from comparison (optional). + + Returns: + A dictionary of changed fields with their old and new values. + """ + difference = {} + + fields_to_compare = first_instance._meta.fields + + if exclude_fields: + fields_to_compare = [ + field for field in fields_to_compare if field.name not in exclude_fields + ] + + for field in fields_to_compare: + old_value = getattr(first_instance, field.name) + new_value = getattr(other_instance, field.name) + + if old_value != new_value: + difference[field.name] = (old_value, new_value) + + return difference + + +def get_diff_dict(first_dict, other_dict, model=None): + """ + Compare two dictionaries and identify differing key-value pairs. + + Args: + first_dict: The first dictionary to compare. + other_dict: The second dictionary to compare. + model: The model class + + Returns: + A dictionary of differing keys with their old and new values. + """ + # model is passed as argument if any need of verbose name instead of field name + difference = {} + if model is None: + for key in first_dict: + if first_dict[key] != other_dict[key]: + # get the verbose name of the field + difference[key] = (first_dict[key], other_dict[key]) + return difference + for key in first_dict: + if first_dict[key] != other_dict[key]: + # get the verbose name of the field + field = model._meta.get_field(key) + verb_key = field.verbose_name + value = first_dict[key] + other_value = other_dict[key] + if isinstance(field, models.DateField): + if value is not None and value != "None": + value = datetime.strptime(value, "%Y-%m-%d").strftime("%d %b %Y") + if other_value is not None and other_value != "None": + other_value = datetime.strptime(other_value, "%Y-%m-%d").strftime( + "%d %b %Y" + ) + elif isinstance(field, models.TimeField): + if value is not None and value != "None": + if len(value.split(":")) == 2: + value = value + ":00" + value = datetime.strptime(value, "%H:%M:%S").strftime("%I:%M %p") + if other_value is not None and value != "None": + if len(other_value.split(":")) == 2: + other_value = other_value + ":00" + if other_value != "None": + other_value = datetime.strptime( + other_value, "%H:%M:%S" + ).strftime("%I:%M %p") + else: + other_value = "None" + elif isinstance(field, models.ForeignKey): + if value is not None and len(str(value)): + value = field.related_model.objects.get(id=value) + if other_value is not None and len(str(other_value)): + other_value = field.related_model.objects.get(id=other_value) + difference[verb_key] = (value, other_value) + return difference + + +def employee_exists(request): + """ + This method return the employee instance and work info if not exists return None instead + """ + employee, employee_work_info = None, None + try: + employee = request.user.employee_get + employee_work_info = employee.employee_work_info + finally: + return (employee, employee_work_info) + + +def shift_schedule_today(day, shift): + """ + This function is used to find shift schedules for the day, + it will returns min hour,start time seconds end time seconds + args: + shift : shift instance + day : shift day object + """ + schedule_today = day.day_schedule.filter(shift_id=shift) + start_time_sec, end_time_sec, minimum_hour = 0, 0, "00:00" + if schedule_today.exists(): + schedule_today = schedule_today[0] + minimum_hour = schedule_today.minimum_working_hour + start_time_sec = strtime_seconds(schedule_today.start_time.strftime("%H:%M")) + end_time_sec = strtime_seconds(schedule_today.end_time.strftime("%H:%M")) + return (minimum_hour, start_time_sec, end_time_sec) + + +def overtime_calculation(attendance): + """ + This method is used to calculate overtime of the attendance, it will + return difference between attendance worked hour and minimum hour if + and only worked hour greater than minimum hour, else return 00:00 + args: + attendance : attendance instance + """ + + minimum_hour = attendance.minimum_hour + at_work = attendance.attendance_worked_hour + at_work_sec = strtime_seconds(at_work) + minimum_hour_sec = strtime_seconds(minimum_hour) + if at_work_sec > minimum_hour_sec: + return format_time((at_work_sec - minimum_hour_sec)) + return "00:00" + + +def is_reportingmanger(request, instance): + """ + if the instance have employee id field then you can use this method to know the + request user employee is the reporting manager of the instance + args : + request : request + instance : an object or instance of any model contain employee_id foreign key field + """ + + manager = request.user.employee_get + try: + employee_workinfo_manager = ( + instance.employee_id.employee_work_info.reporting_manager_id + ) + except Exception: + return HttpResponse("This Employee Dont Have any work information") + return manager == employee_workinfo_manager + + +def validate_hh_mm_ss_format(value): + timeformat = "%H:%M:%S" + try: + validtime = datetime.strptime(value, timeformat) + return validtime.time() # Return the time object if needed + except ValueError as e: + raise ValidationError(_("Invalid format, it should be HH:MM:SS format")) + + +def validate_time_format(value): + """ + this method is used to validate the format of duration like fields. + """ + if len(value) > 6: + raise ValidationError(_("Invalid format, it should be HH:MM format")) + try: + hour, minute = value.split(":") + if len(hour) > 3 or len(minute) > 2: + raise ValidationError(_("Invalid time")) + hour = int(hour) + minute = int(minute) + if len(str(hour)) > 3 or len(str(minute)) > 2 or minute not in range(60): + raise ValidationError(_("Invalid time, excepted MM:SS")) + except ValueError as error: + raise ValidationError(_("Invalid format")) from error + + +def attendance_date_validate(date): + """ + Validates if the provided date is not a future date. + + :param date: The date to validate. + :raises ValidationError: If the provided date is in the future. + """ + today = datetime.today().date() + if not date: + raise ValidationError(_("Check date format.")) + elif date > today: + raise ValidationError(_("You cannot choose a future date.")) + + +def activity_datetime(attendance_activity): + """ + This method is used to convert clock-in and clock-out of activity as datetime object + args: + attendance_activity : attendance activity instance + """ + + # in + in_year = attendance_activity.clock_in_date.year + in_month = attendance_activity.clock_in_date.month + in_day = attendance_activity.clock_in_date.day + in_hour = attendance_activity.clock_in.hour + in_minute = attendance_activity.clock_in.minute + # out + out_year = attendance_activity.clock_out_date.year + out_month = attendance_activity.clock_out_date.month + out_day = attendance_activity.clock_out_date.day + out_hour = attendance_activity.clock_out.hour + out_minute = attendance_activity.clock_out.minute + return datetime(in_year, in_month, in_day, in_hour, in_minute), datetime( + out_year, out_month, out_day, out_hour, out_minute + ) + + +def get_week_start_end_dates(week): + """ + This method is use to return the start and end date of the week + """ + # Parse the ISO week date + year, week_number = map(int, week.split("-W")) + + # Get the date of the first day of the week + start_date = datetime.strptime(f"{year}-W{week_number}-1", "%Y-W%W-%w").date() + + # Calculate the end date by adding 6 days to the start date + end_date = start_date + timedelta(days=6) + + return start_date, end_date + + +def get_month_start_end_dates(year_month): + """ + This method is use to return the start and end date of the month + """ + # split year and month separately + year, month = map(int, year_month.split("-")) + # Get the first day of the month + start_date = datetime(year, month, 1).date() + + # Get the last day of the month + _, last_day = calendar.monthrange(year, month) + end_date = datetime(year, month, last_day).date() + + return start_date, end_date + + +def worked_hour_data(labels, records): + """ + To find all the worked hours + """ + data = { + "label": "Worked Hours", + "backgroundColor": "rgba(75, 192, 192, 0.6)", + } + dept_records = [] + for dept in labels: + total_sum = records.filter( + employee_id__employee_work_info__department_id__department=dept + ).aggregate(total_sum=Sum("hour_account_second"))["total_sum"] + dept_records.append(total_sum / 3600 if total_sum else 0) + data["data"] = dept_records + return data + + +def pending_hour_data(labels, records): + """ + To find all the pending hours + """ + data = { + "label": "Pending Hours", + "backgroundColor": "rgba(255, 99, 132, 0.6)", + } + dept_records = [] + for dept in labels: + total_sum = records.filter( + employee_id__employee_work_info__department_id__department=dept + ).aggregate(total_sum=Sum("hour_pending_second"))["total_sum"] + dept_records.append(total_sum / 3600 if total_sum else 0) + data["data"] = dept_records + return data + + +def get_employee_last_name(attendance): + """ + This method is used to return the last name + """ + if attendance.employee_id.employee_last_name: + return attendance.employee_id.employee_last_name + return "" + + +def attendance_day_checking(attendance_date, minimum_hour): + # Convert the string to a datetime object + attendance_datetime = datetime.strptime(attendance_date, "%Y-%m-%d") + + # Extract name of the day + attendance_day = attendance_datetime.strftime("%A") + + # Taking all holidays into a list + leaves = [] + holidays = Holidays.objects.all() + for holi in holidays: + start_date = holi.start_date + end_date = holi.end_date + + # Convert start_date and end_date to datetime objects + start_date = datetime.strptime(str(start_date), "%Y-%m-%d") + end_date = datetime.strptime(str(end_date), "%Y-%m-%d") + + # Add dates in between start date and end date including both + current_date = start_date + while current_date <= end_date: + leaves.append(current_date.strftime("%Y-%m-%d")) + current_date += timedelta(days=1) + + # Checking attendance date is in holiday list, if found making the minimum hour to 00:00 + for leave in leaves: + if str(leave) == str(attendance_date): + minimum_hour = "00:00" + break + + # Making a dictonary contains week day value and leave day pairs + company_leaves = {} + company_leave = CompanyLeaves.objects.all() + for com_leave in company_leave: + a = dict(WEEK_DAYS).get(com_leave.based_on_week_day) + b = com_leave.based_on_week + company_leaves[b] = a + + # Checking the attendance date is in which week + week_in_month = str(((attendance_datetime.day - 1) // 7 + 1) - 1) + + # Checking the attendance date is in the company leave or not + for pairs in company_leaves.items(): + # For all weeks based_on_week is None + if str(pairs[0]) == "None": + if str(pairs[1]) == str(attendance_day): + minimum_hour = "00:00" + break + # Checking with based_on_week and attendance_date week + if str(pairs[0]) == week_in_month: + if str(pairs[1]) == str(attendance_day): + minimum_hour = "00:00" + break + return minimum_hour + + +def paginator_qry(qryset, page_number): + """ + This method is used to paginate queryset + """ + paginator = Paginator(qryset, get_pagination()) + qryset = paginator.get_page(page_number) + return qryset + + +def monthly_leave_days(month, year): + leave_dates = [] + holidays = Holidays.objects.filter(start_date__month=month, start_date__year=year) + leave_dates += list(holidays.values_list("start_date", flat=True)) + + company_leaves = CompanyLeaves.objects.all() + for company_leave in company_leaves: + year = year + month = month + based_on_week = company_leave.based_on_week + based_on_week_day = company_leave.based_on_week_day + if based_on_week != None: + calendar.setfirstweekday(6) + month_calendar = calendar.monthcalendar(year, month) + weeks = month_calendar[int(based_on_week)] + weekdays_in_weeks = [day for day in weeks if day != 0] + for day in weekdays_in_weeks: + date_name = datetime.strptime( + f"{year}-{month:02}-{day:02}", "%Y-%m-%d" + ).date() + if ( + date_name.weekday() == int(based_on_week_day) + and date_name not in leave_dates + ): + leave_dates.append(date_name) + else: + calendar.setfirstweekday(0) + month_calendar = calendar.monthcalendar(year, month) + for week in month_calendar: + if week[int(based_on_week_day)] != 0: + date_name = datetime.strptime( + f"{year}-{month:02}-{week[int(based_on_week_day)]:02}", + "%Y-%m-%d", + ).date() + if date_name not in leave_dates: + leave_dates.append(date_name) + return leave_dates + + +def validate_time_in_minutes(value): + """ + this method is used to validate the format of duration like fields. + """ + if len(value) > 5: + raise ValidationError(_("Invalid format, it should be MM:SS format")) + try: + minutes, sec = value.split(":") + if len(minutes) > 2 or len(sec) > 2: + raise ValidationError(_("Invalid time, excepted MM:SS")) + minutes = int(minutes) + sec = int(sec) + if minutes not in range(60) or sec not in range(60): + raise ValidationError(_("Invalid time, excepted MM:SS")) + except ValueError as e: + raise ValidationError(_("Invalid format, excepted MM:SS")) from e diff --git a/attendance/models.py b/attendance/models.py index 94092af7d..8ae21e290 100644 --- a/attendance/models.py +++ b/attendance/models.py @@ -12,129 +12,36 @@ from collections.abc import Iterable from datetime import date, datetime, timedelta import pandas as pd +from django.apps import apps from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q -from django.db.models.signals import post_save +from django.db.models.signals import post_save, pre_delete, pre_save from django.dispatch import receiver +from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from attendance.methods.differentiate import get_diff_dict +from attendance.methods.utils import ( + MONTH_MAPPING, + attendance_date_validate, + format_time, + get_diff_dict, + strtime_seconds, + validate_hh_mm_ss_format, + validate_time_format, + validate_time_in_minutes, +) from base.horilla_company_manager import HorillaCompanyManager -from base.models import Company, EmployeeShift, EmployeeShiftDay, WorkType +from base.methods import is_company_leave, is_holiday +from base.models import Company, EmployeeShift, EmployeeShiftDay, Holidays, WorkType from employee.models import Employee +from horilla.methods import get_horilla_model_class from horilla.models import HorillaModel from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog -from leave.methods import is_company_leave, is_holiday -from leave.models import ( - WEEK_DAYS, - WEEKS, - CompanyLeave, - Holiday, - LeaveRequest, - LeaveType, -) # Create your models here. -def strtime_seconds(time): - """ - this method is used to reconvert time in H:M formate string back to seconds and return it - args: - time : time in H:M format - """ - ftr = [3600, 60, 1] - return sum(a * b for a, b in zip(ftr, map(int, time.split(":")))) - - -def format_time(seconds): - """ - This method is used to formate seconds to H:M and return it - args: - seconds : seconds - """ - hour = int(seconds // 3600) - minutes = int((seconds % 3600) // 60) - seconds = int((seconds % 3600) % 60) - return f"{hour:02d}:{minutes:02d}" - - -def validate_hh_mm_ss_format(value): - timeformat = "%H:%M:%S" - try: - validtime = datetime.strptime(value, timeformat) - return validtime.time() # Return the time object if needed - except ValueError as e: - raise ValidationError(_("Invalid format, it should be HH:MM:SS format")) - - -def validate_time_format(value): - """ - this method is used to validate the format of duration like fields. - """ - if len(value) > 6: - raise ValidationError(_("Invalid format, it should be HH:MM format")) - try: - hour, minute = value.split(":") - if len(hour) > 3 or len(minute) > 2: - raise ValidationError(_("Invalid time")) - hour = int(hour) - minute = int(minute) - if len(str(hour)) > 3 or len(str(minute)) > 2 or minute not in range(60): - raise ValidationError(_("Invalid time, excepted MM:SS")) - except ValueError as error: - raise ValidationError(_("Invalid format")) from error - - -def validate_time_in_minutes(value): - """ - this method is used to validate the format of duration like fields. - """ - if len(value) > 5: - raise ValidationError(_("Invalid format, it should be MM:SS format")) - try: - minutes, sec = value.split(":") - if len(minutes) > 2 or len(sec) > 2: - raise ValidationError(_("Invalid time, excepted MM:SS")) - minutes = int(minutes) - sec = int(sec) - if minutes not in range(60) or sec not in range(60): - raise ValidationError(_("Invalid time, excepted MM:SS")) - except ValueError as e: - raise ValidationError(_("Invalid format, excepted MM:SS")) from e - - -def attendance_date_validate(date): - """ - Validates if the provided date is not a future date. - - :param date: The date to validate. - :raises ValidationError: If the provided date is in the future. - """ - today = datetime.today().date() - if not date: - raise ValidationError(_("Check date format.")) - elif date > today: - raise ValidationError(_("You cannot choose a future date.")) - - -month_mapping = { - "january": 1, - "february": 2, - "march": 3, - "april": 4, - "may": 5, - "june": 6, - "july": 7, - "august": 8, - "september": 9, - "october": 10, - "november": 11, - "december": 12, -} - - class AttendanceActivity(HorillaModel): """ AttendanceActivity model @@ -378,7 +285,7 @@ class Attendance(HorillaModel): # Taking all holidays into a list leaves = [] - holidays = Holiday.objects.all() + holidays = Holidays.objects.all() for holi in holidays: start_date = holi.start_date end_date = holi.end_date @@ -511,11 +418,14 @@ class Attendance(HorillaModel): Args: employee_ot (obj): AttendanceOverTime instance """ - approved_leave_requests = self.employee_id.leaverequest_set.filter( - start_date__lte=self.attendance_date, - end_date__gte=self.attendance_date, - status="approved", - ) + if apps.is_installed("leave"): + approved_leave_requests = self.employee_id.leaverequest_set.filter( + start_date__lte=self.attendance_date, + end_date__gte=self.attendance_date, + status="approved", + ) + else: + approved_leave_requests = [] # Create a Q object to combine multiple conditions for the exclude clause exclude_condition = Q() @@ -698,7 +608,7 @@ class AttendanceOverTime(HorillaModel): hrs_to_vlaidate = sum( list( Attendance.objects.filter( - attendance_date__month=month_mapping[self.month], + attendance_date__month=MONTH_MAPPING[self.month], attendance_date__year=self.year, employee_id=self.employee_id, attendance_validated=False, @@ -714,7 +624,7 @@ class AttendanceOverTime(HorillaModel): hrs_to_approve = sum( list( Attendance.objects.filter( - attendance_date__month=month_mapping[self.month], + attendance_date__month=MONTH_MAPPING[self.month], attendance_date__year=self.year, employee_id=self.employee_id, attendance_validated=True, @@ -728,7 +638,7 @@ class AttendanceOverTime(HorillaModel): """ This method will return the index of the month """ - return month_mapping[self.month] + return MONTH_MAPPING[self.month] def save(self, *args, **kwargs): self.hour_account_second = strtime_seconds(self.worked_hours) @@ -787,7 +697,7 @@ class AttendanceLateComeEarlyOut(HorillaModel): """ This method is used to return the total penalties in the late early instance """ - return self.penaltyaccount_set.count() + return self.penaltyaccounts_set.count() def save(self, *args, **kwargs) -> None: super().save(*args, **kwargs) @@ -833,132 +743,6 @@ class AttendanceValidationCondition(HorillaModel): raise ValidationError(_("You cannot add more conditions.")) -months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", -] - - -class PenaltyAccount(HorillaModel): - """ - LateComeEarlyOutPenaltyAccount - """ - - employee_id = models.ForeignKey( - Employee, - on_delete=models.PROTECT, - related_name="penalty_set", - editable=False, - verbose_name="Employee", - null=True, - ) - late_early_id = models.ForeignKey( - AttendanceLateComeEarlyOut, on_delete=models.CASCADE, null=True, editable=False - ) - leave_request_id = models.ForeignKey( - LeaveRequest, null=True, on_delete=models.CASCADE, editable=False - ) - leave_type_id = models.ForeignKey( - LeaveType, - on_delete=models.DO_NOTHING, - blank=True, - null=True, - verbose_name="Leave type", - ) - minus_leaves = models.FloatField(default=0.0, null=True) - deduct_from_carry_forward = models.BooleanField(default=False) - penalty_amount = models.FloatField(default=0.0, null=True) - - def clean(self) -> None: - super().clean() - if not self.leave_type_id and self.minus_leaves: - raise ValidationError( - {"leave_type_id": _("Specify the leave type to deduct the leave.")} - ) - if self.leave_type_id and not self.minus_leaves: - raise ValidationError( - { - "minus_leaves": _( - "If a leave type is chosen for a penalty, minus leaves are required." - ) - } - ) - if not self.minus_leaves and not self.penalty_amount: - raise ValidationError( - { - "leave_type_id": _( - "Either minus leaves or a penalty amount is required" - ) - } - ) - - if ( - self.minus_leaves or self.deduct_from_carry_forward - ) and not self.leave_type_id: - raise ValidationError({"leave_type_id": _("Leave type is required")}) - return - - class Meta: - ordering = ["-created_at"] - - -@receiver(post_save, sender=PenaltyAccount) -def create_initial_stage(sender, instance, created, **kwargs): - """ - This is post save method, used to create initial stage for the recruitment - """ - # only work when creating - if created: - penalty_amount = instance.penalty_amount - if penalty_amount: - from payroll.models.models import Deduction - - penalty = Deduction() - if instance.late_early_id: - penalty.title = f"{instance.late_early_id.get_type_display()} penalty" - penalty.one_time_date = ( - instance.late_early_id.attendance_id.attendance_date - ) - elif instance.leave_request_id: - penalty.title = f"Leave penalty {instance.leave_request_id.end_date}" - penalty.one_time_date = instance.leave_request_id.end_date - else: - penalty.title = f"Penalty on {datetime.today()}" - penalty.one_time_date = datetime.today() - penalty.include_active_employees = False - penalty.is_fixed = True - penalty.amount = instance.penalty_amount - penalty.only_show_under_employee = True - penalty.save() - penalty.include_active_employees = False - penalty.specific_employees.add(instance.employee_id) - penalty.save() - - if instance.leave_type_id and instance.minus_leaves: - available = instance.employee_id.available_leave.filter( - leave_type_id=instance.leave_type_id - ).first() - unit = round(instance.minus_leaves * 2) / 2 - if not instance.deduct_from_carry_forward: - available.available_days = max(0, (available.available_days - unit)) - else: - available.carryforward_days = max( - 0, (available.carryforward_days - unit) - ) - - available.save() - - class GraceTime(HorillaModel): """ Model for saving Grace time @@ -1038,3 +822,278 @@ class AttendanceGeneralSetting(HorillaModel): time_runner = models.BooleanField(default=True) company_id = models.ForeignKey(Company, on_delete=models.CASCADE, null=True) + + +if apps.is_installed("leave") and apps.is_installed("payroll"): + + class PenaltyAccount(HorillaModel): + """ + LateComeEarlyOutPenaltyAccount + """ + + employee_id = models.ForeignKey( + Employee, + on_delete=models.PROTECT, + related_name="penalty_set", + editable=False, + verbose_name="Employee", + null=True, + ) + late_early_id = models.ForeignKey( + AttendanceLateComeEarlyOut, + on_delete=models.CASCADE, + null=True, + editable=False, + ) + leave_request_id = models.ForeignKey( + "leave.LeaveRequest", null=True, on_delete=models.CASCADE, editable=False + ) + leave_type_id = models.ForeignKey( + "leave.LeaveType", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + verbose_name="Leave type", + ) + minus_leaves = models.FloatField(default=0.0, null=True) + deduct_from_carry_forward = models.BooleanField(default=False) + penalty_amount = models.FloatField(default=0.0, null=True) + + def clean(self) -> None: + super().clean() + if not self.leave_type_id and self.minus_leaves: + raise ValidationError( + {"leave_type_id": _("Specify the leave type to deduct the leave.")} + ) + if self.leave_type_id and not self.minus_leaves: + raise ValidationError( + { + "minus_leaves": _( + "If a leave type is chosen for a penalty, minus leaves are required." + ) + } + ) + if not self.minus_leaves and not self.penalty_amount: + raise ValidationError( + { + "leave_type_id": _( + "Either minus leaves or a penalty amount is required" + ) + } + ) + + if ( + self.minus_leaves or self.deduct_from_carry_forward + ) and not self.leave_type_id: + raise ValidationError({"leave_type_id": _("Leave type is required")}) + return + + class Meta: + ordering = ["-created_at"] + + @receiver(post_save, sender=PenaltyAccount) + def create_initial_stage(sender, instance, created, **kwargs): + """ + This is post save method, used to create initial stage for the recruitment + """ + # only work when creating + if created: + penalty_amount = instance.penalty_amount + if penalty_amount: + Deduction = get_horilla_model_class( + app_label="payroll", model="deduction" + ) + penalty = Deduction() + if instance.late_early_id: + penalty.title = ( + f"{instance.late_early_id.get_type_display()} penalty" + ) + penalty.one_time_date = ( + instance.late_early_id.attendance_id.attendance_date + ) + elif instance.leave_request_id: + penalty.title = ( + f"Leave penalty {instance.leave_request_id.end_date}" + ) + penalty.one_time_date = instance.leave_request_id.end_date + else: + penalty.title = f"Penalty on {datetime.today()}" + penalty.one_time_date = datetime.today() + penalty.include_active_employees = False + penalty.is_fixed = True + penalty.amount = instance.penalty_amount + penalty.only_show_under_employee = True + penalty.save() + penalty.include_active_employees = False + penalty.specific_employees.add(instance.employee_id) + penalty.save() + + if instance.leave_type_id and instance.minus_leaves: + available = instance.employee_id.available_leave.filter( + leave_type_id=instance.leave_type_id + ).first() + unit = round(instance.minus_leaves * 2) / 2 + if not instance.deduct_from_carry_forward: + available.available_days = max(0, (available.available_days - unit)) + else: + available.carryforward_days = max( + 0, (available.carryforward_days - unit) + ) + + available.save() + + +class WorkRecords(models.Model): + """ + WorkRecord Model + """ + + choices = [ + ("FDP", _("Present")), + ("HDP", _("Half Day Present")), + ("ABS", _("Absent")), + ("HD", _("Holiday/Company Leave")), + ("CONF", _("Conflict")), + ("DFT", _("Draft")), + ] + + record_name = models.CharField(max_length=250, null=True, blank=True) + work_record_type = models.CharField(max_length=5, null=True, choices=choices) + employee_id = models.ForeignKey( + Employee, on_delete=models.PROTECT, verbose_name=_("Employee") + ) + date = models.DateField(null=True, blank=True) + at_work = models.CharField( + null=True, + blank=True, + validators=[ + validate_time_format, + ], + default="00:00", + max_length=5, + ) + min_hour = models.CharField( + null=True, + blank=True, + validators=[ + validate_time_format, + ], + default="00:00", + max_length=5, + ) + at_work_second = models.IntegerField(null=True, blank=True, default=0) + min_hour_second = models.IntegerField(null=True, blank=True, default=0) + note = models.TextField(max_length=255) + message = models.CharField(max_length=30, null=True, blank=True) + is_attendance_record = models.BooleanField(default=False) + is_leave_record = models.BooleanField(default=False) + day_percentage = models.FloatField(default=0) + last_update = models.DateTimeField(null=True, blank=True) + objects = HorillaCompanyManager("employee_id__employee_work_info__company_id") + + def save(self, *args, **kwargs): + self.last_update = timezone.now() + + super().save(*args, **kwargs) + + def clean(self): + super().clean() + if not 0.0 <= self.day_percentage <= 1.0: + raise ValidationError(_("Day percentage must be between 0.0 and 1.0")) + + def __str__(self): + return ( + self.record_name + if self.record_name is not None + else f"{self.work_record_type}-{self.date}" + ) + + +class OverrideAttendances(Attendance): + """ + Class to override Attendance model save method + """ + + # Additional fields and methods specific to AnotherModel + @receiver(post_save, sender=Attendance) + def attendance_post_save(sender, instance, **kwargs): + """ + Overriding Attendance model save method + """ + if instance.first_save: + min_hour_second = strtime_seconds(instance.minimum_hour) + at_work_second = strtime_seconds(instance.attendance_worked_hour) + + status = "FDP" if instance.at_work_second >= min_hour_second else "HDP" + + status = "CONF" if instance.attendance_validated is False else status + message = ( + _("Validate the attendance") if status == "CONF" else _("Validated") + ) + + message = ( + _("Incomplete minimum hour") + if status == "HDP" and min_hour_second > at_work_second + else message + ) + work_record = WorkRecords.objects.filter( + date=instance.attendance_date, + is_attendance_record=True, + employee_id=instance.employee_id, + ) + work_record = ( + WorkRecords() + if not WorkRecords.objects.filter( + date=instance.attendance_date, + employee_id=instance.employee_id, + ).exists() + else WorkRecords.objects.filter( + date=instance.attendance_date, + employee_id=instance.employee_id, + ).first() + ) + work_record.employee_id = instance.employee_id + work_record.date = instance.attendance_date + work_record.at_work = instance.attendance_worked_hour + work_record.min_hour = instance.minimum_hour + work_record.min_hour_second = min_hour_second + work_record.at_work_second = at_work_second + work_record.work_record_type = status + work_record.message = message + work_record.is_attendance_record = True + if instance.attendance_validated: + work_record.day_percentage = ( + 1.00 if at_work_second > min_hour_second / 2 else 0.50 + ) + work_record.save() + + if status == "HDP" and work_record.is_leave_record: + message = _("Half day leave") + + if status == "FDP": + message = _("Present") + + work_record.message = message + work_record.save() + + message = work_record.message + status = work_record.work_record_type + if not instance.attendance_clock_out: + status = "FDP" + message = _("Currently working") + work_record.message = message + work_record.work_record_type = status + work_record.save() + + @receiver(pre_delete, sender=Attendance) + def attendance_pre_delete(sender, instance, **_kwargs): + """ + Overriding Attendance model delete method + """ + # Perform any actions before deleting the instance + # ... + WorkRecords.objects.filter( + employee_id=instance.employee_id, + is_attendance_record=True, + date=instance.attendance_date, + ).delete() diff --git a/attendance/sidebar.py b/attendance/sidebar.py index 4c4f40d8e..e6e9d6787 100644 --- a/attendance/sidebar.py +++ b/attendance/sidebar.py @@ -4,6 +4,7 @@ attendance/sidebar.py from datetime import datetime +from django.apps import apps from django.urls import reverse from django.utils.translation import gettext_lazy as trans @@ -53,6 +54,14 @@ SUBMENUS = [ "redirect": reverse("view-my-attendance"), }, ] +if apps.is_installed("leave"): + SUBMENUS.append( + { + "menu": trans("Work Records"), + "redirect": reverse("work-records"), + "accessibility": "attendance.sidebar.work_record_accessibility", + }, + ) def attendances_accessibility(request, submenu, user_perms, *args, **kwargs): diff --git a/attendance/static/attendance/actions.js b/attendance/static/attendance/actions.js index c8eefa3a3..520d700f3 100644 --- a/attendance/static/attendance/actions.js +++ b/attendance/static/attendance/actions.js @@ -959,7 +959,7 @@ $("#validateAttendances").click(function (e) { }); } else { Swal.fire({ - text: "confirmMessage", + text: confirmMessage, icon: "info", showCancelButton: true, confirmButtonColor: "#008000", diff --git a/attendance/static/dashboard/attendanceChart.js b/attendance/static/dashboard/attendanceChart.js index eb95f1499..a521f3bc6 100644 --- a/attendance/static/dashboard/attendanceChart.js +++ b/attendance/static/dashboard/attendanceChart.js @@ -1,12 +1,14 @@ +staticUrl = $("#statiUrl").attr("data-url"); $(document).ready(function () { - - // initializing the department overtime chart. + // initializing the department overtime chart. var departmentChartData = { labels: [], datasets: [], }; window["departmentOvertimeChart"] = {}; - const departmentOvertimeChart = document.getElementById("departmentOverChart"); + const departmentOvertimeChart = document.getElementById( + "departmentOverChart" + ); if (departmentOvertimeChart) { var departmentAttendanceChart = new Chart(departmentOvertimeChart, { type: "pie", @@ -17,7 +19,8 @@ $(document).ready(function () { }, plugins: [ { - afterRender: (departmentAttendanceChart) => emptyOvertimeChart(departmentAttendanceChart), + afterRender: (departmentAttendanceChart) => + emptyOvertimeChart(departmentAttendanceChart), }, ], }); @@ -43,14 +46,14 @@ $(document).ready(function () { }, }); - // Function to update the department overtime chart according to the response fetched from backend. + // Function to update the department overtime chart according to the response fetched from backend. function departmentDataUpdate(response) { departmentChartData.labels = response.labels; - departmentChartData.datasets=response.dataset; - departmentChartData.message=response.message; - departmentChartData.emptyImageSrc=response.emptyImageSrc; - if (departmentAttendanceChart){ + departmentChartData.datasets = response.dataset; + departmentChartData.message = response.message; + departmentChartData.emptyImageSrc = response.emptyImageSrc; + if (departmentAttendanceChart) { departmentAttendanceChart.update(); } } @@ -77,7 +80,7 @@ $(document).ready(function () { }); } - // Function to update the input fields according to type select field. + // Function to update the input fields according to type select field. function changeDepartmentView(element) { var dataType = $(element).val(); @@ -93,10 +96,9 @@ $(document).ready(function () { $("#department_month2").remove(); if (dataType === "weekly") { $("#department_month").prop("type", "week"); - if (currentWeek <10){ + if (currentWeek < 10) { $("#department_month").val(`${year}-W0${currentWeek}`); - } - else { + } else { $("#department_month").val(`${year}-W${currentWeek}`); } changeDepartmentMonth(); @@ -112,12 +114,13 @@ $(document).ready(function () { } } - // Function for empty message for department overtime chart. + // Function for empty message for department overtime chart. function emptyOvertimeChart(departmentAttendanceChart, args, options) { flag = false; for (let i = 0; i < departmentAttendanceChart.data.datasets.length; i++) { - flag = flag + departmentAttendanceChart.data.datasets[i].data.some(Boolean); + flag = + flag + departmentAttendanceChart.data.datasets[i].data.some(Boolean); } if (!flag) { const { ctx, canvas } = departmentAttendanceChart; @@ -133,7 +136,7 @@ $(document).ready(function () { var noDataImage = new Image(); noDataImage.src = departmentAttendanceChart.data.emptyImageSrc ? departmentAttendanceChart.data.emptyImageSrc - : "/static/images/ui/joiningchart.png"; + : staticUrl + "images/ui/joiningchart.png"; message = departmentAttendanceChart.data.message ? departmentAttendanceChart.data.message @@ -153,7 +156,7 @@ $(document).ready(function () { } } - // Ajax request to create department overtime chart initially. + // Ajax request to create department overtime chart initially. $.ajax({ url: "/attendance/department-overtime-chart", @@ -163,29 +166,26 @@ $(document).ready(function () { "X-Requested-With": "XMLHttpRequest", }, success: (response) => { - departmentDataUpdate(response); - }, error: (error) => { console.log("Error", error); }, }); - // Functions to update department overtime chart while changing the date input field and select input field. + // Functions to update department overtime chart while changing the date input field and select input field. - $("#departmentChartCard").on("change","#department_date_type", function (e) { - changeDepartmentView($(this)) - }) + $("#departmentChartCard").on("change", "#department_date_type", function (e) { + changeDepartmentView($(this)); + }); - $("#departmentChartCard").on("change","#department_month", function (e) { - changeDepartmentMonth() - }) - - $("#departmentChartCard").on("change","#department_month2", function (e) { - changeDepartmentMonth() - }) + $("#departmentChartCard").on("change", "#department_month", function (e) { + changeDepartmentMonth(); + }); + $("#departmentChartCard").on("change", "#department_month2", function (e) { + changeDepartmentMonth(); + }); }); var data; @@ -222,7 +222,7 @@ function createAttendanceChart(dataSet, labels) { }; // Create chart using the Chart.js library window["attendanceChart"] = {}; - if (document.getElementById("dailyAnalytic")){ + if (document.getElementById("dailyAnalytic")) { const ctx = document.getElementById("dailyAnalytic").getContext("2d"); attendanceChart = new Chart(ctx, { type: "bar", @@ -278,7 +278,8 @@ function createAttendanceChart(dataSet, labels) { error: (error) => {}, }); } else { - window.location.href = "/attendance/late-come-early-out-view" + parms; + window.location.href = + "/attendance/late-come-early-out-view" + parms; } }, }, @@ -326,10 +327,9 @@ function changeView(element) { $("#attendance_month2").remove(); if (dataType === "weekly") { $("#attendance_month").prop("type", "week"); - if (currentWeek <10){ + if (currentWeek < 10) { $("#attendance_month").val(`${year}-W0${currentWeek}`); - } - else { + } else { $("#attendance_month").val(`${year}-W${currentWeek}`); } changeMonth(); @@ -344,11 +344,8 @@ function changeView(element) { } } } -if (document.getElementById("pendingHoursCanvas")){ - var chart = new Chart( - document.getElementById("pendingHoursCanvas"), - {} - ); +if (document.getElementById("pendingHoursCanvas")) { + var chart = new Chart(document.getElementById("pendingHoursCanvas"), {}); } window["pendingHoursCanvas"] = chart; function pendingHourChart(year, month) { @@ -358,7 +355,7 @@ function pendingHourChart(year, month) { data: { month: month, year: year }, success: function (response) { var ctx = document.getElementById("pendingHoursCanvas"); - if (ctx){ + if (ctx) { pendingHoursCanvas.destroy(); pendingHoursCanvas = new Chart(ctx, { type: "bar", // Bar chart type diff --git a/attendance/templates/attendance/dashboard/dashboard.html b/attendance/templates/attendance/dashboard/dashboard.html index df8b32e11..8112d8579 100644 --- a/attendance/templates/attendance/dashboard/dashboard.html +++ b/attendance/templates/attendance/dashboard/dashboard.html @@ -210,7 +210,7 @@ {% else %}
- +

{% trans "No employees on break...." %}

diff --git a/attendance/templates/attendance/dashboard/overtime_table.html b/attendance/templates/attendance/dashboard/overtime_table.html index 3e79f8789..bc164d32e 100644 --- a/attendance/templates/attendance/dashboard/overtime_table.html +++ b/attendance/templates/attendance/dashboard/overtime_table.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if overtime_attendances %}
@@ -85,7 +85,7 @@ {% else %}
- +

{% trans "No Overtime to Validate...." %}

diff --git a/attendance/templates/attendance/dashboard/to_validate_table.html b/attendance/templates/attendance/dashboard/to_validate_table.html index 18fc51933..3caaf7a0b 100644 --- a/attendance/templates/attendance/dashboard/to_validate_table.html +++ b/attendance/templates/attendance/dashboard/to_validate_table.html @@ -133,7 +133,7 @@ {% else %}
- +

{% trans "All Attendance Validated." %}

diff --git a/attendance/templates/attendance/grace_time/grace_time_table.html b/attendance/templates/attendance/grace_time/grace_time_table.html index 119c053f6..5d340920b 100644 --- a/attendance/templates/attendance/grace_time/grace_time_table.html +++ b/attendance/templates/attendance/grace_time/grace_time_table.html @@ -68,7 +68,7 @@ {% endif %} {% if perms.base.delete_gracetime %}
{% csrf_token %}
{% trans "At Work" %}
{% trans "Penalties" %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
{% trans "Actions" %}
{% endif %}
@@ -101,10 +101,10 @@
Penalties :{{late_in_early_out.get_penalties_count}}
{% endif %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts %}
{% trans "At Work" %}
{% trans "Penalties" %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
{% trans "Actions" %}
{% endif %} {% comment %}
{% endcomment %} @@ -90,10 +90,10 @@
Penalties :{{late_in_early_out.get_penalties_count}}
{% endif %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount or perms.attendance.delete_attendancelatecomeearlyout %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts or perms.attendance.delete_attendancelatecomeearlyout %}
- {% if request.user|is_reportingmanager or perms.attendance.chanage_penaltyaccount %} + {% if request.user|is_reportingmanager or perms.attendance.change_penaltyaccounts %} - {% for message in messages %} -
{{ message }}
- {% endfor %} -
- - {% if late_in_early_out_ids %} - - {% endif %} -{% endif %} -
- -
-
-
-
-
- Mary Magdalene -
-
- {{ instance.employee_id.get_full_name }} - - {{ instance.employee_id.get_department }} / {{ instance.employee_id.get_job_position }} - -
-
-
- - {{ form.as_p }} +{% load i18n %} {% load horillafilters %} -
-
-
-
{% trans "Leave Type" %}
-
{% trans "Available Days" %}
-
- {% trans "Carry Forward Days" %} -
-
-
-
- {% for acc in available %} -
-
{{ acc.leave_type_id }}
-
{{ acc.available_days }}
-
{{ acc.carryforward_days }}
-
+{% if messages %} +
+ {% for message in messages %} +
{{ message }}
{% endfor %} -
-
    -
  1. - {% trans "Leave type is optional when 'minus leave' is 0" %} -
  2. -
  3. - {% trans "Penalty amount will affect payslip on the date" %} -
  4. -
  5. - {% trans "By default minus leave will cut/deduct from available leaves" %} + setTimeout(() => { + $(".oh-modal__close--custom").click(); + const spanElement = document.querySelector(".oh-span__class"); + if (spanElement) { + spanElement.click(); + } + }, 1000); + + {% if late_in_early_out_ids %} + + {% endif %} +{% endif %} + +
    +
  6. -
  7. - {% trans "By enabling 'Deduct from carry forward' leave will cut/deduct from carry forward days" %} -
  8. -
-
- -
- + + +
+ +
+
+
+
+ Mary Magdalene +
+
+ {{ instance.employee_id.get_full_name }} + + {{ instance.employee_id.get_department }} / {{ instance.employee_id.get_job_position }} + +
+
+
+ +
+ {{ form.as_p }} + {% if "leave"|app_installed %} +
+
+
+
{% trans "Leave Type" %}
+
{% trans "Available Days" %}
+
+ {% trans "Carry Forward Days" %} +
+
+
+
+ {% for acc in available %} +
+
{{ acc.leave_type_id }}
+
{{ acc.available_days }}
+
{{ acc.carryforward_days }}
+
+ {% endfor %} +
+
+
    +
  1. + {% trans "Leave type is optional when 'minus leave' is 0" %} +
  2. +
  3. + {% trans "Penalty amount will affect payslip on the date" %} +
  4. +
  5. + {% trans "By default minus leave will cut/deduct from available leaves" %} +
  6. +
  7. + {% trans "By enabling 'Deduct from carry forward' leave will cut/deduct from carry forward days" + %} +
  8. +
+ {% endif %} +
+ +
+
+
diff --git a/attendance/templates/attendance/work_record/work_record_create.html b/attendance/templates/attendance/work_record/work_record_create.html new file mode 100644 index 000000000..020c67c3f --- /dev/null +++ b/attendance/templates/attendance/work_record/work_record_create.html @@ -0,0 +1,16 @@ +{% extends 'index.html' %} +{% load i18n %} + +{% block content %} +
+
+ {% csrf_token %} + {{contract_form.as_p}} + +
+ + +
+ + +{% endblock content %} diff --git a/attendance/templates/attendance/work_record/work_record_employees_view.html b/attendance/templates/attendance/work_record/work_record_employees_view.html new file mode 100644 index 000000000..4f5ec3e85 --- /dev/null +++ b/attendance/templates/attendance/work_record/work_record_employees_view.html @@ -0,0 +1,74 @@ +{% extends 'index.html' %} +{% load current_month_record %} +{% block content %} + + + +
+
+
+

{% trans "Date:" %} {{ current_date }}

+

{% trans "Month:" %} {{ current_date|date:"F" }}

+
+ + + + + {% for day in current_month_dates_list %} + + {% endfor %} + + + + + {% for employee in employees %} + + + {% for date in current_month_dates_list %} + + {% endfor %} + + {% endfor %} + +
{% trans "Work Records" %}{{ day.day }}
{{ employee }} + {% for work_record in employee.workrecord_set.all|current_month_record %} + {% if work_record.start_datetime.date == date %} + {% if work_record.work_record_type.is_timeoff %} +
{% trans "A" %}
+ {% else %} +
{% trans "P" %}
+ {% endif %} + {% endif %} + {% endfor %} +
+
+
+ +{% endblock content %} diff --git a/attendance/templates/attendance/work_record/work_record_view copy.html b/attendance/templates/attendance/work_record/work_record_view copy.html new file mode 100644 index 000000000..a95ab3b2b --- /dev/null +++ b/attendance/templates/attendance/work_record/work_record_view copy.html @@ -0,0 +1,37 @@ +{% extends 'index.html' %} + +{% block content %} + + + +
+
+
+
{% trans "record_type_name" %}
+ +
+
+
+ {% for work_record in work_records %} +
+
+
+
+ Mary Magdalene +
+ {{work_record.work_record_name}} +
+
+ +
+ {% endfor %} +
+
+ + +{% endblock content %} diff --git a/attendance/templates/requests/attendance/attendance_comment.html b/attendance/templates/requests/attendance/attendance_comment.html index c2dde32a0..4c251d858 100644 --- a/attendance/templates/requests/attendance/attendance_comment.html +++ b/attendance/templates/requests/attendance/attendance_comment.html @@ -136,7 +136,7 @@ >
diff --git a/attendance/templates/requests/attendance/comment_view.html b/attendance/templates/requests/attendance/comment_view.html index 2e627b3bd..09ea01885 100644 --- a/attendance/templates/requests/attendance/comment_view.html +++ b/attendance/templates/requests/attendance/comment_view.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load i18n %} +{% load i18n static %} {% if messages %}
{% for message in messages %} @@ -13,7 +13,7 @@
{% trans "There is no comments to show." %} - +
diff --git a/attendance/urls.py b/attendance/urls.py index 0fb68ad03..4b43e7ad8 100644 --- a/attendance/urls.py +++ b/attendance/urls.py @@ -5,6 +5,7 @@ This page is used to map request or url path with function """ +from django.apps import apps from django.urls import path import attendance.views.clock_in_out @@ -12,10 +13,19 @@ import attendance.views.dashboard import attendance.views.penalty import attendance.views.requests import attendance.views.search +import base +from base.forms import AttendanceAllowedIPForm +from base.models import AttendanceAllowedIP from .views import views urlpatterns = [ + path( + "profile-attendance-tab", + views.profile_attendance_tab, + name="profile-attendance-tab", + ), + path("attendance-tab/", views.attendance_tab, name="attendance-tab"), path("attendance-create", views.attendance_create, name="attendance-create"), path("attendance-excel", views.attendance_excel, name="attendance-excel"), path( @@ -334,14 +344,6 @@ urlpatterns = [ path( "pending-hours/", attendance.views.dashboard.pending_hours, name="pending-hours" ), - path( - "cut-penalty//", - attendance.views.penalty.cut_available_leave, - name="cut-penalty", - ), - path( - "view-penalties", attendance.views.penalty.view_penalties, name="view-penalties" - ), path("create-garce-time", views.create_grace_time, name="create-grace-time"), path( "update-garce-time//", @@ -391,4 +393,86 @@ urlpatterns = [ attendance.views.requests.get_employee_shift, name="get-employee-shift", ), + path( + "cut-penalty//", + attendance.views.penalty.cut_available_leave, + name="cut-penalty", + ), + path( + "dashboard-overtime-approve", + attendance.views.dashboard.dashboard_overtime_approve, + name="dashboard-overtime-approve", + ), + path( + "dashboard-attendance-validate", + attendance.views.dashboard.dashboard_attendance_validate, + name="dashboard-attendance-validate", + ), + path( + "attendance-settings-view/", + views.validation_condition_view, + name="attendance-settings-view", + ), + path( + "track-late-come-early-out", + views.track_late_come_early_out, + name="track-late-come-early-out", + ), + path( + "enable-disable-tracking-late-come-early-out", + views.enable_disable_tracking_late_come_early_out, + name="enable-disable-tracking-late-come-early-out", + ), + path( + "grace-settings-view/", + views.grace_time_view, + name="grace-settings-view", + ), + path( + "settings/attendance-settings-create/", + views.validation_condition_create, + name="attendance-settings-create", + ), + path( + "settings/attendance-settings-update//", + views.validation_condition_update, + name="attendance-settings-update", + ), + path( + "allowed-ips/", + views.allowed_ips, + name="allowed-ips", + ), + path( + "settings/enable-ip-restriction/", + views.enable_ip_restriction, + name="enable-ip-restriction", + ), + path( + "settings/create-allowed-ip/", + views.create_allowed_ips, + name="create-allowed-ip", + ), + path( + "settings/delete-allowed-ip/", + views.delete_allowed_ips, + name="delete-allowed-ip", + ), + path( + "settings/edit-allowed-ip/", + views.edit_allowed_ips, + name="edit-allowed-ip", + ), + path( + "settings/add-remove-ip-fields/", + base.views.add_remove_dynamic_fields, + name="add-remove-ip-fields", + kwargs={ + "model": AttendanceAllowedIP, + "form_class": AttendanceAllowedIPForm, + "template": "attendance/ip_restriction/add_more_ip_fields.html", + "field_type": "character", + "field_name_pre": "ip_address", + }, + ), ] diff --git a/attendance/views.py b/attendance/views.py index 016ba9f96..4851bf821 100644 --- a/attendance/views.py +++ b/attendance/views.py @@ -40,6 +40,15 @@ from attendance.forms import ( 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, @@ -61,56 +70,6 @@ from notifications.signals import notify # Create your views here. -def intersection_list(list1, list2): - """ - This method is used to intersect two list - """ - return [value for value in list1 if value in list2] - - -def format_time(seconds): - """ - this method is used to formate seconds to H:M and return it - args: - seconds : seconds - """ - - hour = int(seconds // 3600) - minutes = int((seconds % 3600) // 60) - seconds = int((seconds % 3600) % 60) - return f"{hour:02d}:{minutes:02d}" - - -def strtime_seconds(time): - """ - this method is used reconvert time in H:M formate string back to seconds and return it - args: - time : time in H:M format - """ - - ftr = [3600, 60, 1] - return sum(a * b for a, b in zip(ftr, map(int, time.split(":")))) - - -def is_reportingmanger(request, instance): - """ - if the instance have employee id field then you can use this method to know the - request user employee is the reporting manager of the instance - args : - request : request - instance : an object or instance of any model contain employee_id foreign key field - """ - - manager = request.user.employee_get - try: - employee_workinfo_manager = ( - instance.employee_id.employee_work_info.reporting_manager_id - ) - except Exception: - return HttpResponse("This Employee Dont Have any work information") - return manager == employee_workinfo_manager - - def late_come_create(attendance): """ used to create late come report @@ -722,54 +681,6 @@ def attendance_activity_delete(request, obj_id): return redirect("/attendance/attendance-activity-view") -def employee_exists(request): - """ - This method return the employee instance and work info if not exists return None instead - """ - employee, employee_work_info = None, None - try: - employee = request.user.employee_get - employee_work_info = employee.employee_work_info - finally: - return (employee, employee_work_info) - - -def shift_schedule_today(day, shift): - """ - This function is used to find shift schedules for the day, - it will returns min hour,start time seconds end time seconds - args: - shift : shift instance - day : shift day object - """ - schedule_today = day.day_schedule.filter(shift_id=shift) - start_time_sec, end_time_sec, minimum_hour = 0, 0, "00:00" - if schedule_today.exists(): - schedule_today = schedule_today[0] - minimum_hour = schedule_today.minimum_working_hour - start_time_sec = strtime_seconds(schedule_today.start_time.strftime("%H:%M")) - end_time_sec = strtime_seconds(schedule_today.end_time.strftime("%H:%M")) - return (minimum_hour, start_time_sec, end_time_sec) - - -def overtime_calculation(attendance): - """ - This method is used to calculate overtime of the attendance, it will - return difference between attendance worked hour and minimum hour if - and only worked hour greater than minimum hour, else return 00:00 - args: - attendance : attendance instance - """ - - minimum_hour = attendance.minimum_hour - at_work = attendance.attendance_worked_hour - at_work_sec = strtime_seconds(at_work) - minimum_hour_sec = strtime_seconds(minimum_hour) - if at_work_sec > minimum_hour_sec: - return format_time((at_work_sec - minimum_hour_sec)) - return "00:00" - - def clock_in_attendance_and_activity( employee, date_today, @@ -898,30 +809,6 @@ def clock_in(request): ) -def activity_datetime(attendance_activity): - """ - This method is used to convert clock-in and clock-out of activity as datetime object - args: - attendance_activity : attendance activity instance - """ - - # in - in_year = attendance_activity.clock_in_date.year - in_month = attendance_activity.clock_in_date.month - in_day = attendance_activity.clock_in_date.day - in_hour = attendance_activity.clock_in.hour - in_minute = attendance_activity.clock_in.minute - # out - out_year = attendance_activity.clock_out_date.year - out_month = attendance_activity.clock_out_date.month - out_day = attendance_activity.clock_out_date.day - out_hour = attendance_activity.clock_out.hour - out_minute = attendance_activity.clock_out.minute - return datetime(in_year, in_month, in_day, in_hour, in_minute), datetime( - out_year, out_month, out_day, out_hour, out_minute - ) - - def clock_out_attendance_and_activity(employee, date_today, now): """ Clock out the attendance and activity diff --git a/attendance/views/clock_in_out.py b/attendance/views/clock_in_out.py index 4ffdf1a9c..2e9f7efea 100644 --- a/attendance/views/clock_in_out.py +++ b/attendance/views/clock_in_out.py @@ -10,21 +10,21 @@ from django.db.models import Q from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ -from attendance.models import ( - Attendance, - AttendanceActivity, - AttendanceLateComeEarlyOut, - GraceTime, -) -from attendance.views.views import ( +from attendance.methods.utils import ( activity_datetime, - attendance_validate, employee_exists, format_time, overtime_calculation, shift_schedule_today, strtime_seconds, ) +from attendance.models import ( + Attendance, + AttendanceActivity, + AttendanceLateComeEarlyOut, + GraceTime, +) +from attendance.views.views import attendance_validate from base.context_processors import ( enable_late_come_early_out_tracking, timerunner_enabled, diff --git a/attendance/views/dashboard.py b/attendance/views/dashboard.py index 7cb7fe1a4..88e2e7f8e 100644 --- a/attendance/views/dashboard.py +++ b/attendance/views/dashboard.py @@ -4,11 +4,11 @@ dashboard.py This module is used to register endpoints for dashboard-related requests """ -import calendar import json -from datetime import date, datetime, timedelta +from datetime import date, datetime -from django.db.models import Q, Sum +from django.apps import apps +from django.db.models import Q from django.http import JsonResponse from django.shortcuts import render from django.utils.translation import gettext_lazy as _ @@ -18,19 +18,25 @@ from attendance.filters import ( AttendanceOverTimeFilter, LateComeEarlyOutFilter, ) +from attendance.methods.utils import ( + get_month_start_end_dates, + get_week_start_end_dates, + pending_hour_data, + worked_hour_data, +) from attendance.models import ( Attendance, AttendanceLateComeEarlyOut, - AttendanceOverTime, AttendanceValidationCondition, ) from attendance.views.views import strtime_seconds from base.methods import filtersubordinates -from base.models import Department, EmployeeShiftSchedule +from base.models import Department from employee.models import Employee from employee.not_in_out_dashboard import paginator_qry +from horilla import settings from horilla.decorators import hx_request_required, login_required -from leave.models import LeaveRequest +from horilla.methods import get_horilla_model_class def find_on_time(request, today, week_day, department=None): @@ -56,7 +62,11 @@ def find_expected_attendances(week_day): This method is used to find count of expected attendances for the week day """ employees = Employee.objects.filter(is_active=True) - on_leave = LeaveRequest.objects.filter(status="Approved") + if apps.is_installed("leave"): + LeaveRequest = get_horilla_model_class(app_label="leave", model="leaverequest") + on_leave = LeaveRequest.objects.filter(status="Approved") + else: + on_leave = [] expected_attendances = len(employees) - len(on_leave) return expected_attendances @@ -232,38 +242,6 @@ def find_early_out(start_date, end_date=None, department=None): return early_out_obj -def get_week_start_end_dates(week): - """ - This method is use to return the start and end date of the week - """ - # Parse the ISO week date - year, week_number = map(int, week.split("-W")) - - # Get the date of the first day of the week - start_date = datetime.strptime(f"{year}-W{week_number}-1", "%Y-W%W-%w").date() - - # Calculate the end date by adding 6 days to the start date - end_date = start_date + timedelta(days=6) - - return start_date, end_date - - -def get_month_start_end_dates(year_month): - """ - This method is use to return the start and end date of the month - """ - # split year and month separately - year, month = map(int, year_month.split("-")) - # Get the first day of the month - start_date = datetime(year, month, 1).date() - - # Get the last day of the month - _, last_day = calendar.monthrange(year, month) - end_date = datetime(year, month, last_day).date() - - return start_date, end_date - - def generate_data_set(request, start_date, type, end_date, dept): """ This method is used to generate all the dashboard data @@ -347,42 +325,6 @@ def dashboard_attendance(request): return JsonResponse({"dataSet": data_set, "labels": labels, "message": message}) -def worked_hour_data(labels, records): - """ - To find all the worked hours - """ - data = { - "label": "Worked Hours", - "backgroundColor": "rgba(75, 192, 192, 0.6)", - } - dept_records = [] - for dept in labels: - total_sum = records.filter( - employee_id__employee_work_info__department_id__department=dept - ).aggregate(total_sum=Sum("hour_account_second"))["total_sum"] - dept_records.append(total_sum / 3600 if total_sum else 0) - data["data"] = dept_records - return data - - -def pending_hour_data(labels, records): - """ - To find all the pending hours - """ - data = { - "label": "Pending Hours", - "backgroundColor": "rgba(255, 99, 132, 0.6)", - } - dept_records = [] - for dept in labels: - total_sum = records.filter( - employee_id__employee_work_info__department_id__department=dept - ).aggregate(total_sum=Sum("hour_pending_second"))["total_sum"] - dept_records.append(total_sum / 3600 if total_sum else 0) - data["data"] = dept_records - return data - - def pending_hours(request): """ pending hours chart dashboard view @@ -472,7 +414,67 @@ def department_overtime_chart(request): "labels": departments, "department_total": department_total, "message": _("No validated Overtimes were found"), - "emptyImageSrc": "/static/images/ui/overtime-icon.png", + "emptyImageSrc": f"/{settings.STATIC_URL}images/ui/overtime-icon.png", } return JsonResponse(response) + + +@login_required +def dashboard_overtime_approve(request): + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + min_ot = strtime_seconds("00:00") + if apps.is_installed("attendance"): + from attendance.models import Attendance, AttendanceValidationCondition + + condition = AttendanceValidationCondition.objects.first() + if condition is not None and condition.minimum_overtime_to_approve is not None: + min_ot = strtime_seconds(condition.minimum_overtime_to_approve) + ot_attendances = Attendance.objects.filter( + overtime_second__gte=min_ot, + attendance_validated=True, + employee_id__is_active=True, + attendance_overtime_approve=False, + ) + else: + ot_attendances = None + ot_attendances = filtersubordinates( + request, ot_attendances, "attendance.change_attendance" + ) + ot_attendances = paginator_qry(ot_attendances, page_number) + ot_attendances_ids = json.dumps([instance.id for instance in ot_attendances]) + return render( + request, + "request_and_approve/overtime_approve.html", + { + "overtime_attendances": ot_attendances, + "ot_attendances_ids": ot_attendances_ids, + "pd": previous_data, + }, + ) + + +@login_required +def dashboard_attendance_validate(request): + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + validate_attendances = Attendance.objects.filter( + attendance_validated=False, employee_id__is_active=True + ) + validate_attendances = filtersubordinates( + request, validate_attendances, "attendance.change_attendance" + ) + validate_attendances = paginator_qry(validate_attendances, page_number) + validate_attendances_ids = json.dumps( + [instance.id for instance in validate_attendances] + ) + return render( + request, + "request_and_approve/attendance_validate.html", + { + "validate_attendances": validate_attendances, + "validate_attendances_ids": validate_attendances_ids, + "pd": previous_data, + }, + ) diff --git a/attendance/views/penalty.py b/attendance/views/penalty.py index eb88bbe0a..81d18e336 100644 --- a/attendance/views/penalty.py +++ b/attendance/views/penalty.py @@ -4,17 +4,18 @@ attendance/views/penalty.py This module is used to write late come early out penatly methods """ +from django.apps import apps from django.contrib import messages +from django.db.models.query import QuerySet from django.http import HttpResponse from django.shortcuts import redirect, render from django.utils.translation import gettext_lazy as _ -from attendance.filters import PenaltyFilter -from attendance.forms import PenaltyAccountForm -from attendance.models import AttendanceLateComeEarlyOut, PenaltyAccount -from employee.models import Employee +from attendance.models import AttendanceLateComeEarlyOut +from base.forms import PenaltyAccountForm +from base.models import PenaltyAccounts from horilla.decorators import hx_request_required, login_required, manager_can_enter -from leave.models import AvailableLeave +from horilla.methods import get_horilla_model_class @login_required @@ -34,21 +35,29 @@ def cut_available_leave(request, instance_id): previous_data = request_copy.urlencode() instance = AttendanceLateComeEarlyOut.objects.get(id=instance_id) form = PenaltyAccountForm(employee=instance.employee_id) - available = AvailableLeave.objects.filter(employee_id=instance.employee_id) + if apps.is_installed("leave"): + AvailableLeave = get_horilla_model_class( + app_label="leave", model="availableleave" + ) + available = AvailableLeave.objects.filter(employee_id=instance.employee_id) + else: + available = QuerySet().none() if request.method == "POST": form = PenaltyAccountForm(request.POST) if form.is_valid(): penalty_instance = form.instance - penalty = PenaltyAccount() - # late come early out id - penalty.late_early_id = instance - penalty.deduct_from_carry_forward = ( - penalty_instance.deduct_from_carry_forward - ) + penalty = PenaltyAccounts() penalty.employee_id = instance.employee_id - penalty.leave_type_id = penalty_instance.leave_type_id - penalty.minus_leaves = penalty_instance.minus_leaves + penalty.late_early_id = instance penalty.penalty_amount = penalty_instance.penalty_amount + + if apps.is_installed("leave"): + penalty.leave_type_id = penalty_instance.leave_type_id + penalty.minus_leaves = penalty_instance.minus_leaves + penalty.deduct_from_carry_forward = ( + penalty_instance.deduct_from_carry_forward + ) + penalty.save() messages.success(request, _("Penalty/Fine added")) form = PenaltyAccountForm() @@ -63,13 +72,3 @@ def cut_available_leave(request, instance_id): "pd": previous_data, }, ) - - -@login_required -@hx_request_required -def view_penalties(request): - """ - This method is used to filter or view the penalties - """ - records = PenaltyFilter(request.GET).qs - return render(request, "attendance/penalty/penalty_view.html", {"records": records}) diff --git a/attendance/views/requests.py b/attendance/views/requests.py index 1b8644cd8..0112fc0a3 100644 --- a/attendance/views/requests.py +++ b/attendance/views/requests.py @@ -22,10 +22,14 @@ from attendance.forms import ( BulkAttendanceRequestForm, NewRequestForm, ) -from attendance.methods.differentiate import get_diff_dict +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 attendance.views.views import paginator_qry, shift_schedule_today from base.methods import ( choosesubordinates, closest_numbers, @@ -39,15 +43,6 @@ from horilla.decorators import hx_request_required, login_required, manager_can_ from notifications.signals import notify -def get_employee_last_name(attendance): - """ - This method is used to return the last name - """ - if attendance.employee_id.employee_last_name: - return attendance.employee_id.employee_last_name - return "" - - @login_required def request_attendance(request): """ diff --git a/attendance/views/search.py b/attendance/views/search.py index a0c418054..1b8429d78 100644 --- a/attendance/views/search.py +++ b/attendance/views/search.py @@ -8,6 +8,7 @@ import json from datetime import datetime from urllib.parse import parse_qs +from django.http import JsonResponse from django.shortcuts import render from django.utils.translation import gettext_lazy as _ @@ -478,9 +479,6 @@ def search_attendance_requests(request): ) -from django.http import JsonResponse - - @login_required def widget_filter(request): """ diff --git a/attendance/views/views.py b/attendance/views/views.py index 58ffa87b1..16bfa2626 100644 --- a/attendance/views/views.py +++ b/attendance/views/views.py @@ -22,7 +22,9 @@ from urllib.parse import parse_qs import pandas as pd from django.contrib import messages from django.core.paginator import Paginator +from django.core.validators import validate_ipv46_address from django.db.models import ProtectedError +from django.forms import ValidationError from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import redirect, render from django.urls import reverse @@ -52,6 +54,14 @@ from attendance.forms import ( GraceTimeForm, LateComeEarlyOutExportForm, ) +from attendance.methods.utils import ( + attendance_day_checking, + format_time, + is_reportingmanger, + monthly_leave_days, + paginator_qry, + strtime_seconds, +) from attendance.models import ( Attendance, AttendanceActivity, @@ -62,18 +72,30 @@ from attendance.models import ( AttendanceRequestFile, AttendanceValidationCondition, GraceTime, + WorkRecords, ) from attendance.views.handle_attendance_errors import handle_attendance_errors from attendance.views.process_attendance_data import process_attendance_data +from base.forms import ( + AttendanceAllowedIPForm, + AttendanceAllowedIPUpdateForm, + TrackLateComeEarlyOutForm, +) from base.methods import ( choosesubordinates, closest_numbers, export_data, filtersubordinates, get_key_instances, - get_pagination, ) -from base.models import EmployeeShiftSchedule +from base.models import ( + WEEK_DAYS, + AttendanceAllowedIP, + CompanyLeaves, + EmployeeShiftSchedule, + Holidays, + TrackLateComeEarlyOut, +) from employee.filters import EmployeeFilter from employee.models import Employee, EmployeeWorkInformation from horilla.decorators import ( @@ -83,61 +105,7 @@ from horilla.decorators import ( manager_can_enter, permission_required, ) -from leave.models import WEEK_DAYS, CompanyLeave, Holiday from notifications.signals import notify -from payroll.models.models import WorkRecord - -# Create your views here. - - -def intersection_list(list1, list2): - """ - This method is used to intersect two list - """ - return [value for value in list1 if value in list2] - - -def format_time(seconds): - """ - this method is used to formate seconds to H:M and return it - args: - seconds : seconds - """ - - hour = int(seconds // 3600) - minutes = int((seconds % 3600) // 60) - seconds = int((seconds % 3600) % 60) - return f"{hour:02d}:{minutes:02d}" - - -def strtime_seconds(time): - """ - this method is used reconvert time in H:M formate string back to seconds and return it - args: - time : time in H:M format - """ - - ftr = [3600, 60, 1] - return sum(a * b for a, b in zip(ftr, map(int, time.split(":")))) - - -def is_reportingmanger(request, instance): - """ - if the instance have employee id field then you can use this method to know the - request user employee is the reporting manager of the instance - args : - request : request - instance : an object or instance of any model contain employee_id foreign key field - """ - - manager = request.user.employee_get - try: - employee_workinfo_manager = ( - instance.employee_id.employee_work_info.reporting_manager_id - ) - except Exception: - return HttpResponse("This Employee Dont Have any work information") - return manager == employee_workinfo_manager def attendance_validate(attendance): @@ -157,60 +125,66 @@ def attendance_validate(attendance): return condition_for_at_work >= at_work -def attendance_day_checking(attendance_date, minimum_hour): - # Convert the string to a datetime object - attendance_datetime = datetime.strptime(attendance_date, "%Y-%m-%d") +@login_required +@hx_request_required +def profile_attendance_tab(request): + """ + This function is used to view attendance tab of an employee in profile view. - # Extract name of the day - attendance_day = attendance_datetime.strftime("%A") + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. - # Taking all holidays into a list - leaves = [] - holidays = Holiday.objects.all() - for holi in holidays: - start_date = holi.start_date - end_date = holi.end_date + Returns: return asset-request-tab template - # Convert start_date and end_date to datetime objects - start_date = datetime.strptime(str(start_date), "%Y-%m-%d") - end_date = datetime.strptime(str(end_date), "%Y-%m-%d") + """ + user = request.user + employee = user.employee_get + employee_attendances = employee.employee_attendances.all() + attendances_ids = json.dumps([instance.id for instance in employee_attendances]) + context = { + "attendances": employee_attendances, + "attendances_ids": attendances_ids, + } + return render(request, "tabs/profile-attendance-tab.html", context) - # Add dates in between start date and end date including both - current_date = start_date - while current_date <= end_date: - leaves.append(current_date.strftime("%Y-%m-%d")) - current_date += timedelta(days=1) - # Checking attendance date is in holiday list, if found making the minimum hour to 00:00 - for leave in leaves: - if str(leave) == str(attendance_date): - minimum_hour = "00:00" - break +@login_required +@manager_can_enter("employee.view_employee") +def attendance_tab(request, emp_id): + """ + This function is used to view attendance tab of an employee in individual view. - # Making a dictonary contains week day value and leave day pairs - company_leaves = {} - company_leave = CompanyLeave.objects.all() - for com_leave in company_leave: - a = dict(WEEK_DAYS).get(com_leave.based_on_week_day) - b = com_leave.based_on_week - company_leaves[b] = a + Parameters: + request (HttpRequest): The HTTP request object. + emp_id (int): The id of the employee. - # Checking the attendance date is in which week - week_in_month = str(((attendance_datetime.day - 1) // 7 + 1) - 1) + Returns: return attendance-tab template + """ - # Checking the attendance date is in the company leave or not - for pairs in company_leaves.items(): - # For all weeks based_on_week is None - if str(pairs[0]) == "None": - if str(pairs[1]) == str(attendance_day): - minimum_hour = "00:00" - break - # Checking with based_on_week and attendance_date week - if str(pairs[0]) == week_in_month: - if str(pairs[1]) == str(attendance_day): - minimum_hour = "00:00" - break - return minimum_hour + requests = Attendance.objects.filter( + is_validate_request=True, + employee_id=emp_id, + ) + attendances_ids = json.dumps([instance.id for instance in requests]) + validate_attendances = Attendance.objects.filter( + attendance_validated=False, employee_id=emp_id + ) + validate_attendances_ids = json.dumps( + [instance.id for instance in validate_attendances] + ) + accounts = AttendanceOverTime.objects.filter(employee_id=emp_id) + accounts_ids = json.dumps([instance.id for instance in accounts]) + + context = { + "requests": requests, + "attendances_ids": attendances_ids, + "accounts": accounts, + "accounts_ids": accounts_ids, + "validate_attendances": validate_attendances, + "validate_attendances_ids": validate_attendances_ids, + } + return render(request, "tabs/attendance-tab.html", context=context) @login_required @@ -237,22 +211,6 @@ def attendance_create(request): return render(request, "attendance/attendance/form.html", {"form": form}) -def get_record_per_page(): - """ - This method will return the record per page count - """ - return 50 - - -def paginator_qry(qryset, page_number): - """ - This method is used to paginate queryset - """ - paginator = Paginator(qryset, get_pagination()) - qryset = paginator.get_page(page_number) - return qryset - - def attendance_excel(_request): """ Generate an empty Excel template for attendance data with predefined columns. @@ -908,80 +866,6 @@ def attendance_activity_export(request): ) -def employee_exists(request): - """ - This method return the employee instance and work info if not exists return None instead - """ - employee, employee_work_info = None, None - try: - employee = request.user.employee_get - employee_work_info = employee.employee_work_info - finally: - return (employee, employee_work_info) - - -def shift_schedule_today(day, shift): - """ - This function is used to find shift schedules for the day, - it will returns min hour,start time seconds end time seconds - args: - shift : shift instance - day : shift day object - """ - schedule_today = day.day_schedule.filter(shift_id=shift) - start_time_sec, end_time_sec, minimum_hour = 0, 0, "00:00" - if schedule_today.exists(): - schedule_today = schedule_today[0] - minimum_hour = schedule_today.minimum_working_hour - start_time_sec = strtime_seconds(schedule_today.start_time.strftime("%H:%M")) - end_time_sec = strtime_seconds(schedule_today.end_time.strftime("%H:%M")) - return (minimum_hour, start_time_sec, end_time_sec) - - -def overtime_calculation(attendance): - """ - This method is used to calculate overtime of the attendance, it will - return difference between attendance worked hour and minimum hour if - and only worked hour greater than minimum hour, else return 00:00 - args: - attendance : attendance instance - """ - - minimum_hour = attendance.minimum_hour - at_work = attendance.attendance_worked_hour - at_work_sec = strtime_seconds(at_work) - minimum_hour_sec = strtime_seconds(minimum_hour) - if at_work_sec > minimum_hour_sec: - return format_time((at_work_sec - minimum_hour_sec)) - return "00:00" - - -def activity_datetime(attendance_activity): - """ - This method is used to convert clock-in and clock-out of activity as datetime object - args: - attendance_activity : attendance activity instance - """ - - # in - in_year = attendance_activity.clock_in_date.year - in_month = attendance_activity.clock_in_date.month - in_day = attendance_activity.clock_in_date.day - in_hour = attendance_activity.clock_in.hour - in_minute = attendance_activity.clock_in.minute - in_seconds = attendance_activity.clock_in.second - # out - out_year = attendance_activity.clock_out_date.year - out_month = attendance_activity.clock_out_date.month - out_day = attendance_activity.clock_out_date.day - out_hour = attendance_activity.clock_out.hour - out_minute = attendance_activity.clock_out.minute - out_seconds = attendance_activity.clock_out.second - return datetime( - in_year, in_month, in_day, in_hour, in_minute, in_seconds - ), datetime(out_year, out_month, out_day, out_hour, out_minute, out_seconds) - - @login_required def on_time_view(request): """ @@ -1986,45 +1870,6 @@ def delete_comment_file(request): ) -def monthly_leave_days(month, year): - leave_dates = [] - holidays = Holiday.objects.filter(start_date__month=month, start_date__year=year) - leave_dates += list(holidays.values_list("start_date", flat=True)) - - company_leaves = CompanyLeave.objects.all() - for company_leave in company_leaves: - year = year - month = month - based_on_week = company_leave.based_on_week - based_on_week_day = company_leave.based_on_week_day - if based_on_week != None: - calendar.setfirstweekday(6) - month_calendar = calendar.monthcalendar(year, month) - weeks = month_calendar[int(based_on_week)] - weekdays_in_weeks = [day for day in weeks if day != 0] - for day in weekdays_in_weeks: - date_name = datetime.strptime( - f"{year}-{month:02}-{day:02}", "%Y-%m-%d" - ).date() - if ( - date_name.weekday() == int(based_on_week_day) - and date_name not in leave_dates - ): - leave_dates.append(date_name) - else: - calendar.setfirstweekday(0) - month_calendar = calendar.monthcalendar(year, month) - for week in month_calendar: - if week[int(based_on_week_day)] != 0: - date_name = datetime.strptime( - f"{year}-{month:02}-{week[int(based_on_week_day)]:02}", - "%Y-%m-%d", - ).date() - if date_name not in leave_dates: - leave_dates.append(date_name) - return leave_dates - - @login_required def work_records(request): today = date.today() @@ -2063,7 +1908,7 @@ def work_records_change_month(request): days = [day for week in month_matrix for day in week if day != 0] current_month_date_list = [datetime(year, month, day).date() for day in days] - all_work_records = WorkRecord.objects.filter( + all_work_records = WorkRecords.objects.filter( date__in=current_month_date_list ).select_related("employee_id") @@ -2118,11 +1963,11 @@ def work_records_change_month(request): @login_required -@permission_required("leave.add_leaverequest") +@permission_required("attendance.view_workrecords") def work_record_export(request): month = int(request.GET.get("month", date.today().month)) year = int(request.GET.get("year", date.today().year)) - records = WorkRecord.objects.filter(date__month=month, date__year=year) + records = WorkRecords.objects.filter(date__month=month, date__year=year) num_days = calendar.monthrange(year, month)[1] all_date_objects = [date(year, month, day) for day in range(1, num_days + 1)] leave_dates = monthly_leave_days(month, year) @@ -2243,3 +2088,259 @@ def enable_timerunner(request): time_runner.time_runner = "time_runner" in request.GET.keys() time_runner.save() return HttpResponse("success") + + +@login_required +@permission_required("attendance.view_attendancevalidationcondition") +def validation_condition_view(request): + """ + This method view attendance validation conditions. + """ + + condition = AttendanceValidationCondition.objects.first() + default_grace_time = GraceTime.objects.filter(is_default=True).first() + return render( + request, + "attendance/break_point/condition.html", + {"condition": condition, "default_grace_time": default_grace_time}, + ) + + +@login_required +@permission_required("base.view_tracklatecomeearlyout") +def track_late_come_early_out(request): + """ + Renders the form to track late arrivals and early departures in attendance. + """ + tracking = TrackLateComeEarlyOut.objects.first() + form = TrackLateComeEarlyOutForm( + initial={"is_enable": tracking.is_enable} if tracking else {} + ) + return render( + request, "attendance/late_come_early_out/tracking.html", {"form": form} + ) + + +@login_required +@permission_required("base.change_tracklatecomeearlyout") +def enable_disable_tracking_late_come_early_out(request): + """ + Enables or disables the tracking of late arrivals and early departures in attendance. + """ + if request.method == "POST": + enable = bool(request.POST.get("is_enable")) + tracking, created = TrackLateComeEarlyOut.objects.get_or_create() + tracking.is_enable = enable + tracking.save() + message = _("enabled") if enable else _("disabled") + messages.success( + request, _("Tracking late come early out {} successfully").format(message) + ) + return HttpResponse("") + + +@login_required +@permission_required("attendance.view_attendancevalidationcondition") +def grace_time_view(request): + """ + This method view attendance validation conditions. + """ + condition = AttendanceValidationCondition.objects.first() + default_grace_time = GraceTime.objects.filter(is_default=True).first() + grace_times = GraceTime.objects.all().exclude(is_default=True) + return render( + request, + "attendance/grace_time/grace_time.html", + { + "condition": condition, + "default_grace_time": default_grace_time, + "grace_times": grace_times, + }, + ) + + +@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() + if request.method == "POST": + form = AttendanceValidationConditionForm(request.POST) + if form.is_valid(): + form.save() + messages.success(request, _("Attendance Break-point settings created.")) + return HttpResponse("") + return render( + request, + "attendance/break_point/condition_form.html", + {"form": form}, + ) + + +@login_required +@hx_request_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() + messages.success(request, _("Attendance Break-point settings updated.")) + return HttpResponse("") + return render( + request, + "attendance/break_point/condition_form.html", + {"form": form, "condition": condition}, + ) + + +@login_required +@permission_required("attendance.add_attendance") +def allowed_ips(request): + """ + This function is used to view the allowed ips + """ + allowed_ips = AttendanceAllowedIP.objects.first() + return render( + request, + "attendance/ip_restriction/ip_restriction.html", + {"allowed_ips": allowed_ips}, + ) + + +@login_required +@permission_required("attendance.add_attendance") +def enable_ip_restriction(request): + """ + This function is used to enable the allowed ips + """ + form = AttendanceAllowedIPForm() + if request.method == "POST": + ip_restiction = AttendanceAllowedIP.objects.first() + + if not ip_restiction: + ip_restiction = AttendanceAllowedIP.objects.create(is_enabled=True) + return HttpResponse("") + + if not ip_restiction.is_enabled: + ip_restiction.is_enabled = True + elif ip_restiction.is_enabled: + ip_restiction.is_enabled = False + + ip_restiction.save() + return HttpResponse("") + + +def validate_ip_address(self, value): + """ + This function is used to check if the provided IP is in the ipv4 or ipv6 format. + + Args: + value: The IP address to validate + """ + try: + validate_ipv46_address(value) + except ValidationError: + raise ValidationError("Enter a valid IPv4 or IPv6 address.") + return value + + +@login_required +@permission_required("attendance.add_attendance") +def create_allowed_ips(request): + """ + This function is used to create the allowed ips + """ + form = AttendanceAllowedIPForm() + if request.method == "POST": + form = AttendanceAllowedIPForm(request.POST) + if form.is_valid(): + values = [request.POST[key] for key in request.POST.keys()] + allowed_ips = AttendanceAllowedIP.objects.first() + for value in values: + try: + validate_ipv46_address(value) + if value not in allowed_ips.additional_data["allowed_ips"]: + allowed_ips.additional_data["allowed_ips"].append(value) + messages.success(request, f"IP address saved successfully") + else: + messages.error(request, "IP address already exists") + + except ValidationError: + messages.error( + request, f"Enter a valid IPv4 or IPv6 address: {value}" + ) + + allowed_ips.save() + + return HttpResponse("") + return render( + request, "attendance/ip_restriction/restrict_form.html", {"form": form} + ) + + +@login_required +@permission_required("attendance.delete_attendance") +def delete_allowed_ips(request): + """ + This function is used to delete the allowed ips + """ + try: + ids = request.GET.getlist("id") + allowed_ips = AttendanceAllowedIP.objects.first() + ips = allowed_ips.additional_data["allowed_ips"] + for id in ids: + ips.pop(eval(id)) + + allowed_ips.additional_data["allowed_ips"] = ips + allowed_ips.save() + + messages.success(request, "IP address removed successfully") + except: + messages.error(request, "Invalid id") + return redirect("allowed-ips") + + +@login_required +@permission_required("attendance.change_attendance") +def edit_allowed_ips(request): + """ + This function is used to edit the allowed ips + """ + try: + + allowed_ips = AttendanceAllowedIP.objects.first() + ips = allowed_ips.additional_data["allowed_ips"] + id = request.GET.get("id") + + form = AttendanceAllowedIPUpdateForm(initial={"ip_address": ips[eval(id)]}) + if request.method == "POST": + form = AttendanceAllowedIPUpdateForm(request.POST) + if form.is_valid(): + new_ip = form.cleaned_data["ip_address"] + ips[eval(id)] = new_ip + if not new_ip in allowed_ips.additional_data["allowed_ips"]: + allowed_ips.additional_data["allowed_ips"] = ips + allowed_ips.save() + messages.success(request, "IP address updated successfully") + else: + messages.error(request, "IP address already exists") + + return HttpResponse("") + except: + messages.error(request, "Invalid id") + return render( + request, + "attendance/ip_restriction/restrict_update_form.html", + {"form": form, "id": id}, + ) diff --git a/base/admin.py b/base/admin.py index 492426d06..9ceef5fae 100644 --- a/base/admin.py +++ b/base/admin.py @@ -11,6 +11,7 @@ from base.models import ( Announcement, Attachment, Company, + CompanyLeaves, DashboardEmployeeCharts, Department, DynamicEmailConfiguration, @@ -20,9 +21,11 @@ from base.models import ( EmployeeShiftDay, EmployeeShiftSchedule, EmployeeType, + Holidays, JobPosition, JobRole, MultipleApprovalManagers, + PenaltyAccounts, RotatingShift, RotatingShiftAssign, RotatingWorkType, @@ -62,3 +65,6 @@ admin.site.register(Announcement) admin.site.register(Attachment) admin.site.register(EmailLog) admin.site.register(DashboardEmployeeCharts) +admin.site.register(Holidays) +admin.site.register(CompanyLeaves) +admin.site.register(PenaltyAccounts) diff --git a/base/context_processors.py b/base/context_processors.py index 7fb9da00f..ae62bde65 100644 --- a/base/context_processors.py +++ b/base/context_processors.py @@ -4,18 +4,16 @@ context_processor.py This module is used to register context processor` """ +from django.apps import apps from django.http import HttpResponse from django.urls import path -from attendance.models import AttendanceGeneralSetting from base.models import Company, TrackLateComeEarlyOut from base.urls import urlpatterns from employee.models import EmployeeGeneralSetting from horilla import horilla_apps from horilla.decorators import hx_request_required, login_required, permission_required -from offboarding.models import OffboardingGeneralSetting -from payroll.models.models import PayrollGeneralSetting -from recruitment.models import RecruitmentGeneralSetting +from horilla.methods import get_horilla_model_class class AllCompany: @@ -132,8 +130,13 @@ def resignation_request_enabled(request): """ Check weather resignation_request enabled of not in offboarding """ - first = OffboardingGeneralSetting.objects.first() enabled_resignation_request = False + first = None + if apps.is_installed("offboarding"): + OffboardingGeneralSetting = get_horilla_model_class( + app_label="offboarding", model="offboardinggeneralsetting" + ) + first = OffboardingGeneralSetting.objects.first() if first: enabled_resignation_request = first.resignation_request return {"enabled_resignation_request": enabled_resignation_request} @@ -143,8 +146,13 @@ def timerunner_enabled(request): """ Check weather resignation_request enabled of not in offboarding """ - first = AttendanceGeneralSetting.objects.first() + first = None enabled_timerunner = True + if apps.is_installed("attendance"): + AttendanceGeneralSetting = get_horilla_model_class( + app_label="attendance", model="attendancegeneralsetting" + ) + first = AttendanceGeneralSetting.objects.first() if first: enabled_timerunner = first.time_runner return {"enabled_timerunner": enabled_timerunner} @@ -154,8 +162,13 @@ def intial_notice_period(request): """ Check weather resignation_request enabled of not in offboarding """ - first = PayrollGeneralSetting.objects.first() initial = 30 + first = None + if apps.is_installed("payroll"): + PayrollGeneralSetting = get_horilla_model_class( + app_label="payroll", model="payrollgeneralsetting" + ) + first = PayrollGeneralSetting.objects.first() if first: initial = first.notice_period return {"get_initial_notice_period": initial} @@ -165,8 +178,15 @@ def check_candidate_self_tracking(request): """ This method is used to get the candidate self tracking is enabled or not """ - first = RecruitmentGeneralSetting.objects.first() + candidate_self_tracking = False + if apps.is_installed("recruitment"): + RecruitmentGeneralSetting = get_horilla_model_class( + app_label="recruitment", model="recruitmentgeneralsetting" + ) + first = RecruitmentGeneralSetting.objects.first() + else: + first = None if first: candidate_self_tracking = first.candidate_self_tracking return {"check_candidate_self_tracking": candidate_self_tracking} @@ -176,8 +196,14 @@ def check_candidate_self_tracking_rating(request): """ This method is used to check enabled/disabled of rating option """ - first = RecruitmentGeneralSetting.objects.first() rating_option = False + if apps.is_installed("recruitment"): + RecruitmentGeneralSetting = get_horilla_model_class( + app_label="recruitment", model="recruitmentgeneralsetting" + ) + first = RecruitmentGeneralSetting.objects.first() + else: + first = None if first: rating_option = first.show_overall_rating return {"check_candidate_self_tracking_rating": rating_option} diff --git a/base/filters.py b/base/filters.py index 39ef976a1..bf1e168e1 100644 --- a/base/filters.py +++ b/base/filters.py @@ -7,9 +7,13 @@ import uuid import django_filters from django import forms -from django_filters import CharFilter +from django.utils.translation import gettext as __ +from django_filters import CharFilter, DateFilter, FilterSet, NumberFilter, filters from base.models import ( + CompanyLeaves, + Holidays, + PenaltyAccounts, RotatingShiftAssign, RotatingWorkTypeAssign, ShiftRequest, @@ -279,3 +283,116 @@ class RotatingShiftRequestReGroup: ("employee_id__employee_work_info__job_role_id", "Job Role"), ("employee_id__employee_work_info__reporting_manager_id", "Reporting Manager"), ] + + +class HolidayFilter(FilterSet): + """ + Filter class for Holidays model. + + This filter allows searching Holidays objects based on name and date range. + """ + + search = filters.CharFilter(field_name="name", lookup_expr="icontains") + from_date = DateFilter( + field_name="start_date", + lookup_expr="gte", + widget=forms.DateInput(attrs={"type": "date"}), + ) + to_date = DateFilter( + field_name="end_date", + lookup_expr="lte", + widget=forms.DateInput(attrs={"type": "date"}), + ) + + start_date = DateFilter( + field_name="start_date", + lookup_expr="exact", + widget=forms.DateInput(attrs={"type": "date"}), + ) + + end_date = DateFilter( + field_name="end_date", + lookup_expr="exact", + widget=forms.DateInput(attrs={"type": "date"}), + ) + + class Meta: + """ + Meta class defines the model and fields to filter + """ + + model = Holidays + fields = { + "recurring": ["exact"], + } + + def __init__(self, data=None, queryset=None, *, request=None, prefix=None): + super().__init__(data=data, queryset=queryset, request=request, prefix=prefix) + for field in self.form.fields.keys(): + self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}" + + +class CompanyLeaveFilter(FilterSet): + """ + Filter class for CompanyLeaves model. + + This filter allows searching CompanyLeaves objects based on + name, week day and based_on_week choices. + """ + + name = filters.CharFilter(field_name="based_on_week_day", lookup_expr="icontains") + search = filters.CharFilter(method="filter_week_day") + + class Meta: + """ " + Meta class defines the model and fields to filter + """ + + model = CompanyLeaves + fields = { + "based_on_week": ["exact"], + "based_on_week_day": ["exact"], + } + + def filter_week_day(self, queryset, _, value): + week_qry = CompanyLeaves.objects.none() + weekday_values = [] + week_values = [] + WEEK_DAYS = [ + ("0", __("Monday")), + ("1", __("Tuesday")), + ("2", __("Wednesday")), + ("3", __("Thursday")), + ("4", __("Friday")), + ("5", __("Saturday")), + ("6", __("Sunday")), + ] + WEEKS = [ + (None, __("All")), + ("0", __("First Week")), + ("1", __("Second Week")), + ("2", __("Third Week")), + ("3", __("Fourth Week")), + ("4", __("Fifth Week")), + ] + + for day_value, day_name in WEEK_DAYS: + if value.lower() in day_name.lower(): + weekday_values.append(day_value) + for day_value, day_name in WEEKS: + if value.lower() in day_name.lower() and value.lower() != __("All").lower(): + week_values.append(day_value) + week_qry = queryset.filter(based_on_week__in=week_values) + elif value.lower() in __("All").lower(): + week_qry = queryset.filter(based_on_week__isnull=True) + return queryset.filter(based_on_week_day__in=weekday_values) | week_qry + + +class PenaltyFilter(FilterSet): + """ + PenaltyFilter + """ + + class Meta: + model = PenaltyAccounts + fields = "__all__" diff --git a/base/forms.py b/base/forms.py index c7bdbbeaf..aa3e1568c 100644 --- a/base/forms.py +++ b/base/forms.py @@ -12,6 +12,7 @@ from datetime import date, timedelta from typing import Any from django import forms +from django.apps import apps from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.auth.forms import SetPasswordForm, _unicode_ci_compare @@ -38,6 +39,7 @@ from base.models import ( AttendanceAllowedIP, BaserequestFile, Company, + CompanyLeaves, Department, DriverViewed, DynamicEmailConfiguration, @@ -46,9 +48,12 @@ from base.models import ( EmployeeShiftDay, EmployeeShiftSchedule, EmployeeType, + Holidays, + HorillaMailTemplate, JobPosition, JobRole, MultipleApprovalCondition, + PenaltyAccounts, RotatingShift, RotatingShiftAssign, RotatingWorkType, @@ -63,9 +68,10 @@ from base.models import ( ) from employee.filters import EmployeeFilter from employee.forms import MultipleFileField -from employee.models import Employee, EmployeeTag +from employee.models import Employee from horilla import horilla_middlewares from horilla.horilla_middlewares import _thread_locals +from horilla.methods import get_horilla_model_class from horilla_audit.models import AuditTag from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField from horilla_widgets.widgets.select_widgets import HorillaMultiSelectWidget @@ -1527,7 +1533,7 @@ class ShiftRequestForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html def save(self, commit: bool = ...): @@ -1590,7 +1596,7 @@ class ShiftAllocationForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html def save(self, commit: bool = ...): @@ -1639,7 +1645,7 @@ class WorkTypeRequestForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html def save(self, commit: bool = ...): @@ -1891,26 +1897,10 @@ class TagsForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html -class EmployeeTagForm(ModelForm): - """ - Employee Tags form - """ - - class Meta: - """ - Meta class for additional options - """ - - model = EmployeeTag - fields = "__all__" - exclude = ["is_active"] - widgets = {"color": TextInput(attrs={"type": "color", "style": "height:50px"})} - - class AuditTagForm(ModelForm): """ Audit Tags form @@ -1971,7 +1961,7 @@ class DynamicMailConfForm(ModelForm): Render the form fields as HTML table rows with Bootstrap styling. """ context = {"form": self} - table_html = render_to_string("attendance_form.html", context) + table_html = render_to_string("horilla_form.html", context) return table_html @@ -1983,6 +1973,46 @@ class DynamicMailTestForm(forms.Form): to_email = forms.EmailField(label="To email", required=True) +class MailTemplateForm(ModelForm): + """ + MailTemplateForm + """ + + class Meta: + model = HorillaMailTemplate + fields = "__all__" + widgets = { + "body": forms.Textarea( + attrs={"data-summernote": "", "style": "display:none;"} + ), + } + + def get_template_language(self): + mail_data = { + "Receiver|Full name": "instance.get_full_name", + "Sender|Full name": "self.get_full_name", + "Receiver|Recruitment": "instance.recruitment_id", + "Sender|Recruitment": "self.recruitment_id", + "Receiver|Company": "instance.get_company", + "Sender|Company": "self.get_company", + "Receiver|Job position": "instance.get_job_position", + "Sender|Job position": "self.get_job_position", + "Receiver|Email": "instance.get_mail", + "Sender|Email": "self.get_mail", + "Receiver|Employee Type": "instance.get_employee_type", + "Sender|Employee Type": "self.get_employee_type", + "Receiver|Work Type": "instance.get_work_type", + "Sender|Work Type": "self.get_work_type", + "Candidate|Full name": "instance.get_full_name", + "Candidate|Recruitment": "instance.recruitment_id", + "Candidate|Company": "instance.get_company", + "Candidate|Job position": "instance.get_job_position", + "Candidate|Email": "instance.get_email", + "Candidate|Interview Table": "instance.get_interview|safe", + } + return mail_data + + class MultipleApproveConditionForm(ModelForm): CONDITION_CHOICE = [ ("equal", _("Equal (==)")), @@ -2315,8 +2345,135 @@ class TrackLateComeEarlyOutForm(ModelForm): super().__init__(*args, **kwargs) self.fields["is_enable"].widget.attrs.update( { - "hx-post": "/settings/enable-disable-tracking-late-come-early-out", + "hx-post": "enable-disable-tracking-late-come-early-out", "hx-target": "this", "hx-trigger": "change", } ) + + +class HolidayForm(ModelForm): + """ + Form for creating or updating a holiday. + + This form allows users to create or update holiday data by specifying details such as + the start date and end date. + + Attributes: + - start_date: A DateField representing the start date of the holiday. + - end_date: A DateField representing the end date of the holiday. + """ + + start_date = forms.DateField( + widget=forms.DateInput(attrs={"type": "date"}), + ) + end_date = forms.DateField( + widget=forms.DateInput(attrs={"type": "date"}), + ) + + def clean_end_date(self): + start_date = self.cleaned_data.get("start_date") + end_date = self.cleaned_data.get("end_date") + + if start_date and end_date and end_date < start_date: + raise ValidationError( + _("End date should not be earlier than the start date.") + ) + + return end_date + + class Meta: + """ + Meta class for additional options + """ + + model = Holidays + fields = "__all__" + exclude = ["is_active"] + labels = { + "name": _("Name"), + } + + def __init__(self, *args, **kwargs): + super(HolidayForm, self).__init__(*args, **kwargs) + self.fields["name"].widget.attrs["autocomplete"] = "name" + + +class HolidaysColumnExportForm(forms.Form): + """ + Form for selecting columns to export in holiday data. + + This form allows users to select specific columns from the Holidays model + for export. The available columns are dynamically generated based on the + model's meta information, excluding specified excluded_fields. + + Attributes: + - model_fields: A list of fields in the Holidays model. + - field_choices: A list of field choices for the form, consisting of field names + and their verbose names, excluding specified excluded_fields. + - selected_fields: A MultipleChoiceField representing the selected columns + to be exported. + """ + + model_fields = Holidays._meta.get_fields() + field_choices = [ + (field.name, field.verbose_name) + for field in model_fields + if hasattr(field, "verbose_name") and field.name not in excluded_fields + ] + selected_fields = forms.MultipleChoiceField( + choices=field_choices, + widget=forms.CheckboxSelectMultiple, + initial=[ + "name", + "start_date", + "end_date", + "recurring", + ], + ) + + +class CompanyLeaveForm(ModelForm): + """ + Form for managing company leave data. + + This form allows users to manage company leave data by including all fields from + the CompanyLeaves model except for is_active. + + Attributes: + - Meta: Inner class defining metadata options. + - model: The model associated with the form (CompanyLeaves). + - fields: A special value indicating all fields should be included in the form. + - exclude: A list of fields to exclude from the form (is_active). + """ + + class Meta: + """ + Meta class for additional options + """ + + model = CompanyLeaves + fields = "__all__" + exclude = ["is_active"] + + +class PenaltyAccountForm(ModelForm): + """ + PenaltyAccountForm + """ + + class Meta: + model = PenaltyAccounts + fields = "__all__" + exclude = ["is_active"] + + def __init__(self, *args, **kwargs): + employee = kwargs.pop("employee", None) + super().__init__(*args, **kwargs) + if apps.is_installed("leave") and employee: + LeaveType = get_horilla_model_class(app_label="leave", model="leavetype") + available_leaves = employee.available_leave.all() + assigned_leave_types = LeaveType.objects.filter( + id__in=available_leaves.values_list("leave_type_id", flat=True) + ) + self.fields["leave_type_id"].queryset = assigned_leave_types diff --git a/base/methods.py b/base/methods.py index 3b5d1e453..cffe10250 100644 --- a/base/methods.py +++ b/base/methods.py @@ -1,8 +1,9 @@ +import calendar import io import json import os import random -from datetime import date, datetime, time +from datetime import date, datetime, time, timedelta import pandas as pd from django.apps import apps @@ -10,7 +11,7 @@ from django.conf import settings from django.contrib.staticfiles import finders from django.core.exceptions import ObjectDoesNotExist from django.db import models -from django.db.models import F, ForeignKey, ManyToManyField, OneToOneField +from django.db.models import F, ForeignKey, ManyToManyField, OneToOneField, Q from django.db.models.functions import Lower from django.forms.models import ModelChoiceField from django.http import HttpResponse @@ -18,11 +19,9 @@ from django.template.loader import get_template, render_to_string from django.utils.translation import gettext as _ from xhtml2pdf import pisa -from base.models import Company, DynamicPagination +from base.models import Company, CompanyLeaves, DynamicPagination, Holidays from employee.models import Employee, EmployeeWorkInformation from horilla.decorators import login_required -from leave.models import LeaveRequest, LeaveRequestConditionApproval -from recruitment.models import Candidate def filtersubordinates(request, queryset, perm=None, field=None): @@ -586,15 +585,21 @@ def reload_queryset(fields): """ This method is used to reload the querysets in the form """ - for k, v in fields.items(): - if isinstance(v, ModelChoiceField): - if v.queryset.model == Employee: - v.queryset = v.queryset.model.objects.filter(is_active=True) - elif v.queryset.model == Candidate: - v.queryset = v.queryset.model.objects.filter(is_active=True) + model_filters = { + "Employee": {"is_active": True}, + "Candidate": {"is_active": True} if apps.is_installed("recruitment") else None, + } + + for field in fields.values(): + if isinstance(field, ModelChoiceField): + model_name = field.queryset.model.__name__ + filter_criteria = model_filters.get(model_name) + if filter_criteria is not None: + field.queryset = field.queryset.model.objects.filter(**filter_criteria) else: - v.queryset = v.queryset.model.objects.all() - return + field.queryset = field.queryset.model.objects.all() + + return fields def check_manager(employee, instance): @@ -680,26 +685,6 @@ def generate_pdf(template_path, context, path=True, title=None, html=True): return response -def filter_conditional_leave_request(request): - approval_manager = Employee.objects.filter(employee_user_id=request.user).first() - leave_request_ids = [] - multiple_approval_requests = LeaveRequestConditionApproval.objects.filter( - manager_id=approval_manager - ) - for instance in multiple_approval_requests: - if instance.sequence > 1: - pre_sequence = instance.sequence - 1 - leave_request_id = instance.leave_request_id - instance = LeaveRequestConditionApproval.objects.filter( - leave_request_id=leave_request_id, sequence=pre_sequence - ).first() - if instance and instance.is_approved: - leave_request_ids.append(instance.leave_request_id.id) - else: - leave_request_ids.append(instance.leave_request_id.id) - return LeaveRequest.objects.filter(pk__in=leave_request_ids) - - def get_pagination(): from horilla.horilla_middlewares import _thread_locals @@ -710,3 +695,200 @@ def get_pagination(): if page: count = page.pagination return count + + +def is_holiday(date): + """ + Check if the given date is a holiday. + Args: + date (datetime.date): The date to check. + Returns: + Holidays or bool: The Holidays object if the date is a holiday, otherwise False. + """ + holidays = Holidays.objects.all() + for holiday in holidays: + start_date = holiday.start_date + end_date = holiday.end_date + # Check if the date is within the range of the holiday dates + if start_date <= date <= end_date: + return holiday + # Check for recurring holidays + if holiday.recurring: + try: + # Create a new date object for comparison without the year + start_date_without_year = datetime( + year=date.year, month=start_date.month, day=start_date.day + ).date() + end_date_without_year = datetime( + year=date.year, month=end_date.month, day=end_date.day + ).date() + if start_date_without_year <= date <= end_date_without_year: + return holiday + except: + return False + return False + + +def is_company_leave(input_date): + """ + Check if the given date is a company leave. + Args: + input_date (datetime.date): The date to check. + Returns: + CompanyLeaves or bool: The CompanyLeaves object if the date is a company leave, otherwise False. + """ + # Calculate the week number within the month (1-5) + first_day_of_month = input_date.replace(day=1) + first_week_day = first_day_of_month.weekday() # Monday is 0 and Sunday is 6 + adjusted_day = input_date.day + first_week_day + date_week_no = (adjusted_day - 1) // 7 + # Calculate the weekday (1 for Monday to 7 for Sunday) + date_week_day = input_date.isoweekday() - 1 + company_leaves = CompanyLeaves.objects.all() + for company_leave in company_leaves: + week_no = ( + company_leave.based_on_week + if not company_leave.based_on_week + else int(company_leave.based_on_week) + ) # from 0 for the first week to 4 for the fifth week + week_day = int( + company_leave.based_on_week_day + ) # from 0 to 6 for Monday to Sunday + if not week_no: + if date_week_day == week_day: + return company_leave + if date_week_no == week_no and date_week_day == week_day: + return company_leave + return False + + +def get_date_range(start_date, end_date): + """ + Returns a list of all dates within a given date range. + + Args: + start_date (date): The start date of the range. + end_date (date): The end date of the range. + + Returns: + list: A list of date objects representing all dates within the range. + + Example: + start_date = date(2023, 1, 1) + end_date = date(2023, 1, 10) + date_range = get_date_range(start_date, end_date) + for date_obj in date_range: + print(date_obj) + """ + date_list = [] + delta = end_date - start_date + + for i in range(delta.days + 1): + current_date = start_date + timedelta(days=i) + date_list.append(current_date) + return date_list + + +def get_holiday_dates(range_start: date, range_end: date) -> list: + """ + :return: this functions returns a list of all holiday dates. + """ + pay_range_dates = get_date_range(start_date=range_start, end_date=range_end) + query = Q() + for check_date in pay_range_dates: + query |= Q(start_date__lte=check_date, end_date__gte=check_date) + holidays = Holidays.objects.filter(query) + holiday_dates = set([]) + for holiday in holidays: + holiday_dates = holiday_dates | ( + set( + get_date_range(start_date=holiday.start_date, end_date=holiday.end_date) + ) + ) + return list(set(holiday_dates)) + + +def get_company_leave_dates(year): + """ + :return: This function returns a list of all company leave dates + """ + company_leaves = CompanyLeaves.objects.all() + company_leave_dates = [] + for company_leave in company_leaves: + based_on_week = company_leave.based_on_week + based_on_week_day = company_leave.based_on_week_day + for month in range(1, 13): + if based_on_week is not None: + # Set Sunday as the first day of the week + calendar.setfirstweekday(6) + month_calendar = calendar.monthcalendar(year, month) + weeks = month_calendar[int(based_on_week)] + weekdays_in_weeks = [day for day in weeks if day != 0] + for day in weekdays_in_weeks: + leave_date = datetime.strptime( + f"{year}-{month:02}-{day:02}", "%Y-%m-%d" + ).date() + if ( + leave_date.weekday() == int(based_on_week_day) + and leave_date not in company_leave_dates + ): + company_leave_dates.append(leave_date) + else: + # Set Monday as the first day of the week + calendar.setfirstweekday(0) + month_calendar = calendar.monthcalendar(year, month) + for week in month_calendar: + if week[int(based_on_week_day)] != 0: + leave_date = datetime.strptime( + f"{year}-{month:02}-{week[int(based_on_week_day)]:02}", + "%Y-%m-%d", + ).date() + if leave_date not in company_leave_dates: + company_leave_dates.append(leave_date) + return company_leave_dates + + +def get_working_days(start_date, end_date): + """ + This method is used to calculate the total working days, total leave, worked days on that period + + Args: + start_date (_type_): the start date from the data needed + end_date (_type_): the end date till the date needed + """ + + holiday_dates = get_holiday_dates(start_date, end_date) + + # appending company/holiday leaves + # Note: Duplicate entry may exist + company_leave_dates = ( + list( + set( + get_company_leave_dates(start_date.year) + + get_company_leave_dates(end_date.year) + ) + ) + + holiday_dates + ) + + date_range = get_date_range(start_date, end_date) + + # making unique list of company/holiday leave dates then filtering + # the leave dates only between the start and end date + company_leave_dates = [ + date + for date in list(set(company_leave_dates)) + if start_date <= date <= end_date + ] + + working_days_between_ranges = list(set(date_range) - set(company_leave_dates)) + total_working_days = len(working_days_between_ranges) + + return { + # Total working days on that period + "total_working_days": total_working_days, + # All the working dates between the start and end date + "working_days_on": working_days_between_ranges, + # All the company/holiday leave dates between the range + "company_leave_dates": company_leave_dates, + } diff --git a/base/migrations/__init__.py b/base/migrations/__init__.py index e69de29bb..19cd0e37c 100644 --- a/base/migrations/__init__.py +++ b/base/migrations/__init__.py @@ -0,0 +1,144 @@ +from django.apps import apps + +try: + RecruitmentMailTemplate = apps.get_model("recruitment", "RecruitmentMailTemplate") + HorillaMailTemplate = apps.get_model("base", "HorillaMailTemplate") + + recruitment_mail_templates = RecruitmentMailTemplate.objects.all() + for recruitment_mail in recruitment_mail_templates: + if not HorillaMailTemplate.objects.filter( + title=recruitment_mail.title + ).exists(): + horilla_mail = HorillaMailTemplate( + id=recruitment_mail.id, + title=recruitment_mail.title, + body=recruitment_mail.body, + company_id=recruitment_mail.company_id, + ) + horilla_mail.save() + + horilla_mail_templates = HorillaMailTemplate.objects.all() +except Exception as e: + pass + +try: + LeaveHoliday = apps.get_model("leave", "Holiday") + BaseHoliday = apps.get_model("base", "Holidays") + + leave_holidays = LeaveHoliday.objects.all() + for holiday in leave_holidays: + if not BaseHoliday.objects.filter( + name=holiday.name, + start_date=holiday.start_date, + end_date=holiday.end_date, + ).exists(): + horilla = BaseHoliday( + id=holiday.id, + name=holiday.name, + start_date=holiday.start_date, + end_date=holiday.end_date, + ) + horilla.save() + + base_leaves = BaseHoliday.objects.all() +except Exception as e: + pass + +try: + PenaltyAccount = apps.get_model("attendance", "PenaltyAccount") + PenaltyAccounts = apps.get_model("base", "PenaltyAccounts") + + penalties = PenaltyAccount.objects.all() + for penalty in penalties: + filter_conditions = { + "employee_id": penalty.employee_id, + "penalty_amount": penalty.penalty_amount, + } + if apps.is_installed("attendance"): + filter_conditions.update( + { + "late_early_id": penalty.late_early_id, + } + ) + if apps.is_installed("leave"): + filter_conditions.update( + { + "leave_request_id": penalty.leave_request_id, + "leave_type_id": penalty.leave_type_id, + "minus_leaves": penalty.minus_leaves, + "deduct_from_carry_forward": penalty.deduct_from_carry_forward, + } + ) + + if not PenaltyAccounts.objects.filter(**filter_conditions).exists(): + horilla = PenaltyAccounts( + id=penalty.id, + employee_id=penalty.employee_id, + penalty_amount=penalty.penalty_amount, + ) + if apps.is_installed("attendance"): + horilla.late_early_id = penalty.late_early_id + if apps.is_installed("leave"): + horilla.leave_request_id = penalty.leave_request_id + horilla.leave_type_id = penalty.leave_type_id + horilla.minus_leaves = penalty.minus_leaves + horilla.deduct_from_carry_forward = penalty.deduct_from_carry_forward + horilla.save() + penalty_accounts = PenaltyAccounts.objects.all() +except Exception as e: + pass + +try: + CompanyLeave = apps.get_model("leave", "CompanyLeave") + BaseCompanyLeave = apps.get_model("base", "CompanyLeaves") + + company_leaves = CompanyLeave.objects.all() + for leave in company_leaves: + if not BaseCompanyLeave.objects.filter( + based_on_week=leave.based_on_week, + based_on_week_day=leave.based_on_week_day, + ).exists(): + horilla = BaseCompanyLeave( + id=leave.id, + based_on_week=leave.based_on_week, + based_on_week_day=leave.based_on_week_day, + ) + horilla.save() + + base_leaves = BaseCompanyLeave.objects.all() +except Exception as e: + pass + +try: + WorkRecord = apps.get_model("payroll", "WorkRecord") + WorkRecords = apps.get_model("attendance", "WorkRecords") + + work_records = WorkRecord.objects.all() + for work_record in work_records: + if not WorkRecords.objects.filter( + record_name=work_record.record_name, + employee_id=work_record.employee_id, + date=work_record.date, + ).exists(): + new_work_record = WorkRecords( + id=work_record.id, + record_name=work_record.record_name, + work_record_type=work_record.work_record_type, + employee_id=work_record.employee_id, + date=work_record.date, + at_work=work_record.at_work, + min_hour=work_record.min_hour, + at_work_second=work_record.at_work_second, + min_hour_second=work_record.min_hour_second, + note=work_record.note, + message=work_record.message, + is_attendance_record=work_record.is_attendance_record, + is_leave_record=work_record.is_leave_record, + day_percentage=work_record.day_percentage, + last_update=work_record.last_update, + ) + new_work_record.save() + + new_work_records = WorkRecords.objects.all() +except Exception as e: + print(f"An error occurred: {e}") diff --git a/base/models.py b/base/models.py index e4ae7b048..a2ac1ec23 100644 --- a/base/models.py +++ b/base/models.py @@ -4,22 +4,45 @@ models.py This module is used to register django models """ +from datetime import date, datetime, timedelta from typing import Iterable import django +from django.apps import apps from django.contrib import messages from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from base.horilla_company_manager import HorillaCompanyManager from horilla import horilla_middlewares from horilla.horilla_middlewares import _thread_locals +from horilla.methods import get_horilla_model_class from horilla.models import HorillaModel from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog # Create your models here. +WEEKS = [ + ("0", _("First Week")), + ("1", _("Second Week")), + ("2", _("Third Week")), + ("3", _("Fourth Week")), + ("4", _("Fifth Week")), +] + + +WEEK_DAYS = [ + ("0", _("Monday")), + ("1", _("Tuesday")), + ("2", _("Wednesday")), + ("3", _("Thursday")), + ("4", _("Friday")), + ("5", _("Saturday")), + ("6", _("Sunday")), +] def validate_time_format(value): @@ -491,14 +514,15 @@ class EmployeeShift(HorillaModel): max_length=6, default="200:00", validators=[validate_time_format] ) company_id = models.ManyToManyField(Company, blank=True, verbose_name=_("Company")) - grace_time_id = models.ForeignKey( - "attendance.GraceTime", - null=True, - blank=True, - related_name="employee_shift", - on_delete=models.PROTECT, - verbose_name=_("Grace Time"), - ) + if apps.is_installed("attendance"): + grace_time_id = models.ForeignKey( + "attendance.GraceTime", + null=True, + blank=True, + related_name="employee_shift", + on_delete=models.PROTECT, + verbose_name=_("Grace Time"), + ) objects = HorillaCompanyManager("employee_shift__company_id") @@ -1092,6 +1116,21 @@ class Tags(HorillaModel): return self.title +class HorillaMailTemplate(HorillaModel): + title = models.CharField(max_length=25, unique=True) + body = models.TextField() + company_id = models.ForeignKey( + Company, + null=True, + blank=True, + on_delete=models.CASCADE, + verbose_name=_("Company"), + ) + + def __str__(self) -> str: + return f"{self.title}" + + class DynamicEmailConfiguration(HorillaModel): """ SingletonModel to keep the mail server configurations @@ -1545,3 +1584,151 @@ class TrackLateComeEarlyOut(HorillaModel): def __str__(self): tracking = _("enabled") if self.is_enable else _("disabled") return f"Tracking late come early out {tracking}" + + +class Holidays(HorillaModel): + name = models.CharField(max_length=30, null=False, verbose_name=_("Name")) + start_date = models.DateField(verbose_name=_("Start Date")) + end_date = models.DateField(null=True, blank=True, verbose_name=_("End Date")) + recurring = models.BooleanField(default=False, verbose_name=_("Recurring")) + company_id = models.ForeignKey( + Company, null=True, editable=False, on_delete=models.PROTECT + ) + objects = HorillaCompanyManager(related_company_field="company_id") + + def __str__(self): + return self.name + + +class CompanyLeaves(HorillaModel): + based_on_week = models.CharField( + max_length=100, choices=WEEKS, blank=True, null=True + ) + based_on_week_day = models.CharField(max_length=100, choices=WEEK_DAYS) + company_id = models.ForeignKey( + Company, null=True, editable=False, on_delete=models.PROTECT + ) + objects = HorillaCompanyManager(related_company_field="company_id") + + class Meta: + unique_together = ("based_on_week", "based_on_week_day") + + def __str__(self): + return f"{dict(WEEK_DAYS).get(self.based_on_week_day)} | {dict(WEEKS).get(self.based_on_week)}" + + +class PenaltyAccounts(HorillaModel): + """ + LateComeEarlyOutPenaltyAccount + """ + + employee_id = models.ForeignKey( + "employee.Employee", + on_delete=models.PROTECT, + related_name="penalty_accounts", + editable=False, + verbose_name="Employee", + null=True, + ) + if apps.is_installed("attendance"): + late_early_id = models.ForeignKey( + "attendance.AttendanceLateComeEarlyOut", + on_delete=models.CASCADE, + null=True, + editable=False, + ) + if apps.is_installed("leave"): + leave_request_id = models.ForeignKey( + "leave.LeaveRequest", null=True, on_delete=models.CASCADE, editable=False + ) + leave_type_id = models.ForeignKey( + "leave.LeaveType", + on_delete=models.DO_NOTHING, + blank=True, + null=True, + verbose_name="Leave type", + ) + minus_leaves = models.FloatField(default=0.0, null=True) + deduct_from_carry_forward = models.BooleanField(default=False) + penalty_amount = models.FloatField(default=0.0, null=True) + + def clean(self) -> None: + super().clean() + if apps.is_installed("leave") and not self.leave_type_id and self.minus_leaves: + raise ValidationError( + {"leave_type_id": _("Specify the leave type to deduct the leave.")} + ) + if apps.is_installed("leave") and self.leave_type_id and not self.minus_leaves: + raise ValidationError( + { + "minus_leaves": _( + "If a leave type is chosen for a penalty, minus leaves are required." + ) + } + ) + if not self.minus_leaves and not self.penalty_amount: + raise ValidationError( + { + "leave_type_id": _( + "Either minus leaves or a penalty amount is required" + ) + } + ) + + if ( + self.minus_leaves or self.deduct_from_carry_forward + ) and not self.leave_type_id: + raise ValidationError({"leave_type_id": _("Leave type is required")}) + return + + class Meta: + ordering = ["-created_at"] + + +@receiver(post_save, sender=PenaltyAccounts) +def create_deduction_cutleave_from_penalty(sender, instance, created, **kwargs): + """ + This is post save method, used to create deduction and cut availabl leave days""" + # only work when creating + if created: + penalty_amount = instance.penalty_amount + if apps.is_installed("payroll") and penalty_amount: + Deduction = get_horilla_model_class(app_label="payroll", model="Deduction") + penalty = Deduction() + if instance.late_early_id: + penalty.title = f"{instance.late_early_id.get_type_display()} penalty" + penalty.one_time_date = ( + instance.late_early_id.attendance_id.attendance_date + ) + elif instance.leave_request_id: + penalty.title = f"Leave penalty {instance.leave_request_id.end_date}" + penalty.one_time_date = instance.leave_request_id.end_date + else: + penalty.title = f"Penalty on {datetime.today()}" + penalty.one_time_date = datetime.today() + penalty.include_active_employees = False + penalty.is_fixed = True + penalty.amount = instance.penalty_amount + penalty.only_show_under_employee = True + penalty.save() + penalty.include_active_employees = False + penalty.specific_employees.add(instance.employee_id) + penalty.save() + + if ( + apps.is_installed("leave") + and instance.leave_type_id + and instance.minus_leaves + ): + available = instance.employee_id.available_leave.filter( + leave_type_id=instance.leave_type_id + ).first() + unit = round(instance.minus_leaves * 2) / 2 + if not instance.deduct_from_carry_forward: + available.available_days = max(0, (available.available_days - unit)) + else: + available.carryforward_days = max( + 0, (available.carryforward_days - unit) + ) + + available.save() diff --git a/base/request_and_approve.py b/base/request_and_approve.py index 0a2dc86cb..6202d8fec 100644 --- a/base/request_and_approve.py +++ b/base/request_and_approve.py @@ -5,20 +5,15 @@ This module is used to map url patterns with request and approve methods in Dash """ import json -from datetime import date +from django.apps import apps from django.core.paginator import Paginator from django.db.models import Q from django.shortcuts import render -from asset.models import AssetRequest -from attendance.models import Attendance, AttendanceValidationCondition -from attendance.views.views import strtime_seconds from base.methods import filtersubordinates from base.models import ShiftRequest, WorkTypeRequest from horilla.decorators import login_required -from leave.models import LeaveAllocationRequest, LeaveRequest -from pms.models import Feedback def paginator_qry(qryset, page_number): @@ -62,149 +57,3 @@ def dashboard_work_type_request(request): "requests_ids": requests_ids, }, ) - - -@login_required -def dashboard_overtime_approve(request): - previous_data = request.GET.urlencode() - page_number = request.GET.get("page") - condition = AttendanceValidationCondition.objects.first() - min_ot = strtime_seconds("00:00") - if condition is not None and condition.minimum_overtime_to_approve is not None: - min_ot = strtime_seconds(condition.minimum_overtime_to_approve) - ot_attendances = Attendance.objects.filter( - overtime_second__gte=min_ot, - attendance_validated=True, - employee_id__is_active=True, - attendance_overtime_approve=False, - ) - ot_attendances = filtersubordinates( - request, ot_attendances, "attendance.change_attendance" - ) - ot_attendances = paginator_qry(ot_attendances, page_number) - ot_attendances_ids = json.dumps([instance.id for instance in ot_attendances]) - return render( - request, - "request_and_approve/overtime_approve.html", - { - "overtime_attendances": ot_attendances, - "ot_attendances_ids": ot_attendances_ids, - "pd": previous_data, - }, - ) - - -@login_required -def dashboard_attendance_validate(request): - previous_data = request.GET.urlencode() - page_number = request.GET.get("page") - validate_attendances = Attendance.objects.filter( - attendance_validated=False, employee_id__is_active=True - ) - validate_attendances = filtersubordinates( - request, validate_attendances, "attendance.change_attendance" - ) - validate_attendances = paginator_qry(validate_attendances, page_number) - validate_attendances_ids = json.dumps( - [instance.id for instance in validate_attendances] - ) - return render( - request, - "request_and_approve/attendance_validate.html", - { - "validate_attendances": validate_attendances, - "validate_attendances_ids": validate_attendances_ids, - "pd": previous_data, - }, - ) - - -@login_required -def leave_request_and_approve(request): - previous_data = request.GET.urlencode() - page_number = request.GET.get("page") - leave_requests = LeaveRequest.objects.filter( - status="requested", employee_id__is_active=True, start_date__gte=date.today() - ) - leave_requests = filtersubordinates( - request, leave_requests, "leave.change_leaverequest" - ) - leave_requests = paginator_qry(leave_requests, page_number) - leave_requests_ids = json.dumps([instance.id for instance in leave_requests]) - return render( - request, - "request_and_approve/leave_request_approve.html", - { - "leave_requests": leave_requests, - "requests_ids": leave_requests_ids, - "pd": previous_data, - # "current_date":date.today(), - }, - ) - - -@login_required -def leave_allocation_approve(request): - previous_data = request.GET.urlencode() - page_number = request.GET.get("page") - allocation_reqests = LeaveAllocationRequest.objects.filter( - status="requested", employee_id__is_active=True - ) - allocation_reqests = filtersubordinates( - request, allocation_reqests, "leave.view_leaveallocationrequest" - ) - # allocation_reqests = paginator_qry(allocation_reqests, page_number) - allocation_reqests_ids = json.dumps( - [instance.id for instance in allocation_reqests] - ) - return render( - request, - "request_and_approve/leave_allocation_approve.html", - { - "allocation_reqests": allocation_reqests, - "reqests_ids": allocation_reqests_ids, - "pd": previous_data, - # "current_date":date.today(), - }, - ) - - -@login_required -def dashboard_feedback_answer(request): - employee = request.user.employee_get - feedback_requested = Feedback.objects.filter( - Q(manager_id=employee, manager_id__is_active=True) - | Q(colleague_id=employee, colleague_id__is_active=True) - | Q(subordinate_id=employee, subordinate_id__is_active=True) - ).distinct() - feedbacks = feedback_requested.exclude(feedback_answer__employee_id=employee) - - return render( - request, - "request_and_approve/feedback_answer.html", - {"feedbacks": feedbacks, "current_date": date.today()}, - ) - - -@login_required -def dashboard_asset_request_approve(request): - asset_requests = AssetRequest.objects.filter( - asset_request_status="Requested", requested_employee_id__is_active=True - ) - - asset_requests = filtersubordinates( - request, - asset_requests, - "asset.change_assetrequest", - field="requested_employee_id", - ) - requests_ids = json.dumps([instance.id for instance in asset_requests]) - - return render( - request, - "request_and_approve/asset_requests_approve.html", - { - "asset_requests": asset_requests, - "requests_ids": requests_ids, - }, - ) diff --git a/base/scheduler.py b/base/scheduler.py index 75bbf4522..6d0d91901 100644 --- a/base/scheduler.py +++ b/base/scheduler.py @@ -1,4 +1,5 @@ import calendar +import datetime as dt from datetime import date, datetime, timedelta from apscheduler.schedulers.background import BackgroundScheduler @@ -406,6 +407,28 @@ def undo_work_type(): return +def recurring_holiday(): + from .models import Holidays + + recurring_holidays = Holidays.objects.filter(recurring=True) + today = datetime.now() + # Looping through all recurring holiday + for recurring_holiday in recurring_holidays: + start_date = recurring_holiday.start_date + end_date = recurring_holiday.end_date + new_start_date = dt.date(start_date.year + 1, start_date.month, start_date.day) + new_end_date = dt.date(end_date.year + 1, end_date.month, end_date.day) + # Checking that end date is not none + if end_date is None: + # checking if that start date is day before today + if start_date == (today - timedelta(days=1)).date(): + recurring_holiday.start_date = new_start_date + elif end_date == (today - timedelta(days=1)).date(): + recurring_holiday.start_date = new_start_date + recurring_holiday.end_date = new_end_date + recurring_holiday.save() + + scheduler = BackgroundScheduler() # Set the initial start time to the current time @@ -467,5 +490,5 @@ try: except: pass - +scheduler.add_job(recurring_holiday, "interval", hours=4) scheduler.start() diff --git a/base/static/holiday/action.js b/base/static/holiday/action.js new file mode 100644 index 000000000..ddbbdd9f6 --- /dev/null +++ b/base/static/holiday/action.js @@ -0,0 +1,384 @@ +var rowMessages = { + ar: " تم الاختيار", + de: " Ausgewählt", + es: " Seleccionado", + en: " Selected", + fr: " Sélectionné", +}; + +var excelMessages = { + ar: "هل ترغب في تنزيل ملف Excel؟", + de: "Möchten Sie die Excel-Datei herunterladen?", + es: "¿Desea descargar el archivo de Excel?", + en: "Do you want to download the excel file?", + fr: "Voulez-vous télécharger le fichier Excel?", +}; + +var deleteHolidayMessages = { + ar: "هل تريد حقًا حذف جميع العطل المحددة؟", + de: "Möchten Sie wirklich alle ausgewählten Feiertage löschen?", + es: "¿Realmente quieres eliminar todas las vacaciones seleccionadas?", + en: "Do you really want to delete all the selected holidays?", + fr: "Voulez-vous vraiment supprimer toutes les vacances sélectionnées?", +}; + +var no_rows_deleteMessages = { + ar: "لم تتم تحديد صفوف لحذف العطلات.", + de: "Es wurden keine Zeilen zum Löschen von Feiertagen ausgewählt.", + es: "No se han seleccionado filas para eliminar las vacaciones.", + en: "No rows are selected for deleting holidays.", + fr: "Aucune ligne n'a été sélectionnée pour supprimer les vacances.", +}; +var downloadMessages = { + ar: "هل ترغب في تنزيل القالب؟", + de: "Möchten Sie die Vorlage herunterladen?", + es: "¿Quieres descargar la plantilla?", + en: "Do you want to download the template?", + fr: "Voulez-vous télécharger le modèle ?", +}; +function makeListUnique(list) { + return Array.from(new Set(list)); +} + +function createHolidayHxValue() { + var pd = $(".oh-pagination").attr("data-pd"); + var hxValue = JSON.stringify(pd); + $("#holidayCreateButton").attr("hx-vals", `{"pd":${hxValue}}`); +} + +tickHolidayCheckboxes(); +function makeHolidayListUnique(list) { + return Array.from(new Set(list)); +} + +function getCurrentLanguageCode(callback) { + var languageCode = $("#main-section-data").attr("data-lang"); + var allowedLanguageCodes = ["ar", "de", "es", "en", "fr"]; + if (allowedLanguageCodes.includes(languageCode)) { + callback(languageCode); + } else { + $.ajax({ + type: "GET", + url: "/employee/get-language-code/", + success: function (response) { + var ajaxLanguageCode = response.language_code; + $("#main-section-data").attr("data-lang", ajaxLanguageCode); + callback( + allowedLanguageCodes.includes(ajaxLanguageCode) + ? ajaxLanguageCode + : "en" + ); + }, + error: function () { + callback("en"); + }, + }); + } +} + +function tickHolidayCheckboxes() { + var ids = JSON.parse($("#selectedHolidays").attr("data-ids") || "[]"); + uniqueIds = makeHolidayListUnique(ids); + toggleHighlight(uniqueIds); + click = $("#selectedHolidays").attr("data-clicked"); + if (click === "1") { + $(".all-holidays").prop("checked", true); + } + uniqueIds.forEach(function (id) { + $("#" + id).prop("checked", true); + }); + var selectedCount = uniqueIds.length; + getCurrentLanguageCode(function (code) { + languageCode = code; + var message = rowMessages[languageCode]; + if (selectedCount > 0) { + $("#unselectAllHolidays").css("display", "inline-flex"); + $("#exportHolidays").css("display", "inline-flex"); + $("#selectedShowHolidays").css("display", "inline-flex"); + $("#selectedShowHolidays").text(selectedCount + " -" + message); + } else { + $("#unselectAllHolidays").css("display", "none "); + $("#selectedShowHolidays").css("display", "none"); + $("#exportHolidays").css("display", "none"); + } + }); +} + +function addingHolidayIds() { + var ids = JSON.parse($("#selectedHolidays").attr("data-ids") || "[]"); + var selectedCount = 0; + + $(".all-holidays-row").each(function () { + if ($(this).is(":checked")) { + ids.push(this.id); + } else { + var index = ids.indexOf(this.id); + if (index > -1) { + ids.splice(index, 1); + $(".all-holidays").prop("checked", false); + } + } + }); + + ids = makeHolidayListUnique(ids); + toggleHighlight(ids); + selectedCount = ids.length; + + getCurrentLanguageCode(function (code) { + languageCode = code; + var message = rowMessages[languageCode]; + $("#selectedHolidays").attr("data-ids", JSON.stringify(ids)); + if (selectedCount === 0) { + $("#selectedShowHolidays").css("display", "none"); + $("#exportHolidays").css("display", "none"); + $('#unselectAllHolidays').css("display", "none"); + } else { + $("#unselectAllHolidays").css("display", "inline-flex"); + $("#exportHolidays").css("display", "inline-flex"); + $("#selectedShowHolidays").css("display", "inline-flex"); + $("#selectedShowHolidays").text(selectedCount + " - " + message); + } + }); +} + +function selectAllHolidays() { + $("#selectedHolidays").attr("data-clicked", 1); + $("#selectedShowHolidays").removeAttr("style"); + var savedFilters = JSON.parse(localStorage.getItem("savedFilters")); + + if (savedFilters && savedFilters["filterData"] !== null) { + var filter = savedFilters["filterData"]; + $.ajax({ + url: "/holiday-select-filter", + data: { page: "all", filter: JSON.stringify(filter) }, + type: "GET", + dataType: "json", + success: function (response) { + var employeeIds = response.employee_ids; + + if (Array.isArray(employeeIds)) { + // Continue + } else { + console.error("employee_ids is not an array:", employeeIds); + } + + for (var i = 0; i < employeeIds.length; i++) { + var empId = employeeIds[i]; + $("#" + empId).prop("checked", true); + } + $("#selectedHolidays").attr("data-ids", JSON.stringify(employeeIds)); + + count = makeHolidayListUnique(employeeIds); + tickHolidayCheckboxes(count); + }, + error: function (xhr, status, error) { + console.error("Error:", error); + }, + }); + } else { + $.ajax({ + url: "/holiday-select", + data: { page: "all" }, + type: "GET", + dataType: "json", + success: function (response) { + var employeeIds = response.employee_ids; + if (Array.isArray(employeeIds)) { + // Continue + } else { + console.error("employee_ids is not an array:", employeeIds); + } + + for (var i = 0; i < employeeIds.length; i++) { + var empId = employeeIds[i]; + $("#" + empId).prop("checked", true); + } + var previousIds = $("#selectedHolidays").attr("data-ids"); + $("#selectedHolidays").attr( + "data-ids", + JSON.stringify( + Array.from(new Set([...employeeIds, ...JSON.parse(previousIds)])) + ) + ); + count = makeHolidayListUnique(employeeIds); + tickHolidayCheckboxes(count); + }, + error: function (xhr, status, error) { + console.error("Error:", error); + }, + }); + } +} + +function unselectAllHolidays() { + $("#selectedHolidays").attr("data-clicked", 0); + $.ajax({ + url: "/holiday-select", + data: { page: "all", filter: "{}" }, + type: "GET", + dataType: "json", + success: function (response) { + var employeeIds = response.employee_ids; + + if (Array.isArray(employeeIds)) { + // Continue + } else { + console.error("employee_ids is not an array:", employeeIds); + } + + for (var i = 0; i < employeeIds.length; i++) { + var empId = employeeIds[i]; + $("#" + empId).prop("checked", false); + $(".all-holidays").prop("checked", false); + } + var ids = JSON.parse($("#selectedHolidays").attr("data-ids") || "[]"); + var uniqueIds = makeListUnique(ids); + toggleHighlight(uniqueIds); + $("#selectedHolidays").attr("data-ids", JSON.stringify([])); + + count = []; + tickHolidayCheckboxes(count); + }, + error: function (xhr, status, error) { + console.error("Error:", error); + }, + }); +} + +function exportHolidays() { + var currentDate = new Date().toISOString().slice(0, 10); + var language_code = null; + getCurrentLanguageCode(function (code) { + language_code = code; + var confirmMessage = excelMessages[language_code]; + ids = []; + ids = JSON.parse($("#selectedHolidays").attr("data-ids")); + Swal.fire({ + text: confirmMessage, + icon: "question", + showCancelButton: true, + confirmButtonColor: "#008000", + cancelButtonColor: "#d33", + confirmButtonText: "Confirm", + }).then(function (result) { + if (result.isConfirmed) { + $.ajax({ + type: "GET", + url: "/holiday-info-export", + data: { + ids: JSON.stringify(ids), + }, + dataType: "binary", + xhrFields: { + responseType: "blob", + }, + success: function (response) { + const file = new Blob([response], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + const url = URL.createObjectURL(file); + const link = document.createElement("a"); + link.href = url; + link.download = "holiday_leaves" + currentDate + ".xlsx"; + document.body.appendChild(link); + link.click(); + }, + error: function (xhr, textStatus, errorThrown) { + console.error("Error downloading file:", errorThrown); + }, + }); + } + }); + }); +} + +$("#bulkHolidaysDelete").click(function (e) { + e.preventDefault(); + var languageCode = null; + getCurrentLanguageCode(function (code) { + languageCode = code; + var confirmMessage = deleteHolidayMessages[languageCode]; + var textMessage = no_rows_deleteMessages[languageCode]; + ids = []; + ids.push($("#selectedHolidays").attr("data-ids")); + ids = JSON.parse($("#selectedHolidays").attr("data-ids")); + if (ids.length === 0) { + Swal.fire({ + text: textMessage, + icon: "warning", + confirmButtonText: "Close", + }); + } else { + Swal.fire({ + text: confirmMessage, + icon: "error", + showCancelButton: true, + confirmButtonColor: "#008000", + cancelButtonColor: "#d33", + confirmButtonText: "Confirm", + }).then(function (result) { + if (result.isConfirmed) { + ids = []; + ids.push($("#selectedHolidays").attr("data-ids")); + ids = JSON.parse($("#selectedHolidays").attr("data-ids")); + $.ajax({ + type: "POST", + url: "/holidays-bulk-delete", + data: { + csrfmiddlewaretoken: getCookie("csrftoken"), + ids: JSON.stringify(ids), + }, + success: function (response, textStatus, jqXHR) { + if (jqXHR.status === 200) { + location.reload(); + } else { + } + }, + }); + } + }); + } + }); +}); + +$("#holidaysInfoImport").click(function (e) { + e.preventDefault(); + var languageCode = null; + getCurrentLanguageCode(function (code) { + languageCode = code; + var confirmMessage = downloadMessages[languageCode]; + Swal.fire({ + text: confirmMessage, + icon: "question", + showCancelButton: true, + confirmButtonColor: "#008000", + cancelButtonColor: "#d33", + confirmButtonText: "Confirm", + }).then(function (result) { + if (result.isConfirmed) { + $.ajax({ + type: "GET", + url: "holidays-excel-template", + dataType: "binary", + xhrFields: { + responseType: "blob", + }, + success: function (response) { + const file = new Blob([response], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + const url = URL.createObjectURL(file); + const link = document.createElement("a"); + link.href = url; + link.download = "holiday_excel.xlsx"; + document.body.appendChild(link); + link.click(); + }, + error: function (xhr, textStatus, errorThrown) { + console.error("Error downloading file:", errorThrown); + }, + }); + } + }); + }); +}); diff --git a/base/templates/announcement/announcement_one.html b/base/templates/announcement/announcement_one.html index c3e587f14..aa64f1e62 100644 --- a/base/templates/announcement/announcement_one.html +++ b/base/templates/announcement/announcement_one.html @@ -60,10 +60,8 @@ {% endif %} - {% trans "Posted on" %}  {{ anoun.created_at|date:"F j, Y" - }}   - {% trans "at" %}   {{ anoun.created_at|time:"g:i A" - }} + {% trans "Posted on" %}  {{ anoun.created_at|date:"F j, Y"}}   + {% trans "at" %}   {{ anoun.created_at|time:"g:i A"}}
diff --git a/base/templates/announcement/comment_view.html b/base/templates/announcement/comment_view.html index f13f4c2d7..eb42a2caa 100644 --- a/base/templates/announcement/comment_view.html +++ b/base/templates/announcement/comment_view.html @@ -1,4 +1,4 @@ -{% load basefilters %} +{% load basefilters static %} {% load i18n %} @@ -54,7 +54,7 @@ >
diff --git a/base/templates/base/general_settings.html b/base/templates/base/general_settings.html index 486d330c5..1a5b52dbb 100644 --- a/base/templates/base/general_settings.html +++ b/base/templates/base/general_settings.html @@ -1,18 +1,18 @@ -{% extends 'settings.html' %} {% block settings %}{% load i18n %} +{% extends 'settings.html' %} {% block settings %}{% load i18n %} {% load horillafilters %} -{% if perms.recruitment.change_recruitmentgeneralsetting %} +{% if "recruitment"|app_installed and perms.recruitment.change_recruitmentgeneralsetting %} {% include "recruitment/settings/settings.html" %} {% endif %} -{% if perms.offboarding.change_offboardinggeneralsetting %} +{% if "offboarding"|app_installed and perms.offboarding.change_offboardinggeneralsetting %} {% include "offboarding/settings/settings.html" %} {% endif %} -{% if perms.attendance.change_attendancegeneralsetting %} +{% if "attendance"|app_installed and perms.attendance.change_attendancegeneralsetting %} {% include "attendance/settings/settings.html" %} {% endif %} -{% if perms.payroll.change_payrollgeneralsetting %} +{% if "payroll"|app_installed and perms.payroll.change_payrollgeneralsetting %} {% include "payroll/settings/settings.html" %} {% endif %} @@ -24,8 +24,10 @@ {% include "announcement/expiry_day.html" %} {% endif %} -{% if perms.payroll.change_encashmentgeneralsetting %} - {% include "settings/encashment_settings.html" %} +{% if "payroll"|app_installed %} + {% if perms.payroll.change_encashmentgeneralsetting %} + {% include "settings/encashment_settings.html" %} + {% endif %} {% endif %} {% if perms.base.view_historytrackingfields %} @@ -36,7 +38,7 @@ {% include "base/audit_tag/employee_account_block_unblock.html" %} {% endif %} -{% if perms.payroll.view_payrollsettings %} +{% if "payroll"|app_installed and perms.payroll.view_payrollsettings %} {% include "payroll/settings/payroll_settings.html" %} {% endif %} diff --git a/base/templates/base/rotating_shift/htmx/group_by.html b/base/templates/base/rotating_shift/htmx/group_by.html index 4e61cf592..a55353a9c 100644 --- a/base/templates/base/rotating_shift/htmx/group_by.html +++ b/base/templates/base/rotating_shift/htmx/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} {% if messages %}
diff --git a/base/templates/base/rotating_work_type/htmx/group_by.html b/base/templates/base/rotating_work_type/htmx/group_by.html index dd8c15791..42fa830d9 100644 --- a/base/templates/base/rotating_work_type/htmx/group_by.html +++ b/base/templates/base/rotating_work_type/htmx/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} {% if messages %}
diff --git a/base/templates/common_form.html b/base/templates/common_form.html new file mode 100644 index 000000000..d01a7db98 --- /dev/null +++ b/base/templates/common_form.html @@ -0,0 +1,49 @@ +{% load widget_tweaks %} +{% load i18n %} + +
+ {% if form.verbose_name %} +
+
+
{{ form.verbose_name }}
+
+
+ {% endif %} +
+
+
{{ form.non_field_errors }}
+ {% for field in form.visible_fields %} +
+
+ + {% if field.help_text != '' %} + + {% endif %} +
+ + {% if field.field.widget.input_type == 'checkbox' %} +
{{ field|add_class:'oh-switch__checkbox' }}
+ {% else %} + {{ field|add_class:'form-control' }} + {% endif %} + {{ field.errors }} +
+ {% endfor %} +
+ + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + +
+ +
+
+
+ diff --git a/base/templates/company_leave/company_leave.html b/base/templates/company_leave/company_leave.html new file mode 100644 index 000000000..725c53632 --- /dev/null +++ b/base/templates/company_leave/company_leave.html @@ -0,0 +1,127 @@ +{% load i18n %} {% load static %} +{% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+{% endif %} +{% include 'filter_tags.html' %} +{% if company_leaves %} +
+ + + +{% else %} +
+
+ +

{% trans "There are no company leaves at the moment." %}

+
+
+{% endif %} diff --git a/base/templates/company_leave/company_leave_creation_form.html b/base/templates/company_leave/company_leave_creation_form.html new file mode 100644 index 000000000..512922714 --- /dev/null +++ b/base/templates/company_leave/company_leave_creation_form.html @@ -0,0 +1,56 @@ +{% load i18n %} {% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+ +{% endif %} {% if form.errors %} +
+
+ {% for error in form.non_field_errors %} +
{{ error }}
+ {% endfor %} +
+
+{% endif %} +
+ {% trans "Create Company Leaves" %} + +
+
+
+ + {{form.based_on_week}} {{form.based_on_week.errors}} + + {{form.based_on_week_day}} {{form.based_on_week_day.errors}} + +
+
diff --git a/base/templates/company_leave/company_leave_update_form.html b/base/templates/company_leave/company_leave_update_form.html new file mode 100644 index 000000000..5643d56b4 --- /dev/null +++ b/base/templates/company_leave/company_leave_update_form.html @@ -0,0 +1,58 @@ +{% load i18n %} {% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+ +{% endif %} +{% if form.errors %} + +
+
+ {% for error in form.non_field_errors %} +
{{ error }}
+ {% endfor %} +
+
+{% endif %} +
+ {% trans "Update Company Leaves" %} + +
+
+
+ + {{form.based_on_week}} + + {{form.based_on_week_day}} + +
+
diff --git a/base/templates/company_leave/company_leave_view.html b/base/templates/company_leave/company_leave_view.html new file mode 100644 index 000000000..bf5495aa5 --- /dev/null +++ b/base/templates/company_leave/company_leave_view.html @@ -0,0 +1,100 @@ +{% extends 'index.html' %} +{% block content %} +{% load static %} +{% load i18n %} + +
+
+

{% trans "Company Leaves" %}

+ + + +
+
+
+ {% if company_leaves %} +
+ + +
+
+
+ + + +
+ {% endif %} + {% if perms.base.add_companyleave %} +
+
+ +
+
+ {% endif %} + +
+
+
+
+ +
+ {% include 'company_leave/company_leave.html' %} +
+ + + + + + +{% endblock %} diff --git a/base/templates/holiday/holiday.html b/base/templates/holiday/holiday.html new file mode 100644 index 000000000..35e60dba1 --- /dev/null +++ b/base/templates/holiday/holiday.html @@ -0,0 +1,262 @@ +{% load i18n %} {% load static %} {% include 'filter_tags.html' %} +{% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+{% endif %} {% if holidays %} +
+ {% trans "Select All Holidays" %} +
+
+ {% trans "Unselect All Holidays" %} +
+ + + +
+
+
+
+
+
+ +
+
+
+ {% trans "Holiday Name" %} +
+
+ {% trans "Start Date" %} +
+
+ {% trans "End Date" %} +
+
{% trans "Recurring" %}
+ {% if perms.base.change_holiday or perms.base.delete_holiday %} +
{% trans "Actions" %}
+ {% endif %} +
+
+
+ {% for holiday in holidays %} +
+
+
+ +
+
+
{{holiday.name}}
+
+ {{holiday.start_date}} +
+
+ {{holiday.end_date}} +
+
+ {% if holiday.recurring %} + {% trans "Yes" %} + {% else %} + {% trans "No"%} + {% endif %} +
+ {% if perms.base.change_holiday or perms.base.delete_holiday %} +
+
+ {% if perms.base.change_holiday %} + + {% endif %} {% if perms.base.delete_holiday %} + + + + {% endif %} +
+
+ {% endif %} +
+ {% endfor %} +
+
+
+ +
+ + {% trans "Page" %} {{ holidays.number }} + {% trans "of" %} {{ holidays.paginator.num_pages }}. + + +
+{% else %} + +
+
+ +

+ {% trans "There are no holidays at the moments." %} +

+
+
+ +{% endif %} + + + diff --git a/base/templates/holiday/holiday_export_filter_form.html b/base/templates/holiday/holiday_export_filter_form.html new file mode 100644 index 000000000..9b2af929e --- /dev/null +++ b/base/templates/holiday/holiday_export_filter_form.html @@ -0,0 +1,78 @@ +{% load i18n %} +
+

+ {% trans "Export Holidays" %} +

+ +
+
+
+ {% csrf_token %} +
+
+
{% trans "Excel columns" %}
+
+
+ {% for field in export_column.selected_fields %} +
+
+ +
+
+ {% endfor %} +
+
+
+
+
{% trans "Holiday" %}
+
+
+
+
+ + {{export_filter.form.from_date}} +
+
+
+
+ + {{export_filter.form.to_date}} +
+
+
+
+ + {{export_filter.form.recurring}} +
+
+
+
+
+
+ +
+
diff --git a/base/templates/holiday/holiday_filter.html b/base/templates/holiday/holiday_filter.html new file mode 100644 index 000000000..bf906b124 --- /dev/null +++ b/base/templates/holiday/holiday_filter.html @@ -0,0 +1,45 @@ +{% load i18n %} +
+ +
diff --git a/base/templates/holiday/holiday_form.html b/base/templates/holiday/holiday_form.html new file mode 100644 index 000000000..73b238b0e --- /dev/null +++ b/base/templates/holiday/holiday_form.html @@ -0,0 +1,70 @@ +{% load i18n %} {% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+ +{% endif %} +
+ {% trans "Create Holiday" %} + +
+
+
+ + {{form.name}} {{form.name.errors}} +
+
+
+ + {{form.start_date }} {{form.start_date.errors }} +
+
+
+
+ + {{form.end_date }} {{form.end_date.errors}} +
+
+
+ +
{{form.recurring}} {{form.recurring.errors}}
+ +
+
diff --git a/base/templates/holiday/holiday_update_form.html b/base/templates/holiday/holiday_update_form.html new file mode 100644 index 000000000..020c20dab --- /dev/null +++ b/base/templates/holiday/holiday_update_form.html @@ -0,0 +1,63 @@ +{% load i18n %} {% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} +
+ +{% endif %} +
+ {% trans "Update Holiday" %} + +
+
+
+ + {{form.name}} {{form.name.errors}} + + + {{form.start_date}} {{form.start_date.errors}} + + + {{form.end_date}} {{form.end_date.errors}} + + +
{{form.recurring}} {{form.recurring.errors}}
+ +
+
diff --git a/base/templates/holiday/holiday_view.html b/base/templates/holiday/holiday_view.html new file mode 100644 index 000000000..1ffc336d2 --- /dev/null +++ b/base/templates/holiday/holiday_view.html @@ -0,0 +1,297 @@ +{% extends 'index.html' %} {% block content %} {% load static %} {% load i18n %} + +
+ +
+

{% trans "Holidays" %}

+ + + +
+ + +
+ {% if holidays %} + +
+ + +
+ + + +
+ +
+ + {% include "holiday/holiday_filter.html" %} +
+ + {% endif %} + + + {% if perms.base.add_holiday or perms.base.delete_holiday%} +
+ + +
+ {% endif %} + + + + {% if perms.base.add_holiday %} +
+
+ +
+
+ {% endif %} + +
+ +
+
+ + + + + +
+ {% if holidays %} + {% include 'holiday/holiday.html' %} + {% else %} + +
+
+ +

+ {% trans "There are no holidays at the moment." %} +

+
+
+ + {% endif %} +
+ + + + + + + + + + + + + + + + +{% endblock %} diff --git a/base/templates/horilla_form.html b/base/templates/horilla_form.html new file mode 100644 index 000000000..4d131f2ab --- /dev/null +++ b/base/templates/horilla_form.html @@ -0,0 +1,56 @@ +{% load i18n %}{% load widget_tweaks %} {% load horillafilters %} +{% for field in form %} + {% if field.field.widget.is_hidden %} + {{ field }} + {% endif %} +{% endfor %} +
+
+
+
{{form.non_field_errors}}
+ {% for field in form.visible_fields %} + {% if field.field.widget|is_select_multiple or field.field.widget|is_text_area %} + +
+ + {{ field|add_class:"form-control" }} +
+ {{field.errors}} + {% else %} +
+ + {% if field.field.widget.input_type == "checkbox" %} +
+ {{ field|add_class:"oh-switch__checkbox" }} + +
+ {% else %} + {{ field|add_class:"form-control" }} + {% endif %} + {{field.errors}} +
+ {% endif %} + {% endfor %} + +
+ + +
+
diff --git a/base/templates/mail/empty_mail_template.html b/base/templates/mail/empty_mail_template.html new file mode 100644 index 000000000..7f077c06a --- /dev/null +++ b/base/templates/mail/empty_mail_template.html @@ -0,0 +1,89 @@ +{% extends 'index.html' %} +{% load static %} +{% load i18n %} +{% block content %} + + +
+
+

{% trans "Mail Templates" %}

+
+ {% if perms.base.add_horillamailtemplate %} + + {% endif %} +
+
+ +
+
+ Page not found. 404. +
{% trans "There are currently no email templates." %}
+
+
+
+ + + + + +{% endblock content %} diff --git a/base/templates/mail/htmx/form.html b/base/templates/mail/htmx/form.html new file mode 100644 index 000000000..5b741f632 --- /dev/null +++ b/base/templates/mail/htmx/form.html @@ -0,0 +1,65 @@ +{% load i18n %} +{% if form.instance.id %} +
+{% else %} + +{% endif %} +
+
+
+
+
+ + {{ form.title }} +
+
+ + {{ form.body }} +
+
+ {% trans "Hint: Type '{' to get sender or receiver data" %} +
+
+ + {{ form.company_id }} +
+
+
+
+
+ +
+ diff --git a/base/templates/mail/view_templates.html b/base/templates/mail/view_templates.html new file mode 100644 index 000000000..bde8f3e8e --- /dev/null +++ b/base/templates/mail/view_templates.html @@ -0,0 +1,109 @@ +{% extends 'index.html' %} +{% load static %} +{% load i18n %} +{% block content %} + + +
+
+

{% trans "Mail Templates" %}

+
+ {% if perms.base.add_horillamailtemplate %} + + {% endif %} +
+ +
+ {% for template in templates %} +
+ {% if perms.base.delete_horillamailtemplate %} +

{{ template.title }} + +

+ {% endif %} + {% if perms.base.change_horillamailtemplate %} + + {% endif %} +
+
{{ template.body|safe }}
+
+ {% if perms.base.change_horillamailtemplate %} + {% trans "View Template" %} + {% endif %} +
+ {% endfor %} +
+ + + + +{% endblock %} diff --git a/base/templates/multi_approval_condition/condition_table.html b/base/templates/multi_approval_condition/condition_table.html index 74d80a834..8df524093 100644 --- a/base/templates/multi_approval_condition/condition_table.html +++ b/base/templates/multi_approval_condition/condition_table.html @@ -20,7 +20,7 @@
{% trans "Condition Operator" %}
{% trans "Condition Value" %}
{% trans "Approval Managers" %}
- {% if perms.leave.change_availableleave or perms.leave.delete_availableleave or request.user|is_reportingmanager %} + {% if perms.base.change_multipleapprovalcondition or perms.base.delete_multipleapprovalcondition or request.user|is_reportingmanager %}
{% trans "Actions" %}
{% endif %}
@@ -47,10 +47,10 @@ {{ forloop.counter}}. {{ manager }}
{% endfor %}
- {% if perms.leave.change_availableleave or perms.leave.delete_availableleave or request.user|is_reportingmanager %} + {% if perms.base.change_multipleapprovalcondition or perms.base.delete_multipleapprovalcondition or request.user|is_reportingmanager %}
- {% if request.user|is_reportingmanager or perms.leave.change_availableleave %} + {% if request.user|is_reportingmanager or perms.base.change_multipleapprovalcondition %} {% endif %} - {% if request.user|is_reportingmanager or perms.leave.delete_availableleave %} + {% if request.user|is_reportingmanager or perms.base.delete_multipleapprovalcondition %} diff --git a/base/templates/penalty/penalty_view.html b/base/templates/penalty/penalty_view.html new file mode 100644 index 000000000..0c741d2e1 --- /dev/null +++ b/base/templates/penalty/penalty_view.html @@ -0,0 +1,42 @@ +{% load static %} {% load i18n %} {% load horillafilters %} +{% if records %} +
+
+
+ {% if "leave"|app_installed %} +
{% trans "Leave Type" %}
+
{% trans "Minus Days" %}
+
{% trans "Deducted From" %}{% trans "CFD" %}
+ {% endif %} +
{% trans "Penalty amount" %}
+
{% trans "Created Date" %}
+
+
+
+ {% for acc in records %} +
+ {% if "leave"|app_installed %} +
{{ acc.leave_type_id }}
+
{{ acc.minus_leaves }}
+
{{acc.deduct_from_carry_forward|yes_no}}
+ {% endif %} +
+ {{currency}} {{ acc.penalty_amount }} +
+
{{ acc.created_at }}
+
+ {% endfor %} +
+
+{% else %} +
+
+ Page not found. 404. +
+ {% trans "No penalties found" %} +
+
+
+{% endif %} \ No newline at end of file diff --git a/base/templates/request_and_approve/asset_requests_approve.html b/base/templates/request_and_approve/asset_requests_approve.html index 5c864621a..bc5c4f4a4 100644 --- a/base/templates/request_and_approve/asset_requests_approve.html +++ b/base/templates/request_and_approve/asset_requests_approve.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if asset_requests %}
@@ -80,7 +80,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/attendance_validate.html b/base/templates/request_and_approve/attendance_validate.html index 4e38f640b..921624de6 100644 --- a/base/templates/request_and_approve/attendance_validate.html +++ b/base/templates/request_and_approve/attendance_validate.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %}
@@ -78,7 +78,7 @@
diff --git a/base/templates/request_and_approve/feedback_answer.html b/base/templates/request_and_approve/feedback_answer.html index 02f995d55..1c632a5a5 100644 --- a/base/templates/request_and_approve/feedback_answer.html +++ b/base/templates/request_and_approve/feedback_answer.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if feedbacks %}
@@ -49,7 +49,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/leave_allocation_approve.html b/base/templates/request_and_approve/leave_allocation_approve.html index 6ff4e077b..be1720307 100644 --- a/base/templates/request_and_approve/leave_allocation_approve.html +++ b/base/templates/request_and_approve/leave_allocation_approve.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if allocation_reqests %}
@@ -64,7 +64,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/leave_request_approve.html b/base/templates/request_and_approve/leave_request_approve.html index 0774c00a0..f7547382b 100644 --- a/base/templates/request_and_approve/leave_request_approve.html +++ b/base/templates/request_and_approve/leave_request_approve.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if leave_requests %}
@@ -45,7 +45,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/overtime_approve.html b/base/templates/request_and_approve/overtime_approve.html index 3479e7488..d7cbc2d84 100644 --- a/base/templates/request_and_approve/overtime_approve.html +++ b/base/templates/request_and_approve/overtime_approve.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %}
@@ -80,7 +80,7 @@
diff --git a/base/templates/request_and_approve/shift_request.html b/base/templates/request_and_approve/shift_request.html index ac4f1a47d..574e0821e 100644 --- a/base/templates/request_and_approve/shift_request.html +++ b/base/templates/request_and_approve/shift_request.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if requests %}
@@ -66,7 +66,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/request_and_approve/work_type_request.html b/base/templates/request_and_approve/work_type_request.html index 7538b38e5..d73b2e299 100644 --- a/base/templates/request_and_approve/work_type_request.html +++ b/base/templates/request_and_approve/work_type_request.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %} {% if requests %}
@@ -66,7 +66,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/base/templates/shift_request/htmx/allocation_details.html b/base/templates/shift_request/htmx/allocation_details.html index 958938714..06ce545a2 100644 --- a/base/templates/shift_request/htmx/allocation_details.html +++ b/base/templates/shift_request/htmx/allocation_details.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load i18n %} {% load yes_no %} +{% load i18n %} {% load horillafilters %}
{% trans "There is no comments to show." %} - +
diff --git a/base/templates/shift_request/htmx/group_by.html b/base/templates/shift_request/htmx/group_by.html index fe5dc2a17..db0d81f12 100644 --- a/base/templates/shift_request/htmx/group_by.html +++ b/base/templates/shift_request/htmx/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %}
diff --git a/base/templates/shift_request/htmx/requests.html b/base/templates/shift_request/htmx/requests.html index bd4df1648..07770995a 100755 --- a/base/templates/shift_request/htmx/requests.html +++ b/base/templates/shift_request/htmx/requests.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load attendancefilters %} +{% load horillafilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} diff --git a/base/templates/shift_request/htmx/shift_comment.html b/base/templates/shift_request/htmx/shift_comment.html index 8d3deb4ad..cdaf89199 100644 --- a/base/templates/shift_request/htmx/shift_comment.html +++ b/base/templates/shift_request/htmx/shift_comment.html @@ -136,7 +136,7 @@ >
diff --git a/base/templates/shift_request/htmx/shift_request_detail.html b/base/templates/shift_request/htmx/shift_request_detail.html index 34655e44b..197fbb907 100644 --- a/base/templates/shift_request/htmx/shift_request_detail.html +++ b/base/templates/shift_request/htmx/shift_request_detail.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load i18n %} {% load yes_no %} +{% load i18n %} {% load horillafilters %}
+ diff --git a/base/templates/shift_request/shift_request.html b/base/templates/shift_request/shift_request.html index 2113bbbee..2d34e81e4 100644 --- a/base/templates/shift_request/shift_request.html +++ b/base/templates/shift_request/shift_request.html @@ -25,4 +25,4 @@ {{form}} -
+
diff --git a/base/templates/shift_request/shift_request_export.html b/base/templates/shift_request/shift_request_export.html index 4045b59e9..12ee10a14 100644 --- a/base/templates/shift_request/shift_request_export.html +++ b/base/templates/shift_request/shift_request_export.html @@ -141,7 +141,7 @@
- diff --git a/base/templates/work_type_request/htmx/group_by.html b/base/templates/work_type_request/htmx/group_by.html index 89dae2135..b1e0a4bd1 100644 --- a/base/templates/work_type_request/htmx/group_by.html +++ b/base/templates/work_type_request/htmx/group_by.html @@ -1,4 +1,4 @@ -{% load attendancefilters %} {% load basefilters %} {% load static %} +{% load horillafilters %} {% load basefilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} {% if messages %}
{% for message in messages %} diff --git a/base/templates/work_type_request/htmx/requests.html b/base/templates/work_type_request/htmx/requests.html index c11f53106..7ad41757a 100755 --- a/base/templates/work_type_request/htmx/requests.html +++ b/base/templates/work_type_request/htmx/requests.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load attendancefilters %} +{% load horillafilters %} {% load static %} {% load i18n %} {% include 'filter_tags.html' %} diff --git a/base/templates/work_type_request/htmx/work_type_comment.html b/base/templates/work_type_request/htmx/work_type_comment.html index 607f15f3f..5ed25c055 100644 --- a/base/templates/work_type_request/htmx/work_type_comment.html +++ b/base/templates/work_type_request/htmx/work_type_comment.html @@ -136,7 +136,7 @@ >
diff --git a/base/templates/work_type_request/htmx/work_type_request_single_view.html b/base/templates/work_type_request/htmx/work_type_request_single_view.html index b1c61e135..ceb4396bd 100644 --- a/base/templates/work_type_request/htmx/work_type_request_single_view.html +++ b/base/templates/work_type_request/htmx/work_type_request_single_view.html @@ -1,5 +1,5 @@ {% load basefilters %} -{% load i18n %} {% load yes_no %} {% load static %} +{% load i18n %} {% load horillafilters %} {% load static %} {% if messages %}
{% for message in messages %} diff --git a/base/templates/work_type_request/request_update_form.html b/base/templates/work_type_request/request_update_form.html index b519c9bc3..700c3cc7f 100644 --- a/base/templates/work_type_request/request_update_form.html +++ b/base/templates/work_type_request/request_update_form.html @@ -30,4 +30,4 @@ function toggleFunctionWorkTypeRequestForm(){ }); }) toggleFunctionWorkTypeRequestForm(); - + diff --git a/base/templatetags/basefilters.py b/base/templatetags/basefilters.py index dece37dd2..99905c95f 100644 --- a/base/templatetags/basefilters.py +++ b/base/templatetags/basefilters.py @@ -1,6 +1,7 @@ import json from django import template +from django.apps import apps from django.core.paginator import Page, Paginator from django.template.defaultfilters import register @@ -127,18 +128,26 @@ def abs_value(value): @register.filter(name="config_perms") def config_perms(user): - permissions = [ - "leave.add_holiday", - "leave.change_holiday", - "leave.add_companyleaves", - "leave.change_companyleaves", - "leave.view_restrictleave", - "recruitment.add_recritmentmailtemplates", - "recruitment.view_recritmentmailtemplates", - ] - for perm in permissions: - if user.has_perm(perm): - return True + app_permissions = { + "leave": [ + "leave.add_holiday", + "leave.change_holiday", + "leave.add_companyleaves", + "leave.change_companyleaves", + "leave.view_restrictleave", + ], + "base": [ + "base.add_horillamailtemplates", + "base.view_horillamailtemplates", + ], + } + + for app, perms in app_permissions.items(): + if apps.is_installed(app): + for perm in perms: + if user.has_perm(perm): + return True + return False @register.filter(name="startswith") diff --git a/base/templatetags/horillafilters.py b/base/templatetags/horillafilters.py new file mode 100644 index 000000000..77397c37e --- /dev/null +++ b/base/templatetags/horillafilters.py @@ -0,0 +1,288 @@ +""" +horillafilters.py + +This module is used to write custom template filters. + +""" + +import base64 +from datetime import date, datetime, timedelta +from itertools import groupby + +from django import template +from django.apps import apps +from django.forms.widgets import SelectMultiple, Textarea +from django.template import TemplateSyntaxError +from django.template.defaultfilters import register +from django.utils.translation import gettext as _ + +from horilla.horilla_middlewares import _thread_locals +from horilla.methods import get_horilla_model_class +from base.models import EmployeeShiftSchedule +from employee.methods.duration_methods import strtime_seconds + +register = template.Library() + + +@register.filter(name="is_string") +def is_string(value): + return isinstance(value, str) + + +@register.filter(name="checkminimumot") +def checkminimumot(ot=None): + """ + This filter method is used to check minimum overtime from + the attendance validation condition + """ + if ot is not None: + if apps.is_installed("attendance"): + AttendanceValidationCondition = get_horilla_model_class( + app_label="attendance", model="attendancevalidationcondition" + ) + condition = AttendanceValidationCondition.objects.all() + else: + condition = None + if condition.exists(): + minimum_overtime_to_approve = condition[0].minimum_overtime_to_approve + overtime_second = strtime_seconds(ot) + minimum_ot_approve_seconds = strtime_seconds(minimum_overtime_to_approve) + if overtime_second > minimum_ot_approve_seconds: + return True + return False + + +@register.filter(name="checkmanager") +def checkmanager(user, employee): + """ + This filter method is used to check request user is manager of the employee + args: + user : request.user + employee : employee instance + + """ + + employee_user = user.employee_get + employee_manager = employee.employee_work_info.reporting_manager_id + return bool( + employee_user == employee_manager + or user.is_superuser + or user.has_perm("attendance.change_attendance") + ) + + +@register.filter(name="is_clocked_in") +def is_clocked_in(user): + """ + This filter method is used to check the user is clocked in or not + args: + user : request.user + """ + + try: + employee = user.employee_get + except: + return False + if apps.is_installed("attendance"): + last_attendance = ( + employee.employee_attendances.all().order_by("attendance_date", "id").last() + ) + if last_attendance is not None: + last_activity = employee.employee_attendance_activities.filter( + attendance_date=last_attendance.attendance_date + ).last() + return False if last_activity is None else last_activity.clock_out is None + return False + + +class DynamicRegroupNode(template.Node): + """ + DynamicRegroupNode + """ + + def __init__(self, target, parser, expression, var_name): + self.target = target + self.expression = template.Variable(expression) + self.var_name = var_name + self.parser = parser + + def render(self, context): + obj_list = self.target.resolve(context, True) + if obj_list is None: + # target variable wasn't found in context; fail silently. + context[self.var_name] = [] + return "" + # List of dictionaries in the format: + # {'grouper': 'key', 'list': [list of contents]}. + + # ---- + # Try to resolve the filter expression from the template context. + # If the variable doesn't exist, accept the value that passed to the + # template tag and convert it to a string + # ---- + try: + exp = self.expression.resolve(context) + except template.VariableDoesNotExist: + exp = str(self.expression) + + filter_exp = self.parser.compile_filter(exp) + + context[self.var_name] = [ + {"grouper": key, "list": list(val)} + for key, val in groupby( + obj_list, lambda v, f=filter_exp.resolve: f(v, True) + ) + ] + + return "" + + +@register.tag +def dynamic_regroup(parser, token): + """ + A template tag that allows dynamic grouping of objects based on a provided attribute. + + Usage: {% dynamic_regroup target by expression as var_name %} + + :param parser: The template parser. + :param token: The tokenized tag contents. + :return: A DynamicRegroupNode object. + :raises TemplateSyntaxError: If the tag is not properly formatted. + """ + firstbits = token.contents.split(None, 3) + if len(firstbits) != 4: + raise TemplateSyntaxError("'regroup' tag takes five arguments") + target = parser.compile_filter(firstbits[1]) + if firstbits[2] != "by": + raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") + lastbits_reversed = firstbits[3][::-1].split(None, 2) + if lastbits_reversed[1][::-1] != "as": + raise TemplateSyntaxError( + "next-to-last argument to 'regroup' tag must" " be 'as'" + ) + + # --- + # Django expects the value of `expression` to be an attribute available on + # your objects. The value you pass to the template tag gets converted into a + # FilterExpression object from the literal. + + # Sometimes we need the attribute to group on to be dynamic. So, instead + # of converting the value to a FilterExpression here, we're going to pass the + # value as-is and convert it in the Node. + # ---- + expression = lastbits_reversed[2][::-1] + var_name = lastbits_reversed[0][::-1] + + # ---- + # We also need to hand the parser to the node in order to convert the value + # for `expression` to a FilterExpression. + # ---- + return DynamicRegroupNode(target, parser, expression, var_name) + + +@register.filter(name="any_permission") +def any_permission(user, app_label): + """ + This method is used to check any on the module + + Args: + user (obj): Django user model instance + app_label (str): app label + + Returns: + bool: True if any permission on the module + """ + return user.has_module_perms(app_label) + + +@register.filter +def is_select_multiple(widget): + """ + Custom template filter to check if a widget is an instance of SelectMultiple. + + Usage: + {% load custom_filters %} + + {% if field.field.widget|is_select_multiple %} + + {% endif %} + """ + return isinstance(widget, SelectMultiple) + + +@register.filter +def is_text_area(widget): + """ + Custom template filter to check if a widget is an instance of SelectMultiple. + + Usage: + {% load custom_filters %} + + {% if field.field.widget|Textarea %} + + {% endif %} + """ + return isinstance(widget, Textarea) + + +@register.filter +def base64_encode(value): + try: + return base64.b64encode(value).decode("utf-8") + except: + pass + + +@register.filter(name="current_month_record") +def current_month_record(queryset): + current_month_start_date = datetime.now().replace(day=1) + next_month_start_date = current_month_start_date + timedelta(days=31) + + return queryset.filter( + start_datetime__gte=current_month_start_date, + start_datetime__lt=next_month_start_date, + ).order_by("start_datetime") + + +@register.filter +def get_item(list, i): + try: + return list[i] + except: + return None + + +@register.filter(name="app_installed") +def app_installed(app_name): + """ + Returns True if the app with the given name is installed, otherwise False. + """ + return apps.is_installed(app_name) + + +@register.filter(name="is_stagemanager") +def is_stagemanager(user): + """ + This method is used to check the employee is stage or recruitment manager + """ + try: + employee_obj = user.employee_get + return ( + employee_obj.stage_set.all().exists() + or employee_obj.recruitment_set.exists() + ) + except Exception: + return False + + +@register.filter(name="yes_no") +def yesno(value): + return _("Yes") if value else _("No") + + +@register.filter(name="on_off") +def on_off(value): + if value == "on": + return _("Yes") + elif value == "off": + return _("No") diff --git a/base/urls.py b/base/urls.py index a51d67f27..0172a9ade 100644 --- a/base/urls.py +++ b/base/urls.py @@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ from base import announcement, request_and_approve, views from base.forms import ( AttendanceAllowedIPForm, + MailTemplateForm, RotatingShiftAssignForm, RotatingShiftForm, RotatingWorkTypeAssignForm, @@ -20,6 +21,7 @@ from base.models import ( EmployeeShift, EmployeeShiftSchedule, EmployeeType, + HorillaMailTemplate, JobPosition, JobRole, RotatingShift, @@ -67,7 +69,6 @@ urlpatterns = [ views.initialize_database_job_position, name="initialize-database-job-position", ), - path("404", views.custom404, name="404"), path( "initialize-job-position-edit/", views.initialize_job_position_edit, @@ -78,6 +79,7 @@ urlpatterns = [ views.initialize_job_position_delete, name="initialize-job-position-delete", ), + path("404", views.custom404, name="404"), path("login/", views.login_user, name="login"), path( "forgot-password", @@ -160,6 +162,36 @@ urlpatterns = [ path( "replace-primary-mail", views.replace_primary_mail, name="replace-primary-mail" ), + path( + "configuration/view-mail-templates/", + views.view_mail_templates, + name="view-mail-templates", + ), + path( + "view-mail-template//", + views.view_mail_template, + name="view-mail-template", + ), + path( + "create-mail-template/", + views.create_mail_templates, + name="create-mail-template", + ), + path( + "duplicate-mail-template//", + views.object_duplicate, + name="duplicate-mail-template", + kwargs={ + "model": HorillaMailTemplate, + "form": MailTemplateForm, + "template": "mail/htmx/form.html", + }, + ), + path( + "delete-mail-template/", + views.delete_mail_templates, + name="delete-mail-template", + ), path("settings/company-create/", views.company_create, name="company-create"), path("settings/company-view/", views.company_view, name="company-view"), path( @@ -446,16 +478,6 @@ urlpatterns = [ "redirect": "/settings/rotating-shift-view", }, ), - path( - "settings/department-manager-view/", - views.view_department_managers, - name="department-manager-view", - ), - path( - "settings/candidate-reject-reasons/", - views.candidate_reject_reasons, - name="candidate-reject-reasons", - ), path( "employee/rotating-shift-assign/", views.rotating_shift_assign, @@ -711,36 +733,6 @@ urlpatterns = [ views.enable_account_block_unblock, name="enable-account-block-unblock", ), - path( - "settings/attendance-settings-view/", - views.validation_condition_view, - name="attendance-settings-view", - ), - path( - "settings/track-late-come-early-out", - views.track_late_come_early_out, - name="track-late-come-early-out", - ), - path( - "settings/enable-disable-tracking-late-come-early-out", - views.enable_disable_tracking_late_come_early_out, - name="enable-disable-tracking-late-come-early-out", - ), - path( - "settings/grace-settings-view/", - views.grace_time_view, - name="grace-settings-view", - ), - path( - "settings/attendance-settings-create/", - views.validation_condition_create, - name="attendance-settings-create", - ), - path( - "settings/attendance-settings-update//", - views.validation_condition_update, - name="attendance-settings-update", - ), path( "rwork-individual-view//", views.rotating_work_individual_view, @@ -779,22 +771,7 @@ urlpatterns = [ views.rotating_work_type_select_filter, name="r-work-type-select-filter", ), - path("settings/ticket-type-view/", views.ticket_type_view, name="ticket-type-view"), - path("ticket-type-create", views.ticket_type_create, name="ticket-type-create"), - path( - "ticket-type-update/", - views.ticket_type_update, - name="ticket-type-update", - ), - path( - "ticket-type-delete/", - views.ticket_type_delete, - name="ticket-type-delete", - ), path("settings/tag-view/", views.tag_view, name="tag-view"), - path( - "settings/employee-tag-view/", views.employee_tag_view, name="employee-tag-view" - ), path( "settings/helpdesk-tag-view/", views.helpdesk_tag_view, name="helpdesk-tag-view" ), @@ -806,18 +783,6 @@ urlpatterns = [ name="tag-delete", kwargs={"model": Tags, "redirect": "/settings/tag-view/"}, ), - path("employee-tag-create", views.employee_tag_create, name="employee-tag-create"), - path( - "employee-tag-update/", - views.employee_tag_update, - name="employee-tag-update", - ), - path( - "employee-tag-delete//", - views.object_delete, - name="employee-tag-delete", - kwargs={"model": EmployeeTag, "redirect": "/settings/tag-view/"}, - ), path("audit-tag-create", views.audit_tag_create, name="audit-tag-create"), path( "audit-tag-update/", views.audit_tag_update, name="audit-tag-update" @@ -918,36 +883,6 @@ urlpatterns = [ request_and_approve.dashboard_work_type_request, name="dashboard-work-type-request", ), - path( - "dashboard-overtime-approve", - request_and_approve.dashboard_overtime_approve, - name="dashboard-overtime-approve", - ), - path( - "dashboard-attendance-validate", - request_and_approve.dashboard_attendance_validate, - name="dashboard-attendance-validate", - ), - path( - "leave-request-and-approve", - request_and_approve.leave_request_and_approve, - name="leave-request-and-approve", - ), - path( - "leave-allocation-approve", - request_and_approve.leave_allocation_approve, - name="leave-allocation-approve", - ), - path( - "dashboard-feedback-answer", - request_and_approve.dashboard_feedback_answer, - name="dashboard-feedback-answer", - ), - path( - "dashboard-asset-request-approve", - request_and_approve.dashboard_asset_request_approve, - name="dashboard-asset-request-approve", - ), path( "settings/pagination-settings-view/", views.pagination_settings_view, @@ -1028,45 +963,55 @@ urlpatterns = [ name="emp-workinfo-complete", ), path( - "settings/allowed-ips/", - views.allowed_ips, - name="allowed-ips", + "get-horilla-installed-apps/", + views.get_horilla_installed_apps, + name="get-horilla-installed-apps", + ), + path("configuration/holiday-view", views.holiday_view, name="holiday-view"), + path( + "configuration/holidays-excel-template", + views.holidays_excel_template, + name="holidays-excel-template", ), path( - "settings/enable-ip-restriction/", - views.enable_ip_restriction, - name="enable-ip-restriction", + "holidays-info-import", views.holidays_info_import, name="holidays-info-import" + ), + path("holiday-info-export", views.holiday_info_export, name="holiday-info-export"), + path("holiday-creation", views.holiday_creation, name="holiday-creation"), + path("holiday-update/", views.holiday_update, name="holiday-update"), + path("holiday-delete/", views.holiday_delete, name="holiday-delete"), + path( + "holidays-bulk-delete", views.bulk_holiday_delete, name="holidays-bulk-delete" + ), + path("holiday-filter", views.holiday_filter, name="holiday-filter"), + path("holiday-select/", views.holiday_select, name="holiday-select"), + path( + "holiday-select-filter/", + views.holiday_select_filter, + name="holiday-select-filter", ), path( - "settings/add-remove-ip-fields/", - views.add_remove_dynamic_fields, - name="add-remove-ip-fields", - kwargs={ - "model": AttendanceAllowedIP, - "form_class": AttendanceAllowedIPForm, - "template": "attendance/ip_restriction/add_more_ip_fields.html", - "field_type": "character", - "field_name_pre": "ip_address", - }, + "company-leave-creation", + views.company_leave_creation, + name="company-leave-creation", ), path( - "settings/create-allowed-ip/", - views.create_allowed_ips, - name="create-allowed-ip", + "configuration/company-leave-view", + views.company_leave_view, + name="company-leave-view", ), path( - "settings/delete-allowed-ip/", - views.delete_allowed_ips, - name="delete-allowed-ip", + "company-leave-update/", + views.company_leave_update, + name="company-leave-update", ), path( - "settings/edit-allowed-ip/", - views.edit_allowed_ips, - name="edit-allowed-ip", + "company-leave-delete/", + views.company_leave_delete, + name="company-leave-delete", ), path( - "settings/skills-view/", - views.skills_view, - name="skills-view", + "company-leave-filter", views.company_leave_filter, name="company-leave-filter" ), + path("view-penalties", views.view_penalties, name="view-penalties"), ] diff --git a/base/views.py b/base/views.py index e7c463629..cd4ead945 100644 --- a/base/views.py +++ b/base/views.py @@ -5,11 +5,11 @@ This module is used to map url pattens with django views or methods """ import json -import uuid from datetime import datetime, timedelta from os import path from urllib.parse import parse_qs, unquote, urlencode +import pandas as pd from django import forms from django.apps import apps from django.conf import settings @@ -30,14 +30,15 @@ from django.utils.translation import gettext as _ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods -from attendance.forms import AttendanceValidationConditionForm -from attendance.models import AttendanceValidationCondition, GraceTime from base.backends import ConfiguredEmailBackend from base.decorators import ( shift_request_change_permission, work_type_request_change_permission, ) from base.filters import ( + CompanyLeaveFilter, + HolidayFilter, + PenaltyFilter, RotatingShiftAssignFilters, RotatingShiftRequestReGroup, RotatingWorkTypeAssignFilter, @@ -51,11 +52,10 @@ from base.forms import ( AnnouncementExpireForm, AssignPermission, AssignUserGroup, - AttendanceAllowedIPForm, - AttendanceAllowedIPUpdateForm, AuditTagForm, ChangePasswordForm, CompanyForm, + CompanyLeaveForm, DepartmentForm, DriverForm, DynamicMailConfForm, @@ -64,10 +64,12 @@ from base.forms import ( EmployeeShiftForm, EmployeeShiftScheduleForm, EmployeeShiftScheduleUpdateForm, - EmployeeTagForm, EmployeeTypeForm, + HolidayForm, + HolidaysColumnExportForm, JobPositionForm, JobRoleForm, + MailTemplateForm, MultipleApproveConditionForm, PassWordResetForm, ResetPasswordForm, @@ -85,7 +87,6 @@ from base.forms import ( ShiftRequestCommentForm, ShiftRequestForm, TagsForm, - TrackLateComeEarlyOutForm, UserGroupForm, WorkTypeForm, WorkTypeRequestColumnForm, @@ -102,13 +103,15 @@ from base.methods import ( sortby, ) from base.models import ( + WEEK_DAYS, + WEEKS, Announcement, AnnouncementExpire, AnnouncementView, - AttendanceAllowedIP, BaserequestFile, BiometricAttendance, Company, + CompanyLeaves, DashboardEmployeeCharts, Department, DynamicEmailConfiguration, @@ -117,6 +120,8 @@ from base.models import ( EmployeeShiftDay, EmployeeShiftSchedule, EmployeeType, + Holidays, + HorillaMailTemplate, JobPosition, JobRole, MultipleApprovalCondition, @@ -127,35 +132,29 @@ from base.models import ( ShiftRequest, ShiftRequestComment, Tags, - TrackLateComeEarlyOut, WorkType, WorkTypeRequest, WorkTypeRequestComment, ) from employee.filters import EmployeeFilter from employee.forms import ActiontypeForm -from employee.models import Actiontype, Employee, EmployeeTag, EmployeeWorkInformation -from helpdesk.forms import TicketTypeForm -from helpdesk.models import DepartmentManager, TicketType +from employee.models import Actiontype, Employee, EmployeeWorkInformation from horilla.decorators import ( delete_permission, duplicate_permission, hx_request_required, + install_required, login_required, manager_can_enter, permission_required, ) from horilla.group_by import group_by_queryset +from horilla.methods import get_horilla_model_class from horilla_audit.forms import HistoryTrackingFieldsForm from horilla_audit.models import AccountBlockUnblock, AuditTag, HistoryTrackingFields from notifications.base.models import AbstractNotification from notifications.models import Notification from notifications.signals import notify -from payroll.forms.component_forms import PayrollSettingsForm -from payroll.models.models import EncashmentGeneralSettings -from payroll.models.tax_models import PayrollSettings -from pms.models import KeyResult -from recruitment.models import RejectReason, Skill def custom404(request): @@ -747,28 +746,6 @@ def common_settings(request): return render(request, "settings.html") -@login_required -def view_department_managers(request): - department_managers = DepartmentManager.objects.all() - - context = { - "department_managers": department_managers, - } - return render(request, "department_managers/department_managers.html", context) - - -@login_required -@permission_required("recruitment.view_rejectreason") -def candidate_reject_reasons(request): - """ - This method is used to view all the reject reasons - """ - reject_reasons = RejectReason.objects.all() - return render( - request, "settings/reject_reasons.html", {"reject_reasons": reject_reasons} - ) - - @login_required @hx_request_required @permission_required("auth.add_group") @@ -1065,7 +1042,8 @@ def object_delete(request, id, **kwargs): _("This {} is already in use for {}.").format(instance, model_names_str), ), - if redirect_path == "/pms/filter-key-result/": + if apps.is_installed("pms") and redirect_path == "/pms/filter-key-result/": + KeyResult = get_horilla_model_class(app_label="pms", model="keyresult") key_results = KeyResult.objects.all() if key_results.exists(): previous_data = request.GET.urlencode() @@ -1325,7 +1303,6 @@ def replace_primary_mail(request): messages.success(request, "Primary Mail server configuration replaced") return redirect("mail-server-conf") - # return HttpResponse("") @login_required @@ -1347,6 +1324,76 @@ def mail_server_create_or_update(request): ) +@login_required +@permission_required("base.view_horillamailtemplate") +def view_mail_templates(request): + """ + This method will render template to disply the offerletter templates + """ + templates = HorillaMailTemplate.objects.all() + form = MailTemplateForm() + if templates.exists(): + template = "mail/view_templates.html" + else: + template = "mail/empty_mail_template.html" + searchWords = form.get_template_language() + return render( + request, + template, + {"templates": templates, "form": form, "searchWords": searchWords}, + ) + + +@login_required +@hx_request_required +@permission_required("base.change_horillamailtemplate") +def view_mail_template(request, obj_id): + """ + This method is used to display the template/form to edit + """ + template = HorillaMailTemplate.objects.get(id=obj_id) + form = MailTemplateForm(instance=template) + searchWords = form.get_template_language() + if request.method == "POST": + form = MailTemplateForm(request.POST, instance=template) + if form.is_valid(): + form.save() + messages.success(request, "Template updated") + return HttpResponse("") + + return render( + request, + "mail/htmx/form.html", + {"form": form, "duplicate": False, "searchWords": searchWords}, + ) + + +@login_required +@require_http_methods(["POST"]) +@permission_required("base.add_horillamailtemplate") +def create_mail_templates(request): + """ + This method is used to create offerletter template + """ + if request.method == "POST": + form = MailTemplateForm(request.POST) + if form.is_valid(): + instance = form.save() + instance.save() + messages.success(request, "Template created") + return HttpResponse("") + return redirect(view_mail_templates) + + +@login_required +@permission_required("base.delete_horillamailtemplate") +def delete_mail_templates(request): + ids = request.GET.getlist("ids") + result = HorillaMailTemplate.objects.filter(id__in=ids).delete() + messages.success(request, "Template deleted") + return redirect(view_mail_templates) + + @login_required @hx_request_required @permission_required("base.add_company") @@ -2225,7 +2272,11 @@ def employee_shift_view(request): """ shifts = EmployeeShift.objects.all() - grace_times = GraceTime.objects.all().exclude(is_default=True) + if apps.is_installed("attendance"): + GraceTime = get_horilla_model_class(app_label="attendance", model="gracetime") + grace_times = GraceTime.objects.all().exclude(is_default=True) + else: + grace_times = None return render( request, "base/shift/shift.html", {"shifts": shifts, "grace_times": grace_times} ) @@ -2840,7 +2891,7 @@ def employee_permission_assign(request): ).distinct() context["show_assign"] = True permissions = [] - apps = [ + horilla_apps = [ "base", "recruitment", "employee", @@ -2855,7 +2906,8 @@ def employee_permission_assign(request): "horilla_documents", "helpdesk", ] - for app_name in apps: + installed_apps = [app for app in settings.INSTALLED_APPS if app in horilla_apps] + for app_name in installed_apps: app_models = [] for model in get_models_in_app(app_name): app_models.append( @@ -2864,7 +2916,9 @@ def employee_permission_assign(request): "model_name": model._meta.model_name, } ) - permissions.append({"app": app_name.capitalize(), "app_models": app_models}) + permissions.append( + {"app": app_name.capitalize().replace("_", " "), "app_models": app_models} + ) context["permissions"] = permissions context["employees"] = paginator_qry(employees, request.GET.get("page")) return render( @@ -2926,9 +2980,6 @@ def employee_permission_search(request, codename=None, uid=None): ) -# add_recruitment - - @login_required @require_http_methods(["POST"]) @permission_required("auth.add_permission") @@ -3847,12 +3898,6 @@ def shift_request_search(request): data_dict = parse_qs(previous_data) template = "shift_request/htmx/requests.html" - # if field != "" and field is not None: - # field_copy = field.replace(".", "__") - # shift_requests = shift_requests.order_by(f"-{field_copy}") - # allocated_shift_requests = allocated_shift_requests.order_by(f"-{field_copy}") - # template = "shift_request/htmx/group_by.html" - if field != "" and field is not None: shift_requests = group_by_queryset( shift_requests, field, request.GET.get("page"), "page" @@ -4601,16 +4646,30 @@ def general_settings(request): """ This method is used to render settings template """ - from payroll.forms.forms import EncashmentGeneralSettingsForm + if apps.is_installed("payroll"): + PayrollSettings = get_horilla_model_class( + app_label="payroll", model="payrollsettings" + ) + EncashmentGeneralSettings = get_horilla_model_class( + app_label="payroll", model="encashmentgeneralsettings" + ) + from payroll.forms.component_forms import PayrollSettingsForm + from payroll.forms.forms import EncashmentGeneralSettingsForm + + currency_instance = PayrollSettings.objects.first() + currency_form = PayrollSettingsForm(instance=currency_instance) + encashment_instance = EncashmentGeneralSettings.objects.first() + encashment_form = EncashmentGeneralSettingsForm(instance=encashment_instance) + else: + encashment_form = None + currency_form = None instance = AnnouncementExpire.objects.first() form = AnnouncementExpireForm(instance=instance) - encashment_instance = EncashmentGeneralSettings.objects.first() enabled_block_unblock = ( AccountBlockUnblock.objects.exists() and AccountBlockUnblock.objects.first().is_enabled ) - encashment_form = EncashmentGeneralSettingsForm(instance=encashment_instance) history_tracking_instance = HistoryTrackingFields.objects.first() history_fields_form_initial = {} if history_tracking_instance and history_tracking_instance.tracking_fields: @@ -4620,8 +4679,7 @@ def general_settings(request): ] } history_fields_form = HistoryTrackingFieldsForm(initial=history_fields_form_initial) - currency_instance = PayrollSettings.objects.first() - currency_form = PayrollSettingsForm(instance=currency_instance) + if DynamicPagination.objects.filter(user_id=request.user).exists(): pagination = DynamicPagination.objects.filter(user_id=request.user).first() pagination_form = DynamicPaginationForm(instance=pagination) @@ -4819,114 +4877,6 @@ def enable_account_block_unblock(request): return redirect(general_settings) -@login_required -@permission_required("attendance.view_attendancevalidationcondition") -def validation_condition_view(request): - """ - This method view attendance validation conditions. - """ - condition = AttendanceValidationCondition.objects.first() - default_grace_time = GraceTime.objects.filter(is_default=True).first() - return render( - request, - "attendance/break_point/condition.html", - {"condition": condition, "default_grace_time": default_grace_time}, - ) - - -@login_required -@permission_required("base.view_tracklatecomeearlyout") -def track_late_come_early_out(request): - tracking = TrackLateComeEarlyOut.objects.first() - form = TrackLateComeEarlyOutForm( - initial={"is_enable": tracking.is_enable} if tracking else {} - ) - return render( - request, "attendance/late_come_early_out/tracking.html", {"form": form} - ) - - -@login_required -@permission_required("base.change_tracklatecomeearlyout") -def enable_disable_tracking_late_come_early_out(request): - if request.method == "POST": - enable = bool(request.POST.get("is_enable")) - tracking, created = TrackLateComeEarlyOut.objects.get_or_create() - tracking.is_enable = enable - tracking.save() - message = _("enabled") if enable else _("disabled") - messages.success( - request, _("Tracking late come early out {} successfully").format(message) - ) - return HttpResponse("") - - -@login_required -@permission_required("attendance.view_attendancevalidationcondition") -def grace_time_view(request): - """ - This method view attendance validation conditions. - """ - condition = AttendanceValidationCondition.objects.first() - default_grace_time = GraceTime.objects.filter(is_default=True).first() - grace_times = GraceTime.objects.all().exclude(is_default=True) - - return render( - request, - "attendance/grace_time/grace_time.html", - { - "condition": condition, - "default_grace_time": default_grace_time, - "grace_times": grace_times, - }, - ) - - -@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() - if request.method == "POST": - form = AttendanceValidationConditionForm(request.POST) - if form.is_valid(): - form.save() - messages.success(request, _("Attendance Break-point settings created.")) - return HttpResponse("") - return render( - request, - "attendance/break_point/condition_form.html", - {"form": form}, - ) - - -@login_required -@hx_request_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() - messages.success(request, _("Attendance Break-point settings updated.")) - return HttpResponse("") - return render( - request, - "attendance/break_point/condition_form.html", - {"form": form, "condition": condition}, - ) - - @login_required def shift_select(request): page_number = request.GET.get("page") @@ -5104,90 +5054,6 @@ def rotating_work_type_select_filter(request): return JsonResponse(context) -@login_required -@permission_required("helpdesk.view_tickettype") -def ticket_type_view(request): - """ - This method is used to show Ticket type - """ - ticket_types = TicketType.objects.all() - return render( - request, "base/ticket_type/ticket_type.html", {"ticket_types": ticket_types} - ) - - -@login_required -@hx_request_required -@permission_required("helpdesk.create_tickettype") -def ticket_type_create(request): - """ - This method renders form and template to create Ticket type - """ - form = TicketTypeForm() - if request.method == "POST": - form = TicketTypeForm(request.POST) - if request.GET.get("ajax"): - if form.is_valid(): - instance = form.save() - response = { - "errors": "no_error", - "ticket_id": instance.id, - "title": instance.title, - } - return JsonResponse(response) - - errors = form.errors.as_json() - return JsonResponse({"errors": errors}) - if form.is_valid(): - form.save() - form = TicketTypeForm() - messages.success(request, _("Ticket type has been created successfully!")) - return HttpResponse("") - return render( - request, - "base/ticket_type/ticket_type_form.html", - { - "form": form, - }, - ) - - -@login_required -@hx_request_required -@permission_required("helpdesk.update_tickettype") -def ticket_type_update(request, t_type_id): - """ - This method renders form and template to create Ticket type - """ - ticket_type = TicketType.objects.get(id=t_type_id) - form = TicketTypeForm(instance=ticket_type) - if request.method == "POST": - form = TicketTypeForm(request.POST, instance=ticket_type) - if form.is_valid(): - form.save() - form = TicketTypeForm() - messages.success(request, _("Ticket type has been updated successfully!")) - return HttpResponse("") - return render( - request, - "base/ticket_type/ticket_type_form.html", - {"form": form, "t_type_id": t_type_id}, - ) - - -@login_required -@require_http_methods(["POST", "DELETE"]) -@permission_required("helpdesk.delete_tickettype") -def ticket_type_delete(request, t_type_id): - ticket_type = TicketType.find(t_type_id) - if ticket_type: - ticket_type.delete() - messages.success(request, _("Ticket type has been deleted successfully!")) - else: - messages.error(request, _("Ticket type not found")) - return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) - - @login_required @permission_required("horilla_audit.view_audittag") def tag_view(request): @@ -5202,20 +5068,6 @@ def tag_view(request): ) -@login_required -@permission_required("employee.view_employeetag") -def employee_tag_view(request): - """ - This method is used to Employee tags - """ - employeetags = EmployeeTag.objects.all() - return render( - request, - "base/tags/employee_tags.html", - {"employeetags": employeetags}, - ) - - @login_required @permission_required("helpdesk.view_tag") def helpdesk_tag_view(request): @@ -5277,53 +5129,6 @@ def tag_update(request, tag_id): ) -@login_required -@hx_request_required -@permission_required("employee.add_employeetag") -def employee_tag_create(request): - """ - This method renders form and template to create Ticket type - """ - form = EmployeeTagForm() - if request.method == "POST": - form = EmployeeTagForm(request.POST) - if form.is_valid(): - form.save() - form = EmployeeTagForm() - messages.success(request, _("Tag has been created successfully!")) - return HttpResponse("") - return render( - request, - "base/employee_tag/employee_tag_form.html", - { - "form": form, - }, - ) - - -@login_required -@hx_request_required -@permission_required("employee.add_employeetag") -def employee_tag_update(request, tag_id): - """ - This method renders form and template to create Ticket type - """ - tag = EmployeeTag.objects.get(id=tag_id) - form = EmployeeTagForm(instance=tag) - if request.method == "POST": - form = EmployeeTagForm(request.POST, instance=tag) - if form.is_valid(): - form.save() - form = EmployeeTagForm() - messages.success(request, _("Tag has been updated successfully!")) - return HttpResponse("") - return render( - request, - "base/employee_tag/employee_tag_form.html", - {"form": form, "tag_id": tag_id}, - ) - - @login_required @hx_request_required @permission_required("horilla_audit.add_audittag") @@ -5372,6 +5177,7 @@ def audit_tag_update(request, tag_id): @login_required +@install_required @permission_required("base.view_multipleapprovalcondition") def multiple_approval_condition(request): form = MultipleApproveConditionForm() @@ -5544,6 +5350,9 @@ def multiple_level_approval_edit(request, condition_id): form = MultipleApproveConditionForm(request.POST, instance=condition) if form.is_valid(): instance = form.save() + messages.success( + request, _("Multiple approval condition updated successfully") + ) sequence = 0 MultipleApprovalManagers.objects.filter(condition_id=condition).delete() for key, value in request.POST.items(): @@ -5555,8 +5364,6 @@ def multiple_level_approval_edit(request, condition_id): sequence=sequence, employee_id=employee_id, ) - return HttpResponse("") - conditions = MultipleApprovalCondition.objects.all().order_by("department")[::-1] return render( request, @@ -6096,17 +5903,45 @@ def employee_charts(request): return HttpResponse("") -def check_permission(request, charts): +def check_chart_permission(request, charts): """ This function is used to check the permissions for the charts Args: charts: dashboard charts """ - from recruitment.templatetags.recruitmentfilters import ( - is_recruitmentmangers, - is_stagemanager, - ) + from base.templatetags.basefilters import is_reportingmanager + if apps.is_installed("recruitment"): + from recruitment.templatetags.recruitmentfilters import is_stagemanager + + need_stage_manager = [ + "hired_candidates", + "onboarding_candidates", + "recruitment_analytics", + ] + chart_apps = { + "offline_employees": "attendance", + "online_employees": "attendance", + "overall_leave_chart": "leave", + "hired_candidates": "recruitment", + "onboarding_candidates": "onboarding", + "recruitment_analytics": "recruitment", + "attendance_analytic": "attendance", + "hours_chart": "attendance", + "objective_status": "pms", + "key_result_status": "pms", + "feedback_status": "pms", + "shift_request_approve": "base", + "work_type_request_approve": "base", + "overtime_approve": "attendance", + "attendance_validate": "attendance", + "leave_request_approve": "leave", + "leave_allocation_approve": "leave", + "asset_request_approve": "asset", + "employees_chart": "employee", + "gender_chart": "employee", + "department_chart": "base", + } permissions = { "offline_employees": "employee.view_employee", "online_employees": "employee.view_employee", @@ -6128,7 +5963,7 @@ def check_permission(request, charts): "asset_request_approve": "asset.change_assetrequest", } chart_list = [] - need_recruitment_manager = [ + need_reporting_manager = [ "offline_employees", "online_employees", "attendance_analytic", @@ -6144,27 +5979,25 @@ def check_permission(request, charts): "leave_allocation_approve", "asset_request_approve", ] - need_stage_manager = [ - "hired_candidates", - "onboarding_candidates", - "recruitment_analytics", - ] for chart in charts: - if ( - chart[0] in permissions.keys() - or chart[0] in need_recruitment_manager - or chart[0] in need_stage_manager - ): - if request.user.has_perm(permissions[chart[0]]): + if apps.is_installed(chart_apps.get(chart[0])): + if ( + chart[0] in permissions.keys() + or chart[0] in need_reporting_manager + or (apps.is_installed("recruitment") and chart[0] in need_stage_manager) + ): + if request.user.has_perm(permissions[chart[0]]): + chart_list.append(chart) + elif chart[0] in need_reporting_manager: + if is_reportingmanager(request.user): + chart_list.append(chart) + elif ( + apps.is_installed("recruitment") and chart[0] in need_stage_manager + ): + if is_stagemanager(request.user): + chart_list.append(chart) + else: chart_list.append(chart) - elif chart[0] in need_recruitment_manager: - if is_recruitmentmangers(request.user): - chart_list.append(chart) - elif chart[0] in need_stage_manager: - if is_stagemanager(request.user): - chart_list.append(chart) - else: - chart_list.append(chart) return chart_list @@ -6186,7 +6019,7 @@ def employee_chart_show(request): ("recruitment_analytics", _("Recruitment Analytics")), ("attendance_analytic", _("Attendance analytics")), ("hours_chart", _("Hours Chart")), - ("employees_chart", _("Employee Chart")), + ("employees_chart", _("Employees Chart")), ("department_chart", _("Department Chart")), ("gender_chart", _("Gender Chart")), ("objective_status", _("Objective Status")), @@ -6201,7 +6034,8 @@ def employee_chart_show(request): ("feedback_answer", _("Feedbacks to Answer")), ("asset_request_approve", _("Asset Request to Approve")), ] - charts = check_permission(request, charts) + charts = check_chart_permission(request, charts) + if request.method == "POST": employee_charts.charts = [] employee_charts.save() @@ -6258,151 +6092,505 @@ def activate_biometric_attendance(request): @login_required -@permission_required("attendance.add_attendance") -def allowed_ips(request): - """ - This function is used to view the allowed ips - """ - allowed_ips = AttendanceAllowedIP.objects.first() - return render( - request, - "attendance/ip_restriction/ip_restriction.html", - {"allowed_ips": allowed_ips}, +def get_horilla_installed_apps(request): + installed_apps = settings.INSTALLED_APPS + return JsonResponse({"installed_apps": installed_apps}) + + +def generate_error_report(error_list, error_data, file_name): + for item in error_list: + for key, value in error_data.items(): + if key in item: + value.append(item[key]) + else: + value.append(None) + + keys_to_remove = [ + key for key, value in error_data.items() if all(v is None for v in value) + ] + for key in keys_to_remove: + del error_data[key] + + data_frame = pd.DataFrame(error_data, columns=error_data.keys()) + styled_data_frame = data_frame.style.map( + lambda x: "text-align: center", subset=pd.IndexSlice[:, :] ) + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = f'attachment; filename="{file_name}"' + writer = pd.ExcelWriter(response, engine="xlsxwriter") + styled_data_frame.to_excel(writer, index=False, sheet_name="Sheet1") -@login_required -@permission_required("attendance.add_attendance") -def enable_ip_restriction(request): - """ - This function is used to enable the allowed ips - """ - form = AttendanceAllowedIPForm() - if request.method == "POST": - ip_restiction = AttendanceAllowedIP.objects.first() + worksheet = writer.sheets["Sheet1"] + worksheet.set_column("A:Z", 30) - if not ip_restiction: - ip_restiction = AttendanceAllowedIP.objects.create(is_enabled=True) - return HttpResponse("") - - if not ip_restiction.is_enabled: - ip_restiction.is_enabled = True - elif ip_restiction.is_enabled: - ip_restiction.is_enabled = False - - ip_restiction.save() - return HttpResponse("") - - -def validate_ip_address(self, value): - """ - This function is used to check if the provided IP is in the ipv4 or ipv6 format. - - Args: - value: The IP address to validate - """ - try: - validate_ipv46_address(value) - except ValidationError: - raise ValidationError("Enter a valid IPv4 or IPv6 address.") - return value + writer.close() + return response @login_required -@permission_required("attendance.add_attendance") -def create_allowed_ips(request): +@hx_request_required +@permission_required("leave.add_holiday") +def holiday_creation(request): """ - This function is used to create the allowed ips + function used to create holidays. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return holiday creation form template + POST : return holiday view template """ - form = AttendanceAllowedIPForm() + + query_string = request.GET.urlencode() + if query_string.startswith("pd="): + previous_data = unquote(query_string[len("pd=") :]) + else: + previous_data = unquote(query_string) + form = HolidayForm() if request.method == "POST": - form = AttendanceAllowedIPForm(request.POST) + form = HolidayForm(request.POST) if form.is_valid(): - values = [request.POST[key] for key in request.POST.keys()] - allowed_ips = AttendanceAllowedIP.objects.first() - for value in values: - try: - validate_ipv46_address(value) - if value not in allowed_ips.additional_data["allowed_ips"]: - allowed_ips.additional_data["allowed_ips"].append(value) - messages.success(request, f"IP address saved successfully") - else: - messages.error(request, "IP address already exists") - - except ValidationError: - messages.error( - request, f"Enter a valid IPv4 or IPv6 address: {value}" - ) - - allowed_ips.save() - - return HttpResponse("") + form.save() + messages.success(request, _("New holiday created successfully..")) + if Holidays.objects.filter().count() == 1: + return HttpResponse("") return render( - request, "attendance/ip_restriction/restrict_form.html", {"form": form} + request, "holiday/holiday_form.html", {"form": form, "pd": previous_data} + ) + + +def holidays_excel_template(request): + try: + columns = [ + "Holiday Name", + "Start Date", + "End Date", + "Recurring", + ] + data_frame = pd.DataFrame(columns=columns) + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = ( + 'attachment; filename="assign_leave_type_excel.xlsx"' + ) + data_frame.to_excel(response, index=False) + print(response) + return response + except Exception as exception: + return HttpResponse(exception) + + +@login_required +@permission_required("base.add_holiday") +def holidays_info_import(request): + file_name = "HolidaysImportError.xlsx" + error_list = [] + error_data = { + "Holiday Name": [], + "Start Date": [], + "End Date": [], + "Recurring": [], + "Error1": [], + "Error2": [], + "Error3": [], + "Error4": [], + } + + if request.method == "POST": + file = request.FILES["holidays_import"] + data_frame = pd.read_excel(file) + holiday_dicts = data_frame.to_dict("records") + for holiday in holiday_dicts: + save = True + try: + name = holiday["Holiday Name"] + try: + start_date = pd.to_datetime(holiday["Start Date"]).date() + except Exception as e: + save = False + holiday["Error1"] = _("Invalid start date format {}").format( + holiday["Start Date"] + ) + try: + end_date = pd.to_datetime(holiday["End Date"]).date() + except Exception as e: + save = False + holiday["Error2"] = _("Invalid end date format {}").format( + holiday["End Date"] + ) + if holiday["Recurring"].lower() in ["yes", "no"]: + recurring = True if holiday["Recurring"].lower() == "yes" else False + else: + save = False + holiday["Error3"] = _("Recurring must be {} or {}").format( + "yes", "no" + ) + if save: + holiday = Holidays( + name=name, + start_date=start_date, + end_date=end_date, + recurring=recurring, + ) + holiday.save() + else: + error_list.append(holiday) + except Exception as e: + holiday["Error4"] = f"{str(e)}" + error_list.append(holiday) + + if error_list: + return generate_error_report(error_list, error_data, file_name) + else: + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + + +@login_required +def holiday_info_export(request): + if request.META.get("HTTP_HX_REQUEST"): + export_filter = HolidayFilter() + export_column = HolidaysColumnExportForm() + content = { + "export_filter": export_filter, + "export_column": export_column, + } + return render( + request, "holiday/holiday_export_filter_form.html", context=content + ) + return export_data( + request=request, + model=Holidays, + filter_class=HolidayFilter, + form_class=HolidaysColumnExportForm, + file_name="Holidays_export", ) @login_required -@permission_required("attendance.delete_attendance") -def delete_allowed_ips(request): +def holiday_view(request): """ - This function is used to delete the allowed ips + function used to view holidays. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return holiday view template """ - try: - ids = request.GET.getlist("id") - allowed_ips = AttendanceAllowedIP.objects.first() - ips = allowed_ips.additional_data["allowed_ips"] - for id in ids: - ips.pop(eval(id)) + queryset = Holidays.objects.all()[::-1] + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + page_obj = paginator_qry(queryset, page_number) + holiday_filter = HolidayFilter() - allowed_ips.additional_data["allowed_ips"] = ips - allowed_ips.save() - - messages.success(request, "IP address removed successfully") - except: - messages.error(request, "Invalid id") - return redirect("allowed-ips") + return render( + request, + "holiday/holiday_view.html", + { + "holidays": page_obj, + "form": holiday_filter.form, + "pd": previous_data, + }, + ) @login_required -@permission_required("attendance.change_attendance") -def edit_allowed_ips(request): +@hx_request_required +def holiday_filter(request): """ - This function is used to edit the allowed ips + function used to filter holidays. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return holiday view template """ - try: - - allowed_ips = AttendanceAllowedIP.objects.first() - ips = allowed_ips.additional_data["allowed_ips"] - id = request.GET.get("id") - - form = AttendanceAllowedIPUpdateForm(initial={"ip_address": ips[eval(id)]}) - if request.method == "POST": - form = AttendanceAllowedIPUpdateForm(request.POST) - if form.is_valid(): - new_ip = form.cleaned_data["ip_address"] - ips[eval(id)] = new_ip - if not new_ip in allowed_ips.additional_data["allowed_ips"]: - allowed_ips.additional_data["allowed_ips"] = ips - allowed_ips.save() - messages.success(request, "IP address updated successfully") - else: - messages.error(request, "IP address already exists") - - return HttpResponse("") - except: - messages.error(request, "Invalid id") + queryset = Holidays.objects.all() + previous_data = request.GET.urlencode() + holiday_filter = HolidayFilter(request.GET, queryset).qs + if request.GET.get("sortby"): + holiday_filter = sortby(request, holiday_filter, "sortby") + page_number = request.GET.get("page") + page_obj = paginator_qry(holiday_filter[::-1], page_number) + data_dict = parse_qs(previous_data) + get_key_instances(Holidays, data_dict) return render( request, - "attendance/ip_restriction/restrict_update_form.html", + "holiday/holiday.html", + {"holidays": page_obj, "pd": previous_data, "filter_dict": data_dict}, + ) + + +@login_required +@hx_request_required +@permission_required("base.change_holiday") +def holiday_update(request, id): + """ + function used to update holiday. + + Parameters: + request (HttpRequest): The HTTP request object. + id : holiday id + + Returns: + GET : return holiday update form template + POST : return holiday view template + """ + query_string = request.GET.urlencode() + if query_string.startswith("pd="): + previous_data = unquote(query_string[len("pd=") :]) + else: + previous_data = unquote(query_string) + holiday = Holidays.objects.get(id=id) + form = HolidayForm(instance=holiday) + if request.method == "POST": + form = HolidayForm(request.POST, instance=holiday) + if form.is_valid(): + form.save() + messages.success(request, _("Holidays updated successfully..")) + return render( + request, + "holiday/holiday_update_form.html", + {"form": form, "id": id, "pd": previous_data}, + ) + + +@login_required +@hx_request_required +@permission_required("base.delete_holiday") +def holiday_delete(request, id): + """ + function used to delete holiday. + + Parameters: + request (HttpRequest): The HTTP request object. + id : holiday id + + Returns: + GET : return holiday view template + """ + query_string = request.GET.urlencode() + try: + Holidays.objects.get(id=id).delete() + messages.success(request, _("Holidays deleted successfully..")) + except Holidays.DoesNotExist: + messages.error(request, _("Holidays not found.")) + except ProtectedError: + messages.error(request, _("Related entries exists")) + if not Holidays.objects.filter(): + return HttpResponse("") + return redirect(f"/holiday-filter?{query_string}") + + +@require_http_methods(["POST"]) +@permission_required("base.delete_holiday") +def bulk_holiday_delete(request): + """ + This method is used to delete bulk of holidays + """ + ids = request.POST["ids"] + ids = json.loads(ids) + del_ids = [] + for holiday_id in ids: + try: + holiday = Holidays.objects.get(id=holiday_id) + holiday.delete() + del_ids.append(holiday_id) + except Exception as e: + messages.error(request, _("Holidays not found.")) + messages.success( + request, _("{} Holidays have been successfully deleted.".format(len(del_ids))) + ) + return JsonResponse({"message": "Success"}) + + +@login_required +def holiday_select(request): + page_number = request.GET.get("page") + + if page_number == "all": + employees = Holidays.objects.all() + + employee_ids = [str(emp.id) for emp in employees] + total_count = employees.count() + + context = {"employee_ids": employee_ids, "total_count": total_count} + + return JsonResponse(context, safe=False) + + +@login_required +def holiday_select_filter(request): + page_number = request.GET.get("page") + filtered = request.GET.get("filter") + filters = json.loads(filtered) if filtered else {} + + if page_number == "all": + employee_filter = HolidayFilter(filters, queryset=Holidays.objects.all()) + + # 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 +@hx_request_required +@permission_required("base.add_companyleave") +def company_leave_creation(request): + """ + function used to create company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return company leave creation form template + POST : return company leave view template + """ + form = CompanyLeaveForm() + if request.method == "POST": + form = CompanyLeaveForm(request.POST) + if form.is_valid(): + form.save() + messages.success(request, _("New company leave created successfully..")) + if CompanyLeaves.objects.filter().count() == 1: + return HttpResponse("") + return render( + request, "company_leave/company_leave_creation_form.html", {"form": form} + ) + + +@login_required +def company_leave_view(request): + """ + function used to view company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return company leave view template + """ + queryset = CompanyLeaves.objects.all() + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + page_obj = paginator_qry(queryset, page_number) + company_leave_filter = CompanyLeaveFilter() + return render( + request, + "company_leave/company_leave_view.html", + { + "company_leaves": page_obj, + "weeks": WEEKS, + "week_days": WEEK_DAYS, + "form": company_leave_filter.form, + "pd": previous_data, + }, + ) + + +@login_required +@hx_request_required +def company_leave_filter(request): + """ + function used to filter company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return company leave view template + """ + queryset = CompanyLeaves.objects.all() + previous_data = request.GET.urlencode() + page_number = request.GET.get("page") + company_leave_filter = CompanyLeaveFilter(request.GET, queryset).qs + page_obj = paginator_qry(company_leave_filter, page_number) + data_dict = parse_qs(previous_data) + get_key_instances(CompanyLeaves, data_dict) + + return render( + request, + "company_leave/company_leave.html", + { + "company_leaves": page_obj, + "weeks": WEEKS, + "week_days": WEEK_DAYS, + "pd": previous_data, + "filter_dict": data_dict, + }, + ) + + +@login_required +@hx_request_required +@permission_required("base.change_companyleave") +def company_leave_update(request, id): + """ + function used to update company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + id : company leave id + + Returns: + GET : return company leave update form template + POST : return company leave view template + """ + company_leave = CompanyLeaves.objects.get(id=id) + form = CompanyLeaveForm(instance=company_leave) + if request.method == "POST": + form = CompanyLeaveForm(request.POST, instance=company_leave) + if form.is_valid(): + form.save() + messages.success(request, _("Company leave updated successfully..")) + return render( + request, + "company_leave/company_leave_update_form.html", {"form": form, "id": id}, ) @login_required -def skills_view(request): +@hx_request_required +@permission_required("base.delete_companyleave") +def company_leave_delete(request, id): """ - This function is used to view skills page in settings + function used to create company leave. + + Parameters: + request (HttpRequest): The HTTP request object. + + Returns: + GET : return company leave creation form template + POST : return company leave view template """ - skills = Skill.objects.all() - return render(request, "settings/skills/skills_view.html", {"skills": skills}) + query_string = request.GET.urlencode() + try: + CompanyLeaves.objects.get(id=id).delete() + messages.success(request, _("Company leave deleted successfully..")) + except CompanyLeaves.DoesNotExist: + messages.error(request, _("Company leave not found.")) + except ProtectedError: + messages.error(request, _("Related entries exists")) + if not CompanyLeaves.objects.filter(): + return HttpResponse("") + return redirect(f"/company-leave-filter?{query_string}") + + +@login_required +@hx_request_required +def view_penalties(request): + """ + This method is used to filter or view the penalties + """ + records = PenaltyFilter(request.GET).qs + return render(request, "penalty/penalty_view.html", {"records": records}) diff --git a/employee/filters.py b/employee/filters.py index 38a4f9b32..ed51357b9 100644 --- a/employee/filters.py +++ b/employee/filters.py @@ -15,7 +15,7 @@ from django.contrib.auth.models import Group, Permission from django.utils.translation import gettext as _ from django_filters import CharFilter, DateFilter -from attendance.models import Attendance +# from attendance.models import Attendance from base.methods import reload_queryset from base.models import WorkType from employee.models import DisciplinaryAction, Employee, Policy @@ -171,11 +171,12 @@ class EmployeeFilter(FilterSet): today = datetime.datetime.now().date() yesterday = today - datetime.timedelta(days=1) - working_employees = Attendance.objects.filter( - attendance_date__gte=yesterday, - attendance_date__lte=today, - attendance_clock_out_date__isnull=True, - ).values_list("employee_id", flat=True) + # working_employees = Attendance.objects.filter( + # attendance_date__gte=yesterday, + # attendance_date__lte=today, + # attendance_clock_out_date__isnull=True, + # ).values_list("employee_id", flat=True) + working_employees = [] if value: queryset = queryset.filter(id__in=working_employees) else: @@ -236,41 +237,40 @@ class EmployeeFilter(FilterSet): def __init__(self, data=None, queryset=None, *, request=None, prefix=None): super().__init__(data=data, queryset=queryset, request=request, prefix=prefix) - if getattr(request, "exclude_filter_form", False) != True: - self.form.fields["is_active"].initial = True - self.form.fields["email"].widget.attrs["autocomplete"] = "email" - self.form.fields["phone"].widget.attrs["autocomplete"] = "phone" - self.form.fields["country"].widget.attrs["autocomplete"] = "country" - for field in self.form.fields.keys(): - self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}" - self.model_choice_filters = [ - filter - for filter in self.filters.values() - if isinstance(filter, django_filters.ModelMultipleChoiceFilter) + self.form.fields["is_active"].initial = True + self.form.fields["email"].widget.attrs["autocomplete"] = "email" + self.form.fields["phone"].widget.attrs["autocomplete"] = "phone" + self.form.fields["country"].widget.attrs["autocomplete"] = "country" + for field in self.form.fields.keys(): + self.form.fields[field].widget.attrs["id"] = f"{uuid.uuid4()}" + self.model_choice_filters = [ + filter + for filter in self.filters.values() + if isinstance(filter, django_filters.ModelMultipleChoiceFilter) + ] + for model_choice_filter in self.model_choice_filters: + queryset = ( + model_choice_filter.queryset.filter(is_active=True) + if model_choice_filter.queryset.model == Employee + else model_choice_filter.queryset + ) + choices = [ + ("not_set", _("Not Set")), ] - for model_choice_filter in self.model_choice_filters: - queryset = ( - model_choice_filter.queryset.filter(is_active=True) - if model_choice_filter.queryset.model == Employee - else model_choice_filter.queryset - ) - choices = [ - ("not_set", _("Not Set")), - ] - choices.extend([(obj.id, str(obj)) for obj in queryset]) + choices.extend([(obj.id, str(obj)) for obj in queryset]) - self.form.fields[model_choice_filter.field_name] = ( - forms.MultipleChoiceField( - choices=choices, - required=False, - widget=forms.SelectMultiple( - attrs={ - "class": "oh-select oh-select-2 select2-hidden-accessible", - "id": uuid.uuid4(), - } - ), - ) + self.form.fields[model_choice_filter.field_name] = ( + forms.MultipleChoiceField( + choices=choices, + required=False, + widget=forms.SelectMultiple( + attrs={ + "class": "oh-select oh-select-2 select2-hidden-accessible", + "id": uuid.uuid4(), + } + ), ) + ) class EmployeeReGroup: diff --git a/employee/forms.py b/employee/forms.py index f75d0acd3..cfcc7981a 100644 --- a/employee/forms.py +++ b/employee/forms.py @@ -42,6 +42,7 @@ from employee.models import ( Employee, EmployeeBankDetails, EmployeeNote, + EmployeeTag, EmployeeWorkInformation, NoteFiles, Policy, @@ -681,3 +682,19 @@ class ActiontypeForm(ModelForm): "onchange": "actionChange($(this))", } ) + + +class EmployeeTagForm(ModelForm): + """ + Employee Tags form + """ + + class Meta: + """ + Meta class for additional options + """ + + model = EmployeeTag + fields = "__all__" + exclude = ["is_active"] + widgets = {"color": TextInput(attrs={"type": "color", "style": "height:50px"})} diff --git a/employee/models.py b/employee/models.py index eac4f0889..9de587cfb 100644 --- a/employee/models.py +++ b/employee/models.py @@ -7,11 +7,13 @@ This module is used to register models for employee app from datetime import date, datetime, timedelta +from django.apps import apps from django.conf import settings from django.contrib.auth.models import Permission, User from django.core.exceptions import ValidationError from django.core.files.storage import default_storage from django.db import models +from django.db.models.query import QuerySet from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.translation import gettext as _ @@ -30,6 +32,7 @@ from base.models import ( ) from employee.methods.duration_methods import format_time, strtime_seconds from horilla import horilla_middlewares +from horilla.methods import get_horilla_model_class from horilla.models import HorillaModel from horilla_audit.methods import get_diff from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog @@ -200,42 +203,52 @@ class Employee(models.Model): This method is used to get the leave status of the employee """ today = date.today() - leaves_requests = self.leaverequest_set.filter( - start_date__lte=today, end_date__gte=today + leaves_requests = ( + self.leaverequest_set.filter(start_date__lte=today, end_date__gte=today) + if apps.is_installed("leave") + else QuerySet().none() ) - status = "Expected working" + status = _("Expected working") if leaves_requests.exists(): if leaves_requests.filter(status="approved").exists(): - status = "On Leave" + status = _("On Leave") elif leaves_requests.filter(status="requested"): - status = "Waiting Approval" + status = _("Waiting Approval") else: - status = "Canceled / Rejected" - elif self.employee_attendances.filter( - attendance_date=today, - ).exists(): - status = "On a break" + status = _("Canceled / Rejected") + elif ( + apps.is_installed("attendance") + and self.employee_attendances.filter( + attendance_date=today, + ).exists() + ): + status = _("On a break") return status def get_forecasted_at_work(self): """ This method is used to the employees current day shift status """ - today = datetime.today() - attendance = self.employee_attendances.filter(attendance_date=today).first() - minimum_hour_seconds = strtime_seconds(getattr(attendance, "minimum_hour", "0")) - at_work = 0 - forecasted_pending_hours = 0 - if attendance: - at_work = attendance.get_at_work_from_activities() - forecasted_pending_hours = max(0, (minimum_hour_seconds - at_work)) + if apps.is_installed("attendance"): + today = datetime.today() + attendance = self.employee_attendances.filter(attendance_date=today).first() + minimum_hour_seconds = strtime_seconds( + getattr(attendance, "minimum_hour", "0") + ) + at_work = 0 + forecasted_pending_hours = 0 + if attendance: + at_work = attendance.get_at_work_from_activities() + forecasted_pending_hours = max(0, (minimum_hour_seconds - at_work)) - return { - "forecasted_at_work": format_time(at_work), - "forecasted_pending_hours": format_time(forecasted_pending_hours), - "forecasted_at_work_seconds": at_work, - "forecasted_pending_hours_seconds": forecasted_pending_hours, - } + return { + "forecasted_at_work": format_time(at_work), + "forecasted_pending_hours": format_time(forecasted_pending_hours), + "forecasted_at_work_seconds": at_work, + "forecasted_pending_hours_seconds": forecasted_pending_hours, + } + else: + return {} def get_today_attendance(self): """ @@ -261,25 +274,33 @@ class Employee(models.Model): they are considered eligible for archiving. If they are associated, a dictionary is returned with a list of related models of that employee. """ - from onboarding.models import OnboardingStage, OnboardingTask - from recruitment.models import Recruitment, Stage - + if apps.is_installed("onboarding"): + OnboardingStage = get_horilla_model_class("onboarding", "onboardingstage") + OnboardingTask = get_horilla_model_class("onboarding", "onboardingtask") + onboarding_stage_query = OnboardingStage.objects.filter(employee_id=self.pk) + onboarding_task_query = OnboardingTask.objects.filter(employee_id=self.pk) + else: + onboarding_stage_query = None + onboarding_task_query = None + if apps.is_installed("recruitment"): + Recruitment = get_horilla_model_class("recruitment", "recruitment") + Stage = get_horilla_model_class("recruitment", "stage") + recruitment_stage_query = Stage.objects.filter(stage_managers=self.pk) + recruitment_manager_query = Recruitment.objects.filter( + recruitment_managers=self.pk + ) + else: + recruitment_stage_query = None + recruitment_manager_query = None reporting_manager_query = EmployeeWorkInformation.objects.filter( reporting_manager_id=self.pk ) - recruitment_stage_query = Stage.objects.filter(stage_managers=self.pk) - onboarding_stage_query = OnboardingStage.objects.filter(employee_id=self.pk) - onboarding_task_query = OnboardingTask.objects.filter(employee_id=self.pk) - recruitment_manager_query = Recruitment.objects.filter( - recruitment_managers=self.pk - ) - if not ( reporting_manager_query.exists() - or recruitment_stage_query.exists() - or onboarding_stage_query.exists() - or onboarding_task_query.exists() - or recruitment_manager_query.exists() + or (recruitment_stage_query and recruitment_stage_query.exists()) + or (onboarding_stage_query and onboarding_stage_query.exists()) + or (onboarding_task_query and onboarding_task_query.exists()) + or (recruitment_manager_query and recruitment_manager_query.exists()) ): return False else: @@ -347,22 +368,28 @@ class Employee(models.Model): def check_online(self): """ - This method is used to check the user in online users or not + This method is used to check if the user is in the list of online users. """ - from attendance.models import Attendance + if apps.is_installed("attendance"): + Attendance = get_horilla_model_class("attendance", "attendance") + request = getattr(horilla_middlewares._thread_locals, "request", None) - request = getattr(horilla_middlewares._thread_locals, "request", None) - if not getattr(request, "working_employees", None): - today = datetime.now().date() - yesterday = today - timedelta(days=1) - working_employees = Attendance.objects.filter( - attendance_date__gte=yesterday, - attendance_date__lte=today, - attendance_clock_out_date__isnull=True, - ).values_list("employee_id", flat=True) - setattr(request, "working_employees", working_employees) - working_employees = request.working_employees - return self.pk in working_employees + if request is not None: + if ( + not hasattr(request, "working_employees") + or request.working_employees is None + ): + today = datetime.now().date() + yesterday = today - timedelta(days=1) + working_employees = Attendance.objects.filter( + attendance_date__gte=yesterday, + attendance_date__lte=today, + attendance_clock_out_date__isnull=True, + ).values_list("employee_id", flat=True) + setattr(request, "working_employees", working_employees) + working_employees = request.working_employees + return self.pk in working_employees + return False class Meta: """ diff --git a/employee/not_in_out_dashboard.py b/employee/not_in_out_dashboard.py index c5882d82c..badb32de1 100644 --- a/employee/not_in_out_dashboard.py +++ b/employee/not_in_out_dashboard.py @@ -16,11 +16,11 @@ from django.shortcuts import render from base.backends import ConfiguredEmailBackend from base.methods import generate_pdf +from base.models import HorillaMailTemplate from employee.filters import EmployeeFilter from employee.models import Employee from horilla import settings from horilla.decorators import login_required, manager_can_enter -from recruitment.models import RecruitmentMailTemplate def paginator_qry(qryset, page_number): @@ -83,7 +83,7 @@ def send_mail(request, emp_id=None): employee = Employee.objects.get(id=emp_id) employees = Employee.objects.all() - templates = RecruitmentMailTemplate.objects.all() + templates = HorillaMailTemplate.objects.all() return render( request, "employee/send_mail.html", @@ -96,7 +96,7 @@ def get_template(request, emp_id): """ This method is used to return the mail template """ - body = RecruitmentMailTemplate.objects.get(id=emp_id).body + body = HorillaMailTemplate.objects.get(id=emp_id).body instance_id = request.GET.get("instance_id") if instance_id: instance = Employee.objects.get(id=instance_id) @@ -138,7 +138,7 @@ def send_mail_to_employee(request): template_attachment_ids = request.POST.getlist("template_attachments") for employee in employees: bodys = list( - RecruitmentMailTemplate.objects.filter( + HorillaMailTemplate.objects.filter( id__in=template_attachment_ids ).values_list("body", flat=True) ) diff --git a/employee/templates/dashboard/not_in_yet.html b/employee/templates/dashboard/not_in_yet.html index ba9f19717..3fd6c41a9 100644 --- a/employee/templates/dashboard/not_in_yet.html +++ b/employee/templates/dashboard/not_in_yet.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n static %}
diff --git a/employee/templates/dashboard/not_out_yet.html b/employee/templates/dashboard/not_out_yet.html index 5d3e25015..0a9177f8b 100644 --- a/employee/templates/dashboard/not_out_yet.html +++ b/employee/templates/dashboard/not_out_yet.html @@ -1,4 +1,5 @@ {% load i18n %} +{% load static %}
{% trans 'Online Employees' %} @@ -41,7 +42,7 @@ {% else %}
- +

{% trans "No data Found..." %}

diff --git a/employee/templates/dashboard/upcoming_birthdays.html b/employee/templates/dashboard/upcoming_birthdays.html index c8cb6f55c..5e4bd2afc 100644 --- a/employee/templates/dashboard/upcoming_birthdays.html +++ b/employee/templates/dashboard/upcoming_birthdays.html @@ -42,4 +42,4 @@ -{% endblock %} +{% endblock %} diff --git a/employee/templates/disciplinary_actions/disciplinary_nav.html b/employee/templates/disciplinary_actions/disciplinary_nav.html index 4487a7427..b0e1a1acc 100644 --- a/employee/templates/disciplinary_actions/disciplinary_nav.html +++ b/employee/templates/disciplinary_actions/disciplinary_nav.html @@ -182,16 +182,6 @@ {% endif %} - - + - - -
@@ -116,141 +131,127 @@
+
- Username + $('#enlargeImageContainer').addClass('enlarge-image-container');" onmouseout="hideEnlargeImage() + $('#enlargeImageContainer').removeClass('enlarge-image-container');" {% endif %} />
- {% if employee.check_online %} - - - {% else %} - - + {% if "attendance"|app_installed %} + {% if employee.check_online %} + + + {% else %} + + + {% endif %} {% endif %}
+

{{employee}}

- {{employee.job_position_id}} + {{employee.job_position_id}}

-
+ +
  • @@ -259,6 +260,7 @@ {{employee.employee_work_info.email}}
  • +
  • @@ -272,6 +274,7 @@ {% endif %}
  • +
  • @@ -279,6 +282,7 @@ {{employee.employee_work_info.mobile}}
  • +
  • @@ -288,7 +292,8 @@ {{employee.phone}} {% else %} ********** - {% endif %} + {% endif %} +
@@ -296,334 +301,227 @@
{% if request.user.is_superuser or request.user|check_manager:employee or request.user == employee.employee_user_id %} -
-
- -
-
    -
+
+
+ +
+
    +
+
+
-
-
{% endif %}
-
    -
  • - {% trans "About" %} -
  • - {% if request.user == employee.employee_user_id or request.user|check_manager:employee or perms.attendance.view_worktyperequest or perms.attendance.view_shiftrequest %} -
  • - {% trans "Work Type & Shift" %} -
  • - {% endif %} - {% if perms.attendance.view_attendance or request.user|check_manager:employee %} -
  • - {% trans "Attendance" %} -
  • - {% endif %} - {% if perms.leave.view_leaverequest or request.user|check_manager:employee or request.user == employee.employee_user_id %} -
  • - {% trans "Leave" %} -
  • - {% endif %} - {% if perms.payroll.view_payslip or request.user == employee.employee_user_id %} -
  • - {% trans "Payroll" %} -
  • - {% endif %} - {% if perms.payroll.view_payslip or request.user == employee.employee_user_id %} -
  • - {% trans "Allowance & Deduction" %} -
  • - {% endif %} - {% if perms.attendance.view_penaltyaccount or request.user == employee.employee_user_id %} -
  • - {% trans "Penalty Account" %} -
  • - {% endif %} - {% comment %}
  • - {% trans "Team" %} -
  • {% endcomment %} - {% if perms.employee.view_historicalemployeeworkinformation or request.user|check_manager:employee %} -
  • - {% trans "History" %} -
  • - {% endif %} - {% if perms.asset.view_asset or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
      -
    • - {% trans "Assets" %} +
    • + {% trans "About" %}
    • + + {% if request.user == employee.employee_user_id or request.user|check_manager:employee or perms.attendance.view_worktyperequest or perms.attendance.view_shiftrequest %} +
    • + + {% trans "Work Type & Shift"%} + +
    • {% endif %} - {% if perms.pms.view_feedback or request.user|check_manager:employee or request.user == employee.employee_user_id %} -
    • - {% trans "Performance" %} -
    • + + {% if "attendance"|app_installed %} + {% if perms.attendance.view_attendance or request.user|check_manager:employee %} +
    • + {% trans "Attendance" %} +
    • + {% endif %} {% endif %} + + {% if "leave"|app_installed %} + {% if perms.leave.view_leaverequest or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
    • + {% trans "Leave" %} +
    • + {% endif %} + {% endif %} + + {% if "payroll"|app_installed %} + {% if perms.payroll.view_payslip or request.user == employee.employee_user_id %} +
    • + {% trans "Payroll" %} +
    • + {% endif %} + + {% if perms.payroll.view_payslip or request.user == employee.employee_user_id %} +
    • + + {% trans "Allowance & Deduction" %} + +
    • + {% endif %} + {% endif %} + + {% if "attendance"|app_installed or "leave"|app_installed %} + {% if perms.attendance.view_penaltyaccount or request.user == employee.employee_user_id %} +
    • + {% trans "Penalty Account" %} +
    • + {% endif %} + {% endif %} + + {% if perms.employee.view_historicalemployeeworkinformation or request.user|check_manager:employee %} +
    • + {% trans "History" %} +
    • + {% endif %} + + {% if "asset"|app_installed %} + {% if perms.asset.view_asset or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
    • + {% trans "Assets" %} + +
    • + {% endif %} + {% endif %} + + {% if "pms"|app_installed %} + {% if perms.pms.view_feedback or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
    • + {% trans "Performance" %} + +
    • + {% endif %} + {% endif %} + {% if perms.auth.view_permission or perms.auth.view_group %} -
    • - {% trans "Groups & Permissions" %} -
    • +
    • + + {% trans "Groups & Permissions" %} + +
    • {% endif %} + {% if perms.employee.view_employeenote or request.user|check_manager:employee %} -
    • - {% trans "Note" %} -
    • +
    • + {% trans "Note" %} + +
    • {% endif %} + {% if perms.horilla_documents.view_document or request.user == employee.employee_user_id %} -
    • - {% trans "Documents" %} -
    • +
    • + + {% trans "Documents" %} + +
    • {% endif %} + {% if perms.employee.view_employee %}
    • - {% trans "Mail Log" %} + {% trans "Mail Log" %}
    • {% endif %} - {% if perms.employee.view_employeenote or request.user|check_manager:employee or request.user == employee.employee_user_id %} -
    • - {% trans "Bonus Points" %} -
    • + + {% if "payroll"|app_installed %} + {% if perms.employee.view_bonuspoint or request.user|check_manager:employee or request.user == employee.employee_user_id %} +
    • + {% trans "Bonus Points" %} +
    • + {% endif %} {% endif %}
    -
    +
    + +
    + +
    + {% include "tabs/bonus_points.html" %}
    -
    +
    + {% include "tabs/note_tab.html" %}
    -
    - {% include "tabs/bonus_points.html" %} -
    -
    - {% include "tabs/note_tab.html" %} -
    +
    -
    -
    + {% if "payroll"|app_installed %} +
    + {% endif %} -
    -
    -
    -
    + {% if "attendance"|app_installed %} +
    + {% endif %} -
    -
    + {% if "attendance"|app_installed or "leave"|app_installed %} +
    + {% include 'tabs/penalty_account.html' %} +
    + {% endif %} -
    - {% include 'tabs/penalty_account.html' %} -
    + {% if "pms"|app_installed %} +
    + {% endif %} -
    -
    + {% if "asset"|app_installed %} +
    + {% endif %} -
    -
    +
    -
    -
    - -
    -
    -
    +
    {% if perms.auth.view_permission or perms.auth.view_group or request.user|is_reportingmanager or request.user == employee.employee_user_id %} {% include "tabs/group_permissions.html" %} {% endif %}
    -
    - {% if request.user|check_manager:employee or request.user == employee.employee_user_id or perms.employee.view_historicalemployeeworkinformation %} + {% if request.user|check_manager:employee or request.user == employee.employee_user_id or perms.employee.view_historicalemployeeworkinformation %} +
    {% include "tabs/history.html" %} - {% endif %} +
    + {% endif %} -
    - -
    + {% if "payroll"|app_installed %} {% if perms.view_payslip or request.user == employee.employee_user_id %} - {% include "tabs/payroll-tab.html" %} +
    + {% include "tabs/payroll-tab.html" %} +
    {% endif %} -
    + {% endif %} -
    - {% if perms.leave.view_leaverequest or perms.leave.view_leavetype or request.user|check_manager:employee or request.user == employee.employee_user_id %} - {% include "tabs/leave-tab.html" %} - {% endif %} -
    + {% if "leave"|app_installed %} +
    + {% endif %}
    @@ -641,16 +539,16 @@ } }); - toggleColumns("employee-tab","fieldContainerTable") + toggleColumns("employee-tab", "fieldContainerTable") if (!localStorage.getItem("employee_tab")) { - $("#fieldContainerTable").find("[type=checkbox]").prop("checked",true).change() - } + $("#fieldContainerTable").find("[type=checkbox]").prop("checked", true).change() + } function handleFormSubmit() { $('#successMessage').show(); - setTimeout(function() { + setTimeout(function () { $('#successMessage').hide(); }, 3000); @@ -660,36 +558,36 @@ + $(document).on('click', function (event) { + if (!$(event.target).closest('#enlargeDocContainer').length) { + hideEnlargeDoc() + } + }) + {% endblock content %} diff --git a/employee/templates/employee_export_filter.html b/employee/templates/employee_export_filter.html index ab48537e1..c9fa558f8 100644 --- a/employee/templates/employee_export_filter.html +++ b/employee/templates/employee_export_filter.html @@ -1,114 +1,107 @@ {% load i18n %} {% load static %} -
    -
    -
    {% trans "Excel columns" %}
    -
    -
    -
    -
    - -
    -
    -
    -
    - {% for field in export_form.selected_fields %} -
    -
    - -
    -
    - {% endfor %} -
    +
    +

    + {% trans "Export Employees" %} +

    + +
    +
    + {% csrf_token %} +
    +
    +
    {% trans "Excel columns" %}
    +
    +
    +
    +
    + +
    +
    +
    +
    + {% for field in export_form.selected_fields %} +
    +
    + +
    +
    + {% endfor %} +
    +
    +
    +
    +
    {% trans "Employee" %}
    +
    +
    +
    +
    + + {{export_filter.form.country}} +
    +
    +
    +
    + + {{export_filter.form.gender}} +
    +
    +
    +
    +
    +
    +
    {% trans "Work Info" %}
    +
    +
    +
    +
    + + {{export_filter.form.employee_work_info__company_id}} +
    +
    + + {{export_filter.form.employee_work_info__department_id}} +
    +
    + + {{export_filter.form.employee_work_info__shift_id}} +
    +
    +
    +
    + + {{export_filter.form.employee_work_info__reporting_manager_id}} +
    +
    + + {{export_filter.form.employee_work_info__job_position_id}} +
    +
    + + {{export_filter.form.employee_work_info__work_type_id}} +
    +
    +
    +
    +
    +
    + +
    -
    -
    -
    {% trans "Employee" %}
    -
    -
    -
    -
    - - {{export_filter.form.country}} -
    -
    -
    -
    - - {{export_filter.form.gender}} -
    -
    -
    -
    -
    -
    -
    {% trans "Work Info" %}
    -
    -
    -
    -
    - - {{export_filter.form.employee_work_info__company_id}} -
    -
    - - {{export_filter.form.employee_work_info__department_id}} -
    -
    - - {{export_filter.form.employee_work_info__shift_id}} -
    -
    -
    -
    - - {{export_filter.form.employee_work_info__reporting_manager_id}} -
    -
    - - {{export_filter.form.employee_work_info__job_position_id}} -
    -
    - - {{export_filter.form.employee_work_info__work_type_id}} -
    -
    -
    -
    -
    - diff --git a/employee/templates/employee_nav.html b/employee/templates/employee_nav.html index 0ead74620..c077d6d15 100644 --- a/employee/templates/employee_nav.html +++ b/employee/templates/employee_nav.html @@ -100,39 +100,11 @@
- diff --git a/employee/templates/policies/form.html b/employee/templates/policies/form.html index ffc0804de..ceb6296f6 100644 --- a/employee/templates/policies/form.html +++ b/employee/templates/policies/form.html @@ -1,7 +1,22 @@ {% load i18n %} -
- {{ form.as_p }} -
- -
-
+ +
+

+ {% trans "Policy" %} +

+ +
+ +
+
+ {{ form.as_p }} + +
+
diff --git a/employee/templates/policies/nav.html b/employee/templates/policies/nav.html index 55fe3d86a..a2be9f987 100644 --- a/employee/templates/policies/nav.html +++ b/employee/templates/policies/nav.html @@ -13,7 +13,7 @@ {% if perms.payroll.add_policyaccount %}
- + {% trans 'Create' %} @@ -22,13 +22,3 @@ {% endif %}
- - diff --git a/employee/templates/policies/records.html b/employee/templates/policies/records.html index fced99592..be614598a 100644 --- a/employee/templates/policies/records.html +++ b/employee/templates/policies/records.html @@ -14,7 +14,7 @@ {{ policy.title }}
{% if perms.employee.change_policiy %} - + {% endif %} {% if perms.employee.delete_policy %} @@ -24,7 +24,7 @@
{{ policy.body|safe }}
- {% trans 'View policy' %} + {% trans 'View policy' %}
{% endfor %} diff --git a/employee/templates/policies/view_policy.html b/employee/templates/policies/view_policy.html index 5defb980d..d4d084626 100644 --- a/employee/templates/policies/view_policy.html +++ b/employee/templates/policies/view_policy.html @@ -7,9 +7,20 @@ width: 200px; } -

{{ policy.title }}

- -{{ policy.body|safe }} -
+
+

+ {{ policy.title }} +

+ +
-
+
+ + {{ policy.body|safe }} +
+ +
+
diff --git a/employee/templates/tabs/allowance_deduction-tab.html b/employee/templates/tabs/allowance_deduction-tab.html index e534100e3..91b45321f 100644 --- a/employee/templates/tabs/allowance_deduction-tab.html +++ b/employee/templates/tabs/allowance_deduction-tab.html @@ -1,7 +1,7 @@ {% load i18n %} {% load static %} {% load basefilters %} -{% load attendancefilters %} +{% load horillafilters %} {% if messages %}
{% for message in messages %} diff --git a/employee/templates/tabs/attendance-tab.html b/employee/templates/tabs/attendance-tab.html index 607f08939..3f0cc5da9 100644 --- a/employee/templates/tabs/attendance-tab.html +++ b/employee/templates/tabs/attendance-tab.html @@ -1,7 +1,7 @@ {% load i18n %} {% load static %} {% load basefilters %} -{% load attendancefilters %} +{% load horillafilters %}
    @@ -170,7 +170,7 @@
    {% trans "Year" %}
    {% trans "Hour Account" %}
    {% trans "Overtime" %}
    - {% if perms.recruitment.change_attendanceovertime or perms.recruitment.delete_attendanceovertime %} + {% if perms.attendance.change_attendanceovertime or perms.attendance.delete_attendanceovertime %}
    {% trans "Actions" %}
    {% endif %}
@@ -196,7 +196,7 @@
{{ot.year}}
{{ot.worked_hours}}
{{ot.overtime}}
- {% if perms.recruitment.change_attendanceovertime or perms.recruitment.delete_attendanceovertime %} + {% if perms.attendance.change_attendanceovertime or perms.attendance.delete_attendanceovertime %}
{% if perms.attendance.change_attendanceovertime %} diff --git a/employee/templates/tabs/contract-tab.html b/employee/templates/tabs/contract-tab.html index a87b2dbb2..7b7c588e3 100644 --- a/employee/templates/tabs/contract-tab.html +++ b/employee/templates/tabs/contract-tab.html @@ -1,4 +1,4 @@ -{% load onboardingfilters %} {% load i18n %} +{% load i18n %}
-
- - - -
-
-
- {% if perms.employee.view_employee %} - - - - {% endif %} -
- {% if not 'offline_employees' in charts %} - {% if perms.employee.view_employee or request.user|is_reportingmanager %} -
- {% include "dashboard/not_in_yet.html" %} -
- {% endif %} - {% endif %} - {% if not 'online_employees' in charts %} - {% if perms.employee.view_employee or request.user|is_reportingmanager %} -
- {% include "dashboard/not_out_yet.html" %} -
- {% endif %} - {% endif %} - {% if not 'overall_leave_chart' in charts %} - {% if perms.leave.view_leaverequest %} -
-
-
- {% trans "Overall Leave" %} - - close - - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if perms.recruitment.view_candidate or request.user|is_stagemanager %} - {% if not 'hired_candidates' in charts %} -
-
-
- {% trans "Hired Candidates" %} - - close - -
-
- -
-
-
- {% endif %} - {% if not 'onboarding_candidates' in charts %} -
-
-
- {% trans "Candidates Started Onboarding" %} - - close - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'recruitment_analytics' in charts %} - {% if request.user|is_stagemanager or perms.recruitment.view_recruitment %} -
-
-
- {% trans "Recruitment Analytics" %} - - close - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'attendance_analytic' in charts %} - {% if request.user|is_reportingmanager or perms.attendance.view_attendance %} -
-
-
-
- {% trans "Attendance Analytics" %} - - close - -
-
- - - - -
-
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'hours_chart' in charts %} - {% if request.user|is_reportingmanager or perms.attendance.view_attendance %} -
-
-
-
- {% trans "Hours Chart" %} - - close - -
-
- -
-
- -
-
-
-
- {% endif %} - {% endif %} - {% if not 'employees_chart' in charts %} -
-
-
- {% trans "Employees Chart" %} - - close - -
-
- -
-
-
- {% endif %} - {% if not 'department_chart' in charts %} -
-
-
- {% trans "Department Chart" %} - - close - -
-
- -
-
-
- {% endif %} - {% if not 'gender_chart' in charts %} -
-
-
- {% trans "Gender Chart" %} - - close - -
-
- -
-
-
- {% endif %} - {% if not 'objective_status' in charts %} - {% if perms.pms.view_employeeobjective or request.user|is_reportingmanager %} -
-
-
- {% trans "Objective Status" %} - - close - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'key_result_status' in charts %} - {% if perms.pms.view_employeekeyresult or request.user|is_reportingmanager %} -
-
-
- {% trans "Key Result Status" %} - - close - -
-
- -
-
-
- {% endif %} - {% endif %} - {% if not 'feedback_status' in charts %} - {% if perms.pms.view_feedback or request.user|is_reportingmanager %} -
-
-
- {% trans "Feedback Status" %} - - close - -
-
-
- -
-
-
-
- {% endif %} - {% endif %} - {% if not 'shift_request_approve' in charts %} - {% if perms.base.change_shiftrequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Shift Requests To Approve" %} - - close - -
-
- {% include "request_and_approve/shift_request.html" %} -
-
-
- {% endif %} - {% endif %} - {% if not 'work_type_request_approve' in charts %} - {% if perms.base.change_worktyperequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Work Type Requests To Approve" %} - - close - -
-
- {% include "request_and_approve/work_type_request.html" %} -
-
-
- {% endif %} - {% endif %} - {% if not 'overtime_approve' in charts %} - {% if perms.attendance.change_attendance or request.user|is_reportingmanager %} -
-
- {% include "request_and_approve/overtime_approve.html" %} -
-
- {% endif %} - {% endif %} - {% if not 'attendance_validate' in charts %} - {% if perms.attendance.change_attendance or request.user|is_reportingmanager %} -
-
- {% include "request_and_approve/attendance_validate.html" %} -
-
- {% endif %} - {% endif %} - {% if not 'leave_request_approve' in charts %} - {% if perms.leave.change_leaverequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Leave Requests To Approve" %} - - close - -
-
-
-
-
- {% endif %} - {% endif %} - {% if not 'leave_allocation_approve' in charts %} - {% if perms.leave.change_leaveallocationrequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Leave Allocation Request To Approve" %} - - close - -
-
- {% include "request_and_approve/leave_request_approve.html" %} -
-
-
- {% endif %} - {% endif %} - {% if not 'feedback_answer' in charts %} -
-
-
- {% trans "Feedback To Answers" %} - - close - -
-
- {% include "request_and_approve/feedback_answer.html" %} -
-
-
- {% endif %} - {% if not 'asset_request_approve' in charts %} - {% if perms.asset.change_assetrequest or request.user|is_reportingmanager %} -
-
-
- {% trans "Asset Requests To Approve" %} - - close - -
-
- {% include "request_and_approve/asset_requests_approve.html" %} -
-
-
- {% endif %} - {% endif %} -
-
-
-
-
-
-
    -
+
+ + + +
+
+
+ {% if perms.employee.view_employee %} + {% if "recruitment"|app_installed %} + + + {% endif %} + + {% endif %} +
+ {% if "attendance"|app_installed %} + {% if not 'offline_employees' in charts %} + {% if perms.employee.view_employee or request.user|is_reportingmanager %} +
+ {% include "dashboard/not_in_yet.html" %} +
+ {% endif %} + {% endif %} + {% if not 'online_employees' in charts %} + {% if perms.employee.view_employee or request.user|is_reportingmanager %} +
+ {% include "dashboard/not_out_yet.html" %} +
+ {% endif %} + {% endif %} + {% endif %} + {% if "leave"|app_installed and not 'overall_leave_chart' in charts %} + {% if perms.leave.view_leaverequest %} +
+
+
+ {% trans "Overall Leave" %} + + close + + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "recruitment"|app_installed and perms.recruitment.view_candidate or request.user|is_stagemanager %} + {% if not 'hired_candidates' in charts %} +
+
+
+ {% trans "Hired Candidates" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% if "onboarding"|app_installed and not 'onboarding_candidates' in charts %} +
+
+
+ {% trans "Candidates Started Onboarding" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "recruitment"|app_installed and not 'recruitment_analytics' in charts %} + {% if request.user|is_stagemanager or perms.recruitment.view_recruitment %} +
+
+
+ {% trans "Recruitment Analytics" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "attendance"|app_installed and not 'attendance_analytic' in charts %} + {% if request.user|is_reportingmanager or perms.attendance.view_attendance %} +
+
+
+
+ {% trans "Attendance Analytics" %} + + close + +
+
+ + + + +
+
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "attendance"|app_installed and not 'hours_chart' in charts %} + {% if request.user|is_reportingmanager or perms.attendance.view_attendance %} +
+
+
+
+ {% trans "Hours Chart" %} + + close + +
+
+ +
+
+ +
+
+
+
+ {% endif %} + {% endif %} + {% if not 'employees_chart' in charts %} +
+
+
+ {% trans "Employees Chart" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% if not 'department_chart' in charts %} +
+
+
+ {% trans "Department Chart" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% if not 'gender_chart' in charts %} +
+
+
+ {% trans "Gender Chart" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% if "pms"|app_installed and not 'objective_status' in charts %} + {% if perms.pms.view_employeeobjective or request.user|is_reportingmanager %} +
+
+
+ {% trans "Objective Status" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "pms"|app_installed and not 'key_result_status' in charts %} + {% if perms.pms.view_employeekeyresult or request.user|is_reportingmanager %} +
+
+
+ {% trans "Key Result Status" %} + + close + +
+
+ +
+
+
+ {% endif %} + {% endif %} + {% if "pms"|app_installed and not 'feedback_status' in charts %} + {% if perms.pms.view_feedback or request.user|is_reportingmanager %} +
+
+
+ {% trans "Feedback Status" %} + + close + +
+
+
+ +
+
+
+
+ {% endif %} + {% endif %} + {% if not 'shift_request_approve' in charts %} + {% if perms.base.change_shiftrequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Shift Requests To Approve" %} + + close + +
+
+ {% include "request_and_approve/shift_request.html" %} +
+
+
+ {% endif %} + {% endif %} + {% if not 'work_type_request_approve' in charts %} + {% if perms.base.change_worktyperequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Work Type Requests To Approve" %} + + close + +
+
+ {% include "request_and_approve/work_type_request.html" %} +
+
+
+ {% endif %} + {% endif %} + {% if "attendance"|app_installed and not 'overtime_approve' in charts %} + {% if perms.attendance.change_attendance or request.user|is_reportingmanager %} +
+
+ {% include "request_and_approve/overtime_approve.html" %} +
+
+ {% endif %} + {% endif %} + {% if "attendance"|app_installed %} + {% if not 'attendance_validate' in charts %} + {% if perms.attendance.change_attendance or request.user|is_reportingmanager %} +
+
+ {% include "request_and_approve/attendance_validate.html" %} +
+
+ {% endif %} + {% endif %} + {% endif %} + {% if "leave"|app_installed and not 'leave_request_approve' in charts %} + {% if perms.leave.change_leaverequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Leave Requests To Approve" %} + + close + +
+
+
+
+
+ {% endif %} + {% endif %} + {% if "leave"|app_installed and not 'leave_allocation_approve' in charts %} + {% if perms.leave.change_leaveallocationrequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Leave Allocation Request To Approve" %} + + close + +
+
+ {% include "request_and_approve/leave_request_approve.html" %} +
+
+
+ {% endif %} + {% endif %} + {% if "pms"|app_installed and not 'feedback_answer' in charts %} +
+
+
+ {% trans "Feedback To Answers" %} + + close + +
+
+ {% include "request_and_approve/feedback_answer.html" %} +
+
+
+ {% endif %} + {% if "asset"|app_installed and not 'asset_request_approve' in charts %} + {% if perms.asset.change_assetrequest or request.user|is_reportingmanager %} +
+
+
+ {% trans "Asset Requests To Approve" %} + + close + +
+
+ {% include "request_and_approve/asset_requests_approve.html" %} +
+
+
+ {% endif %} + {% endif %} +
+
+
+
+
+
+
    +
+
-
-
- {% trans "Announcements" %} - {% if perms.base.add_announcement %} - - - - {% endif %} -
- -
- -
-
-
-
- - {% if not announcement %} -
-
- Page not found. 404. -
{% trans "No Announcements to show." %}
-
+ height: 28px;" class="oh-btn oh-btn--secondary-outline float-end ms-3" + hx-get='{% url "create-announcement" %}' hx-target="#objectCreateModalTarget" + hx-swap="innerHTML" data-toggle="oh-modal-toggle" data-target="#objectCreateModal" + title='{% trans "Create Announcement." %}'> + + + + {% endif %}
- {% else %} - {% for i in announcement %} -
- + +
+ +
+
+
+
+ + {% if not announcement %} +
+
+ Page not found. 404. +
{% trans "No Announcements to show." %}
+
+
+ {% else %} + {% for i in announcement %} + + {% endfor %} + {% endif %} +
+
- {% endfor %} - {% endif %} +
+
+ + {% if "leave"|app_installed %} +
+
+ {% trans "On Leave" %} +
+
+
+ {% endif %} +
+ +
+ {% trans "Employee Work Information" %} +
+ + + + + {% if request.user.employee_get.employee_user_id.is_superuser or request.user|is_reportingmanager %} +
+
+ {% endif %} +
-
-
- -
-
- {% trans "On Leave" %} -
-
-
-
-
- -
- {% trans "Employee Work Information" %} -
- - - - - {% if request.user.employee_get.employee_user_id.is_superuser or request.user|is_reportingmanager %} -
-
- {% endif %} - -
-
-
- - - - - -
@@ -936,110 +747,127 @@ - -{% if perms.recruitment.view_recruitment or request.user|is_stagemanager %} - - -{% endif %} {% if perms.employee.view_employee or request.user|is_reportingmanager %} -{% endif %} {% if perms.employee.view_attendance or request.user|is_reportingmanager %} - - - - - +{% if "leave"|app_installed %} + +{% endif %} + +{% if "leave"|app_installed %} + +{% endif %} + +{% if "recruitment"|app_installed %} + {% if perms.recruitment.view_recruitment or request.user|is_stagemanager %} + + {% endif %} +{% endif %} + +{% if "attendance"|app_installed %} + {% if perms.attendance.view_attendance or request.user|is_reportingmanager %} + + {% endif %} +{% endif %} + +{% if "onboarding"|app_installed %} + + +{% endif %} + +{% if "pms"|app_installed %} + + {% endif %} {% if not request.user.driverviewed_set.first or "dashboard" not in request.user.driverviewed_set.first.user_viewed %} -{% endif %} +{% endif %} diff --git a/templates/floating_button.html b/templates/floating_button.html index 5ae47b9ac..bd753404b 100644 --- a/templates/floating_button.html +++ b/templates/floating_button.html @@ -1,4 +1,5 @@ {% load static %} +{% load horillafilters %}