Files
ihrm/offboarding/models.py

610 lines
18 KiB
Python
Raw Normal View History

import json
from ast import literal_eval
from collections.abc import Iterable
from datetime import date, timedelta
[IMP] Remove inter module dependency (#274) This commit introduces significant changes to the architecture of the Horilla HRMS system by decoupling interdependent modules. The following modifications were made: 1. **Module Independence**: Each module has been refactored to eliminate reliance on other modules, promoting a more modular and maintainable codebase. 2. **Refactored Imports and Dependencies**: Adjusted import statements and dependency injections to support independent module operation. 3. **Compatibility and Functionality**: Ensured that all modules are compatible with existing systems and maintain their intended functionality both independently and when integrated with other modules. These changes enhance the modularity, maintainability, and scalability of the Horilla HRMS, allowing developers to work on individual modules without affecting the entire system. Future development and deployment will be more efficient and less prone to issues arising from tightly coupled code. **NOTE** For existing Horilla users, if you face any issues during the migrations, please run the following command and try again the migrations. - `python3 manage.py makemigrations` - `python3 manage.py migrate base` - `python3 manage.py migrate` * [IMP] ASSET: Asset module dependency removal from other Horilla apps * [IMP] ATTENDANCE: Attendance module dependency removal from other Horilla apps * [IMP] BASE: Base module dependency removal from other Horilla apps * [IMP] EMPLOYEE: Employee module dependency removal from other Horilla apps * [IMP] HELPDESK: Helpdesk module dependency removal from other Horilla apps * [IMP] HORILLA AUDIT: Horilla Audit module dependency removal from other Horilla apps * [IMP] HORILLA CRUMBS: Horilla Crumbs module dependency removal from other Horilla apps * [IMP] HORILLA AUTOMATIONS: Horilla Automations module dependency removal from other Horilla apps * [IMP] HORILLA VIEWS: Horilla Views module dependency removal from other Horilla apps * [IMP] LEAVE: Leave module dependency removal from other Horilla apps * [IMP] OFFBOARDING: Offboarding module dependency removal from other Horilla apps * [IMP] ONBOARDING: Onboarding module dependency removal from other Horilla apps * [IMP] PMS: PMS module dependency removal from other Horilla apps * [IMP] PAYROLL: Payroll module dependency removal from other Horilla apps * [IMP] RECRUITMENT: Recruitment module dependency removal from other Horilla apps * [IMP] HORILLA: Dependency removal updates * [IMP] TEMPLATES: Dependency removal updates * [IMP] STATIC: Dependency removal updates * [IMP] HORILLA DOCUMENTS: Horilla Documents module dependency removal from other Horilla apps * [ADD] HORILLA: methods.py * [UPDT] HORILLA: Settings.py * [FIX] EMPLOYEE: About tab issue * Update horilla_settings.py * Remove dummy db init password
2024-08-05 14:22:44 +05:30
from django.apps import apps
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse_lazy
from django.utils.timesince import timesince
from django.utils.translation import gettext_lazy as _
2024-02-26 20:17:39 +05:30
from base.horilla_company_manager import HorillaCompanyManager
2024-01-25 15:59:15 +05:30
from base.models import Company
from employee.models import Employee
from horilla import horilla_middlewares
from horilla.horilla_middlewares import _thread_locals
[IMP] Remove inter module dependency (#274) This commit introduces significant changes to the architecture of the Horilla HRMS system by decoupling interdependent modules. The following modifications were made: 1. **Module Independence**: Each module has been refactored to eliminate reliance on other modules, promoting a more modular and maintainable codebase. 2. **Refactored Imports and Dependencies**: Adjusted import statements and dependency injections to support independent module operation. 3. **Compatibility and Functionality**: Ensured that all modules are compatible with existing systems and maintain their intended functionality both independently and when integrated with other modules. These changes enhance the modularity, maintainability, and scalability of the Horilla HRMS, allowing developers to work on individual modules without affecting the entire system. Future development and deployment will be more efficient and less prone to issues arising from tightly coupled code. **NOTE** For existing Horilla users, if you face any issues during the migrations, please run the following command and try again the migrations. - `python3 manage.py makemigrations` - `python3 manage.py migrate base` - `python3 manage.py migrate` * [IMP] ASSET: Asset module dependency removal from other Horilla apps * [IMP] ATTENDANCE: Attendance module dependency removal from other Horilla apps * [IMP] BASE: Base module dependency removal from other Horilla apps * [IMP] EMPLOYEE: Employee module dependency removal from other Horilla apps * [IMP] HELPDESK: Helpdesk module dependency removal from other Horilla apps * [IMP] HORILLA AUDIT: Horilla Audit module dependency removal from other Horilla apps * [IMP] HORILLA CRUMBS: Horilla Crumbs module dependency removal from other Horilla apps * [IMP] HORILLA AUTOMATIONS: Horilla Automations module dependency removal from other Horilla apps * [IMP] HORILLA VIEWS: Horilla Views module dependency removal from other Horilla apps * [IMP] LEAVE: Leave module dependency removal from other Horilla apps * [IMP] OFFBOARDING: Offboarding module dependency removal from other Horilla apps * [IMP] ONBOARDING: Onboarding module dependency removal from other Horilla apps * [IMP] PMS: PMS module dependency removal from other Horilla apps * [IMP] PAYROLL: Payroll module dependency removal from other Horilla apps * [IMP] RECRUITMENT: Recruitment module dependency removal from other Horilla apps * [IMP] HORILLA: Dependency removal updates * [IMP] TEMPLATES: Dependency removal updates * [IMP] STATIC: Dependency removal updates * [IMP] HORILLA DOCUMENTS: Horilla Documents module dependency removal from other Horilla apps * [ADD] HORILLA: methods.py * [UPDT] HORILLA: Settings.py * [FIX] EMPLOYEE: About tab issue * Update horilla_settings.py * Remove dummy db init password
2024-08-05 14:22:44 +05:30
from horilla.methods import get_horilla_model_class
from horilla.models import HorillaModel, upload_path
from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog
from horilla_views.cbv_methods import render_template
from notifications.signals import notify
# Create your models here.
class Offboarding(HorillaModel):
"""
Offboarding model
"""
statuses = [("ongoing", _("Ongoing")), ("completed", _("Completed"))]
title = models.CharField(max_length=20)
description = models.TextField(max_length=255)
managers = models.ManyToManyField(Employee)
status = models.CharField(max_length=10, default="ongoing", choices=statuses)
2024-01-25 15:59:15 +05:30
company_id = models.ForeignKey(
Company,
on_delete=models.CASCADE,
null=True,
verbose_name="Company",
2024-01-25 15:59:15 +05:30
)
objects = HorillaCompanyManager("company_id")
2024-01-25 15:59:15 +05:30
def __str__(self):
return self.title
def save(self, *args, **kwargs):
is_new = not self.pk
super().save(*args, **kwargs)
if is_new:
stages = [
("Exit interview", "interview"),
("Work Handover", "handover"),
("FNF", "fnf"),
("Farewell", "other"),
("Archived", "archived"),
]
for stage in stages:
new = OffboardingStage()
new.offboarding_id = self
new.title = stage[0]
new.type = stage[1]
new.save()
return
class OffboardingStage(HorillaModel):
"""
Offboarding model
"""
types = [
("notice_period", _("Notice period")),
("fnf", _("FnF Settlement")),
("other", _("Other")),
("interview", _("Interview")),
("handover", _("Work handover")),
("archived", _("Archived")),
]
title = models.CharField(max_length=20)
type = models.CharField(max_length=13, choices=types)
offboarding_id = models.ForeignKey(Offboarding, on_delete=models.CASCADE)
managers = models.ManyToManyField(Employee)
sequence = models.IntegerField(default=0, editable=False)
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"
def get_delete_url(self):
"""
This method is used to get delete url
"""
return f"{reverse_lazy('generic-delete')}?model=offboarding.OffboardingStage&pk={self.pk}"
def get_update_url(self):
"""
This method is used to get update url
"""
return f'{reverse_lazy("create-offboarding-stage", kwargs={"pk": self.pk})}?offboarding_id={self.offboarding_id.pk}'
def get_add_employee_url(self):
"""
This method is used to get add employee url
"""
url = f'{reverse_lazy("add-offboarding-employee")}?stage_id={self.id}'
return url
@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(HorillaModel):
"""
OffboardingStageMultipleFile
"""
attachment = models.FileField(upload_to=upload_path)
class OffboardingEmployee(HorillaModel):
"""
OffboardingEmployee model / Employee on stage
"""
UNIT = [("day", "days"), ("month", "Month")]
employee_id = models.OneToOneField(
Employee, on_delete=models.CASCADE, verbose_name="Employee"
)
stage_id = models.ForeignKey(
OffboardingStage, on_delete=models.CASCADE, verbose_name="Stage", null=True
)
2024-01-25 15:59:15 +05:30
notice_period = models.IntegerField(null=True)
unit = models.CharField(max_length=10, choices=UNIT, default="month", null=True)
2024-01-25 15:59:15 +05:30
notice_period_starts = models.DateField(null=True)
notice_period_ends = models.DateField(null=True, blank=True)
2024-02-26 20:17:39 +05:30
objects = HorillaCompanyManager(
related_company_field="employee_id__employee_work_info__company_id"
)
def __str__(self) -> str:
return self.employee_id.get_full_name()
def detail_subtitle(self):
"""
Return subtitle containing both department and job position information.
"""
return f"{self.employee_id.get_department()} / {self.employee_id.get_job_position()}"
def detail_view_task_custom(self):
"""
This method for get custom column for stage in detail view.
"""
tasks = self.employeetask_set.all()
return render_template(
path="cbv/exit_process/detail_view_tasks.html",
context={
"instance": self,
"tasks": tasks,
},
)
def get_individual_url(self):
"""
This method is used to get individual view url
"""
return f'{reverse_lazy("offboarding-individual-view", kwargs={"pk": self.pk})}'
def get_notice_period_col(self):
"""
This method for get custom column for notice period in detail view.
"""
notice_period_ends = self.notice_period_ends
today = date.today()
if notice_period_ends:
col = (
_("today")
if notice_period_ends == today
else (
_("Notice period ended")
if notice_period_ends < today
else (
_("In") + " " + timesince(today, notice_period_ends)
if notice_period_ends
else ""
)
)
)
return col if notice_period_ends else ""
def get_stage_col(self):
"""
This method for get custom column for stage in Pipeline view.
"""
from offboarding.forms import StageSelectForm
return render_template(
path="cbv/exit_process/pipeline_stage_col.html",
context={
"employee": self,
"stage_form": StageSelectForm(
offboarding=self.stage_id.offboarding_id,
initial={"stage_id": self.stage_id.pk},
),
"stages": self.stage_id.offboarding_id.offboardingstage_set.all(),
},
)
def get_task_status_col(self):
"""
This method for get custom column for task status in Pipeline view.
"""
completed_tasks = self.employeetask_set.filter(status="completed").count()
total_tasks = self.employeetask_set.all().count()
task_status = f"{completed_tasks} / {total_tasks}"
col = f"""
<div class="oh-checkpoint-badge oh-checkpoint-badge--primary" title="Completed {completed_tasks} of {total_tasks} tasks">
{task_status}
</div>
"""
return col
def get_action_col(self):
"""
This method for get custom column for action in Pipeline view.
"""
return render_template(
path="cbv/exit_process/pipeline_action_col.html",
context={"employee": self, "stage": self.stage_id},
)
def __getattribute__(self, name):
if name.startswith("get_") and name.endswith("_task"):
task_id = literal_eval(name[4:-5])
task = EmployeeTask.objects.filter(
task_id__id=task_id,
employee_id_id=self.id,
task_id__stage_id=self.stage_id,
).first()
return render_template(
"cbv/exit_process/tasks_cols.html",
{"instance": self, "task": task, "task_id": task_id},
)
value = super().__getattribute__(name)
return value
def ordered_group_json(self):
"""
This method is used to get ordered group json
"""
Offboarding = self.stage_id.offboarding_id
offboarding_stages = Offboarding.offboardingstage_set.all().order_by("sequence")
ordered_group_json = json.dumps(
[
{
"id": stage.id,
"stage": stage.title,
}
for stage in offboarding_stages
]
)
return ordered_group_json
def get_archive_title(self):
"""
This method is used to get title for the archive in actions
"""
return "Archive" if self.employee_id.is_active else "Un-Archive"
def get_mail_send_url(self):
"""
This method is used to get the mail send url
"""
return f'{reverse_lazy("send-mail-employee", kwargs={"emp_id": self.pk})}'
def get_notes_url(self):
"""
This method is used to get the employee note view url
"""
return (
f'{reverse_lazy("view-offboarding-note", kwargs={"employee_id": self.pk})}'
)
def get_archive_url(self):
"""
This method is used to get the mail send url
"""
return f'{reverse_lazy("employee-archive", kwargs={"obj_id": self.pk})}'
def get_edit_url(self):
"""
This method is used to get the mail send url
"""
return f'{reverse_lazy("add-employee", kwargs={"pk": self.pk})}?stage_id={self.stage_id.id}'
def get_managing_record_url(self):
"""
This method is used to get the mail send url
"""
return f'{reverse_lazy("get-manager-in")}?employee_id={self.employee_id.id}&offboarding=True'
class ResignationLetter(HorillaModel):
2024-01-25 15:59:15 +05:30
"""
Resignation Request Employee model
"""
statuses = [
("requested", _("Requested")),
("approved", _("Approved")),
("rejected", _("Rejected")),
2024-01-25 15:59:15 +05:30
]
employee_id = models.ForeignKey(
Employee, on_delete=models.CASCADE, verbose_name="Employee"
)
title = models.CharField(max_length=100, null=True)
description = models.TextField(null=True, max_length=255)
2024-01-25 15:59:15 +05:30
planned_to_leave_on = models.DateField()
status = models.CharField(max_length=10, choices=statuses, default="requested")
offboarding_employee_id = models.ForeignKey(
OffboardingEmployee, on_delete=models.CASCADE, editable=False, null=True
)
2024-02-26 20:17:39 +05:30
objects = HorillaCompanyManager(
related_company_field="employee_id__employee_work_info__company_id"
)
2024-01-25 15:59:15 +05:30
def get_status(self):
"""
Display status
"""
return dict(self.statuses).get(self.status)
def option_column(self):
"""
This method for get custome coloumn .
"""
return render_template(
path="cbv/resignation/options.html",
context={"instance": self},
)
def actions_column(self):
"""
This method for get custome coloumn .
"""
return render_template(
path="cbv/resignation/actions.html",
context={"instance": self},
)
def description_col(self):
"""
This method for get custome column .
"""
return render_template(
path="cbv/resignation/description.html",
context={"instance": self},
)
def detail_description_col(self):
"""
This method for get custome column .
"""
return render_template(
path="cbv/resignation/detail_description.html",
context={"instance": self},
)
def resignation_subtitle(self):
"""
Detail view subtitle
"""
return f"{self.employee_id.get_department()} / {self.employee_id.get_job_position()}"
def get_detail_url(self):
"""
Detail view url
"""
url = reverse_lazy("resignation-requests-detail-view", kwargs={"pk": self.pk})
return url
def get_detail_tab_url(self):
"""
Detail view url
"""
url = reverse_lazy(
"tab-resignation-requests-detail-view", kwargs={"pk": self.pk}
)
return url
2024-01-25 15:59:15 +05:30
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.status == "approved":
pass
return
def to_offboarding_employee(
self, offboarding, notice_period_starts, notice_period_ends
):
"""
This method is used to convert/add employee to offboarding
"""
offboarding_employee = OffboardingEmployee.objects.filter(
employee_id=self.employee_id
).first()
offboarding_employee = (
offboarding_employee if offboarding_employee else OffboardingEmployee()
)
offboarding_employee.employee_id = self.employee_id
offboarding_employee.stage_id = (
OffboardingStage.objects.order_by("created_at")
.filter(offboarding_id=offboarding)
.first()
)
default_notice_end = (
get_horilla_model_class(
app_label="payroll", model="payrollgeneralsetting"
).objects.first()
[IMP] Remove inter module dependency (#274) This commit introduces significant changes to the architecture of the Horilla HRMS system by decoupling interdependent modules. The following modifications were made: 1. **Module Independence**: Each module has been refactored to eliminate reliance on other modules, promoting a more modular and maintainable codebase. 2. **Refactored Imports and Dependencies**: Adjusted import statements and dependency injections to support independent module operation. 3. **Compatibility and Functionality**: Ensured that all modules are compatible with existing systems and maintain their intended functionality both independently and when integrated with other modules. These changes enhance the modularity, maintainability, and scalability of the Horilla HRMS, allowing developers to work on individual modules without affecting the entire system. Future development and deployment will be more efficient and less prone to issues arising from tightly coupled code. **NOTE** For existing Horilla users, if you face any issues during the migrations, please run the following command and try again the migrations. - `python3 manage.py makemigrations` - `python3 manage.py migrate base` - `python3 manage.py migrate` * [IMP] ASSET: Asset module dependency removal from other Horilla apps * [IMP] ATTENDANCE: Attendance module dependency removal from other Horilla apps * [IMP] BASE: Base module dependency removal from other Horilla apps * [IMP] EMPLOYEE: Employee module dependency removal from other Horilla apps * [IMP] HELPDESK: Helpdesk module dependency removal from other Horilla apps * [IMP] HORILLA AUDIT: Horilla Audit module dependency removal from other Horilla apps * [IMP] HORILLA CRUMBS: Horilla Crumbs module dependency removal from other Horilla apps * [IMP] HORILLA AUTOMATIONS: Horilla Automations module dependency removal from other Horilla apps * [IMP] HORILLA VIEWS: Horilla Views module dependency removal from other Horilla apps * [IMP] LEAVE: Leave module dependency removal from other Horilla apps * [IMP] OFFBOARDING: Offboarding module dependency removal from other Horilla apps * [IMP] ONBOARDING: Onboarding module dependency removal from other Horilla apps * [IMP] PMS: PMS module dependency removal from other Horilla apps * [IMP] PAYROLL: Payroll module dependency removal from other Horilla apps * [IMP] RECRUITMENT: Recruitment module dependency removal from other Horilla apps * [IMP] HORILLA: Dependency removal updates * [IMP] TEMPLATES: Dependency removal updates * [IMP] STATIC: Dependency removal updates * [IMP] HORILLA DOCUMENTS: Horilla Documents module dependency removal from other Horilla apps * [ADD] HORILLA: methods.py * [UPDT] HORILLA: Settings.py * [FIX] EMPLOYEE: About tab issue * Update horilla_settings.py * Remove dummy db init password
2024-08-05 14:22:44 +05:30
if apps.is_installed("payroll")
else None
)
try:
if not notice_period_ends:
notice_period_ends = (
notice_period_starts + timedelta(default_notice_end.notice_period)
if default_notice_end
else notice_period_ends
)
except:
notice_period_ends = notice_period_ends
2024-01-25 15:59:15 +05:30
offboarding_employee.notice_period_starts = notice_period_starts
offboarding_employee.notice_period_ends = notice_period_ends
if (
notice_period_starts
and notice_period_ends
and not isinstance(notice_period_starts, str)
and not isinstance(notice_period_ends, str)
):
diffs = date(
day=notice_period_ends.day,
month=notice_period_ends.month,
year=notice_period_ends.year,
) - date(
day=notice_period_starts.day,
month=notice_period_starts.month,
year=notice_period_starts.year,
)
diffs = diffs.days
offboarding_employee.notice_period = diffs if diffs > 0 else None
offboarding_employee.unit = "day" if diffs > 0 else None
2024-01-25 15:59:15 +05:30
offboarding_employee.save()
class OffboardingTask(HorillaModel):
"""
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"]
def __str__(self) -> str:
return self.title
class EmployeeTask(HorillaModel):
"""
EmployeeTask model
"""
statuses = [
("todo", _("Todo")),
("in_progress", _("In progress")),
("stuck", _("Stuck")),
("completed", _("Completed")),
]
employee_id = models.ForeignKey(
OffboardingEmployee,
on_delete=models.CASCADE,
verbose_name="Employee",
null=True,
)
status = models.CharField(max_length=20, choices=statuses, default="todo")
task_id = models.ForeignKey(OffboardingTask, on_delete=models.CASCADE)
description = models.TextField(null=True, editable=False, max_length=255)
history = HorillaAuditLog(
related_name="history_set",
bases=[
HorillaAuditInfo,
],
)
class Meta:
unique_together = ["employee_id", "task_id"]
2024-01-25 15:59:15 +05:30
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
request = getattr(_thread_locals, "request", None)
notify.send(
request.user.employee_get,
recipient=self.employee_id.employee_id.employee_user_id,
verb=f'Offboarding task "{self.task_id.title}" has been assigned',
verb_ar=f"",
verb_de=f"",
verb_es=f"",
verb_fr=f"",
redirect="offboarding/offboarding-pipeline",
icon="information",
)
class ExitReason(HorillaModel):
"""
ExitReason model
"""
title = models.CharField(max_length=50)
description = models.TextField(max_length=255)
offboarding_employee_id = models.ForeignKey(
OffboardingEmployee, on_delete=models.CASCADE
)
attachments = models.ManyToManyField(OffboardingStageMultipleFile)
class OffboardingNote(HorillaModel):
"""
OffboardingNote
"""
attachments = models.ManyToManyField(
OffboardingStageMultipleFile, blank=True, editable=False
)
description = models.TextField(null=True, blank=True, max_length=255)
note_by = models.ForeignKey(
Employee, on_delete=models.SET_NULL, null=True, editable=False
)
employee_id = models.ForeignKey(
OffboardingEmployee, on_delete=models.CASCADE, null=True, editable=False
)
stage_id = models.ForeignKey(
OffboardingStage, on_delete=models.PROTECT, null=True, editable=False
)
class Meta:
ordering = ["-created_at"]
def save(self, *args, **kwargs):
request = getattr(horilla_middlewares._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)
2024-01-25 15:59:15 +05:30
class OffboardingGeneralSetting(HorillaModel):
2024-01-25 15:59:15 +05:30
"""
OffboardingGeneralSettings
"""
resignation_request = models.BooleanField(default=False)
company_id = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)