Files
ihrm/pms/models.py

729 lines
24 KiB
Python
Raw Normal View History

from dateutil.relativedelta import relativedelta
from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
2023-12-01 15:36:51 +05:30
from base.horilla_company_manager import HorillaCompanyManager
from base.models import Company, Department, JobPosition
2023-09-09 14:10:09 +05:30
from employee.models import Employee
from horilla.models import HorillaModel
from horilla_audit.methods import get_diff
from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog
2023-05-10 15:06:57 +05:30
"""Objectives and key result section"""
class Period(HorillaModel):
2023-09-09 14:10:09 +05:30
"""this is a period model used for creating period"""
2023-05-10 15:06:57 +05:30
2023-09-09 14:10:09 +05:30
period_name = models.CharField(max_length=150, unique=True)
start_date = models.DateField()
end_date = models.DateField()
2023-12-01 15:36:51 +05:30
company_id = models.ManyToManyField(Company, blank=True, verbose_name=_("Company"))
objects = HorillaCompanyManager()
2023-05-10 15:06:57 +05:30
def __str__(self):
return self.period_name
class KeyResult(HorillaModel):
"""model used to create key results"""
PROGRESS_CHOICES = (
("%", _("Percentage")),
("#", _("Number")),
("Currency", (("$", "USD$"), ("", "INR"), ("", "EUR"))),
)
title = models.CharField(
max_length=60, null=True, blank=False, verbose_name="Title"
)
description = models.TextField(
blank=False, null=False, max_length=255, verbose_name="Description"
)
progress_type = models.CharField(
max_length=60, default="%", choices=PROGRESS_CHOICES
)
target_value = models.IntegerField(null=True, blank=True, default=100)
duration = models.IntegerField(null=True, blank=True)
history = HorillaAuditLog(bases=[HorillaAuditInfo])
company_id = models.ForeignKey(
Company,
null=True,
blank=True,
verbose_name=_("Company"),
on_delete=models.CASCADE,
)
objects = HorillaCompanyManager()
class Meta:
"""
Meta class for additional options
"""
ordering = [
"-id",
]
def __str__(self):
return f"{self.title}"
class Objective(HorillaModel):
"""Model used for creating objectives"""
title = models.CharField(
null=False, blank=False, max_length=100, verbose_name="Title"
)
description = models.TextField(
blank=False, null=False, max_length=255, verbose_name="Description"
)
managers = models.ManyToManyField(
Employee, related_name="objective", blank=True, verbose_name="Managers"
)
assignees = models.ManyToManyField(
Employee,
related_name="assignees_objective",
blank=True,
verbose_name="Assignees",
)
key_result_id = models.ManyToManyField(
KeyResult,
blank=True,
related_name="objective",
verbose_name="Default Key results",
)
duration = models.IntegerField(default=1, validators=[MinValueValidator(0)])
add_assignees = models.BooleanField(default=False)
archive = models.BooleanField(default=False, null=True, blank=True)
history = HorillaAuditLog(bases=[HorillaAuditInfo])
company_id = models.ForeignKey(
Company,
null=True,
blank=True,
verbose_name=_("Company"),
on_delete=models.CASCADE,
)
objects = HorillaCompanyManager()
class Meta:
"""
Meta class for additional options
"""
ordering = [
"-id",
]
def __str__(self):
return f"{self.title}"
2023-05-10 15:06:57 +05:30
2023-09-09 14:10:09 +05:30
class EmployeeObjective(HorillaModel):
2023-09-09 14:10:09 +05:30
"""this is a EmployObjective model used for creating Employee objectives"""
2023-05-10 15:06:57 +05:30
2023-07-26 12:15:01 +05:30
STATUS_CHOICES = (
2023-09-09 14:10:09 +05:30
("On Track", _("On Track")),
("Behind", _("Behind")),
("Closed", _("Closed")),
("At Risk", _("At Risk")),
("Not Started", _("Not Started")),
2023-07-26 12:15:01 +05:30
)
objective = models.CharField(
null=True,
blank=True,
max_length=100,
verbose_name="Title",
)
objective_description = models.TextField(
blank=True,
null=True,
max_length=255,
verbose_name="Description",
)
2023-05-10 15:06:57 +05:30
created_at = models.DateField(auto_now_add=True)
objective_id = models.ForeignKey(
Objective,
null=True,
blank=True,
related_name="employee_objective",
verbose_name="Objective",
on_delete=models.PROTECT,
)
2023-09-09 14:10:09 +05:30
employee_id = models.ForeignKey(
Employee,
null=True,
blank=True,
2023-09-09 14:10:09 +05:30
related_name="employee_objective",
on_delete=models.PROTECT,
verbose_name="Employee",
2023-09-09 14:10:09 +05:30
)
key_result_id = models.ManyToManyField(
KeyResult,
blank=True,
related_name="employee_objective",
verbose_name="Key results",
)
2023-05-10 15:06:57 +05:30
updated_at = models.DateField(auto_now=True)
start_date = models.DateField(null=False, blank=False)
end_date = models.DateField(null=False, blank=False)
2023-09-09 14:10:09 +05:30
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
null=False,
blank=False,
default="Not Started",
)
progress_percentage = models.IntegerField(default=0)
history = HorillaAuditLog(bases=[HorillaAuditInfo], related_name="history_set")
archive = models.BooleanField(default=False)
2023-12-01 15:36:51 +05:30
objects = HorillaCompanyManager("employee_id__employee_work_info__company_id")
2023-05-10 15:06:57 +05:30
def update_objective_progress(self):
"""
used for updating progress percentage when current value of key result change
"""
krs = self.employee_key_result.all()
if len(krs) > 0:
current = 0
for kr in krs:
current += kr.progress_percentage
self.progress_percentage = int(current / len(krs))
self.save()
2023-05-10 15:06:57 +05:30
def __str__(self):
return f"{self.objective_id} | {self.employee_id}"
def save(self, *args, **kwargs):
if not self.pk and self.objective_id and self.start_date:
duration = self.objective_id.duration
self.end_date = self.start_date + relativedelta(days=duration)
super().save(*args, **kwargs)
def tracking(self):
return get_diff(self)
2023-05-10 15:06:57 +05:30
class Comment(models.Model):
"""comments for objectives"""
comment = models.CharField(max_length=150)
2023-09-09 14:10:09 +05:30
employee_id = models.ForeignKey(
Employee,
on_delete=models.DO_NOTHING,
related_name="comment",
null=True,
blank=True,
)
employee_objective_id = models.ForeignKey(
EmployeeObjective,
on_delete=models.CASCADE,
related_name="emp_objective",
null=True,
blank=True,
)
2023-05-10 15:06:57 +05:30
created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
history = HorillaAuditLog(excluded_fields=["comment"], bases=[HorillaAuditInfo])
objects = HorillaCompanyManager(
related_company_field="employee_id__employee_work_info__company_id"
)
2023-05-10 15:06:57 +05:30
def __str__(self):
return f"{self.employee_id.employee_first_name} - {self.comment} "
2023-05-10 15:06:57 +05:30
class EmployeeKeyResult(models.Model):
2023-09-09 14:10:09 +05:30
"""employee key result creation"""
2023-05-10 15:06:57 +05:30
PROGRESS_CHOICES = (
2023-09-09 14:10:09 +05:30
("%", _("Percentage")),
("#", _("Number")),
("Currency", (("$", "USD$"), ("", "INR"), ("", "EUR"))),
2023-05-10 15:06:57 +05:30
)
STATUS_CHOICES = (
2023-09-09 14:10:09 +05:30
("On Track", _("On Track")),
("Behind", _("Behind")),
("Closed", _("Closed")),
("At Risk", _("At Risk")),
("Not Started", _("Not Started")),
2023-05-10 15:06:57 +05:30
)
key_result = models.CharField(max_length=60, null=True, blank=True)
key_result_description = models.TextField(blank=True, null=True, max_length=255)
employee_objective_id = models.ForeignKey(
EmployeeObjective,
null=True,
blank=True,
related_name="employee_key_result",
on_delete=models.CASCADE,
)
key_result_id = models.ForeignKey(
KeyResult,
null=True,
blank=True,
related_name="employee_key_result",
verbose_name="Key result",
on_delete=models.PROTECT,
2023-09-09 14:10:09 +05:30
)
progress_type = models.CharField(
max_length=60, null=True, blank=True, choices=PROGRESS_CHOICES
)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
null=True,
blank=True,
default="Not Started",
)
2023-05-10 15:06:57 +05:30
created_at = models.DateField(auto_now_add=True, blank=True, null=True)
updated_at = models.DateField(auto_now=True, null=True, blank=True)
start_value = models.IntegerField(null=True, blank=True, default=0)
current_value = models.IntegerField(null=True, blank=True, default=0)
target_value = models.IntegerField(null=True, blank=True, default=0)
start_date = models.DateField(null=True, blank=True)
end_date = models.DateField(null=True, blank=True)
history = HorillaAuditLog(bases=[HorillaAuditInfo])
objects = HorillaCompanyManager(
related_company_field="employee_objective_id__objective_id__company_id"
)
progress_percentage = models.IntegerField(default=0)
2023-05-10 15:06:57 +05:30
def __str__(self):
return f"{self.key_result_id} | {self.employee_objective_id.employee_id} "
2023-05-10 15:06:57 +05:30
def update_kr_progress(self):
if self.target_value != 0:
self.progress_percentage = (
int(self.current_value) / int(self.target_value)
) * 100
def clean(self):
from pms.forms import validate_date
super().clean()
start_date = self.start_date
end_date = self.end_date
# Check that start date is before end date
validate_date(start_date, end_date)
start_value = self.start_value
current_value = self.current_value
target_value = self.target_value
if target_value == 0:
raise ValidationError(
{"target_value": _("The target value can't be zero.")}
)
if start_value > current_value or start_value > target_value:
raise ValidationError(
"The start value can't be greater than current value or target value."
)
if current_value > target_value:
raise ValidationError(
{
"current_value": _(
"The current value can't be greater than target value."
)
}
)
def save(self, *args, **kwargs):
# if self.employee_id is None:
# self.employee_id = self.employee_objective_id.employee_id
# if self.target_value != 0:
if not self.pk and not self.current_value:
self.current_value = self.start_value
if self.key_result_id:
self.key_result = self.key_result_id.title
self.update_kr_progress()
super().save(*args, **kwargs)
self.employee_objective_id.update_objective_progress()
# class meta:
# """
# Meta class to add some additional options
# """
2023-05-10 15:06:57 +05:30
# unique_together = ("key_result_id", "employee_objective_id")
"""360degree feedback section"""
2023-05-10 15:06:57 +05:30
class QuestionTemplate(HorillaModel):
2023-05-10 15:06:57 +05:30
"""question template creation"""
2023-09-09 14:10:09 +05:30
question_template = models.CharField(
max_length=100, null=False, blank=False, unique=True
)
2023-12-01 15:36:51 +05:30
company_id = models.ManyToManyField(Company, blank=True, verbose_name=_("Company"))
objects = HorillaCompanyManager()
2023-05-10 15:06:57 +05:30
def __str__(self):
return self.question_template
class Question(HorillaModel):
2023-05-10 15:06:57 +05:30
"""question creation"""
2023-09-09 14:10:09 +05:30
2023-05-10 15:06:57 +05:30
QUESTION_TYPE_CHOICE = (
2023-09-09 14:10:09 +05:30
("1", _("Text")),
("2", _("Rating")),
("3", _("Boolean")),
("4", _("Multi-choices")),
("5", _("Likert")),
2023-05-10 15:06:57 +05:30
)
question = models.CharField(max_length=250, null=False, blank=False)
2023-09-09 14:10:09 +05:30
question_type = models.CharField(
choices=QUESTION_TYPE_CHOICE, max_length=100, null=True, blank=True
)
template_id = models.ForeignKey(
QuestionTemplate,
on_delete=models.CASCADE,
related_name="question",
null=True,
blank=True,
)
2023-12-01 15:36:51 +05:30
objects = HorillaCompanyManager("template_id__company_id")
2023-05-10 15:06:57 +05:30
def __str__(self):
return self.question
class QuestionOptions(HorillaModel):
2023-09-09 14:10:09 +05:30
"""options for question"""
question_id = models.ForeignKey(
Question,
2023-09-19 15:56:53 +05:30
on_delete=models.PROTECT,
2023-09-09 14:10:09 +05:30
related_name="question_options",
null=True,
blank=True,
)
2023-05-10 15:06:57 +05:30
option_a = models.CharField(max_length=250, null=True, blank=True)
option_b = models.CharField(max_length=250, null=True, blank=True)
option_c = models.CharField(max_length=250, null=True, blank=True)
option_d = models.CharField(max_length=250, null=True, blank=True)
2023-12-01 15:36:51 +05:30
objects = HorillaCompanyManager("question_id__template_id__company_id")
2023-05-10 15:06:57 +05:30
class Feedback(HorillaModel):
2023-09-09 14:10:09 +05:30
"""feedback model for creating feedback"""
2023-05-10 15:06:57 +05:30
STATUS_CHOICES = (
2023-09-09 14:10:09 +05:30
("On Track", _("On Track")),
("Behind", _("Behind")),
("Closed", _("Closed")),
("At Risk", _("At Risk")),
("Not Started", _("Not Started")),
2023-05-10 15:06:57 +05:30
)
PERIOD = (
("days", _("Days")),
("months", _("Months")),
("years", _("Years")),
)
2023-05-10 15:06:57 +05:30
review_cycle = models.CharField(max_length=100, null=False, blank=False)
2023-09-09 14:10:09 +05:30
manager_id = models.ForeignKey(
Employee,
related_name="feedback_manager",
on_delete=models.DO_NOTHING,
null=True,
blank=False,
)
employee_id = models.ForeignKey(
Employee,
on_delete=models.DO_NOTHING,
related_name="feedback_employee",
null=False,
blank=False,
)
colleague_id = models.ManyToManyField(
Employee, related_name="feedback_colleague", blank=True
)
subordinate_id = models.ManyToManyField(
Employee, related_name="feedback_subordinate", blank=True
)
question_template_id = models.ForeignKey(
QuestionTemplate,
on_delete=models.DO_NOTHING,
related_name="feedback_question_template",
null=False,
blank=False,
)
status = models.CharField(
max_length=50, choices=STATUS_CHOICES, default="Not Started"
)
2023-05-10 15:06:57 +05:30
archive = models.BooleanField(null=True, blank=True, default=False)
start_date = models.DateField(null=False, blank=False)
end_date = models.DateField(null=True, blank=False)
2023-09-09 14:10:09 +05:30
employee_key_results_id = models.ManyToManyField(
EmployeeKeyResult,
blank=True,
)
cyclic_feedback = models.BooleanField(default=False)
cyclic_feedback_days_count = models.IntegerField(blank=True, null=True)
cyclic_feedback_period = models.CharField(
max_length=50, choices=PERIOD, blank=True, null=True
)
cyclic_next_start_date = models.DateField(null=True, blank=True)
cyclic_next_end_date = models.DateField(null=True, blank=True)
objects = HorillaCompanyManager("employee_id__employee_work_info__company_id")
class Meta:
ordering = ["-id"]
def save(self, *args, **kwargs):
start_date = self.start_date
end_date = self.end_date
cyclic_feedback_period = self.cyclic_feedback_period
cyclic_feedback_days_count = self.cyclic_feedback_days_count
if cyclic_feedback_period == "months":
self.cyclic_next_start_date = self.start_date + relativedelta(
months=cyclic_feedback_days_count
)
self.cyclic_next_end_date = end_date + relativedelta(
months=cyclic_feedback_days_count
)
elif cyclic_feedback_period == "years":
self.cyclic_next_start_date = start_date + relativedelta(
years=cyclic_feedback_days_count
)
self.cyclic_next_end_date = end_date + relativedelta(
years=cyclic_feedback_days_count
)
elif cyclic_feedback_period == "days":
self.cyclic_next_start_date = start_date + relativedelta(
days=cyclic_feedback_days_count
)
self.cyclic_next_end_date = end_date + relativedelta(
days=cyclic_feedback_days_count
)
super().save(*args, **kwargs)
2023-05-10 15:06:57 +05:30
def __str__(self):
return f"{self.employee_id.employee_first_name} - {self.review_cycle}"
2023-05-10 15:06:57 +05:30
class AnonymousFeedback(models.Model):
"""feedback model for creating feedback"""
STATUS_CHOICES = (
("On Track", _("On Track")),
("Behind", _("Behind")),
("Closed", _("Closed")),
("At Risk", _("At Risk")),
("Not Started", _("Not Started")),
)
BASED_ON_CHOICES = (
("general", _("General")),
("employee", _("Employee")),
("department", _("Department")),
("job_position", _("Job Position")),
)
feedback_subject = models.CharField(max_length=100, null=False, blank=False)
based_on = models.CharField(
max_length=50, choices=BASED_ON_CHOICES, default="general"
)
employee_id = models.ForeignKey(
Employee,
on_delete=models.CASCADE,
null=True,
blank=True,
verbose_name=_("Employee"),
)
department_id = models.ForeignKey(
Department,
on_delete=models.CASCADE,
null=True,
blank=True,
verbose_name=_("Department"),
)
job_position_id = models.ForeignKey(
JobPosition,
on_delete=models.CASCADE,
null=True,
blank=True,
verbose_name=_("Job Position"),
)
status = models.CharField(
max_length=50, choices=STATUS_CHOICES, default="Not Started"
)
created_at = models.DateField(auto_now_add=True)
archive = models.BooleanField(null=True, blank=True, default=False)
anonymous_feedback_id = models.CharField(
max_length=10, null=True, blank=False, editable=False
)
feedback_description = models.TextField(null=True, blank=True, max_length=255)
objects = models.Manager()
def __str__(self) -> str:
return f"Feedback based on a {self.based_on}"
def clean(self, *args, **kwargs):
if self.based_on == "employee":
self._validate_required_field("employee_id", "Employee")
self.department_id = None
self.job_position_id = None
elif self.based_on == "department":
self._validate_required_field("department_id", "Department")
self.employee_id = None
self.job_position_id = None
elif self.based_on == "job_position":
self._validate_required_field("job_position_id", "Job Position")
self.employee_id = None
self.department_id = None
return super().clean(*args, **kwargs)
def _validate_required_field(self, field_name, field_label):
if not getattr(self, field_name):
raise ValidationError(
{
field_name: _(
f"The {field_label} field is required when the 'Based on' field is set to '{field_label}'."
)
}
)
2023-05-10 15:06:57 +05:30
class Answer(models.Model):
2023-09-09 14:10:09 +05:30
"""feedback answer model"""
2023-05-10 15:06:57 +05:30
answer = models.JSONField(max_length=200, null=True, blank=True)
2023-09-09 14:10:09 +05:30
question_id = models.ForeignKey(
Question,
on_delete=models.DO_NOTHING,
related_name="answer_question_id",
null=True,
blank=True,
)
employee_id = models.ForeignKey(
Employee,
on_delete=models.DO_NOTHING,
related_name="employee_answer",
null=True,
blank=True,
)
feedback_id = models.ForeignKey(
2023-09-19 15:56:53 +05:30
Feedback, on_delete=models.PROTECT, related_name="feedback_answer"
2023-09-09 14:10:09 +05:30
)
2023-12-01 15:36:51 +05:30
objects = HorillaCompanyManager("employee_id__employee_work_info__company_id")
2023-05-10 15:06:57 +05:30
def __str__(self):
return f"{self.employee_id.employee_first_name} - {self.answer}"
2023-09-09 14:10:09 +05:30
class KeyResultFeedback(models.Model):
feedback_id = models.ForeignKey(
Feedback,
2023-09-19 15:56:53 +05:30
on_delete=models.PROTECT,
2023-09-09 14:10:09 +05:30
related_name="feedback_key_result",
null=True,
blank=True,
)
employee_id = models.ForeignKey(
Employee, on_delete=models.DO_NOTHING, related_name="employee_key_result"
)
2023-05-10 15:06:57 +05:30
answer = models.JSONField(max_length=200, null=True, blank=True)
2023-09-09 14:10:09 +05:30
key_result_id = models.ForeignKey(
EmployeeKeyResult,
related_name="key_result_feedback",
null=True,
blank=True,
on_delete=models.DO_NOTHING,
)
2023-12-01 15:36:51 +05:30
objects = HorillaCompanyManager("employee_id__employee_work_info__company_id")
class Meetings(HorillaModel):
title = models.CharField(max_length=100)
date = models.DateTimeField(null=True, blank=True)
employee_id = models.ManyToManyField(
Employee, related_name="meeting_employee", verbose_name="Employee"
)
manager = models.ManyToManyField(Employee, related_name="meeting_manager")
answer_employees = models.ManyToManyField(
Employee,
blank=True,
related_name="meeting_answer_employees",
verbose_name="Answerable Employees",
)
question_template = models.ForeignKey(
QuestionTemplate, on_delete=models.PROTECT, null=True, blank=True
)
response = models.TextField(null=True, blank=True)
show_response = models.BooleanField(default=False)
class Meta:
verbose_name = _("Meetings")
def __str__(self):
return self.title
class MeetingsAnswer(models.Model):
"""feedback answer model"""
answer = models.JSONField(max_length=200, null=True, blank=True)
question_id = models.ForeignKey(
Question,
on_delete=models.DO_NOTHING,
related_name="meeting_answer_question_id",
null=True,
blank=True,
)
employee_id = models.ForeignKey(
Employee,
on_delete=models.DO_NOTHING,
related_name="employee_meeting_answer",
null=True,
blank=True,
verbose_name="Employee",
)
meeting_id = models.ForeignKey(
Meetings, on_delete=models.PROTECT, related_name="meeting_answer"
)
objects = HorillaCompanyManager("employee_id__employee_work_info__company_id")
def __str__(self):
return f"{self.employee_id.employee_first_name} - {self.answer}"
def manipulate_existing_data():
from dateutil.relativedelta import relativedelta
from horilla.decorators import logger
try:
for emp_objective in EmployeeObjective.objects.exclude(objective=None):
objective, _ = Objective.objects.get_or_create(
title=emp_objective.objective
)
objective.duration = 20
objective.save()
emp_objective.end_date = emp_objective.start_date + relativedelta(days=20)
emp_objective.objective_id = objective
emp_objective.objective = None
emp_objective.objective_description = None
emp_objective.save()
for e_kr in EmployeeKeyResult.objects.exclude(key_result=None):
kr, _ = KeyResult.objects.get_or_create(title=e_kr.key_result)
kr.duration = 2
kr.save()
e_kr.end_date = e_kr.start_date + relativedelta(days=2)
e_kr.key_result = None
e_kr.key_result_description = None
e_kr.key_result_id = kr
e_kr.save()
except Exception as e:
return
manipulate_existing_data()