import operator
import re
from datetime import date, datetime, timezone
from dateutil.relativedelta import relativedelta
from django.apps import apps
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Value
from django.db.models.functions import Concat
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from base.horilla_company_manager import HorillaCompanyManager
from base.models import Company, Department, JobPosition
from employee.models import BonusPoint, Employee
from horilla.horilla_middlewares import _thread_locals
from horilla.models import HorillaModel
from horilla_audit.methods import get_diff
from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog
from horilla_views.cbv_methods import render_template
"""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("company_id")
def __str__(self):
return self.period_name
def action_col(self):
"""
For action column
"""
return render_template(
path="cbv/period/actions.html",
context={"instance": self},
)
def detail_view(self):
"""
detail view
"""
url = reverse("period-detail-view", kwargs={"pk": self.pk})
return url
def detail_view_actions(self):
"""
detail view actions
"""
return render_template(
path="cbv/period/detail_view_actions.html",
context={"instance": self},
)
def company_id_detail(self):
"""
interviewer in detail view
"""
company_name = self.company_id.all()
company_names_string = ", ".join([str(company) for company in company_name])
return company_names_string
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, help_text="In Days")
archive = models.BooleanField(default=False)
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}"
def get_progress_type(self):
currency_dict = dict(self.PROGRESS_CHOICES[2][1])
if self.progress_type in currency_dict:
return currency_dict[self.progress_type]
progress_dict = dict(self.PROGRESS_CHOICES)
return progress_dict.get(self.progress_type)
def action_col(self):
"""
This method for get custome coloumn .
"""
return render_template(
path="cbv/key_results/actions.html",
context={"instance": self},
)
def detail_action_col(self):
"""
This method for get custome coloumn .
"""
return render_template(
path="cbv/key_results/detail_view_actions.html",
context={"instance": self},
)
def get_avatar(self):
"""
Method will return the API URL for the avatar or the path to the profile image.
"""
sanitized_title = re.sub(r"[^a-zA-Z0-9\s]", "", self.title)
sanitized_title = sanitized_title.replace(" ", "+")
url = f"https://ui-avatars.com/api/?name={sanitized_title}&background=random"
return url
def get_delete_url(self):
"""
to get the delete url for card action delete
"""
url = reverse("delete-key-result", kwargs={"obj_id": self.pk})
return url
def get_detail_url(self):
"""
Detail view url
"""
url = reverse_lazy("key-result-detail-view", kwargs={"pk": self.pk})
return url
def get_update_url(self):
"""
to get the update url for card action update
"""
url = reverse("update-key-result", kwargs={"pk": self.pk})
return url
class Objective(HorillaModel):
"""Model used for creating objectives"""
DURATION_UNIT = (
("days", _("Days")),
("months", _("Months")),
("years", _("Years")),
)
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_unit = models.CharField(
max_length=20,
choices=DURATION_UNIT,
null=True,
blank=True,
default="days",
verbose_name="Duration Unit",
)
duration = models.IntegerField(default=1, validators=[MinValueValidator(0)])
add_assignees = models.BooleanField(default=False)
archive = models.BooleanField(default=False)
history = HorillaAuditLog(bases=[HorillaAuditInfo])
company_id = models.ForeignKey(
Company,
null=True,
blank=True,
verbose_name=_("Company"),
on_delete=models.CASCADE,
)
self_employee_progress_update = models.BooleanField(default=True)
objects = HorillaCompanyManager()
class Meta:
"""
Meta class for additional options
"""
ordering = [
"-id",
]
def __str__(self):
return f"{self.title}"
def get_instance_id(self):
return self.pk
def title_col(self):
"""
For title column
"""
return render_template(
path="cbv/objectives/title.html",
context={"instance": self},
)
def manager_col(self):
"""
For manager column
"""
return render_template(
path="cbv/objectives/manager.html",
context={"instance": self},
)
def actions_col(self):
"""
For action column
"""
return render_template(
path="cbv/objectives/actions.html",
context={"instance": self},
)
def self_action_col(self):
"""
For self action column
"""
return render_template(
path="cbv/objectives/self_objective_action.html",
context={"instance": self},
)
def key_res_col(self):
"""
For Key results column
"""
return render_template(
path="cbv/objectives/key_results.html",
context={"instance": self},
)
def self_key_res_col(self):
"""
For Key results column for employee objectives
"""
return render_template(
path="cbv/objectives/self_key_results.html",
context={"instance": self},
)
def assingnees_col(self):
"""
For Key results column
"""
return render_template(
path="cbv/objectives/assignees.html",
context={"instance": self},
)
def duration_col(self):
"""
Duration col
"""
return (
str(self.duration) + " " + dict(self.DURATION_UNIT).get(self.duration_unit)
)
def get_employee_objective(self):
request = getattr(_thread_locals, "request", None)
user = request.user.employee_get
emp_object = self.employee_objective.get(employee_id=user, objective_id=self.id)
return emp_object
def get_individual_url(self):
"""
Detail view of employee objective
"""
url = reverse_lazy("objective-detailed-view", kwargs={"obj_id": self.pk})
return url
def save(self, *args, **kwargs):
request = getattr(_thread_locals, "request", None)
selected_company = request.session.get("selected_company")
if (
not self.id
and not self.company_id
and selected_company
and selected_company != "all"
):
self.company_id = Company.find(selected_company)
super().save()
class EmployeeObjective(HorillaModel):
"""this is a EmployObjective model used for creating Employee objectives"""
STATUS_CHOICES = (
("Not Started", _("Not Started")),
("On Track", _("On Track")),
("Behind", _("Behind")),
("At Risk", _("At Risk")),
("Closed", _("Closed")),
)
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")
class Meta:
"""
Meta class for additional options
"""
unique_together = ("employee_id", "objective_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 += min(kr.progress_percentage, 100)
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
if self.objective_id.duration_unit == "days":
self.end_date = self.start_date + relativedelta(days=duration)
elif self.objective_id.duration_unit == "months":
self.end_date = self.start_date + relativedelta(months=duration)
elif self.objective_id.duration_unit == "years":
self.end_date = self.start_date + relativedelta(years=duration)
# Add assignees to the objective
objective = self.objective_id
if self.employee_id not in objective.assignees.all():
objective.assignees.add(self.employee_id)
super().save(*args, **kwargs)
def tracking(self):
return get_diff(self)
def employee_objective_detail_view(self):
"""
for detail view of page
"""
url = reverse("view-employee-objective", kwargs={"pk": self.pk})
return url
def title_col(self):
"""
For title column
"""
return render_template(
path="cbv/objectives/title.html",
context={"instance": self},
)
def emp_obj_action(self):
"""
Action in detail view
"""
return render_template(
path="cbv/objectives/emp_obj_actions.html",
context={"instance": self},
)
def status_col(self):
"""
For status column
"""
objective_key_result_status = self.STATUS_CHOICES
return render_template(
path="cbv/objectives/employee_objective_status.html",
context={
"instance": self,
"objective_key_result_status": objective_key_result_status,
},
)
def objective_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 manager_col(self):
"""
For manager column
"""
return render_template(
path="cbv/objectives/manager.html",
context={"instance": self},
)
def actions_col(self):
"""
For action column
"""
return render_template(
path="cbv/objectives/actions.html",
context={"instance": self},
)
def self_action_col(self):
"""
For self action column
"""
return render_template(
path="cbv/objectives/self_objective_action.html",
context={"instance": self},
)
def key_res_col(self):
"""
For Key results column
"""
return render_template(
path="cbv/objectives/key_results.html",
context={"instance": self},
)
def assingnees_col(self):
"""
For Key results column
"""
return render_template(
path="cbv/objectives/assignees.html",
context={"instance": self},
)
def duration_col(self):
"""
Duration col
"""
return (
str(self.objective_id.duration)
+ " "
+ dict(self.objective_id.DURATION_UNIT).get(self.objective_id.duration_unit)
)
class Comment(models.Model):
"""comments for objectives"""
comment = models.TextField()
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 = (
("Not Started", _("Not Started")),
("On Track", _("On Track")),
("Behind", _("Behind")),
("At Risk", _("At Risk")),
("Closed", _("Closed")),
)
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 get_update_url(self):
"""
to get the update url for card action update
"""
url = reverse("employee-key-result-update", kwargs={"pk": self.pk})
return url
def get_delete_url(self):
"""
to get the delete url for card action delete
"""
url = reverse("delete-employee-keyresult", kwargs={"kr_id": self.pk})
return url
def key_result_column(self):
today = datetime.today().date()
return render_template(
path="cbv/dashboard/keyresult_col.html",
context={"instance": self, "today": today},
)
def actions_col(self):
return render_template(
path="cbv/dashboard/actions.html",
context={"instance": self},
)
def title_col(self):
"""
For title column
"""
due = None
color = "success"
if self.end_date:
due = (
f"due {self.end_date}"
if self.end_date == date.today()
else f"due in{self.end_date - date.today()}"
)
if self.end_date < date.today():
color = "danger"
elif self.end_date == date.today():
color = "warning"
col = f"""
{self.key_result}