diff --git a/employee/admin.py b/employee/admin.py index 25b5d620a..832c96470 100644 --- a/employee/admin.py +++ b/employee/admin.py @@ -13,6 +13,8 @@ from employee.models import ( EmployeeTag, PolicyMultipleFile, Policy, + DisciplinaryAction, + Actiontype ) from simple_history.admin import SimpleHistoryAdmin @@ -23,3 +25,5 @@ admin.site.register(Employee) admin.site.register(EmployeeBankDetails) admin.site.register(EmployeeWorkInformation, SimpleHistoryAdmin) admin.site.register([EmployeeNote, EmployeeTag, PolicyMultipleFile, Policy, BonusPoint]) +admin.site.register([DisciplinaryAction, Actiontype]) + diff --git a/employee/filters.py b/employee/filters.py index bd10c9c73..a242e99ed 100644 --- a/employee/filters.py +++ b/employee/filters.py @@ -4,20 +4,19 @@ filters.py This page is used to register filter for employee models """ -from employee.models import Policy +from employee.models import DisciplinaryAction, Policy import uuid from django import forms import django_filters from django.contrib.auth.models import Permission, Group from django import forms -from django_filters.filters import ModelChoiceFilter from django.utils.translation import gettext as _ from base.methods import reload_queryset from base.models import WorkType -from horilla.filters import FilterSet +from horilla.filters import FilterSet, filter_by_name from employee.models import Employee from horilla_documents.models import Document -from django_filters import CharFilter +from django_filters import CharFilter, DateFilter class EmployeeFilter(FilterSet): @@ -292,3 +291,32 @@ class DocumentRequestFilter(FilterSet): "employee_id__employee_work_info__company_id", "employee_id__employee_work_info__shift_id", ] + + +class DisciplinaryActionFilter(FilterSet): + + """ + Custom filter for Disciplinary Action. + + """ + + search = CharFilter(method=filter_by_name) + + start_date = django_filters.DateFilter( + widget=forms.DateInput(attrs={"type": "date"}), + ) + + class Meta: + + model = DisciplinaryAction + fields = [ + "employee_id", + "action", + "employee_id__employee_work_info__job_position_id", + "employee_id__employee_work_info__department_id", + "employee_id__employee_work_info__work_type_id", + "employee_id__employee_work_info__job_role_id", + "employee_id__employee_work_info__reporting_manager_id", + "employee_id__employee_work_info__company_id", + "employee_id__employee_work_info__shift_id", + ] diff --git a/employee/forms.py b/employee/forms.py index 8623159c4..764b5eada 100644 --- a/employee/forms.py +++ b/employee/forms.py @@ -28,7 +28,9 @@ from django.forms import DateInput, ModelChoiceField, TextInput from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy as trans from employee.models import ( + Actiontype, BonusPoint, + DisciplinaryAction, Employee, EmployeeWorkInformation, EmployeeBankDetails, @@ -536,3 +538,31 @@ class BonusPointRedeemForm(ModelForm): class Meta: model = BonusPoint fields = ["points"] + + + +class DisciplinaryActionForm(ModelForm): + class Meta: + model = DisciplinaryAction + fields = "__all__" + exclude = ["company_id","objects"] + + widgets = { + "start_date": DateInput(attrs={"type": "date"}), + } + + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["action"].widget.attrs.update( + { + "onchange": "actiontypechange($(this))", + } + ) + + +class ActiontypeForm(ModelForm): + + class Meta: + model = Actiontype + fields = "__all__" diff --git a/employee/models.py b/employee/models.py index c2fb2e9f8..5b220dac5 100644 --- a/employee/models.py +++ b/employee/models.py @@ -627,4 +627,41 @@ class BonusPointThreading(threading.Thread): employee_id = employee ) -BonusPointThreading().start() \ No newline at end of file +BonusPointThreading().start() + + +class Actiontype(models.Model): + """ + Action type model + """ + + choice_actions = [ + ("warning", trans("Warning")), + ("suspension", trans("Suspension")), + ("dismissal", trans("Dismissal")), + ] + + title = models.CharField(max_length=50) + action_type = models.CharField(max_length=30, choices=choice_actions) + + def __str__(self) -> str: + return f"{self.title}" + + +class DisciplinaryAction(models.Model): + """ + Disciplinary model + """ + + employee_id = models.ManyToManyField(Employee) + action = models.ForeignKey(Actiontype, on_delete=models.CASCADE) + description = models.TextField() + days = models.IntegerField(null=True, blank = True) + start_date = models.DateField(null=True) + attachment = models.FileField(upload_to="employee/discipline", null=True, blank = True) + company_id = models.ManyToManyField(Company, blank=True) + + objects = HorillaCompanyManager() + + def __str__(self) -> str: + return f"{self.action}" diff --git a/employee/policies.py b/employee/policies.py index c44cc0d08..ce74932ba 100644 --- a/employee/policies.py +++ b/employee/policies.py @@ -5,14 +5,22 @@ This module is used to write operation related to policies """ +import json from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render +from django.http import HttpResponse, JsonResponse +from django.shortcuts import redirect, render from base.views import paginator_qry -from employee.filters import PolicyFilter -from employee.forms import PolicyForm -from employee.models import Policy, PolicyMultipleFile +from urllib.parse import parse_qs +from employee.filters import DisciplinaryActionFilter, PolicyFilter +from employee.forms import DisciplinaryActionForm, PolicyForm +from employee.models import Actiontype, DisciplinaryAction, Policy, PolicyMultipleFile from horilla.decorators import permission_required, login_required +from django.utils.translation import gettext_lazy as _ +from notifications.signals import notify +from base.methods import ( + get_key_instances, +) + @login_required @@ -83,6 +91,16 @@ def view_policy(request): }, ) +@login_required +@permission_required("employee.delete_policy") +def delete_policies(request): + """ + This method is to delete policy + """ + ids = request.GET.getlist("ids") + Policy.objects.filter(id__in=ids).delete() + messages.success(request,"Policies deleted") + return redirect(view_policies) @login_required @permission_required("employee.change_policy") @@ -125,3 +143,162 @@ def get_attachments(request): policy = request.GET["policy_id"] policy = Policy.objects.get(id=policy) return render(request, "policies/attachments.html", {"policy": policy}) + + +@login_required +def disciplinary_actions(request): + """ + This method is used to view all Disciplinaryaction + """ + form = DisciplinaryActionFilter() + queryset = DisciplinaryAction.objects.all() + page_number = request.GET.get("page") + page_obj = paginator_qry(queryset, page_number) + previous_data = request.GET.urlencode() + + return render(request, + "disciplinary_actions/disciplinary_nav.html", + { + "data": page_obj, + "pd": previous_data, + "f": form, + }) + + +@login_required +@permission_required("employee.add_disciplinaryaction") +def create_actions(request): + """ + Method is used to create Disciplinaryaction + """ + + form = DisciplinaryActionForm(request.FILES) + employees = [] + if request.method == "POST": + form = DisciplinaryActionForm(request.POST, request.FILES) + if form.is_valid(): + emp = form.cleaned_data['employee_id'] + + for i in emp: + name = i.employee_user_id + employees.append(name) + + form.save() + messages.success(request, _("Disciplinary action taken.")) + + notify.send( + request.user.employee_get, + recipient=employees, + verb="Disciplinary action is taken on you.", + verb_ar="تم اتخاذ إجراء disziplinarisch ضدك.", + verb_de="Disziplinarische Maßnahmen wurden gegen Sie ergriffen.", + verb_es="Se ha tomado acción disciplinaria en tu contra.", + verb_fr="Des mesures disciplinaires ont été prises à votre encontre.", + redirect="/employee/disciplinary-actions/", + icon="chatbox-ellipses", + ) + + return HttpResponse("") + return render(request, "disciplinary_actions/form.html", {"form": form}) + + +@login_required +@permission_required("employee.change_disciplinaryaction") +def update_actions(request, action_id ): + """ + Method is used to update Disciplinaryaction + """ + + action = DisciplinaryAction.objects.get(id=action_id) + form = DisciplinaryActionForm(instance = action) + employees = [] + if request.method == "POST": + form = DisciplinaryActionForm(request.POST, request.FILES, instance=action) + + if form.is_valid(): + emp = form.cleaned_data['employee_id'] + + for i in emp: + name = i.employee_user_id + employees.append(name) + + form.save() + messages.success(request, _("Disciplinary action updated.")) + + notify.send( + request.user.employee_get, + recipient=employees, + verb="Disciplinary action is taken on you.", + verb_ar="تم اتخاذ إجراء disziplinarisch ضدك.", + verb_de="Disziplinarische Maßnahmen wurden gegen Sie ergriffen.", + verb_es="Se ha tomado acción disciplinaria en tu contra.", + verb_fr="Des mesures disciplinaires ont été prises à votre encontre.", + redirect="/employee/disciplinary-actions/", + icon="chatbox-ellipses", + ) + + return HttpResponse("") + return render(request, "disciplinary_actions/update_form.html", {"form": form}) + + +@login_required +@permission_required("employee.delete_disciplinaryaction") +def delete_actions(request, action_id): + """ + This method is used to delete Disciplinary action + """ + + DisciplinaryAction.objects.filter(id = action_id).delete() + messages.success(request, _("Disciplinary action deleted.")) + return redirect(disciplinary_actions) + + +@login_required +def action_type_details(request): + """ + This method is used to get the action type by the selection of title in the form. + """ + action_id = request.POST["action_type"] + action = Actiontype.objects.get(id=action_id) + action_type = action.action_type + return JsonResponse({"action_type": action_type}) + + +@login_required +def disciplinary_filter_view(request): + """ + This method is used to filter Disciplinary Action. + """ + + previous_data = request.GET.urlencode() + dis_filter = DisciplinaryActionFilter(request.GET).qs + page_number = request.GET.get("page") + page_obj = paginator_qry(dis_filter, page_number) + data_dict = parse_qs(previous_data) + get_key_instances(DisciplinaryAction, data_dict) + return render( + request, + "disciplinary_actions/disciplinary_records.html", + { + "data": page_obj, + "pd": previous_data, + "filter_dict": data_dict, + "dashboard": request.GET.get("dashboard"), + }, + ) + + +@login_required +def search_disciplinary(request): + """ + This method is used to search in Disciplinary Actions + """ + disciplinary = DisciplinaryActionFilter(request.GET).qs + return render( + request, + "disciplinary_actions/disciplinary_records.html", + { + "data": paginator_qry(disciplinary, request.GET.get("page")), + "pd": request.GET.urlencode(), + }, + ) diff --git a/employee/templates/disciplinary_actions/disciplinary_nav.html b/employee/templates/disciplinary_actions/disciplinary_nav.html new file mode 100644 index 000000000..c8e36cfbb --- /dev/null +++ b/employee/templates/disciplinary_actions/disciplinary_nav.html @@ -0,0 +1,197 @@ +{% extends 'index.html' %} +{% block content %} +{% load i18n %} +
+
+

