diff --git a/pms/admin.py b/pms/admin.py
index 5cc9c0ce6..6b7341006 100644
--- a/pms/admin.py
+++ b/pms/admin.py
@@ -13,6 +13,8 @@ from .models import (
Answer,
KeyResultFeedback,
QuestionOptions,
+ Objective,
+ KeyResult
)
@@ -24,5 +26,7 @@ admin.site.register(EmployeeObjective, SimpleHistoryAdmin)
admin.site.register(EmployeeKeyResult, SimpleHistoryAdmin)
admin.site.register(objective)
admin.site.register(feedback)
+admin.site.register(KeyResult)
+admin.site.register(Objective)
admin.site.register(KeyResultFeedback)
admin.site.register(Comment, SimpleHistoryAdmin)
diff --git a/pms/filters.py b/pms/filters.py
index 022812ae7..86168bdb1 100644
--- a/pms/filters.py
+++ b/pms/filters.py
@@ -4,12 +4,14 @@ Module: filters.py
This module contains custom Django filters and filter sets for
the PMS (Performance Management System) app.
"""
+
import datetime
import django_filters
from django import forms
from django_filters import DateFilter
-from pms.models import EmployeeKeyResult, EmployeeObjective, Feedback
+from pms.models import EmployeeKeyResult, EmployeeObjective, Feedback, Objective
from base.methods import reload_queryset
+from base.filters import FilterSet
class DateRangeFilter(django_filters.Filter):
@@ -68,9 +70,7 @@ class CustomFilterSet(django_filters.FilterSet):
forms.CheckboxSelectMultiple,
),
):
- field.widget.attrs.update(
- {"class": "oh-switch__checkbox"}
- )
+ field.widget.attrs.update({"class": "oh-switch__checkbox"})
elif isinstance(widget, (forms.ModelChoiceField)):
field.widget.attrs.update(
{
@@ -81,6 +81,42 @@ class CustomFilterSet(django_filters.FilterSet):
field.lookup_expr = "icontains"
+class ActualObjectiveFilter(FilterSet):
+ """
+ ActualObjectiveFilter
+ """
+
+ search = django_filters.CharFilter(method="search_method")
+
+ class Meta:
+ model = Objective
+ fields = [
+ "managers",
+ "assignees",
+ "duration",
+ "employee_objective",
+ "employee_objective__key_result_id",
+ "employee_objective__progress_percentage",
+ ]
+
+ def search_method(self, queryset, _, value: str):
+ """
+ This method is used to search employees and objective
+ """
+ values = value.split(" ")
+ empty = queryset.model.objects.none()
+ for split in values:
+ empty = empty | (
+ queryset.filter(managers__employee_first_name__icontains=split)
+ | queryset.filter(managers__employee_last_name__icontains=split)
+ | queryset.filter(assignees__employee_first_name__icontains=split)
+ | queryset.filter(assignees__employee_last_name__icontains=split)
+ | queryset.filter(title__icontains=split)
+ )
+
+ return empty.distinct()
+
+
class ObjectiveFilter(CustomFilterSet):
"""
Custom filter set for EmployeeObjective records.
@@ -88,6 +124,17 @@ class ObjectiveFilter(CustomFilterSet):
This filter set allows to filter EmployeeObjective records based on various criteria.
"""
+ employee_objective = django_filters.CharFilter(field_name="id")
+ employee_objective__key_result_id = django_filters.CharFilter(
+ field_name="key_result_id"
+ )
+ employee_objective__progress_percentage = django_filters.CharFilter(
+ field_name="progress_percentage"
+ )
+ managers = django_filters.CharFilter(
+ field_name="objective_id__managers"
+ )
+ search = django_filters.CharFilter(method="search_method")
created_at_date_range = DateRangeFilter(field_name="created_at")
created_at = DateFilter(
widget=forms.DateInput(attrs={"type": "date", "class": "oh-input w-100"}),
@@ -121,9 +168,27 @@ class ObjectiveFilter(CustomFilterSet):
"updated_at",
"end_date",
"archive",
- "emp_obj_id",
+ "objective_id",
]
+ def search_method(self, queryset, _, value: str):
+ """
+ This method is used to search in managers and objective
+ """
+ values = value.split(" ")
+ empty = queryset.model.objects.none()
+ for split in values:
+ empty = empty | (
+ queryset.filter(objective_id__title=split)
+ | queryset.filter(
+ objective_id__managers__employee_first_name__icontains=split
+ )
+ | queryset.filter(
+ objective_id__managers__employee_last_name__icontains=split
+ )
+ )
+ return empty
+
class FeedbackFilter(CustomFilterSet):
"""
@@ -156,8 +221,9 @@ class FeedbackFilter(CustomFilterSet):
data=data, queryset=queryset, request=request, prefix=prefix
)
+
class KeyResultFilter(CustomFilterSet):
-
+
class Meta:
model = EmployeeKeyResult
fields = "__all__"
@@ -167,8 +233,9 @@ class ObjectiveReGroup:
"""
Class to keep the field name for group by option
"""
+
fields = [
- ("","select"),
- ("employee_id","Owner"),
- ("status","Status"),
- ]
\ No newline at end of file
+ ("", "select"),
+ ("employee_id", "Owner"),
+ ("status", "Status"),
+ ]
diff --git a/pms/forms.py b/pms/forms.py
index 0e1916438..b79d20ba6 100644
--- a/pms/forms.py
+++ b/pms/forms.py
@@ -10,12 +10,18 @@ from django.core.exceptions import ValidationError
from django.core.files.base import File
from django.db.models.base import Model
from django.forms.utils import ErrorList
+from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
+from employee.filters import EmployeeFilter
from employee.models import Department, JobPosition
from django.forms import ModelForm
from base.forms import ModelForm as BaseForm
+from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelectField
+from horilla_widgets.widgets.select_widgets import HorillaMultiSelectWidget
from pms.models import (
AnonymousFeedback,
+ KeyResult,
+ Objective,
Question,
EmployeeObjective,
EmployeeKeyResult,
@@ -47,92 +53,253 @@ def set_date_field_initial(instance):
return initial
-class ObjectiveForm(ModelForm):
+class ObjectiveForm(BaseForm):
"""
- A form to create or update instances of the EmployeeObjective model.
+ A form to create or update instances of the Objective, model.
"""
- OBJECTIVE_TYPES = (
- ("none", "----------"),
- ("individual", _("Individual")),
- ("job_position", _("Job position")),
- ("department", _("Department")),
+ # period = forms.ModelChoiceField(
+ # queryset=Period.objects.all(),
+ # widget=forms.Select(
+ # attrs={
+ # "onChange": "periodCheck(this)",
+ # },
+ # ),
+ # required=False,
+ # )
+ # assignees = forms.ModelMultipleChoiceField(
+ # queryset=Employee.objects.all(),
+ # required=False,
+ # widget=forms.SelectMultiple(attrs={'style': 'display:none;'})
+ # )
+ # assignees = HorillaMultiSelectField(
+ # queryset=Employee.objects.all(),
+ # widget=HorillaMultiSelectWidget(
+ # filter_route_name="employee-widget-filter",
+ # filter_class=EmployeeFilter,
+ # filter_instance_contex_name="f",
+ # filter_template_path="employee_filters.html",
+ # required=True,
+ # ),
+ # label="Assignees",
+ # )
+ start_date = forms.DateField(
+ required=False,
+ widget=forms.DateInput(attrs={"class": "oh-input w-100", "type": "date"})
)
+ add_assignees = forms.BooleanField(required=False)
+ # archive = forms.BooleanField()
- objective_type = forms.ChoiceField(
- choices=OBJECTIVE_TYPES,
- widget=forms.Select(
- attrs={
- "class": " oh-input-objective-type-choices oh-input",
- "style": "width:100%",
- }
- ),
- required=False,
- )
- department = forms.ModelChoiceField(
- queryset=Department.objects.all(),
- widget=forms.Select(
- attrs={
- "class": "oh-select oh-select--lg oh-select-no-search w-100 oh-input-objective-type-choices",
- "style": "width:100%; display:none;",
- }
- ),
- required=False,
- )
- job_position = forms.ModelChoiceField(
- queryset=JobPosition.objects.all(),
- widget=forms.Select(
- attrs={
- "class": "oh-select oh-select--lg oh-select-no-search w-100 oh-input-objective-type-choices",
- "style": "width:100%; display:none;",
- }
- ),
- required=False,
- )
- period = forms.ModelChoiceField(
- queryset=Period.objects.all(),
- empty_label="",
- widget=forms.Select(
- attrs={
- "class": " oh-select--period-change",
- "style": "width:100%; display:none;",
- }
- ),
- required=False,
- )
- employee_id = forms.ModelChoiceField(
- queryset=Employee.objects.filter(is_active=True),
- widget=forms.Select(
- attrs={
- "class": "oh-select oh-select-2 ",
- "style": "width:100%; display:none;",
- }
- ),
- required=False,
- )
+ class Meta:
+ """
+ A nested class that specifies the model,fields and style of fields for the form.
+ """
+
+ model = Objective
+ fields=[
+ 'title',
+ 'managers',
+ 'description',
+ 'duration',
+ 'add_assignees',
+ 'assignees',
+ # 'period',
+ 'start_date',
+ # 'end_date',
+ # 'archive',
+ ]
+ # widgets = {
+ # "start_date": forms.DateInput(
+ # attrs={"class": "oh-input w-100", "type": "date"}
+ # ),
+ # "end_date": forms.DateInput(
+ # attrs={"class": "oh-input w-100", "type": "date"}
+ # ),
+ # }
+
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor for ObjectiveForm. If an instance is provided, set initial values for date fields
+ """
+ # if instance := kwargs.get("instance"):
+ # kwargs["initial"] = set_date_field_initial(instance)
+
+ employee = kwargs.pop(
+ "employee", None
+ ) # access the logged-in user's information
+ super().__init__(*args, **kwargs)
+ if self.instance.pk is None:
+ self.fields["assignees"] = HorillaMultiSelectField(
+ queryset=Employee.objects.all(),
+ widget=HorillaMultiSelectWidget(
+ filter_route_name="employee-widget-filter",
+ filter_class=EmployeeFilter,
+ filter_instance_contex_name="f",
+ filter_template_path="employee_filters.html",
+ required=True,
+ ),
+ label="Assignees",
+ )
+ reload_queryset(self.fields)
+ # self.fields['start_date'].widget.attrs.update({"style":"display:none;"})
+ # self.fields['assignees'].widget.attrs.update({"style":"display:none;"})
+
+ # self.fields["period"].choices = list(self.fields["period"].choices)
+ # self.fields["period"].choices.append(("create_new_period", "Create new period"))
+
+ def clean(self):
+ """
+ Validates form fields and raises a validation error if any fields are invalid
+ """
+ cleaned_data = super().clean()
+ add_assignees = cleaned_data.get("add_assignees")
+ for field_name, field_instance in self.fields.items():
+ if isinstance(field_instance, HorillaMultiSelectField):
+ self.errors.pop(field_name, None)
+ if len(self.data.getlist(field_name)) < 1 and add_assignees:
+ raise forms.ValidationError({field_name: "This field is required"})
+ cleaned_data = super().clean()
+ data = self.fields[field_name].queryset.filter(
+ id__in=self.data.getlist(field_name)
+ )
+ cleaned_data[field_name] = data
+ cleaned_data = super().clean()
+ add_assignees = cleaned_data.get("add_assignees")
+ assignees = cleaned_data.get("assignees")
+ start_date = cleaned_data.get("start_date")
+ managers = cleaned_data.get('managers')
+ if not managers or managers == None:
+ raise forms.ValidationError(
+ "Managers is a required field"
+ )
+ if add_assignees :
+ if not assignees.exists() or start_date is None :
+ raise forms.ValidationError(
+ "Assign employees and start date"
+ )
+ start_date = cleaned_data.get("start_date")
+ end_date = cleaned_data.get("end_date")
+ # Check that start date is before end date
+ validate_date(start_date, end_date)
+ return cleaned_data
+ 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 AddAssigneesForm(BaseForm):
+ """
+ A form to create or update instances of the EmployeeObjective, model.
+ """
+ start_date = forms.DateField(
+ required=False,
+ widget=forms.DateInput(attrs={"class": "oh-input w-100", "type": "date"})
+ )
+ class Meta:
+ """
+ A nested class that specifies the model,fields and style of fields for the form.
+ """
+
+ model = Objective
+ fields=[
+ 'assignees',
+ ]
+ 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 EmployeeObjectiveForm(BaseForm):
+ """
+ A form to create or update instances of the EmployeeObjective, model.
+ """
+ key_result_id = forms.ModelChoiceField(
+ queryset=KeyResult.objects.all(),
+ label=_("Key result"),
+ widget=forms.Select(
+ attrs={
+ "class": "oh-select oh-select-2 select2-hidden-accessible",
+ "onchange": "keyResultChange($(this))",
+ }
+ ),
+ )
class Meta:
"""
A nested class that specifies the model,fields and style of fields for the form.
"""
model = EmployeeObjective
- exclude = ["status"]
+ fields=[
+ 'objective_id',
+ 'key_result_id',
+ 'start_date',
+ 'end_date',
+ 'status',
+ 'archive',
+ ]
widgets = {
- "objective": forms.TextInput(
- attrs={
- "class": "oh-input oh-input--block",
- "placeholder": _("Objective"),
- }
+ "objective_id": forms.HiddenInput(),
+ "start_date": forms.DateInput(
+ attrs={"class": "oh-input w-100", "type": "date"}
),
- "objective_description": forms.Textarea(
- attrs={
- "class": "oh-input oh-input--textarea oh-input--block",
- "placeholder": _("Objective description goes here."),
- "rows": 3,
- "cols": 40,
- }
+ "end_date": forms.DateInput(
+ attrs={"class": "oh-input w-100", "type": "date"}
),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ reload_queryset(self.fields)
+ self.fields["key_result_id"].choices = list(self.fields["key_result_id"].choices)
+ self.fields["key_result_id"].choices.append(("create_new_key_result", "Create new Key result"))
+
+
+ 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 EmployeekeyResultForm(BaseForm):
+ """
+ A form to create or update instances of the EmployeeKeyResult, model.
+ """
+ key_result_id = forms.ModelChoiceField(
+ queryset=KeyResult.objects.all(),
+ label=_("Key result"),
+ widget=forms.Select(
+ attrs={
+ "class": "oh-select oh-select-2 select2-hidden-accessible",
+ "onchange": "keyResultChange($(this))",
+ }
+ ),
+ )
+ class Meta:
+ """
+ A nested class that specifies the model,fields and style of fields for the form.
+ """
+ model = EmployeeKeyResult
+ fields=[
+ 'employee_objective_id',
+ 'key_result_id',
+ 'start_value',
+ 'current_value',
+ 'target_value',
+ 'start_date',
+ 'end_date',
+ # 'archive',
+ ]
+ widgets = {
+ 'employee_objective_id':forms.HiddenInput(),
"start_date": forms.DateInput(
attrs={"class": "oh-input w-100", "type": "date"}
),
@@ -141,79 +308,52 @@ class ObjectiveForm(ModelForm):
),
}
+ 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):
- """
- Constructor for ObjectiveForm. If an instance is provided, set initial values for date fields
- """
- if instance := kwargs.get("instance"):
- kwargs["initial"] = set_date_field_initial(instance)
-
- employee = kwargs.pop(
- "employee", None
- ) # access the logged-in user's information
super().__init__(*args, **kwargs)
+ if self.initial.get("employee_objective_id"):
+ if type(self.initial.get("employee_objective_id")) == int :
+ self.verbose_name = EmployeeObjective.objects.get(id=(self.initial.get("employee_objective_id"))).employee_id
+ else:
+ self.verbose_name = self.initial.get("employee_objective_id").employee_id
+
reload_queryset(self.fields)
- self.fields["period"].choices = list(self.fields["period"].choices)
- self.fields["period"].choices.append(("create_new_period", "Create new period"))
+ self.fields["key_result_id"].choices = list(self.fields["key_result_id"].choices)
+ self.fields["key_result_id"].choices.append(("create_new_key_result", "Create new Key result"))
+
+
- if employee and Employee.objects.filter(
- is_active=True, employee_work_info__reporting_manager_id=employee
- ):
- # manager level access
- department = employee.employee_work_info.department_id
- employees = Employee.objects.filter(
- is_active=True, employee_work_info__department_id=department
- )
- self.fields["employee_id"].queryset = employees
- self.fields["department"].queryset = Department.objects.filter(
- id=department.id
- )
- self.fields["job_position"].queryset = department.job_position.all()
+from base.forms import ModelForm as MF
+class KRForm(MF):
+ """
+ A form used for creating KeyResult object
+ """
- # Set unique IDs for employee_id fields to prevent conflicts with other forms on the same page
- self.fields["employee_id"].widget.attrs.update({"id": str(uuid.uuid4())})
-
- def clean(self):
+ class Meta:
"""
- Validates form fields and raises a validation error if any fields are invalid
+ A nested class that specifies the model,fields and exclude fields for the form.
"""
- cleaned_data = super().clean()
- start_date = cleaned_data.get("start_date")
- end_date = cleaned_data.get("end_date")
- objective_type = cleaned_data.get("objective_type")
- department = cleaned_data.get("department")
- job_position = cleaned_data.get("job_position")
- employee_id = cleaned_data.get("employee_id")
- # Check that start date is before end date
- validate_date(start_date, end_date)
-
- # Check that employee ID is provided for individual objective type
- if objective_type == "individual" and not employee_id:
- self.add_error(
- "employee_id",
- "Employee field is required for individual objective type.",
- )
-
- # Check that job position is provided for job position objective type
- if objective_type == "job_position" and not job_position:
- self.add_error(
- "job_position",
- "Job position field is required for job position objective type.",
- )
-
- # Check that department is provided for department objective type
- if objective_type == "department" and not department:
- self.add_error(
- "department",
- "Department field is required for department objective type.",
- )
-
- # Check that an objective type is selected
- if objective_type == "none":
- self.add_error("objective_type", "Please fill the objective type.")
-
- return cleaned_data
+ model = KeyResult
+ fields = "__all__"
+ exclude = [
+ "history",
+ "objects",
+ ]
+ 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 KeyResultForm(ModelForm):
diff --git a/pms/models.py b/pms/models.py
index 1c811b06f..7f8aa28d9 100644
--- a/pms/models.py
+++ b/pms/models.py
@@ -1,13 +1,17 @@
+from django import forms
from django.db import models
-from django.forms import ValidationError
+from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from base.models import Company, Department, JobPosition
from base.horilla_company_manager import HorillaCompanyManager
+from horilla_audit.methods import get_diff
# importing simple history
from simple_history.models import HistoricalRecords
from employee.models import Employee
from horilla_audit.models import HorillaAuditLog, HorillaAuditInfo
+from django.core.validators import MinValueValidator
+from dateutil.relativedelta import relativedelta
"""Objectives and key result section"""
@@ -23,6 +27,63 @@ class Period(models.Model):
def __str__(self):
return self.period_name
+
+class KeyResult(models.Model):
+ """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, null=True, blank=True, choices=PROGRESS_CHOICES
+ )
+ target_value = models.IntegerField(null=True, blank=True, default=0)
+ duration = models.IntegerField(null=True,blank=True)
+ history = HorillaAuditLog(bases=[HorillaAuditInfo])
+ objects = HorillaCompanyManager()
+ def __str__(self):
+ return f"{self.title}"
+
+class Objective(models.Model):
+ """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 = "Key results",
+ )
+ duration = models.IntegerField(default=1,validators=[MinValueValidator(0)])
+ created_at = models.DateField(auto_now_add=True)
+ add_assignees = models.BooleanField(default = False)
+ archive = models.BooleanField(default=False, null=True, blank=True)
+ history = HorillaAuditLog(bases=[HorillaAuditInfo])
+ objects = HorillaCompanyManager()
+
+ class Meta:
+ """
+ Meta class for additional options
+ """
+
+ ordering = [
+ "-id",
+ ]
+ def __str__(self):
+ return f"{self.title}"
class EmployeeObjective(models.Model):
@@ -36,16 +97,32 @@ class EmployeeObjective(models.Model):
("At Risk", _("At Risk")),
("Not Started", _("Not Started")),
)
- objective = models.CharField(null=False, blank=False, max_length=100)
- objective_description = models.TextField(blank=False, null=False,max_length=255)
+ 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,
- on_delete=models.PROTECT,
+ null =True,
+ blank = True,
related_name="employee_objective",
- null=True,
- blank=True,
+ 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)
@@ -56,13 +133,36 @@ class EmployeeObjective(models.Model):
blank=False,
default="Not Started",
)
- history = HorillaAuditLog(bases=[HorillaAuditInfo])
- archive = models.BooleanField(default=False, null=True, blank=True)
+ 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 __str__(self):
- return f"{self.employee_id.employee_first_name} - {self.objective}"
+ 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"""
@@ -89,7 +189,6 @@ class Comment(models.Model):
def __str__(self):
return f"{self.employee_id.employee_first_name} - {self.comment} "
-
class EmployeeKeyResult(models.Model):
"""employee key result creation"""
@@ -106,17 +205,23 @@ class EmployeeKeyResult(models.Model):
("Not Started", _("Not Started")),
)
- key_result = models.CharField(max_length=60, null=True, blank=False)
- key_result_description = models.TextField(blank=False, null=True,max_length=255)
- employee_objective_id = models.ForeignKey(
- EmployeeObjective, on_delete=models.CASCADE, related_name="emp_obj_id"
- )
- employee_id = models.ForeignKey(
- Employee,
- on_delete=models.DO_NOTHING,
- related_name="emp_kpi",
- null=True,
- blank=True,
+ 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
@@ -137,21 +242,57 @@ class EmployeeKeyResult(models.Model):
end_date = models.DateField(null=True, blank=True)
history = HorillaAuditLog(bases=[HorillaAuditInfo])
objects = HorillaCompanyManager("employee_id__employee_work_info__company_id")
- progress_percentage = models.IntegerField(null=True, blank=True, default=0)
+ progress_percentage = models.IntegerField(default=0)
def __str__(self):
- return f"{self.key_result} "
+ return f"{self.key_result_id} | {self.employee_objective_id.employee_id} "
- def save(self, *args, **kwargs):
- if self.employee_id is None:
- self.employee_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:
+ self.current_value = self.start_value
+ 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"""
@@ -397,3 +538,51 @@ class KeyResultFeedback(models.Model):
on_delete=models.DO_NOTHING,
)
objects = HorillaCompanyManager("employee_id__employee_work_info__company_id")
+
+
+def manipulate_existing_data():
+ # correct the existing objectives and key results, run after all apps are loaded
+ from dateutil.relativedelta import relativedelta
+ from horilla.decorators import logger
+
+
+ #for existing employee objectives
+ try:
+ emp_objectives = EmployeeObjective.objects.all()
+ for emp_objective in emp_objectives:
+ if emp_objective.objective != None:
+ objective,created = Objective.objects.get_or_create(title=emp_objective.objective)
+ objective.save()
+ objective.duration = 20
+ objective.save()
+ # emp = emp_objective.employee_id
+ # emp_objective.employee_id = emp
+ 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()
+
+ except Exception as e:
+ logger.error(e)
+
+ #for existing key results
+ try:
+ e_krs = EmployeeKeyResult.objects.all()
+ for e_kr in e_krs:
+ if e_kr.key_result != None:
+ kr,created= 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:
+ logger.error(e)
+
+
+for r in range(1):
+ manipulate_existing_data()
\ No newline at end of file
diff --git a/pms/static/src/okr/objective_creation.js b/pms/static/src/okr/objective_creation.js
index 2eb3cf92f..687f0cbc7 100644
--- a/pms/static/src/okr/objective_creation.js
+++ b/pms/static/src/okr/objective_creation.js
@@ -1,40 +1,20 @@
$(document).ready(function () {
- // objecitve type choosing
- $(".oh-input-objective-type-choices").hide();
- var $select = $('#id_objective_type').select2({
- // updating style for the objective type
- minimumResultsForSearch: -1,
- })
+ alert('ededdsdsdsd')
+ // $('#id_aassignees').hide();
+ // $('#id_start_date').hide();
- $select.data('select2').$selection.addClass('oh-select--lg--custom'); //adding css for the select
- $('#id_objective_type').on('change', function(){
- $(".oh-input-objective-type-choices").hide();
- $(this).show();
- $(this).prop("required",false)
- var value = $(this).val();
- $("#"+value).show();
- if (value=='individual') {
- value='employee_id'
- }
- $(`[name="department"]`).removeAttr('required')
- $(`[name="employee"]`).removeAttr('required')
- $(`[name="job_position"]`).removeAttr('required')
-
- $(`[name=${value}]`).attr('required',true)
- })
-
- $("#id_period").on("change",function(){
- period_id = $(this).val()
- if (period_id === 'create_new_period'){
- $.ajax({
- type: "GET",
- url: 'create-period',
- success: function (response) {
- $("#PeriodModal").addClass("oh-modal--show");
- $("#periodModalTarget").html(response);
- },
- });
- }
- });
+ // $("#id_period").on("change",function(){
+ // period_id = $(this).val()
+ // if (period_id === 'create_new_period'){
+ // $.ajax({
+ // type: "GET",
+ // url: 'create-period',
+ // success: function (response) {
+ // $("#PeriodModal").addClass("oh-modal--show");
+ // $("#periodModalTarget").html(response);
+ // },
+ // });
+ // }
+ // });
});
\ No newline at end of file
diff --git a/pms/templates/okr/add_assignees.html b/pms/templates/okr/add_assignees.html
new file mode 100644
index 000000000..edccd861d
--- /dev/null
+++ b/pms/templates/okr/add_assignees.html
@@ -0,0 +1,36 @@
+{% load static i18n%}
+{% load i18n %}
+
+
+{% if objective_form.non_field_errors %}
+
+
+
+ {% for error in objective_form.non_field_errors %}
+
+ {{ error }}
+
+ {% endfor %}
+
+
+{% endif %}
+
+
+ {% trans "Add assignees" %}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pms/templates/okr/create_period.html b/pms/templates/okr/create_period.html
index 4dd4c70fe..d2285a5cf 100644
--- a/pms/templates/okr/create_period.html
+++ b/pms/templates/okr/create_period.html
@@ -110,7 +110,6 @@
// Close the second modal and reset the form
$('#PeriodModal').removeClass('oh-modal--show');
- console.log('ghjjj')
document.periodForm.reset();
}
},
diff --git a/pms/templates/okr/emp_obj_single.html b/pms/templates/okr/emp_obj_single.html
new file mode 100644
index 000000000..e9c351bbc
--- /dev/null
+++ b/pms/templates/okr/emp_obj_single.html
@@ -0,0 +1,166 @@
+{% load i18n %}
+{% if messages %}
+
+ {% for message in messages %}
+
+
+ {{ message }}
+
+
+ {% endfor %}
+
+{% endif %}
+
+
+
+ {% trans "Details" %}
+
+
+
+
+{% if request.GET.instances_ids %}
+{% comment %}