diff --git a/offboarding/__init__.py b/offboarding/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/offboarding/admin.py b/offboarding/admin.py new file mode 100644 index 000000000..ff57014eb --- /dev/null +++ b/offboarding/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from offboarding.models import ( + OffboardingStageMultipleFile, + OffboardingNote, + OffboardingTask, + EmployeeTask, +) + +# Register your models here. + +admin.site.register( + [OffboardingStageMultipleFile, OffboardingNote, OffboardingTask, EmployeeTask] +) diff --git a/offboarding/apps.py b/offboarding/apps.py new file mode 100644 index 000000000..4eb3f3dd6 --- /dev/null +++ b/offboarding/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class OffboardingConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'offboarding' diff --git a/offboarding/decorators.py b/offboarding/decorators.py new file mode 100644 index 000000000..f05a915ed --- /dev/null +++ b/offboarding/decorators.py @@ -0,0 +1,59 @@ +""" +offboarding/decorators.py + +This module is used to write custom authentication decorators for offboarding module +""" +from django.contrib import messages +from django.http import HttpResponseRedirect +from horilla.decorators import decorator_with_arguments +from offboarding.models import Offboarding, OffboardingStage, OffboardingTask + + +@decorator_with_arguments +def any_manager_can_enter(function, perm): + def _function(request, *args, **kwargs): + employee = request.user.employee_get + if request.user.has_perm(perm) or ( + Offboarding.objects.filter(managers=employee).exists() + | OffboardingStage.objects.filter(managers=employee).exists() + | OffboardingTask.objects.filter(managers=employee).exists() + ): + return function(request, *args, **kwargs) + else: + messages.info(request, "You dont have permission.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + + return _function + + +@decorator_with_arguments +def offboarding_manager_can_enter(function, perm): + def _function(request, *args, **kwargs): + employee = request.user.has_perm(perm) + if ( + request.user.has_perm(perm) + or Offboarding.objects.filter(managers=employee).exists() + ): + return function(request, *args, **kwargs) + else: + messages.info(request, "You dont have permission.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + + return _function + + +@decorator_with_arguments +def offboarding_or_stage_manager_can_enter(function, perm): + def _function(request, *args, **kwargs): + employee = request.user.has_perm(perm) + if ( + request.user.has_perm(perm) + or Offboarding.objects.filter(managers=employee).exists() + or OffboardingStage.objects.filter(managers=employee).exists() + ): + return function(request, *args, **kwargs) + else: + messages.info(request, "You dont have permission.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + + return _function diff --git a/offboarding/forms.py b/offboarding/forms.py new file mode 100644 index 000000000..8df60823a --- /dev/null +++ b/offboarding/forms.py @@ -0,0 +1,200 @@ +""" +offboarding/forms.py + +This module is used to register forms for offboarding app +""" + +from typing import Any +from django import forms +from django.template.loader import render_to_string +from base.forms import ModelForm +from employee.forms import MultipleFileField +from offboarding.models import ( + EmployeeTask, + Offboarding, + OffboardingEmployee, + OffboardingNote, + OffboardingStage, + OffboardingStageMultipleFile, + OffboardingTask, +) + + +class OffboardingForm(ModelForm): + """ + OffboardingForm model form class + """ + + verbose_name = "Offboarding" + + class Meta: + model = Offboarding + fields = "__all__" + + def as_p(self): + """ + Render the form fields as HTML table rows with Bootstrap styling. + """ + context = {"form": self} + table_html = render_to_string("common_form.html", context) + return table_html + + +class OffboardingStageForm(ModelForm): + """ + OffboardingStage model form + """ + + verbose_name = "Stage" + + class Meta: + model = OffboardingStage + fields = "__all__" + exclude = [ + "offboarding_id", + ] + + def as_p(self): + """ + Render the form fields as HTML table rows with Bootstrap styling. + """ + context = {"form": self} + table_html = render_to_string("common_form.html", context) + return table_html + + +class OffboardingEmployeeForm(ModelForm): + """ + OffboardingEmployeeForm model form + """ + + verbose_name = "Offboarding " + + class Meta: + model = OffboardingEmployee + fields = "__all__" + widgets = { + "notice_period_starts": forms.DateTimeInput(attrs={"type": "date"}), + "notice_period_ends": forms.DateTimeInput(attrs={"type": "date"}), + } + + def as_p(self): + """ + Render the form fields as HTML table rows with Bootstrap styling. + """ + context = {"form": self} + table_html = render_to_string("common_form.html", context) + return table_html + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance.pk: + self.initial["notice_period_starts"] = self.instance.notice_period_starts.strftime("%Y-%m-%d") + self.initial["notice_period_ends"] = self.instance.notice_period_ends.strftime("%Y-%m-%d") + +class StageSelectForm(ModelForm): + """ + This form is used to register drop down for the pipeline + """ + + class Meta: + model = OffboardingEmployee + fields = [ + "stage_id", + ] + + def __init__(self, *args, offboarding=None, **kwargs): + super().__init__(*args, **kwargs) + attrs = self.fields["stage_id"].widget.attrs + attrs["onchange"] = "offboardingUpdateStage($(this))" + attrs["class"] = "w-100 oh-select-custom" + self.fields["stage_id"].widget.attrs.update(attrs) + self.fields["stage_id"].empty_label = None + self.fields["stage_id"].queryset = OffboardingStage.objects.filter( + offboarding_id=offboarding + ) + self.fields["stage_id"].label = "" + + +class NoteForm(ModelForm): + """ + Offboarding note model form + """ + + verbose_name = "Add Note" + + class Meta: + model = OffboardingNote + fields = "__all__" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["attachment"] = MultipleFileField(label="Attachements") + self.fields["attachment"].required = False + + def as_p(self): + """ + Render the form fields as HTML table rows with Bootstrap styling. + """ + context = {"form": self} + table_html = render_to_string("common_form.html", context) + return table_html + + def save(self, commit: bool = ...) -> Any: + multiple_attachment_ids = [] + attachemnts = None + if self.files.getlist("attachment"): + attachemnts = self.files.getlist("attachment") + self.instance.attachemnt = attachemnts[0] + multiple_attachment_ids = [] + for attachemnt in attachemnts: + file_instance = OffboardingStageMultipleFile() + file_instance.attachment = attachemnt + file_instance.save() + multiple_attachment_ids.append(file_instance.pk) + instance = super().save(commit) + if commit: + instance.attachments.add(*multiple_attachment_ids) + return instance, attachemnts + + +class TaskForm(ModelForm): + """ + TaskForm model form + """ + + verbose_name = "Offboarding Task" + tasks_to = forms.ModelMultipleChoiceField( + queryset=OffboardingEmployee.objects.all() + ) + + class Meta: + model = OffboardingTask + fields = "__all__" + exclude = [ + "status", + ] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["stage_id"].empty_label = "All Stages in Offboarding" + self.fields["managers"].empty_label = None + + def as_p(self): + """ + Render the form fields as HTML table rows with Bootstrap styling. + """ + context = {"form": self} + table_html = render_to_string("common_form.html", context) + return table_html + + def save(self, commit: bool = ...) -> Any: + super().save(commit) + if commit: + employees = self.cleaned_data["tasks_to"] + print(employees) + for employee in employees: + assinged_task = EmployeeTask.objects.get_or_create( + employee_id=employee, + task_id=self.instance, + ) diff --git a/offboarding/migrations/__init__.py b/offboarding/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/offboarding/models.py b/offboarding/models.py new file mode 100644 index 000000000..20e2c1ee9 --- /dev/null +++ b/offboarding/models.py @@ -0,0 +1,199 @@ +from collections.abc import Iterable +from typing import Any +from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver +from base import thread_local_middleware +from employee.models import Employee +from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog + +# Create your models here. + + +class Offboarding(models.Model): + """ + Offboarding model + """ + + statuses = [("ongoing", "Ongoing"), ("completed", "Completed")] + title = models.CharField(max_length=20) + description = models.TextField() + managers = models.ManyToManyField(Employee) + created_at = models.DateTimeField(auto_now_add=True) + status = models.CharField(max_length=10, default="ongoing", choices=statuses) + is_active = models.BooleanField(default=True) + + +class OffboardingStage(models.Model): + """ + Offboarding model + """ + + types = [ + ("notice_period", "Notice period"), + ("fnf", "FnF Settlement"), + ("other", "Other"), + ("archived", "Archived"), + ] + + title = models.CharField(max_length=20) + type = models.CharField(max_length=13, choices=types) + offboarding_id = models.ForeignKey(Offboarding, on_delete=models.PROTECT) + managers = models.ManyToManyField(Employee) + sequence = models.IntegerField(default=0, editable=False) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self) -> str: + return str(self.title) + + def is_archived_stage(self): + """ + This method is to check the stage is archived or not + """ + return self.type == "archived" + + +@receiver(post_save, sender=Offboarding) +def create_initial_stage(sender, instance, created, **kwargs): + """ + This is post save method, used to create initial stage for the recruitment + """ + if created: + initial_stage = OffboardingStage() + initial_stage.title = "Notice Period" + initial_stage.offboarding_id = instance + initial_stage.type = "notice_period" + initial_stage.save() + + +class OffboardingStageMultipleFile(models.Model): + """ + OffboardingStageMultipleFile + """ + + attachment = models.FileField(upload_to="offboarding/attachments") + created_at = models.DateTimeField(auto_now_add=True) + + +class OffboardingEmployee(models.Model): + """ + OffboardingEmployee model / Employee on stage + """ + + units = [("day", "days"), ("month", "Month")] + employee_id = models.OneToOneField( + Employee, on_delete=models.CASCADE, verbose_name="Employee" + ) + stage_id = models.ForeignKey( + OffboardingStage, on_delete=models.PROTECT, verbose_name="Stage" + ) + notice_period = models.IntegerField() + unit = models.CharField(max_length=10, choices=units) + notice_period_starts = models.DateField() + notice_period_ends = models.DateField() + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self) -> str: + return self.employee_id.get_full_name() + + +class OffboardingTask(models.Model): + """ + OffboardingTask model + """ + + title = models.CharField(max_length=30) + managers = models.ManyToManyField(Employee) + stage_id = models.ForeignKey( + OffboardingStage, + on_delete=models.PROTECT, + verbose_name="Stage", + null=True, + blank=True, + ) + + class Meta: + unique_together = ["title", "stage_id"] + + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self) -> str: + return self.title + + +class EmployeeTask(models.Model): + """ + EmployeeTask model + """ + + statuses = [ + ("todo", "Todo"), + ("inprogress", "Inprogress"), + ("stuck", "Stuck"), + ("completed", "Completed"), + ] + employee_id = models.ForeignKey( + OffboardingEmployee, + on_delete=models.CASCADE, + verbose_name="Employee", + null=True, + ) + status = models.CharField(max_length=10, choices=statuses, default="todo") + task_id = models.ForeignKey(OffboardingTask, on_delete=models.CASCADE) + history = HorillaAuditLog( + related_name="history_set", + bases=[ + HorillaAuditInfo, + ], + ) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + unique_together = ["employee_id", "task_id"] + + +class ExitReason(models.Model): + """ + ExitReason model + """ + + title = models.CharField(max_length=50) + description = models.TextField() + offboarding_employee_id = models.ForeignKey( + OffboardingEmployee, on_delete=models.PROTECT + ) + attacments = models.ManyToManyField(OffboardingStageMultipleFile) + + +class OffboardingNote(models.Model): + """ + OffboardingNote + """ + + attachments = models.ManyToManyField( + OffboardingStageMultipleFile, blank=True, editable=False + ) + title = models.CharField(max_length=20, null=True) + description = models.TextField(null=True, blank=True) + note_by = models.ForeignKey( + Employee, on_delete=models.SET_NULL, null=True, editable=False + ) + employee_id = models.ForeignKey( + OffboardingEmployee, on_delete=models.PROTECT, null=True, editable=False + ) + stage_id = models.ForeignKey( + OffboardingStage, on_delete=models.PROTECT, null=True, editable=False + ) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ["-created_at"] + + def save(self, *args, **kwargs): + request = getattr(thread_local_middleware._thread_locals, "request", None) + if request: + updated_by = request.user.employee_get + self.note_by = updated_by + if self.employee_id: + self.stage_id = self.employee_id.stage_id + return super().save(*args, **kwargs) diff --git a/offboarding/templates/offboarding/employee/form.html b/offboarding/templates/offboarding/employee/form.html new file mode 100644 index 000000000..70a3261b4 --- /dev/null +++ b/offboarding/templates/offboarding/employee/form.html @@ -0,0 +1,3 @@ +
+ {{ form.as_p }} +
diff --git a/offboarding/templates/offboarding/note/form.html b/offboarding/templates/offboarding/note/form.html new file mode 100644 index 000000000..7b1e1033c --- /dev/null +++ b/offboarding/templates/offboarding/note/form.html @@ -0,0 +1,6 @@ +
+ {{form.as_p}} +
+ \ No newline at end of file diff --git a/offboarding/templates/offboarding/note/view_notes.html b/offboarding/templates/offboarding/note/view_notes.html new file mode 100644 index 000000000..9966e7b7c --- /dev/null +++ b/offboarding/templates/offboarding/note/view_notes.html @@ -0,0 +1,43 @@ +{% load i18n static %} + + + + {% trans 'Add note' %} + +
    + {% for note in employee.offboardingnote_set.all %} +
  1. + {{ note.title }} + {{ note.description }} + +
    + {% for attachment in note.attachments.all %} + + {% endfor %} + +
    + {% csrf_token %} + + + +
    +
    + + {% trans 'by' %} + + {{ note.note_by.get_full_name }} @{{ note.stage_id }} + +
    +
    +
    +
  2. + {% endfor %} +
