From 224a5eba8fcbdde92f3d73e736e6b38790b1f5aa Mon Sep 17 00:00:00 2001 From: Horilla Date: Fri, 12 Jan 2024 10:38:53 +0530 Subject: [PATCH] [ADD] BASE: Option for creating multiple approval level --- base/admin.py | 2 + base/forms.py | 34 ++++++- base/methods.py | 19 +++- base/models.py | 163 +++++++++++++++++++++++++++++-- base/templatetags/basefilters.py | 12 ++- base/urls.py | 21 ++++ base/views.py | 74 +++++++++++++- 7 files changed, 313 insertions(+), 12 deletions(-) diff --git a/base/admin.py b/base/admin.py index db821aab0..07bd52454 100644 --- a/base/admin.py +++ b/base/admin.py @@ -16,6 +16,7 @@ from base.models import ( EmployeeShift, EmployeeShiftDay, EmployeeType, + MultipleApprovalManagers, Tags, WorkType, RotatingWorkType, @@ -45,3 +46,4 @@ admin.site.register(ShiftRequest) admin.site.register(WorkTypeRequest) admin.site.register(Tags) admin.site.register(DynamicEmailConfiguration) +admin.site.register(MultipleApprovalManagers) diff --git a/base/forms.py b/base/forms.py index a99633ede..8ce663939 100644 --- a/base/forms.py +++ b/base/forms.py @@ -24,6 +24,7 @@ from base.models import ( DynamicEmailConfiguration, JobPosition, JobRole, + MultipleApprovalCondition, WorkType, EmployeeType, EmployeeShift, @@ -1544,4 +1545,35 @@ class DynamicMailConfForm(ModelForm): """ context = {"form": self} table_html = render_to_string("attendance_form.html", context) - return table_html \ No newline at end of file + return table_html + +class MultipleApproveConditionForm(ModelForm): + CONDITION_CHOICE = [ + ("equal", _("Equal (==)")), + ("notequal", _("Not Equal (!=)")), + ("range", _("Range")), + ("lt", _("Less Than (<)")), + ("gt", _("Greater Than (>)")), + ("le", _("Less Than or Equal To (<=)")), + ("ge", _("Greater Than or Equal To (>=)")), + ("icontains", _("Contains")), + ] + multi_approval_manager = forms.ModelChoiceField( + queryset=Employee.objects.all(), + widget=forms.Select(attrs={"class": "oh-select oh-select-2 mb-2"}), + label=_("Approval Manager"), + required=True, + ) + condition_operator = forms.ChoiceField( + choices=CONDITION_CHOICE, + widget=forms.Select( + attrs={ + "class": "oh-select oh-select-2 mb-2", + "onChange": "toggleFields($('#id_condition_operator'))", + }, + ), + ) + + class Meta: + model = MultipleApprovalCondition + fields = "__all__" \ No newline at end of file diff --git a/base/methods.py b/base/methods.py index 144cfa4d1..778c1a270 100644 --- a/base/methods.py +++ b/base/methods.py @@ -5,7 +5,7 @@ import random from django.apps import apps from django.core.exceptions import ObjectDoesNotExist from django.db.models import ForeignKey, ManyToManyField, OneToOneField -from django.forms.models import ModelMultipleChoiceField, ModelChoiceField +from django.forms.models import ModelChoiceField from django.http import HttpResponse from django.template.loader import render_to_string from django.utils.translation import gettext as _ @@ -14,6 +14,7 @@ from xhtml2pdf import pisa from base.models import Company from employee.models import Employee, EmployeeWorkInformation from horilla.decorators import login_required +from leave.models import LeaveRequest, LeaveRequestConditionApproval def filtersubordinates(request, queryset, perm=None): @@ -545,3 +546,19 @@ def generate_pdf(template_path, context, path=True, title=None): response["Content-Disposition"] = f'''attachment;filename="{title}"''' return response return None + +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.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) + diff --git a/base/models.py b/base/models.py index 38f42c72e..8c7a93971 100644 --- a/base/models.py +++ b/base/models.py @@ -6,6 +6,7 @@ This module is used to register django models from collections.abc import Iterable from typing import Any import django +from base.thread_local_middleware import _thread_locals from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.db import models @@ -64,11 +65,6 @@ class Company(models.Model): return str(self.company) -from base.thread_local_middleware import _thread_locals - -from django import forms - - class Department(models.Model): """ Department model @@ -853,13 +849,15 @@ class Tags(models.Model): title = models.CharField(max_length=30) color = models.CharField(max_length=30) is_active = models.BooleanField(default=True) - company_id = models.ForeignKey(Company,null=True, editable=False, on_delete=models.PROTECT) - objects = HorillaCompanyManager( - related_company_field="company_id" + 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.title + + class DynamicEmailConfiguration(models.Model): """ SingletoneModel to keep the mail server configurations @@ -931,3 +929,152 @@ class DynamicEmailConfiguration(models.Model): class Meta: verbose_name = _("Email Configuration") + + +FIELD_CHOICE = [ + ("", "---------"), + ("requested_days", _("Leave Requested Days")), +] +CONDITION_CHOICE = [ + ("equal", _("Equal (==)")), + ("notequal", _("Not Equal (!=)")), + ("range", _("Range")), + ("lt", _("Less Than (<)")), + ("gt", _("Greater Than (>)")), + ("le", _("Less Than or Equal To (<=)")), + ("ge", _("Greater Than or Equal To (>=)")), + ("icontains", _("Contains")), +] + + +class MultipleApprovalCondition(models.Model): + department = models.ForeignKey(Department, on_delete=models.CASCADE) + condition_field = models.CharField( + max_length=255, + choices=FIELD_CHOICE, + ) + condition_operator = models.CharField( + max_length=255, choices=CONDITION_CHOICE, null=True, blank=True + ) + condition_value = models.CharField( + max_length=100, + null=True, + blank=True, + verbose_name=_("Condition Value"), + ) + condition_start_value = models.CharField( + max_length=100, + null=True, + blank=True, + verbose_name=_("Starting Value"), + ) + condition_end_value = models.CharField( + max_length=100, + null=True, + blank=True, + verbose_name=_("Ending Value"), + ) + + def __str__(self) -> str: + return f"{self.department} {self.condition_field} {self.condition_operator}" + + def clean(self, *args, **kwargs): + if self.condition_value: + instance = MultipleApprovalCondition.objects.filter( + department=self.department, + condition_field=self.condition_field, + condition_operator=self.condition_operator, + condition_value=self.condition_value, + ) + if instance: + raise ValidationError( + _("A condition with the provided fields already exists") + ) + if self.condition_field == "requested_days": + if self.condition_operator != "range": + if not self.condition_value: + raise ValidationError( + { + "condition_operator": _( + "Please enter a numeric value for condition value" + ) + } + ) + try: + float_value = float(self.condition_value) + except ValueError as e: + raise ValidationError( + { + "condition_operator": _( + "Please enter a valid numeric value for the condition value when the condition field is Leave Requested Days." + ) + } + ) + else: + if not self.condition_start_value or not self.condition_end_value: + raise ValidationError( + { + "condition_operator": _( + "Please specify condition value range" + ) + } + ) + try: + start_value = float(self.condition_start_value) + except ValueError as e: + raise ValidationError( + { + "condition_operator": _( + "Please enter a valid numeric value for the starting value when the condition field is Leave Requested Days." + ) + } + ) + try: + end_value = float(self.condition_end_value) + except ValueError as e: + raise ValidationError( + { + "condition_operator": _( + "Please enter a valid numeric value for the ending value when the condition field is Leave Requested Days." + ) + } + ) + + if start_value == end_value: + raise ValidationError( + { + "condition_operator": _( + "End value must be different from the start value in a range." + ) + } + ) + if end_value <= start_value: + raise ValidationError( + { + "condition_operator": _( + "End value must be greater than the start value in a range." + ) + } + ) + super().clean(*args, **kwargs) + + def approval_managers(self, *args, **kwargs): + managers = [] + from employee.models import Employee + queryset = MultipleApprovalManagers.objects.filter(condition_id=self.pk).order_by('sequence') + for query in queryset: + employee = Employee.objects.get(id = query.employee_id) + managers.append(employee) + + return managers + + + + +class MultipleApprovalManagers(models.Model): + + condition_id = models.ForeignKey( + MultipleApprovalCondition, on_delete=models.CASCADE + ) + sequence = models.IntegerField(null=False, blank=False) + employee_id = models.IntegerField(null=False, blank=False) diff --git a/base/templatetags/basefilters.py b/base/templatetags/basefilters.py index b20f7f6b9..4a85e37ab 100644 --- a/base/templatetags/basefilters.py +++ b/base/templatetags/basefilters.py @@ -1,6 +1,7 @@ import json from django.template.defaultfilters import register from django import template +from base.models import MultipleApprovalManagers from employee.models import Employee, EmployeeWorkInformation from django.core.paginator import Page, Paginator @@ -55,10 +56,19 @@ def is_reportingmanager(user): ).exists() +@register.filter(name="is_leave_approval_manager") +def is_leave_approval_manager(user): + """ + This method will return true if the user is comes in MultipleApprovalCondition model as approving manager + """ + employee = Employee.objects.filter(employee_user_id=user).first() + return MultipleApprovalManagers.objects.filter(employee_id=employee.id).exists() + + @register.filter(name="check_manager") def check_manager(user, instance): try: - if isinstance(instance,Employee): + if isinstance(instance, Employee): return instance.employee_work_info.reporting_manager_id == user.employee_get return ( user.employee_get diff --git a/base/urls.py b/base/urls.py index a295662cb..0fa00d9e6 100644 --- a/base/urls.py +++ b/base/urls.py @@ -601,5 +601,26 @@ urlpatterns = [ path( "audit-tag-delete/",views.audit_tag_delete,name="audit-tag-delete" ), + path( + "multiple-approval-condition", + views.multiple_approval_condition, + name="multiple-approval-condition", + ), + path( + "multiple-level-approval-create", + views.multiple_level_approval_create, + name="multiple-level-approval-create", + ), + path( + "multiple-level-approval-edit/", + views.multiple_level_approval_edit, + name="multiple-level-approval-edit", + ), + path( + "multiple-level-approval-delete/", + views.multiple_level_approval_delete, + name="multiple-level-approval-delete", + ), + ] diff --git a/base/views.py b/base/views.py index 7d83e424d..0e41d4528 100644 --- a/base/views.py +++ b/base/views.py @@ -67,7 +67,8 @@ from base.forms import ( AssignPermission, ResetPasswordForm, ChangePasswordForm, - TagsForm + TagsForm, + MultipleApproveConditionForm, ) from base.models import ( Company, @@ -75,6 +76,8 @@ from base.models import ( JobPosition, JobRole, Department, + MultipleApprovalCondition, + MultipleApprovalManagers, WorkType, EmployeeShift, EmployeeShiftDay, @@ -3756,3 +3759,72 @@ def audit_tag_delete(request,tag_id): AuditTag.objects.get(id=tag_id).delete() messages.success(request, _("Tag has been deleted successfully!")) return redirect(tag_view) + + + + +@login_required +def multiple_approval_condition(request): + form = MultipleApproveConditionForm() + conditions = MultipleApprovalCondition.objects.all().order_by('department')[::-1] + return render( + request, + "leave/leave_request/penalty/condition.html", + {"form": form, "conditions": conditions}, + ) + + +@login_required +def multiple_level_approval_create(request): + form = MultipleApproveConditionForm() + if request.method == "POST": + form = MultipleApproveConditionForm(request.POST) + dept_id = request.POST.get("department") + condition_field = request.POST.get("condition_field") + condition_operator = request.POST.get("condition_operator") + condition_value = request.POST.get("condition_value") + condition_start_value = request.POST.get("condition_start_value") + condition_end_value = request.POST.get("condition_end_value") + department = Department.objects.get(id=dept_id) + instance = MultipleApprovalCondition() + if form.is_valid(): + if condition_operator != "range": + instance.department = department + instance.condition_field = condition_field + instance.condition_operator = condition_operator + instance.condition_value = condition_value + else: + instance.department = department + instance.condition_field = condition_field + instance.condition_operator = condition_operator + instance.condition_start_value = condition_start_value + instance.condition_end_value = condition_end_value + instance.save() + sequence = 0 + for key, value in request.POST.items(): + if key.startswith("multi_approval_manager"): + sequence += 1 + employee_id = int(value) + MultipleApprovalManagers.objects.create( + condition_id=instance, + sequence=sequence, + employee_id=employee_id, + ) + form = MultipleApproveConditionForm() + conditions = MultipleApprovalCondition.objects.all().order_by('department')[::-1] + return render( + request, + "leave/leave_request/penalty/create.html", + {"form": form, "conditions": conditions}, + ) + + +def multiple_level_approval_edit(request, condition_id): + return redirect(multiple_level_approval_create) + + +@login_required +def multiple_level_approval_delete(request, condition_id): + condition = MultipleApprovalCondition.objects.get(id=condition_id) + condition.delete() + return redirect(multiple_approval_condition)