{% trans 'Disciplinary Actions' %}

+
+ +
+ + {% if perms.employee.add_disciplinaryaction %} + +
+
+ + +
+
+
+ + +
+ + + + +
+ +
+ {% endif %} +
+
+ + + + + + + + + + +
+
+
+
{% trans "Edit Action." %} + +
+
+ +
+
+
+ + + +
+ {% include 'disciplinary_actions/disciplinary_records.html' %} +
+{% endblock %} diff --git a/employee/templates/disciplinary_actions/disciplinary_records.html b/employee/templates/disciplinary_actions/disciplinary_records.html new file mode 100644 index 000000000..088ccf228 --- /dev/null +++ b/employee/templates/disciplinary_actions/disciplinary_records.html @@ -0,0 +1,187 @@ +{% load basefilters %} +{% load attendancefilters %} +{% load employee_filter %} +{% load static %} +{% load i18n %} +{% include 'filter_tags.html' %} + + +
+
+
+
+
+ {% trans "Employee" %} +
+
{% trans "Action Taken" %}
+
{% trans "Action Date" %}
+
{% trans "Attachments" %}
+
{% trans "Description" %}
+ {% if perms.payroll.change_disciplinaryaction or perms.payroll.delete_disciplinaryaction %} +
{% trans "Options" %}
+ {% endif %} +
+
+
+ {% for i in data %} +
+
+ {% for emp in i.employee_id.all %} +
+
+ {{ emp }} +
+ {{ emp }} +