diff --git a/offboarding/templates/offboarding/pipeline/filter.html b/offboarding/templates/offboarding/pipeline/filter.html new file mode 100644 index 000000000..e69de29bb diff --git a/offboarding/templates/offboarding/pipeline/form.html b/offboarding/templates/offboarding/pipeline/form.html new file mode 100644 index 000000000..af6ac492d --- /dev/null +++ b/offboarding/templates/offboarding/pipeline/form.html @@ -0,0 +1,3 @@ +
+ {{form.as_p}} +
\ No newline at end of file diff --git a/offboarding/templates/offboarding/pipeline/nav.html b/offboarding/templates/offboarding/pipeline/nav.html new file mode 100644 index 000000000..c366d6f1c --- /dev/null +++ b/offboarding/templates/offboarding/pipeline/nav.html @@ -0,0 +1,34 @@ +{% load i18n %} +
+
+

{% trans 'Offboarding' %}

+
+ +
+
+ + +
+ {% include 'offboarding/pipeline/filter.html' %} + {% if perms.offboarding.add_offboarding %} + + {% endif %} +
+
+ + diff --git a/offboarding/templates/offboarding/pipeline/offboardings.html b/offboarding/templates/offboarding/pipeline/offboardings.html new file mode 100644 index 000000000..643e87444 --- /dev/null +++ b/offboarding/templates/offboarding/pipeline/offboardings.html @@ -0,0 +1,73 @@ +{% load i18n offboarding_filter %} + +
+
+
+ +
+ {% for offboarding in offboardings %} +
+ {% if perms.offboarding.add_offboardingstage or request.user.employee_get|any_manager %} + + + {% trans 'Stage' %} + + {% endif %} + {% include "offboarding/stage/stages.html" %} +
+ {% endfor %} +
+
+
+ + \ No newline at end of file diff --git a/offboarding/templates/offboarding/pipeline/pipeline.html b/offboarding/templates/offboarding/pipeline/pipeline.html new file mode 100644 index 000000000..810e45d54 --- /dev/null +++ b/offboarding/templates/offboarding/pipeline/pipeline.html @@ -0,0 +1,44 @@ +{% extends 'index.html' %} +{% block content %} + + {% include 'offboarding/pipeline/nav.html' %} + {% include 'offboarding/pipeline/offboardings.html' %} + +{% endblock %} diff --git a/offboarding/templates/offboarding/stage/form.html b/offboarding/templates/offboarding/stage/form.html new file mode 100644 index 000000000..a2a855f6d --- /dev/null +++ b/offboarding/templates/offboarding/stage/form.html @@ -0,0 +1,3 @@ +
+ {{ form.as_p }} +
diff --git a/offboarding/templates/offboarding/stage/offboarding_body.html b/offboarding/templates/offboarding/stage/offboarding_body.html new file mode 100644 index 000000000..ee017769a --- /dev/null +++ b/offboarding/templates/offboarding/stage/offboarding_body.html @@ -0,0 +1,96 @@ +{% load i18n offboarding_filter %} +{% for stage in offboarding.offboardingstage_set.all %} +
+
+
+ + + + {{ stage.offboardingemployee_set.all|length }} + + {{stage.title}} + {% if stage.is_archived_stage %} +
+ +
+ {% endif %} +
+
+
+
+ +
+
    + {% if perms.offboarding.add_offboardingemployee or request.user.employee_get|any_manager %} +
  • + + {% trans "Add Employee" %} +
  • + {% endif %} + {% if perms.offboarding.change_offboardingstage or request.user.employee_get|is_offboarding_manager %} +
  • + {% trans "Edit" %} +
  • + {% endif %} + {% if perms.offboarding.delete_offboarding %} +
  • + {% trans "Delete" %} +
  • + {% endif %} +
