from django import forms from django.db import models from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.core.validators import MinValueValidator from dateutil.relativedelta import relativedelta from horilla.models import HorillaModel from horilla_audit.methods import get_diff from horilla_audit.models import HorillaAuditLog, HorillaAuditInfo from base.models import Company, Department, JobPosition from base.horilla_company_manager import HorillaCompanyManager from employee.models import Employee """Objectives and key result section""" class Period(HorillaModel): """this is a period model used for creating period""" period_name = models.CharField(max_length=150, unique=True) start_date = models.DateField() end_date = models.DateField() company_id = models.ManyToManyField(Company, blank=True, verbose_name=_("Company")) objects = HorillaCompanyManager() 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}" class EmployeeObjective(HorillaModel): """this is a EmployObjective model used for creating Employee objectives""" STATUS_CHOICES = ( ("On Track", _("On Track")), ("Behind", _("Behind")), ("Closed", _("Closed")), ("At Risk", _("At Risk")), ("Not Started", _("Not Started")), ) 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", ) 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, ) employee_id = models.ForeignKey( Employee, null=True, blank=True, related_name="employee_objective", on_delete=models.PROTECT, verbose_name="Employee", ) key_result_id = models.ManyToManyField( KeyResult, blank=True, related_name="employee_objective", verbose_name="Key results", ) updated_at = models.DateField(auto_now=True) start_date = models.DateField(null=False, blank=False) end_date = models.DateField(null=False, blank=False) 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) objects = HorillaCompanyManager("employee_id__employee_work_info__company_id") 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() 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) class Comment(models.Model): """comments for objectives""" comment = models.CharField(max_length=150) 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, ) 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" ) def __str__(self): return f"{self.employee_id.employee_first_name} - {self.comment} " class EmployeeKeyResult(models.Model): """employee key result creation""" PROGRESS_CHOICES = ( ("%", _("Percentage")), ("#", _("Number")), ("Currency", (("$", "USD$"), ("₹", "INR"), ("€", "EUR"))), ) STATUS_CHOICES = ( ("On Track", _("On Track")), ("Behind", _("Behind")), ("Closed", _("Closed")), ("At Risk", _("At Risk")), ("Not Started", _("Not Started")), ) 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, ) 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", ) 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) def __str__(self): return f"{self.key_result_id} | {self.employee_objective_id.employee_id} " 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 # """ # unique_together = ("key_result_id", "employee_objective_id") """360degree feedback section""" class QuestionTemplate(HorillaModel): """question template creation""" question_template = models.CharField( max_length=100, null=False, blank=False, unique=True ) company_id = models.ManyToManyField(Company, blank=True, verbose_name=_("Company")) objects = HorillaCompanyManager() def __str__(self): return self.question_template class Question(HorillaModel): """question creation""" QUESTION_TYPE_CHOICE = ( ("1", _("Text")), ("2", _("Rating")), ("3", _("Boolean")), ("4", _("Multi-choices")), ("5", _("Likert")), ) question = models.CharField(max_length=250, null=False, blank=False) 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, ) objects = HorillaCompanyManager("template_id__company_id") def __str__(self): return self.question class QuestionOptions(HorillaModel): """options for question""" question_id = models.ForeignKey( Question, on_delete=models.PROTECT, related_name="question_options", null=True, blank=True, ) 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) objects = HorillaCompanyManager("question_id__template_id__company_id") class Feedback(HorillaModel): """feedback model for creating feedback""" STATUS_CHOICES = ( ("On Track", _("On Track")), ("Behind", _("Behind")), ("Closed", _("Closed")), ("At Risk", _("At Risk")), ("Not Started", _("Not Started")), ) PERIOD = ( ("days", _("Days")), ("months", _("Months")), ("years", _("Years")), ) review_cycle = models.CharField(max_length=100, null=False, blank=False) 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" ) 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) 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) def __str__(self): return f"{self.employee_id.employee_first_name} - {self.review_cycle}" 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}'." ) } ) class Answer(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="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( Feedback, on_delete=models.PROTECT, related_name="feedback_answer" ) objects = HorillaCompanyManager("employee_id__employee_work_info__company_id") def __str__(self): return f"{self.employee_id.employee_first_name} - {self.answer}" class KeyResultFeedback(models.Model): feedback_id = models.ForeignKey( Feedback, on_delete=models.PROTECT, related_name="feedback_key_result", null=True, blank=True, ) employee_id = models.ForeignKey( Employee, on_delete=models.DO_NOTHING, related_name="employee_key_result" ) answer = models.JSONField(max_length=200, null=True, blank=True) key_result_id = models.ForeignKey( EmployeeKeyResult, related_name="key_result_feedback", null=True, blank=True, on_delete=models.DO_NOTHING, ) 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()