[ADD] OFFBOARDING: App to manage offboarding procedures of employees from the company
This commit is contained in:
0
offboarding/__init__.py
Normal file
0
offboarding/__init__.py
Normal file
13
offboarding/admin.py
Normal file
13
offboarding/admin.py
Normal file
@@ -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]
|
||||
)
|
||||
6
offboarding/apps.py
Normal file
6
offboarding/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OffboardingConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'offboarding'
|
||||
59
offboarding/decorators.py
Normal file
59
offboarding/decorators.py
Normal file
@@ -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
|
||||
200
offboarding/forms.py
Normal file
200
offboarding/forms.py
Normal file
@@ -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,
|
||||
)
|
||||
0
offboarding/migrations/__init__.py
Normal file
0
offboarding/migrations/__init__.py
Normal file
199
offboarding/models.py
Normal file
199
offboarding/models.py
Normal file
@@ -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)
|
||||
3
offboarding/templates/offboarding/employee/form.html
Normal file
3
offboarding/templates/offboarding/employee/form.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<form hx-post="{% url 'add-employee' %}?stage_id={{ form.instance.stage_id.id }}&instance_id={{ form.instance.pk }}">
|
||||
{{ form.as_p }}
|
||||
</form>
|
||||
6
offboarding/templates/offboarding/note/form.html
Normal file
6
offboarding/templates/offboarding/note/form.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<form hx-post="{% url "add-offboarding-note" %}?employee_id={{employee.id}}" hx-encoding="multipart/form-data">
|
||||
{{form.as_p}}
|
||||
</form>
|
||||
<script>
|
||||
$(".col-md-6").removeClass("col-md-6");
|
||||
</script>
|
||||
43
offboarding/templates/offboarding/note/view_notes.html
Normal file
43
offboarding/templates/offboarding/note/view_notes.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% load i18n static %}
|
||||
<style>
|
||||
#enlargeImageContainer {
|
||||
position: absolute;
|
||||
left: -300px;
|
||||
top: 100px;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
<a hx-get="{% url 'add-offboarding-note' %}?employee_id={{ employee.id }}" style="width: 125px;position: sticky;top: 0;" hx-target="#offboardingModalBody" data-toggle="oh-modal-toggle" data-target="#offboardingModal" class="mb-3 oh-btn oh-btn--secondary">
|
||||
<ion-icon name="add-outline" role="img" class="md hydrated" aria-label="add outline"></ion-icon>
|
||||
{% trans 'Add note' %}
|
||||
</a>
|
||||
<ol class="oh-activity-sidebar__qa-list" role="list">
|
||||
{% for note in employee.offboardingnote_set.all %}
|
||||
<li class="oh-activity-sidebar__qa-item">
|
||||
<span class="oh-activity-sidebar__q">{{ note.title }}</span>
|
||||
<span class="oh-activity-sidebar__a">{{ note.description }}</span>
|
||||
|
||||
<div class="d-flex mt-2 mb-2">
|
||||
{% for attachment in note.attachments.all %}
|
||||
<a href="{{ attachment.attachment.url }}" rel="noopener noreferrer" target="_blank"><span class="oh-file-icon oh-file-icon--pdf" onmouseover="enlargeImage('{{ attachment.attachment.url }}',$(this))" style="width:40px;height:40px"><img src="{% static 'images/ui/minus-icon.png' %}" style="display:block;width:50%;height:50%" hx-get="{% url 'delete-note-attachment' %}?ids={{ attachment.id }}&employee_id={{ employee.id }}" hx-target="#activitySidebar" onclick="event.stopPropagation();event.preventDefault()" /></span></a>
|
||||
{% endfor %}
|
||||
|
||||
<form hx-post="{% url 'view-offboarding-note' %}?note_id={{ note.id }}&employee_id={{ employee.id }}" hx-target="#noteContainer" class="add-files-form" hx-encoding="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<label for="addFile_20" title="Add Files"><ion-icon name="add-outline" style="font-size: 24px" role="img" class="md hydrated" aria-label="add outline"></ion-icon></label>
|
||||
<input type="file" name="files" class="d-none" multiple="true" id="addFile_20" onchange="submitForm(this)" />
|
||||
<input type="submit" class="d-none add_more_submit" value="save" />
|
||||
</form>
|
||||
</div>
|
||||
<span class="oh-activity-sidebar__a">
|
||||
{% trans 'by' %}
|
||||
<img src="{{ note.note_by.get_avatar }}" style="width: 1.5em; border-radius: 100%" />
|
||||
{{ note.note_by.get_full_name }} @{{ note.stage_id }}
|
||||
</span>
|
||||
<div style="width: 50%;">
|
||||
<div id="enlargeImageContainer" class="enlargeImageContainer"></div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
3
offboarding/templates/offboarding/pipeline/form.html
Normal file
3
offboarding/templates/offboarding/pipeline/form.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<form hx-post="{% url "create-offboarding" %}?instance_id={{form.instance.id}}">
|
||||
{{form.as_p}}
|
||||
</form>
|
||||
34
offboarding/templates/offboarding/pipeline/nav.html
Normal file
34
offboarding/templates/offboarding/pipeline/nav.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% load i18n %}
|
||||
<section class="oh-wrapper oh-main__topbar" style="padding-bottom: 1rem;">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left oh-d-flex-column--resp oh-mb-3--small">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans 'Offboarding' %}</h1>
|
||||
</div>
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right oh-d-flex-column--resp oh-mb-3--small">
|
||||
<div class="oh-input-group oh-input__search-group mr-4">
|
||||
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left md hydrated" role="img" aria-label="search outline"></ion-icon>
|
||||
<input name="search" id="pipelineSearch" hx-target="#offboardingContainer" type="text" placeholder="Search" style="margin-right:10px" class="oh-input oh-input__icon mr-3" autocomplete="false" aria-label="Search Input" />
|
||||
</div>
|
||||
{% include 'offboarding/pipeline/filter.html' %}
|
||||
{% if perms.offboarding.add_offboarding %}
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<a hx-get="{% url 'create-offboarding' %}" hx-target="#offboardingModalBody" data-toggle="oh-modal-toggle" data-target="#offboardingModal" class="oh-btn oh-btn--secondary">
|
||||
<ion-icon name="add-outline"></ion-icon>
|
||||
{% trans 'Create' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="oh-modal" id="offboardingModal" role="dialog" aria-hidden="true">
|
||||
<div class="oh-modal__dialog" style="max-width: 550px">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<button type="button" class="oh-modal__close" aria-label="Close"><ion-icon name="close-outline"></ion-icon></button>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal__dialog-body" id="offboardingModalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
73
offboarding/templates/offboarding/pipeline/offboardings.html
Normal file
73
offboarding/templates/offboarding/pipeline/offboardings.html
Normal file
@@ -0,0 +1,73 @@
|
||||
{% load i18n offboarding_filter %}
|
||||
<style>
|
||||
.oh-select-custom {
|
||||
border: 1px solid hsl(213,22%,84%);
|
||||
padding: 0.3rem 0.8rem 0.3rem 0.3rem;
|
||||
border-radius: 0rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div id="messages" class="oh-alert-container"></div>
|
||||
<div class="oh-wrapper">
|
||||
<div class="oh-tabs">
|
||||
<ul class="oh-tabs__tablist">
|
||||
{% for offboarding in offboardings %}
|
||||
<li class="oh-tabs__tab" onclick="localStorage.setItem('activeTabOffboarding',$(this).attr('data-target'));" data-target="#Offboarding{{ offboarding.id }}">
|
||||
{{ offboarding.title }}
|
||||
<div class="d-flex">
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2" id="recruitmentCandidateCount1" title="{{ offboarding.offboardingstage_set.all|length }} Stages" onclick="event.stopPropagation()">
|
||||
{{ offboarding.offboardingstage_set.all|length }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn oh-stop-prop oh-btn--transparent oh-accordion-meta__btn" @click="open = !open" @click.outside="open = false" title="Actions">
|
||||
<ion-icon name="ellipsis-vertical" role="img" class="md hydrated" aria-label="ellipsis vertical"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% if perms.offboarding.change_offboarding or request.user.employee_get|is_offboarding_manager %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-get="{% url "create-offboarding" %}?instance_id={{offboarding.id}}" hx-target="#offboardingModalBody" data-toggle="oh-modal-toggle" data-target="#offboardingModal" class="oh-dropdown__link">{% trans "Edit" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.offboarding.delete_offboarding %}
|
||||
<li class="oh-dropdown__item">
|
||||
<form action="{% url "delete-offboarding" %}" onsubmit="return confirm('Are you sure you want to delete this offboarding?');" method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="oh-dropdown__link oh-dropdown__link--danger">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="oh-tabs__contents">
|
||||
{% for offboarding in offboardings %}
|
||||
<div class="oh-tabs__content" id="Offboarding{{ offboarding.id }}">
|
||||
{% if perms.offboarding.add_offboardingstage or request.user.employee_get|any_manager %}
|
||||
<a hx-get="{% url 'create-offboarding-stage' %}?offboarding_id={{offboarding.id}}" style="width: 100px;" hx-target="#offboardingModalBody" data-toggle="oh-modal-toggle" data-target="#offboardingModal" class="mb-3 oh-btn oh-btn--secondary">
|
||||
<ion-icon name="add-outline"></ion-icon>
|
||||
{% trans 'Stage' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% include "offboarding/stage/stages.html" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let activeTab = localStorage.getItem("activeTabOffboarding")
|
||||
if (activeTab) {
|
||||
$(`.oh-tabs__tab[data-target="${activeTab}"]`).addClass("oh-tabs__tab--active")
|
||||
$(`${activeTab}`).addClass("oh-tabs__content--active")
|
||||
}
|
||||
</script>
|
||||
44
offboarding/templates/offboarding/pipeline/pipeline.html
Normal file
44
offboarding/templates/offboarding/pipeline/pipeline.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
<style>
|
||||
.search-highlight {
|
||||
background-color: rgba(255, 68, 0, 0.076);
|
||||
}
|
||||
</style>
|
||||
{% include 'offboarding/pipeline/nav.html' %}
|
||||
{% include 'offboarding/pipeline/offboardings.html' %}
|
||||
<script>
|
||||
$('#pipelineSearch').keyup(function (e) {
|
||||
e.preventDefault()
|
||||
var search = $(this).val().toLowerCase()
|
||||
$('[data-employee]').each(function () {
|
||||
var employeeFullName = $(this).attr('data-employee')
|
||||
if (employeeFullName.toLowerCase().includes(search)) {
|
||||
$(this).show()
|
||||
$(this).addClass('search-highlight')
|
||||
} else {
|
||||
$(this).hide()
|
||||
$(this).removeClass('search-highlight')
|
||||
}
|
||||
})
|
||||
if (search == '') {
|
||||
$('.search-highlight').removeClass('search-highlight')
|
||||
$('[data-employee]').show()
|
||||
}
|
||||
if (search != '') {
|
||||
$('#filterTagContainerSectionNav').html('')
|
||||
$('#filterTagContainerSectionNav').append(
|
||||
'<span class="oh-titlebar__tag filter-field pipelineSearch">Search :' +
|
||||
search +
|
||||
`<button class="oh-titlebar__tag-close" onclick="$('#pipelineSearch').val('');$('#pipelineSearch').keyup()">
|
||||
<ion-icon name="close-outline">
|
||||
</ion-icon>
|
||||
</button>
|
||||
</span>`
|
||||
)
|
||||
} else {
|
||||
$('#filterTagContainerSectionNav').html('')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
3
offboarding/templates/offboarding/stage/form.html
Normal file
3
offboarding/templates/offboarding/stage/form.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<form hx-post="{% url 'create-offboarding-stage' %}?offboarding_id={{ form.instance.offboarding_id.id }}&instance_id={{ form.instance.pk }}">
|
||||
{{ form.as_p }}
|
||||
</form>
|
||||
@@ -0,0 +1,96 @@
|
||||
{% load i18n offboarding_filter %}
|
||||
{% for stage in offboarding.offboardingstage_set.all %}
|
||||
<div class="oh-accordion-meta" id="accordion{{stage.id}}">
|
||||
<div class="oh-accordion-meta__item">
|
||||
<div class="oh-accordion-meta__header oh-accordion-meta__header--show">
|
||||
<span class="oh-accordion-meta__title" data-offboarding-id="{{offboarding.id}}">
|
||||
<span class="d-flex" onclick="event.stopPropagation()">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2"
|
||||
id="offboardingBadge{{offboarding.id}}_{{forloop.counter}}" title="{{stage.offboardingemployee_set.all|length}} {% trans "Employees" %}" onclick="event.stopPropagation()">
|
||||
{{ stage.offboardingemployee_set.all|length }}
|
||||
</span>
|
||||
{{stage.title}}
|
||||
{% if stage.is_archived_stage %}
|
||||
<div class="oh-switch ml-2">
|
||||
<input type="checkbox" class="show-archived oh-switch__checkbox" title="{% trans "Toggle Archived" %}" onchange="showArchived($(this))">
|
||||
</div>
|
||||
{% endif %}
|
||||
</span>
|
||||
</span>
|
||||
<div class="oh-accordion-meta__actions" onclick="event.stopPropagation()">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn oh-stop-prop oh-accordion-meta__btn" @click="open = !open"
|
||||
@click.outside="open = false">
|
||||
{% trans "Actions" %}
|
||||
<ion-icon class="ms-2 oh-accordion-meta__btn-icon" name="caret-down-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% if perms.offboarding.add_offboardingemployee or request.user.employee_get|any_manager %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-get="{% url "add-employee" %}?stage_id={{stage.id}}" data-target="#offboardingModal"
|
||||
data-toggle="oh-modal-toggle" hx-target="#offboardingModalBody" class="oh-dropdown__link">
|
||||
{% trans "Add Employee" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.offboarding.change_offboardingstage or request.user.employee_get|is_offboarding_manager %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a
|
||||
hx-get="{% url "create-offboarding-stage" %}?offboarding_id={{offboarding.id}}&instance_id={{stage.id}}"
|
||||
data-target="#offboardingModal"
|
||||
data-toggle="oh-modal-toggle" hx-target="#offboardingModalBody" class="oh-dropdown__link">{% trans "Edit" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.offboarding.delete_offboarding %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="{% url "delete-offboarding-stage" %}?ids={{stage.id}}"
|
||||
onclick="return confirm('Are you sure want to delete this stage?')"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger">{% trans "Delete" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion-meta__body">
|
||||
<div class="oh-sticky-table oh-sticky-table--no-overflow mb-5">
|
||||
<div class="oh-sticky-table__table">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Notice Period" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Start Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "End Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Stage" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Options" %}</div>
|
||||
{% for task in stage.offboardingtask_set.all %}
|
||||
<div class="oh-sticky-table__th" style="width: 200px;" hx-get="{% url "offboarding-add-task" %}?stage_id={{stage.id}}&instance_id={{task.id}}" hx-target="#offboardingModalBody" data-toggle="oh-modal-toggle" data-target="#offboardingModal">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span title="Click to edit">
|
||||
{{task.title}}
|
||||
</span>
|
||||
{% if perms.offboarding.delete_offboardingtask %}
|
||||
<a class="text-danger" href="{% url 'delete-offboarding-task' %}?task_ids={{task.id}}" title="{% trans "Delete" %}" onclick="event.stopPropagation();return confirm('Do you want to delete task?')"><ion-icon name="trash-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="oh-sticky-table__th" style="width: 120px;">
|
||||
{% if perms.offboarding.add_offboardingtask or request.user.employee_get|any_manager %}
|
||||
<button class="oh-checkpoint-badge text-success" data-toggle="oh-modal-toggle" data-target="#offboardingModal" hx-get="{% url "offboarding-add-task" %}?stage_id={{stage.id}}" hx-target="#offboardingModalBody">
|
||||
{% trans "Add Task" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody" id="tableBody{{stage.id}}" data-stage-id="{{stage.id}}" data-archive-stage="{{stage.is_archived_stage|lower}}" data-offboarding-id="{{offboarding.id}}">
|
||||
{% include "offboarding/task/table_body.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
59
offboarding/templates/offboarding/stage/stages.html
Normal file
59
offboarding/templates/offboarding/stage/stages.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{% load i18n offboarding_filter %}
|
||||
<script>
|
||||
function offboardingUpdateStage($element) {
|
||||
submitButton = $element.closest("form").find("input[type=submit]")
|
||||
submitButton.click()
|
||||
}
|
||||
</script>
|
||||
<div class="oh-card" id="offboardingBody{{offboarding.id}}">
|
||||
{% include "offboarding/stage/offboarding_body.html" %}
|
||||
</div>
|
||||
<div class="oh-activity-sidebar" id="activitySidebar">
|
||||
<div class="oh-activity-sidebar__header">
|
||||
<a
|
||||
style="cursor: pointer;"
|
||||
onclick="$('.oh-activity-sidebar--show').removeClass('oh-activity-sidebar--show');">
|
||||
<ion-icon
|
||||
name="chevron-back-outline"
|
||||
class="oh-activity-sidebar__header-icon me-2 oh-activity-sidebar__close"
|
||||
data-target="#activitySidebar"
|
||||
></ion-icon>
|
||||
</a>
|
||||
<span class="oh-activity-sidebar__title"> {% trans "Notes" %} </span>
|
||||
</div>
|
||||
<div class="oh-activity-sidebar__body" id="noteContainer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// This lines is used to set default selected stage for exits lines
|
||||
|
||||
function enlargeImage(src,$element) {
|
||||
var enlargeImageContainer = $element.parents().closest("li").find("#enlargeImageContainer")
|
||||
enlargeImageContainer.empty()
|
||||
style = 'width:100%; height:90%; box-shadow: 0 10px 10px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); background:white'
|
||||
var enlargedImage = $('<iframe>').attr({ src: src, style: style })
|
||||
var name = $('<span>').text(src.split('/').pop().replace(/_/g, ' '))
|
||||
enlargeImageContainer.append(enlargedImage)
|
||||
enlargeImageContainer.append(name)
|
||||
setTimeout(function () {
|
||||
enlargeImageContainer.show()
|
||||
|
||||
const iframe = document.querySelector('iframe').contentWindow
|
||||
var iframe_document = iframe.document
|
||||
iframe_image = iframe_document.getElementsByTagName('img')[0]
|
||||
$(iframe_image).attr('style', 'width:100%; height:100%;')
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function hideEnlargeImage() {
|
||||
var enlargeImageContainer = $('.enlargeImageContainer')
|
||||
enlargeImageContainer.empty()
|
||||
}
|
||||
|
||||
$(document).on('click', function (event) {
|
||||
if (!$(event.target).closest('#enlargeImageContainer').length) {
|
||||
hideEnlargeImage()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
7
offboarding/templates/offboarding/task/form.html
Normal file
7
offboarding/templates/offboarding/task/form.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<form hx-post="{% url "offboarding-add-task" %}?instance_id={{form.instance.pk}}" hx-target="#offboardingModalBody">
|
||||
{{form.as_p}}
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(".col-md-6").removeClass("col-md-6");
|
||||
</script>
|
||||
98
offboarding/templates/offboarding/task/table_body.html
Normal file
98
offboarding/templates/offboarding/task/table_body.html
Normal file
@@ -0,0 +1,98 @@
|
||||
{% load i18n offboarding_filter %}
|
||||
{% for employee in stage.offboardingemployee_set.all %}
|
||||
<div class="oh-sticky-table__tr oh-multiple-table-sort__movable" data-employee="{{employee.employee_id.get_full_name}}" data-employee-id="{{ employee.id }}">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img src="{{ employee.employee_id.get_avatar }}" class="oh-profile__image" />
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark">{{ employee.employee_id.get_full_name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{% trans 'In' %} {{ employee.notice_period }}
|
||||
{{ employee.get_unit_display }}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{ employee.notice_period_starts }}</div>
|
||||
<div class="oh-sticky-table__td">{{ employee.notice_period_ends }}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<form hx-get="{% url "offboarding-change-stage" %}?employee_ids={{employee.id}}"
|
||||
hx-target="#offboardingBody{{offboarding.id}}">
|
||||
{{ stage_forms|stages:stage }}
|
||||
<input type="submit" hidden>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
<button type="button" hx-get="{% url 'send-mail-employee' employee.employee_id.id %}"
|
||||
title="{% trans 'Send Mail' %}" hx-target="#offboardingModalBody" class="oh-btn oh-btn--light"
|
||||
data-toggle="oh-modal-toggle" data-target="#offboardingModal"
|
||||
style="flex: 1 0 auto; width:20px;height: 40.68px; padding: 0;"><ion-icon
|
||||
name="mail-open-outline"></ion-icon></button>
|
||||
<button type="button" title="{% trans 'Notes' %}" class="oh-btn oh-btn--light oh-activity-sidebar__open"
|
||||
data-target="#activitySidebar" hx-get="{% url 'view-offboarding-note' %}?employee_id={{ employee.id }}"
|
||||
hx-target="#noteContainer" style="flex: 1 0 auto; width:20px;height: 40.68px; padding: 0;"><ion-icon
|
||||
name="newspaper-outline"></ion-icon>
|
||||
</button>
|
||||
{% if not employee.employee_id.is_active %}
|
||||
<a type="button" href="{% url 'employee-archive' employee.employee_id.id %}" title="{% trans 'Un Archive' %}"
|
||||
class="oh-btn oh-btn--light tex-primary"
|
||||
style="flex: 1 0 auto; width:20px;height: 40.68px; padding: 0;"><ion-icon name="archive"></ion-icon></a>
|
||||
{% else %}
|
||||
<a type="button" hx-target="#offboardingModalBody" data-toggle="oh-modal-toggle" data-target="#offboardingModal" hx-get="{% url "add-employee" %}?instance_id={{employee.id}}&stage_id={{stage.id}}" title="{% trans 'Edit' %}"
|
||||
class="oh-btn oh-btn--light tex-primary"
|
||||
style="flex: 1 0 auto; width:20px;height: 40.68px; padding: 0;"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.offboarding.delete_offboardingemployee %}
|
||||
<a type="button" style="flex: 1 0 auto; width:20px;height: 40.68px; padding: 0;" title="{% trans 'Delete' %}" onclick="return confirm('Do you want to delete this offboarding user?')" class="oh-btn oh-btn--light" href="{% url "delete-offboarding-employee" %}"><ion-icon
|
||||
name="trash-outline"></ion-icon>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% for task in stage.offboardingtask_set.all %}
|
||||
<div class="oh-sticky-table__td">
|
||||
{% if task|have_task:employee %}
|
||||
{% for assinged_tasks in employee|get_assigned_task:task %}
|
||||
<select hx-get="{% url "update-task-status" %}?stage_id={{stage.id}}&employee_ids={{employee.id}}&task_id={{assinged_tasks.task_id.id}}"
|
||||
hx-target="#offboardingBody{{offboarding.id}}" name="task_status" id="task_status{{assinged_tasks.id}}"
|
||||
class="oh-select-custom w-100">
|
||||
{% for assinged_task in assinged_tasks.statuses %}
|
||||
{% if assinged_tasks.status == assinged_task.0 %}
|
||||
<option value="{{ assinged_task.0 }}" selected>{{ assinged_task.1 }}</option>
|
||||
{% else %}
|
||||
<option value="{{ assinged_task.0 }}">{{ assinged_task.1 }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<button hx-get="{% url "offboarding-assign-task" %}?employee_ids={{employee.id}}&task_id={{task.id}}"
|
||||
hx-target="#offboardingBody{{offboarding.id}}" class="oh-checkpoint-badge text-info"
|
||||
data-toggle="oh-modal-toggle">{% trans 'Assign' %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<script>
|
||||
$("[data-archive-stage=true]").find(".oh-sticky-table__tr[data-employee-id]").hide();
|
||||
function showArchived($element) {
|
||||
let checked = $element.is(":checked")
|
||||
if (checked) {
|
||||
$element.closest(".oh-accordion-meta").find(".oh-sticky-table__tr[data-employee-id]").show()
|
||||
} else {
|
||||
$element.closest(".oh-accordion-meta").find(".oh-sticky-table__tr[data-employee-id]").hide()
|
||||
}
|
||||
}
|
||||
var selects = $("[name=stage_id][data-initial-stage]")
|
||||
$.each(selects, function (indexInArray, valueOfElement) {
|
||||
$(valueOfElement).val($(valueOfElement).attr("data-initial-stage"))
|
||||
});
|
||||
function submitForm(elem) {
|
||||
$(elem).siblings('.add_more_submit').click()
|
||||
}
|
||||
</script>
|
||||
0
offboarding/templatetags/__init__.py
Normal file
0
offboarding/templatetags/__init__.py
Normal file
0
offboarding/templatetags/migrations/__init__.py
Normal file
0
offboarding/templatetags/migrations/__init__.py
Normal file
71
offboarding/templatetags/offboarding_filter.py
Normal file
71
offboarding/templatetags/offboarding_filter.py
Normal file
@@ -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()
|
||||
3
offboarding/tests.py
Normal file
3
offboarding/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
33
offboarding/urls.py
Normal file
33
offboarding/urls.py
Normal file
@@ -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"),
|
||||
]
|
||||
359
offboarding/views.py
Normal file
359
offboarding/views.py
Normal file
@@ -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("<script>window.location.reload()</script>")
|
||||
|
||||
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("<script>window.location.reload()</script>")
|
||||
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("<script>window.location.reload()</script>")
|
||||
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"""
|
||||
<div id="asfdoiioe09092u09un320" hx-get="/offboarding/view-offboarding-note?employee_id={employee.pk}"
|
||||
hx-target="#noteContainer"
|
||||
>
|
||||
</div>
|
||||
<script>
|
||||
$("#asfdoiioe09092u09un320").click()
|
||||
$(".oh-modal--show").removeClass("oh-modal--show")
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
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("<script>window.location.reload()</script>")
|
||||
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)
|
||||
Reference in New Issue
Block a user