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 @@
+
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 @@
+
+
\ 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' %}
+
+
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 @@
+
\ 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 %}
+ -
+ {{ offboarding.title }}
+
+
+
+ {{ offboarding.offboardingstage_set.all|length }}
+
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+ {% 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 @@
+
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 %}
+
+ {% 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" %}
+
+
+
+
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 @@
+
+
+
\ 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 }}
+
+
+
+
+
+
+
+
+ {% 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)