+ {% endfor %} +
+ {% if i.action.action_type == 'suspension' %} +
{{ i.action }} +

{% trans "Suspended for" %} {{ i.days }} {% trans "days." %}

+
+ {% else %} +
{{ i.action }}
+ {% endif %} + + {% if i.action.action_type == 'suspension' %} +
+ {{ i.start_date }} +   to  {{ i.start_date | add_days:i.days }} +
+ {% else %} +
{{ i.start_date }}
+ {% endif %} + + {% if i.attachment %} +
+ +
+ {% else %} +
{% trans "No file has been uploaded." %}
+ {% endif %} + +
{{ i.description }}
+ + {% if perms.payroll.change_disciplinaryaction or perms.payroll.delete_disciplinaryaction %} +
+
+ {% if perms.payroll.change_disciplinaryaction %} + + + {% endif %} + {% if perms.payroll.delete_disciplinaryaction %} +
+ {% csrf_token %} + +
+ {% endif %} +
+
+ {% endif %} +
+ {% endfor %} +
+
+
+ + + + +
+ + {% trans "Page" %} {{ data.number }} {% trans "of" %} {{ data.paginator.num_pages }}. + + +
+ + + + + diff --git a/employee/templates/disciplinary_actions/form.html b/employee/templates/disciplinary_actions/form.html new file mode 100644 index 000000000..452a5facc --- /dev/null +++ b/employee/templates/disciplinary_actions/form.html @@ -0,0 +1,35 @@ +{% load i18n %} + +
+ {% csrf_token %} + +
+ + {{ form.employee_id }} +
+
+ + {{ form.action }} +
+ +
+ + {{ form.start_date }} +
+ +
+ + {{ form.description }} +
+
+ + {{ form.attachment }} +
+ + +
+ diff --git a/employee/templates/disciplinary_actions/update_form.html b/employee/templates/disciplinary_actions/update_form.html new file mode 100644 index 000000000..e2292ba7c --- /dev/null +++ b/employee/templates/disciplinary_actions/update_form.html @@ -0,0 +1,49 @@ +{% load i18n %} +{% if form.errors %} + +
+
+ {% for error in form.non_field_errors %} +
+ {{ error }} +
+ {% endfor %} +
+
+{% endif %} +
+ {% csrf_token %} +
+ + {{ form.employee_id }} +
+
+ + {{ form.action }} +
+ + +
+ + {{ form.start_date }} +
+ +
+ + {{ form.description }} +
+
+ + {{ form.attachment }} +
+ + +
+ diff --git a/employee/templates/employee_personal_info/employee_list.html b/employee/templates/employee_personal_info/employee_list.html index 224ea9d74..23935ff55 100644 --- a/employee/templates/employee_personal_info/employee_list.html +++ b/employee/templates/employee_personal_info/employee_list.html @@ -330,5 +330,8 @@ }); }); toggleColumns("employee-table","fieldContainerTable") + if (!localStorage.getItem("employee_table")) { + $("#fieldContainerTable").find("[type=checkbox]").prop("checked",true).change() + } diff --git a/employee/templates/policies/records.html b/employee/templates/policies/records.html index cc4691805..2fb955570 100644 --- a/employee/templates/policies/records.html +++ b/employee/templates/policies/records.html @@ -15,7 +15,7 @@ {% endif %} {% if perms.employee.delete_policy %} - + {% endif %} diff --git a/employee/templatetags/__init__ .py b/employee/templatetags/__init__ .py new file mode 100644 index 000000000..e69de29bb diff --git a/employee/templatetags/employee_filter.py b/employee/templatetags/employee_filter.py new file mode 100644 index 000000000..dea3db83e --- /dev/null +++ b/employee/templatetags/employee_filter.py @@ -0,0 +1,12 @@ +from django import template +from datetime import timedelta + +register = template.Library() + +@register.filter(name='add_days') +def add_days(value, days): + # Check if value is not None before adding days + if value is not None: + return value + timedelta(days=days) + else: + return None diff --git a/employee/templatetags/migrations/__init__.py b/employee/templatetags/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/employee/urls.py b/employee/urls.py index 9ecd7d129..fcffb608d 100644 --- a/employee/urls.py +++ b/employee/urls.py @@ -271,4 +271,15 @@ urlpatterns = [ views.document_delete, name="document-delete", ), + + path("delete-policies",policies.delete_policies,name="delete-policies"), + + path("disciplinary-actions/", policies.disciplinary_actions, name="disciplinary-actions"), + path("create-actions", policies.create_actions, name="create-actions"), + path("update-actions//", policies.update_actions, name="update-actions"), + path("delete-actions//",policies.delete_actions, name="delete-actions"), + path("action-type-details",policies.action_type_details,name="action-type-details",), + path("disciplinary-filter-view", policies.disciplinary_filter_view, name="disciplinary-filter-view"), + path("search-disciplinary", policies.search_disciplinary, name="search-disciplinary"), + ]