+
+
+
+
+
+
+
+
+
+
{% trans "Employee" %}
+
{% trans "Notice Period" %}
+
{% trans "Start Date" %}
+
{% trans "End Date" %}
+
{% trans "Stage" %}
+
{% trans "Options" %}
+ {% for task in stage.offboardingtask_set.all %} +
+
+ + {{task.title}} + + {% if perms.offboarding.delete_offboardingtask %} + + {% endif %} +
+
+ {% endfor %} +
+ {% if perms.offboarding.add_offboardingtask or request.user.employee_get|any_manager %} + + {% endif %} +
+
+
+
+ {% include "offboarding/task/table_body.html" %} +
+
+
+
+
+
+ {% endfor %} diff --git a/offboarding/templates/offboarding/stage/stages.html b/offboarding/templates/offboarding/stage/stages.html new file mode 100644 index 000000000..8f9beb42f --- /dev/null +++ b/offboarding/templates/offboarding/stage/stages.html @@ -0,0 +1,59 @@ +{% load i18n offboarding_filter %} + +
+ {% include "offboarding/stage/offboarding_body.html" %} +
+
+
+ + + + {% trans "Notes" %} +
+
+
+
+ + diff --git a/offboarding/templates/offboarding/task/form.html b/offboarding/templates/offboarding/task/form.html new file mode 100644 index 000000000..d72f152ce --- /dev/null +++ b/offboarding/templates/offboarding/task/form.html @@ -0,0 +1,7 @@ +
+{{form.as_p}} +
+ + \ No newline at end of file diff --git a/offboarding/templates/offboarding/task/table_body.html b/offboarding/templates/offboarding/task/table_body.html new file mode 100644 index 000000000..8ecdcaf4c --- /dev/null +++ b/offboarding/templates/offboarding/task/table_body.html @@ -0,0 +1,98 @@ +{% load i18n offboarding_filter %} +{% for employee in stage.offboardingemployee_set.all %} +
+
+
+
+ +
+ {{ employee.employee_id.get_full_name }} +
+
+
+ {% trans 'In' %} {{ employee.notice_period }} + {{ employee.get_unit_display }} +
+
{{ employee.notice_period_starts }}
+
{{ employee.notice_period_ends }}
+
+
+ {{ stage_forms|stages:stage }} + +
+ +
+
+
+ + + {% if not employee.employee_id.is_active %} + + {% else %} + + {% endif %} + {% if perms.offboarding.delete_offboardingemployee %} + + + {% endif %} + +
+
+ {% for task in stage.offboardingtask_set.all %} +
+ {% if task|have_task:employee %} + {% for assinged_tasks in employee|get_assigned_task:task %} + + {% endfor %} + {% else %} + + {% endif %} +
+ {% endfor %} +
+
+{% endfor %} + \ No newline at end of file diff --git a/offboarding/templatetags/__init__.py b/offboarding/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/offboarding/templatetags/migrations/__init__.py b/offboarding/templatetags/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/offboarding/templatetags/offboarding_filter.py b/offboarding/templatetags/offboarding_filter.py new file mode 100644 index 000000000..7ad0b829a --- /dev/null +++ b/offboarding/templatetags/offboarding_filter.py @@ -0,0 +1,71 @@ +""" +offboarding_filter.py + +This page is used to write custom template filters. +""" + +from django.template.defaultfilters import register +from django import template + +from offboarding.models import ( + EmployeeTask, + Offboarding, + OffboardingStage, + OffboardingTask, +) + + +register = template.Library() + + +@register.filter(name="stages") +def stages(stages_dict, stage): + """ + This method will return stage drop accordingly to the offboarding + """ + form = stages_dict[str(stage.offboarding_id.id)] + attrs = form.fields["stage_id"].widget.attrs + attrs["id"] = "stage" + str(stage.id) + str(stage.offboarding_id.id) + attrs["data-initial-stage"] = stage.id + form.fields["stage_id"].widget.attrs.update(attrs) + return form + + +@register.filter(name="have_task") +def have_task(task, employee): + """ + used to check the task is for the employee + """ + return EmployeeTask.objects.filter(employee_id=employee, task_id=task).exists() + + +@register.filter(name="get_assigned_task") +def get_assigned_tak(employee, task): + """ + This method is used to filterout the assigned task + """ + # retun like list to access it in varialbe when first iteration of the loop + return [ + EmployeeTask.objects.filter(employee_id=employee, task_id=task).first(), + ] + + +@register.filter(name="any_manager") +def any_manager(employee): + """ + This method is used to check the employee is in managers + employee: Employee model instance + """ + return ( + Offboarding.objects.filter(managers=employee).exists() + | OffboardingStage.objects.filter(managers=employee).exists() + | OffboardingTask.objects.filter(managers=employee).exists() + ) + + +@register.filter(name="is_offboarding_manager") +def is_offboarding_manager(employee): + """ + This method is used to check the employee is manager of any offboarding + """ + return Offboarding.objects.filter(managers=employee).exists() diff --git a/offboarding/tests.py b/offboarding/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/offboarding/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/offboarding/urls.py b/offboarding/urls.py new file mode 100644 index 000000000..77c8cf3de --- /dev/null +++ b/offboarding/urls.py @@ -0,0 +1,33 @@ +""" +offboarding/urls.py + +This module is used to register url mappings to functions +""" +from django.urls import path +from offboarding import views + +urlpatterns = [ + path("offboarding-pipeline", views.pipeline, name="offboarding-pipeline"), + path("create-offboarding", views.create_offboarding, name="create-offboarding"), + path("delete-offboarding", views.delete_offboarding, name="delete-offboarding"), + path( + "create-offboarding-stage", views.create_stage, name="create-offboarding-stage" + ), + path("add-employee", views.add_employee, name="add-employee"), + path( + "delete-offboarding-stage", views.delete_stage, name="delete-offboarding-stage" + ), + path( + "offboarding-change-stage", views.change_stage, name="offboarding-change-stage" + ), + path("view-offboarding-note", views.view_notes, name="view-offboarding-note"), + path("add-offboarding-note", views.add_note, name="add-offboarding-note"), + path( + "delete-note-attachment", views.delete_attachment, name="delete-note-attachment" + ), + path("offboarding-add-task", views.add_task, name="offboarding-add-task"), + path("update-task-status", views.update_task_status, name="update-task-status"), + path("offboarding-assign-task", views.task_assign, name="offboarding-assign-task"), + path("delete-offboarding-employee",views.delete_employee,name="delete-offboarding-employee"), + path("delete-offboarding-task",views.delete_task,name="delete-offboarding-task"), +] diff --git a/offboarding/views.py b/offboarding/views.py new file mode 100644 index 000000000..4f1528358 --- /dev/null +++ b/offboarding/views.py @@ -0,0 +1,359 @@ +from django.http import HttpResponse, JsonResponse +from django.shortcuts import redirect, render +from django.contrib import messages +from employee.models import Employee +from horilla.decorators import login_required, permission_required +from offboarding.decorators import any_manager_can_enter, offboarding_manager_can_enter, offboarding_or_stage_manager_can_enter +from offboarding.forms import ( + NoteForm, + OffboardingEmployeeForm, + OffboardingForm, + OffboardingStageForm, + StageSelectForm, + TaskForm, +) +from offboarding.models import ( + EmployeeTask, + Offboarding, + OffboardingEmployee, + OffboardingNote, + OffboardingStage, + OffboardingStageMultipleFile, + OffboardingTask, +) + + +# Create your views here. + + +@login_required +@any_manager_can_enter("offboarding.view_offboarding") +def pipeline(request): + """ + Offboarding pipleine view + """ + offboardings = Offboarding.objects.all() + stage_forms = {} + for offboarding in offboardings: + stage_forms[str(offboarding.id)] = StageSelectForm(offboarding=offboarding) + return render( + request, + "offboarding/pipeline/pipeline.html", + {"offboardings": offboardings, "stage_forms": stage_forms}, + ) + + +@login_required +@permission_required("offboarding.add_offboarding") +def create_offboarding(request): + """ + Create offboarding view + """ + instance_id = eval(str(request.GET.get("instance_id"))) + instance = None + if instance_id and isinstance(instance_id, int): + instance = Offboarding.objects.filter(id=instance_id).first() + form = OffboardingForm(instance=instance) + if request.method == "POST": + form = OffboardingForm(request.POST, instance=instance) + if form.is_valid(): + form.save() + messages.success(request, "Offboarding saved") + return HttpResponse("") + + return render( + request, + "offboarding/pipeline/form.html", + { + "form": form, + }, + ) + + +@login_required +@permission_required("offboarding.delete_offboarding") +def delete_offboarding(request): + """ + This method is used to delete offboardings + """ + ids = request.GET.getlits("id") + Offboarding.objects.filter(id__in=ids).delete() + messages.success(request, "Offboarding deleted") + return redirect(pipeline) + + +@login_required +@offboarding_manager_can_enter("offboarding.add_offboardingstage") +def create_stage(request): + """ + This method is used to create stages for offboardings + """ + offboarding_id = request.GET["offboarding_id"] + instance_id = eval(str(request.GET.get("instance_id"))) + instance = None + if instance_id and isinstance(instance_id, int): + instance = OffboardingStage.objects.get(id=instance_id) + offboarding = Offboarding.objects.get(id=offboarding_id) + form = OffboardingStageForm(instance=instance) + form.instance.offboarding_id = offboarding + if request.method == "POST": + form = OffboardingStageForm(request.POST, instance=instance) + if form.is_valid(): + instance = form.save(commit=False) + instance.offboarding_id = offboarding + instance.save() + instance.managers.set(form.data.getlist("managers")) + messages.success(request, "Stage saved") + return HttpResponse("") + return render(request, "offboarding/stage/form.html", {"form": form}) + + +@login_required +@any_manager_can_enter("offboarding.add_offboardingemployee") +def add_employee(request): + """ + This method is used to add employee to the stage + """ + stage_id = request.GET["stage_id"] + instance_id = eval(str(request.GET.get("instance_id"))) + instance = None + if instance_id and isinstance(instance_id, int): + instance = OffboardingEmployee.objects.get(id=instance_id) + stage = OffboardingStage.objects.get(id=stage_id) + form = OffboardingEmployeeForm(initial={"stage_id": stage}, instance=instance) + form.instance.stage_id = stage + if request.method == "POST": + form = OffboardingEmployeeForm( + request.POST, + instance=instance + ) + if form.is_valid(): + instance = form.save(commit=False) + instance.stage_id = stage + instance.save() + messages.success(request, "Employee added to the stage") + return HttpResponse("") + return render(request, "offboarding/employee/form.html", {"form": form}) + + +@login_required +@permission_required("offboarding.delete_offboardingemployee") +def delete_employee(request): + """ + This method is used to delete the offboarding employee + """ + employee_ids = request.GET.getlist("employee_ids") + OffboardingEmployee.objects.filter(id__in=employee_ids).delete() + messages.success(request, "Offboarding employee deleted") + return redirect(pipeline) + + +@login_required +@permission_required("offboarding.delete_offboardingstage") +def delete_stage(request): + """ + This method is used to delete the offboarding stage + """ + ids = request.GET.getlist("ids") + OffboardingStage.objects.filter(id__in=ids).delete() + messages.success(request, "Stage deleted") + return redirect(pipeline) + + +@login_required +@any_manager_can_enter("offboarding.change_offboarding") +def change_stage(request): + """ + This method is used to update the stages of the employee + """ + employee_ids = request.GET.getlist("employee_ids") + stage_id = request.GET["stage_id"] + employees = OffboardingEmployee.objects.filter(id__in=employee_ids) + stage = OffboardingStage.objects.get(id=stage_id) + # This wont trigger the save method inside the offboarding employee + # employees.update(stage_id=stage) + for employee in employees: + employee.stage_id = stage + employee.save() + if stage.type == "archived": + Employee.objects.filter( + id__in=employees.values_list("employee_id__id", flat=True) + ).update(is_active=False) + stage_forms = {} + stage_forms[str(stage.offboarding_id.id)] = StageSelectForm( + offboarding=stage.offboarding_id + ) + return render( + request, + "offboarding/stage/offboarding_body.html", + {"offboarding": stage.offboarding_id, "stage_forms": stage_forms}, + ) + + +@login_required +@any_manager_can_enter("offboarding.view_offboardingnote") +def view_notes(request): + """ + This method is used to render all the notes of the employee + """ + if request.FILES: + files = request.FILES.getlist("files") + note_id = request.GET["note_id"] + note = OffboardingNote.objects.get(id=note_id) + attachments = [] + for file in files: + attachment = OffboardingStageMultipleFile() + attachment.attachment = file + attachment.save() + attachments.append(attachment) + note.attachments.add(*attachments) + offboarding_employee_id = request.GET["employee_id"] + employee = OffboardingEmployee.objects.get(id=offboarding_employee_id) + return render( + request, + "offboarding/note/view_notes.html", + { + "employee": employee, + }, + ) + + +@login_required +@any_manager_can_enter("offboarding.add_offboardingnote") +def add_note(request): + """ + This method is used to create note for the offboarding employee + """ + employee_id = request.GET["employee_id"] + employee = OffboardingEmployee.objects.get(id=employee_id) + form = NoteForm() + if request.method == "POST": + form = NoteForm(request.POST, request.FILES) + form.instance.employee_id = employee + if form.is_valid(): + form.save() + return HttpResponse( + f""" +
+
+ + """ + ) + return render( + request, "offboarding/note/form.html", {"form": form, "employee": employee} + ) + + +@login_required +@permission_required("offboarding.delete_offboardingnote") +def delete_attachment(request): + """ + Used to delete attachment + """ + ids = request.GET.getlist("ids") + OffboardingStageMultipleFile.objects.filter(id__in=ids).delete() + offboarding_employee_id = request.GET["employee_id"] + employee = OffboardingEmployee.objects.get(id=offboarding_employee_id) + return render( + request, + "offboarding/note/view_notes.html", + { + "employee": employee, + }, + ) + + +@login_required +@offboarding_or_stage_manager_can_enter("offboarding.add_offboardingtask") +def add_task(request): + """ + This method is used to add offboarding tasks + """ + stage_id = request.GET.get("stage_id") + instance_id = eval(str(request.GET.get("instance_id"))) + employees = OffboardingEmployee.objects.filter(stage_id=stage_id) + instance = None + if instance_id: + instance = OffboardingTask.objects.filter(id=instance_id).first() + form = TaskForm( + initial={ + "stage_id": stage_id, + "tasks_to": employees, + }, + instance=instance, + ) + if request.method == "POST": + form = TaskForm(request.POST, instance=instance) + if form.is_valid(): + form.save() + messages.success(request, "Task Added") + return HttpResponse("") + return render(request, "offboarding/task/form.html", {"form": form}) + + +@login_required +@any_manager_can_enter("offboarding.change_employeetask") +def update_task_status(request): + """ + This method is used to update the assigned tasks status + """ + stage_id = request.GET["stage_id"] + employee_ids = request.GET.getlist("employee_ids") + task_id = request.GET["task_id"] + status = request.GET["task_status"] + EmployeeTask.objects.filter( + employee_id__id__in=employee_ids, task_id__id=task_id + ).update(status=status) + + stage = OffboardingStage.objects.get(id=stage_id) + stage_forms = {} + stage_forms[str(stage.offboarding_id.id)] = StageSelectForm( + offboarding=stage.offboarding_id + ) + return render( + request, + "offboarding/stage/offboarding_body.html", + {"offboarding": stage.offboarding_id, "stage_forms": stage_forms}, + ) + + +@login_required +@any_manager_can_enter("offboarding.add_employeetask") +def task_assign(request): + """ + This method is used to assign task to employees + """ + employee_ids = request.GET.getlist("employee_ids") + task_id = request.GET["task_id"] + employees = OffboardingEmployee.objects.filter(id__in=employee_ids) + task = OffboardingTask.objects.get(id=task_id) + for employee in employees: + assinged_task = EmployeeTask() + assinged_task.employee_id = employee + assinged_task.task_id = task + assinged_task.save() + offboarding = employees.first().stage_id.offboarding_id + stage_forms = {} + stage_forms[str(offboarding.id)] = StageSelectForm(offboarding=offboarding) + return render( + request, + "offboarding/stage/offboarding_body.html", + {"offboarding": offboarding, "stage_forms": stage_forms}, + ) + + +@login_required +@permission_required("offboarding.delete_offboardingtask") +def delete_task(request): + """ + This method is used to delete the task + """ + task_ids = request.GET.getlist("task_ids") + OffboardingTask.objects.filter(id__in=task_ids).delete() + messages.success(request, "Task deleted") + return redirect(pipeline)