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" %} +

+ + +
+
+
+ + {% csrf_token %} + {{form.as_p}} +
+
\ 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 %}
+ + + +
{% endcomment %} +{% endif %} +
+ +
+
+ Mary Magdalene +
+
+ {{instance.employee_id}} + + {{instance.employee_id.employee_work_info.department_id}} / + {{instance.employee_id.employee_work_info.job_position_id}} +
+
+
+
+ {% trans "Status" %} + +
+
+
+
+
+ {% trans "Title" %} + {{instance.objective_id}} +
+
+ {% trans "Description" %} + + {{instance.objective_id.description}} + +
+
+ {% comment %}
+
+ {% trans "Status" %} + +
+
{% endcomment %} +
+
+ {% trans "Start Date" %} + + {{instance.start_date}} + +
+
+ {% trans "End Date" %} + {{instance.end_date}} +
+
+
+
+ + + + {% trans "Edit" %} + + {% if instance.archive %} +
+ + +
+ {% else %} +
+ + +
+ {% endif %} +
+ {% csrf_token %} + +
+
+
+
+
diff --git a/pms/templates/okr/emp_objective/emp_objective_list.html b/pms/templates/okr/emp_objective/emp_objective_list.html new file mode 100644 index 000000000..304a83556 --- /dev/null +++ b/pms/templates/okr/emp_objective/emp_objective_list.html @@ -0,0 +1,149 @@ +{% load static i18n %} +{% load i18n %} +{% load widget_tweaks %} {% load basefilters %} + +{% for emp_objective in objective.employee_objective.all %} +
+
+
+
+
+
+ + {{emp_objective.employee_key_result.all|length}} + + {{emp_objective.employee_id}} +
+
+
+
+
+
{{emp_objective.progress_percentage}} %
+
+
+
+ {% if request.user|is_reportingmanager or perms.pms.add_employeeobjective %} + {% comment %}
{% endcomment %} + +
+ {% if request.user|is_reportingmanager or perms.pms.view_employeekeyresult %} + + + + {% endif %} + {% if request.user|is_reportingmanager or perms.pms.view_employeekeyresult %} + + + {% endif %} + {% if request.user|is_reportingmanager or perms.pms.add_employeekeyresult %} + + {% endif %} +
+
+ + +
+
+ +
+ {% endif %} +
+
+
+
+ +
+
+
+ +
+{% endfor %} \ No newline at end of file diff --git a/pms/templates/okr/emp_objective_form.html b/pms/templates/okr/emp_objective_form.html new file mode 100644 index 000000000..ddec985b7 --- /dev/null +++ b/pms/templates/okr/emp_objective_form.html @@ -0,0 +1,46 @@ +{% load static i18n%} +{% load i18n %} + +
+

+ {% trans "Update Employee Objective" %} +

+ +
+
+
+ {% csrf_token %} + {{form.as_p}} +
+
+ + + + + \ No newline at end of file diff --git a/pms/templates/okr/key_result/kr_form.html b/pms/templates/okr/key_result/kr_form.html new file mode 100644 index 000000000..dec67fdf7 --- /dev/null +++ b/pms/templates/okr/key_result/kr_form.html @@ -0,0 +1,78 @@ +{% load static i18n%} +{% load i18n %} + + +{% if objective_form.non_field_errors %} + +
+
+ {% for error in objective_form.non_field_errors %} +
+ {{ error }} +
+ {% endfor %} +
+
+{% endif %} + +
+ {% if update %} +

+ {% trans "Update Key result" %} +

+ {% else %} +

+ {% trans "Create Key result" %} +

+ {% endif %} + + +
+
+ {% if update %} +
+ {% else %} + + {% endif %} + + {% csrf_token %} + {{form.as_p}} +
+
+ + + + + + + diff --git a/pms/templates/okr/kr_list.html b/pms/templates/okr/kr_list.html new file mode 100644 index 000000000..2d87187f9 --- /dev/null +++ b/pms/templates/okr/kr_list.html @@ -0,0 +1,231 @@ +{% load static %}{% load i18n %} +{% if messages %} +
+ {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} +
+{% endif %} +{% if krs %} +
+
+
+
+
+
+ +
+
+
{% trans "Title" %}
+
{% trans "Start Value" %}
+
{% trans "Current Value" %}
+
{% trans "Target Value" %}
+
{% trans "Progress Percentage" %}
+
{% trans "Start Date" %}
+
{% trans "End Date" %}
+
{% trans "Status" %}
+
{% trans "Actions" %}
+
+
+
+ {% for kr in krs %} +
+
+
+ +
+
+
+ + {{kr.key_result_id}} + {% comment %} {% endcomment %} + + + {% comment %} {% endcomment %} + +
+
{{kr.start_value}}
+
+ +
+
{{kr.target_value}}
+
{{kr.progress_percentage}}%
+
{{kr.start_date}}
+
{{kr.end_date}}
+
+ +
+
+
+ +
+ {% csrf_token %} + +
+
+
+
+ {% endfor %} +
+
+
+ + {% comment %}
+ + {% trans "Page" %} {{ my_ticket_list.list.number }} + {%trans "of" %} {{my_ticket_list.list.paginator.num_pages }}. + + +
{% endcomment %} + +{% else %} + + {% comment %}
{% endcomment %} + {% comment %} {% endcomment %} +
+ {% trans "No valid key result assigned!" %} +
+ {% comment %}
{% endcomment %} + +{% endif %} \ No newline at end of file diff --git a/pms/templates/okr/objective_creation.html b/pms/templates/okr/objective_creation.html index 59db199a8..464836f0c 100644 --- a/pms/templates/okr/objective_creation.html +++ b/pms/templates/okr/objective_creation.html @@ -1,14 +1,8 @@ -{% extends 'index.html' %} {% load static i18n%} {% load i18n %} -{% block styles %} - -{% endblock styles %} - -{% block content %} -{% if objective_form.errors %} +{% if objective_form.non_field_errors %}
@@ -21,109 +15,102 @@
{% endif %} -
-
-
- {% csrf_token %} -
-

{% trans "Objectives" %}

-
-
-
- - {{objective_form.objective}} - {{objective_form.objective.errors}} -
-
- - {{objective_form.objective_description}} - {{objective_form.objective_description.errors}} -
-
-
-
-
-
- - {{objective_form.period}} - {{objective_form.period.errors}} -
-
-
-
- - {{objective_form.start_date}} - {{objective_form.start_date.errors}} - -
-
-
-
- - {{objective_form.end_date}} - {{objective_form.end_date.errors}} -
-
-
-
-
-
- - {{objective_form.objective_type}} - {{objective_form.objective_type.errors}} -
-
-
- - - -
-
-
- +
+
+ {% if update %} + + {% else %} + + {% endif %} + + {% csrf_token %} + {{objective_form.as_p}} + +
+ + +
+
+
+ {% csrf_token %} + {{p_form.as_p}} + +
+
+
+ -{% endblock%} - - + diff --git a/pms/templates/okr/objective_detailed_view_activity.html b/pms/templates/okr/objective_detailed_view_activity.html index 9ab002eac..0edbb6d3c 100644 --- a/pms/templates/okr/objective_detailed_view_activity.html +++ b/pms/templates/okr/objective_detailed_view_activity.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n %} {% load pmsfilters %} {% if messages %}
{% for message in messages %} @@ -10,9 +10,34 @@ {% endfor %}
{% endif %} +
+ + + + {% trans "Activities" %} +
+ + {% csrf_token %} - + + diff --git a/pms/templates/okr/objective_update.html b/pms/templates/okr/objective_update.html index 1920e7935..226dacd63 100644 --- a/pms/templates/okr/objective_update.html +++ b/pms/templates/okr/objective_update.html @@ -19,9 +19,11 @@
-
+{% csrf_token %} +{{objective_form.as_p}} + {% comment %}
{% csrf_token %}
+
{% endcomment %} diff --git a/pms/templates/okr/okr_detailed_view.html b/pms/templates/okr/okr_detailed_view.html new file mode 100644 index 000000000..e2f464ba3 --- /dev/null +++ b/pms/templates/okr/okr_detailed_view.html @@ -0,0 +1,448 @@ +{% extends 'index.html' %} +{% load static i18n %} +{% load i18n %} +{% load widget_tweaks %} +{% block content %} +{% load basefilters %} +{% include 'filter_tags.html' %} + +
+
+
+ +
+
+

{{objective}}

+ +
+ + {% if perms.pms.change_objective %} + + {% endif %} + {% if perms.pms.add_employeeobjective %} + + {% endif %} +
+ {% comment %} {% endcomment %} +
+ +
+ {% comment %} Managers : + {% for manager in employee_objective.employee_id.all %} + {{manager}} + {% endfor %} {% endcomment %} +
    +
  • + {% trans "Managers:" %} + +
    +
    + {% for manager in objective.managers.all %} + + {% endfor %} +
    +
    +
    +
  • +
  • + {% trans "Duration:" %} + {{objective.duration}} {% trans "days" %} +
  • +
  • + {% trans "Description:" %} + {{objective.description}} +
  • +
+
+
+ + {% if objective.employee_objective.all %} +
+ {% include "okr/emp_objective/emp_objective_list.html" %} +
+ + {% comment %}
+ + +
{% endcomment %} + {% else %} + +
+ Page not found. 404. +
{% trans "There are no assignees for this objective at the moment." %}
+
+ + {% endif %} + + + {% comment %}
+
{% trans "Activities" %}
+
+ +
+
+ {% trans "Activites" %} +
+
+ +
+
+ + {% include 'okr/objective_detailed_view_activity.html' %} +
+ +
+ + +
+
+ + + +
+
+ {% trans "Documents" %} +
+
+ +
+
+ +
+
{% endcomment %} + +
+ + + + + +
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/pms/templates/okr/okr_list.html b/pms/templates/okr/okr_list.html new file mode 100644 index 000000000..ccf5cbcae --- /dev/null +++ b/pms/templates/okr/okr_list.html @@ -0,0 +1,578 @@ +{% load i18n %} +{% load static %} +{% load basefilters %} + +{% include 'filter_tags.html' %} + +
+ + + {% trans "At Risk" %} + + + + {% trans "Not Started" %} + + + + + {% trans "Closed" %} + + +
+ + +{% if not request.GET.dashboard %} + +
+ {% trans "Select All Objectives" %} +
+
+ {% trans "Unselect All Objectives" %} +
+ {% comment %} {% endcomment %} + + +{% endif %} + + +
+ +
    +
  • + {% trans "Self Objective" %} + {{own_objectives.paginator.count}} +
  • + + {% if perms.pms.view_objective or request.user|filtersubordinates %} +
  • + {% trans "All Objective" %} + {{objectives.paginator.count}} +
  • + {% endif %} +
+
+
+ +
+ {% if objectives %} +
+
+
+
+ +
+
+
+ +
+
    +
+
+
+
+
+ +
+
+ +
+
+
+
{% trans "Title" %}
+
{% trans "Managers" %}
+
{% trans "Key Results" %}
+
{% trans "Assignees" %}
+
{% trans "Duration" %}
+
{% trans "Description" %}
+ +
{% trans "Actions" %}
+
+
+
+ {% for objective in objectives %} +
+
+
+ + {{objective}} +
+
+ +
+ {% for manager in objective.managers.all %} + +
+
+ Baby C. +
+ {{manager.employee_first_name|truncatechars:15}} +
+ {% if perms.pms.change_objective %} + + + + {% endif %} +
+ {% endfor %} + {{objective.managers.all|length}} {% trans "Managers" %} + +
+
+ {% for kr in objective.id|kr_count %} + +
+
+ Baby C. +
+ {{kr.key_result_id|truncatechars:15}} +
+ {% if "perms.pms.delete_keyresult" %} + + + + {% endif %} +
+ {% endfor %} + {{objective.id|kr_count|length}} {% trans "Key results" %} +
+
+ {% for emp_objective in objective.employee_objective.all %} + +
+
+ Baby C. +
+ {{emp_objective.employee_id|truncatechars:15}} +
+ + + +
+ {% endfor %} + {{ objective.employee_objective.all|length}} {% trans "Assignees" %} +
+
+ {{objective.duration}} +
+
+ {{objective.description}} +
+
+
+ {% if perms.pms.add_employeeobjective %} + + {% endif %} + {% if perms.pms.change_objective %} + + {% endif %} + {% comment %} {% endcomment %} + {% if perms.pms.delete_objective %} + {% if objective.archive %} + + {% else %} + + {% endif %} + {% endif %} + {% if perms.pms.delete_objective %} +
+ {% csrf_token %} + +
+ {% endif %} +
+
+
+ {% endfor %} +
+
+ +
+
+
+
+
+
+ +
+ + {% trans "Page" %} {{ objectives.number }} {% trans "of" %} {{ objectives.paginator.num_pages }}. + + + +
+ + {% else %} +
+
+ +
+ {% trans "No search result found!" %} +
+
+
+ {% endif %} +
+ + +
+ {% comment %} {% for objective in own_objectives %} + {{objective}} + {% endfor %} {% endcomment %} + {% if own_objectives %} +
+
+
+
+ +
+
+
+ +
+
    +
+
+
+
+
+ +
+
+ +
+
+
+
{% trans "Title" %}
+
{% trans "Managers" %}
+
{% trans "Key Results" %}
+
{% trans "Duration" %}
+
{% trans "Description" %}
+ +
{% trans "Actions" %}
+
+
+
+ {% for objective in own_objectives %} +
+
+
+ + {{objective.objective_id}} +
+
+ +
+ {% for manager in objective.objective_id.managers.all %} + +
+
+ Baby C. +
+ {{manager.employee_first_name|truncatechars:15}} +
+
+ {% endfor %} + {{objective.objective_id.managers.all|length}} {% trans "Managers" %} + +
+
+ {% for kr in objective.employee_key_result.all %} + +
+
+ Baby C. +
+ {{kr.key_result_id|truncatechars:15}}. +
+
+ {% endfor %} + {{objective.employee_key_result.all|length}} {% trans "Key results" %} + +
+ +
+ {{objective.objective_id.duration}} +
+
+ {{objective.objective_id.description}} +
+
+
+ {% if perms.pms.change_objective %} + + {% endif %} + {% if perms.pms.delete_objective %} + {% if objective.archive %} + + {% else %} + + {% endif %} + {% endif %} + {% if perms.pms.delete_objective %} +
+ {% csrf_token %} + +
+ {% endif %} +
+
+
+ {% endfor %} +
+
+ +
+
+
+
+
+
+ +
+ + {% trans "Page" %} {{ own_objectives.number }} {% trans "of" %} {{ own_objectives.paginator.num_pages }}. + + + +
+ + {% else %} + +
+
+ +
+ {% trans "No search result found!" %} +
+
+
+ + {% endif %} +
+
+
+
+ + \ No newline at end of file diff --git a/pms/templates/okr/okr_nav.html b/pms/templates/okr/okr_nav.html new file mode 100644 index 000000000..4717352a6 --- /dev/null +++ b/pms/templates/okr/okr_nav.html @@ -0,0 +1,232 @@ +{% load i18n %}{% load widget_tweaks %} {% load mathfilters %} {% load basefilters %} +
+
+

+ {% trans "Objectives" %} +

+ + + +
+
+
+
+ + +
+
+
+ + +
+ + {% comment %}
+ + +
{% endcomment %} + +
+
+ + +
+
+ + + {% if perms.pms.add_employeeobjective or request.user|filtersubordinates %} + + {% endif %} +
+
+
+
+ + \ No newline at end of file diff --git a/pms/templates/okr/okr_view.html b/pms/templates/okr/okr_view.html new file mode 100644 index 000000000..ec86d5500 --- /dev/null +++ b/pms/templates/okr/okr_view.html @@ -0,0 +1,137 @@ +{% extends 'index.html' %} +{% block content %} +{% load static %} +{% load i18n %}{% load basefilters %} + + +
+ +
+
+ {% include 'okr/okr_nav.html' %} +
+ {% include 'okr/okr_list.html' %} +
+
+ + + + + +{% endblock content %} diff --git a/pms/templatetags/__init__.py b/pms/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pms/templatetags/pmsfilters.py b/pms/templatetags/pmsfilters.py new file mode 100644 index 000000000..76b2a71c5 --- /dev/null +++ b/pms/templatetags/pmsfilters.py @@ -0,0 +1,23 @@ +from django.template.defaultfilters import register + +from pms.models import Objective + + +@register.filter(name="replace") +def replace(string): + """ + This method is used to return str of the fk fields + """ + + return string.replace("_", " ") + + +@register.filter(name="kr_count") +def kr_count(objective_id): + objective = Objective.objects.get(id=objective_id) + empl_objectives = objective.employee_objective.all() + kr_list=[] + for obj in empl_objectives: + for kr in obj.employee_key_result.all(): + kr_list.append(kr) + return kr_list \ No newline at end of file diff --git a/pms/urls.py b/pms/urls.py index 58ee0ee5c..b29e66af0 100644 --- a/pms/urls.py +++ b/pms/urls.py @@ -21,7 +21,7 @@ urlpatterns = [ "objective-archive/", views.objective_archive, name="objective-archive" ), path( - "objective-detailed-view/", + "objective-detailed-view/", views.objective_detailed_view, name="objective-detailed-view", kwargs={"model": models.EmployeeObjective}, @@ -46,11 +46,31 @@ urlpatterns = [ views.objective_detailed_view_activity, name="objective-detailed-view-activity", ), + path( + "objective-manager-remove//", + views.objective_manager_remove, + name="objective-manager-remove", + ), + path( + "key-reult-remove//", + views.key_result_remove, + name="key-result-remove", + ), + path( + "assignees-remove//", + views.assignees_remove, + name="assignees-remove", + ), path( "objective-detailed-view-comment/", views.objective_detailed_view_comment, name="objective-detailed-view-comment", ), + path( + 'kr-table-view/', + views.kr_table_view, + name="kr-table-view" + ), path( "key-result-view", views.key_result_view, @@ -235,4 +255,45 @@ urlpatterns = [ views.view_single_anonymous_feedback, name="single-anonymous-feedback-view", ), + + path( + 'view-employee-objective//',views.view_employee_objective,name='view-employee-objective' + ), + path( + 'update-employee-objective//',views.update_employee_objective,name='update-employee-objective' + ), + path( + 'archive-employee-objective//',views.archive_employee_objective,name='archive-employee-objective' + ), + path( + 'delete-employee-objective//',views.delete_employee_objective,name='delete-employee-objective' + ), + path( + 'change-employee-objective-status/',views.change_employee_objective_status,name='change-employee-objective-status' + ), + path( + 'add-assignees/',views.add_assignees,name='add-assignees' + ), + path( + 'employee-key-result-creation/',views.employee_keyresult_creation,name='employee-key-result-creation' + ), + path( + 'employee-key-result-update/',views.employee_keyresult_update,name='employee-key-result-update' + ), + path( + 'delete-employee-keyresult/',views.delete_employee_keyresult,name='delete-employee-keyresult' + ), + path( + 'employee-keyresult-update-status/',views.employee_keyresult_update_status,name='employee-keyresult-update-status' + ), + path( + 'key-result-creation', + views.key_result_create, + name='key-result-creation' + ), + path( + 'key-result-current-value-update', + views.key_result_current_value_update, + name='key-result-current-value-update' + ), ] diff --git a/pms/views.py b/pms/views.py index 67ba52e33..0f3c509d7 100644 --- a/pms/views.py +++ b/pms/views.py @@ -2,7 +2,7 @@ import json import datetime from urllib.parse import parse_qs from itertools import tee -from django.http import HttpResponse, JsonResponse +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.db.utils import IntegrityError from django.db.models import Q from django.forms import modelformset_factory @@ -12,13 +12,14 @@ from django.core.paginator import Paginator from django.utils.translation import gettext_lazy as _ from django.shortcuts import get_object_or_404, render, redirect from attendance.methods.group_by import group_by_queryset -from horilla.decorators import manager_can_enter +from horilla.decorators import manager_can_enter, permission_required from horilla.decorators import login_required, hx_request_required from notifications.signals import notify -from base.methods import get_key_instances, get_pagination +from base.methods import get_key_instances, get_pagination, sortby from base.models import Department, JobPosition from employee.models import Employee, EmployeeWorkInformation from pms.filters import ( + ActualObjectiveFilter, KeyResultFilter, ObjectiveFilter, FeedbackFilter, @@ -31,6 +32,8 @@ from pms.models import ( EmployeeObjective, Comment, Feedback, + KeyResult, + Objective, QuestionTemplate, Question, Answer, @@ -39,7 +42,11 @@ from pms.models import ( KeyResultFeedback, ) from .forms import ( + AddAssigneesForm, AnonymousFeedbackForm, + EmployeeObjectiveForm, + EmployeekeyResultForm, + KRForm, QuestionForm, ObjectiveForm, KeyResultForm, @@ -61,83 +68,34 @@ def objective_creation(request): POST: Objective created, and returnes to key result creation function """ - - employees = Employee.objects.all() - departments = Department.objects.all() - job_positions = JobPosition.objects.all() - periods = Period.objects.all() employee = request.user.employee_get objective_form = ObjectiveForm(employee=employee) - context = { - "employee": employees, - "department": departments, - "job_position": job_positions, - "period": periods, - "objective_form": objective_form, - } - if request.method == "POST": - objective_type = request.POST.get("objective_type") - if objective_type == "individual": - # if the objective is for a individual - form_objectives = ObjectiveForm(request.POST) - if form_objectives.is_valid(): - objective = form_objectives.save() - obj_id = objective.id - obj_type = "individual" - messages.success(request, _("Objective created")) - notify.send( - request.user.employee_get, - recipient=objective.employee_id.employee_user_id, - verb="You got an OKR!.", - verb_ar="لقد حققت هدفًا ونتيجة رئيسية!", - verb_de="Du hast ein Ziel-Key-Ergebnis erreicht!", - verb_es="¡Has logrado un Resultado Clave de Objetivo!", - verb_fr="Vous avez atteint un Résultat Clé d'Objectif !", - redirect=f"/pms/objective-detailed-view/{obj_id}", - icon="list-circle", - ) - return redirect(key_result_creation, obj_id, obj_type) - else: - context["objective_form"] = form_objectives - elif objective_type == "job_position" or objective_type == "department": - # if the objective is based on particular job position or department - form_objectives = ObjectiveForm(request.POST) - obj_type = "multiple" - if form_objectives.is_valid(): - objective_type = form_objectives.cleaned_data[objective_type] - # to get all employee workinformation - employee_work_informations = ( - objective_type.employeeworkinformation_set.all() - ) - # getting all the employees in the job position or department and assigning then the objective to them - objective_ids = [] - for employee in employee_work_informations: - form_objectives = ObjectiveForm(request.POST) - form = form_objectives.save(commit=False) - if Employee.objects.get(id=employee.employee_id_id).is_active: - form.employee_id = employee.employee_id - form.save() - notify.send( - request.user.employee_get, - recipient=form.employee_id.employee_user_id, - verb="You got an OKR!.", - verb_ar="لقد حققت هدفًا ونتيجة رئيسية!", - verb_de="Du hast ein Ziel-Key-Ergebnis erreicht!", - verb_es="¡Has logrado un Resultado Clave de Objetivo!", - verb_fr="Vous avez atteint un Résultat Clé d'Objectif !", - redirect=f"/pms/objective-detailed-view/{form.id}", - icon="list-circle", - ) - objective_ids.append(form.id) - obj_id = str(objective_ids) - messages.success(request, _("Objectives created")) - return redirect(key_result_creation, obj_id, obj_type) - else: - context["objective_form"] = form_objectives - elif objective_type == "none": - form_objectives = ObjectiveForm(request.POST) - context["objective_form"] = form_objectives + objective_form = ObjectiveForm(request.POST) + if objective_form.is_valid(): + objective = objective_form.save() + assignees = objective_form.cleaned_data["assignees"] + start_date = objective_form.cleaned_data["start_date"] + + messages.success(request, _("Objective created")) + if assignees: + for emp in assignees: + emp_objective = EmployeeObjective( + objective_id=objective, employee_id=emp, start_date=start_date + ) + emp_objective.save() + notify.send( + request.user.employee_get, + recipient=emp.employee_user_id, + verb="You got an OKR!.", + verb_ar="لقد حققت هدفًا ونتيجة رئيسية!", + verb_de="Du hast ein Ziel-Key-Ergebnis erreicht!", + verb_es="¡Has logrado un Resultado Clave de Objetivo!", + verb_fr="Vous avez atteint un Résultat Clé d'Objectif !", + redirect=f"/pms/objective-detailed-view/{objective.id}", + ) + return HttpResponse("") + context = {"objective_form": objective_form, "p_form": PeriodForm()} return render(request, "okr/objective_creation.html", context=context) @@ -152,37 +110,61 @@ def objective_update(request, obj_id): Returns: A HttpResponse object with the content Form errors. """ - instance = EmployeeObjective.objects.get(id=obj_id) + instance = Objective.objects.get(id=obj_id) objective_form = ObjectiveForm(instance=instance) - context = {"objective_form": objective_form} + context = {"objective_form": objective_form, "update": True} if request.method == "POST": objective_form = ObjectiveForm(request.POST, instance=instance) if objective_form.is_valid(): - objective_form.save() - if not instance.employee_id.id == int(request.POST.get("employee_id")): + objective = objective_form.save() + assignees = objective_form.cleaned_data["assignees"] + start_date = objective_form.cleaned_data["start_date"] + new_emp = [assignee for assignee in assignees] + + delete_list = [] + if objective.employee_objective.exists(): + emp_objectives = objective.employee_objective.all() + existing_emp = [emp.employee_id for emp in emp_objectives] + delete_list = [ + employee for employee in existing_emp if employee not in new_emp + ] + if len(delete_list) > 0: + for emp in delete_list: + EmployeeObjective.objects.filter( + employee_id=emp, objective_id=objective + ).delete() + for emp in new_emp: + if EmployeeObjective.objects.filter( + employee_id=emp, objective_id=objective + ).exists(): + emp_obj = EmployeeObjective.objects.filter( + employee_id=emp, objective_id=objective + ).first() + emp_obj.start_date = start_date + else: + emp_obj = EmployeeObjective( + employee_id=emp, objective_id=objective, start_date=start_date + ) + emp_obj.save() notify.send( request.user.employee_get, - recipient=instance.employee_id.employee_user_id, + recipient=emp.employee_user_id, verb="You got an OKR!.", verb_ar="لقد حققت هدفًا ونتيجة رئيسية!", verb_de="Du hast ein Ziel-Key-Ergebnis erreicht!", verb_es="¡Has logrado un Resultado Clave de Objetivo!", verb_fr="Vous avez atteint un Résultat Clé d'Objectif !", - redirect=f"/pms/objective-detailed-view/{instance.id}", - icon="eye-outline", + redirect=f"/pms/objective-detailed-view/{objective.id}", ) - messages.info( + messages.success( request, - _("Objective %(objective)s Updated") - % {"objective": instance.objective}, - ) - response = render(request, "okr/objective_update.html", context) - return HttpResponse( - response.content.decode("utf-8") + "" + _("Objective %(objective)s Updated") % {"objective": instance}, ) + return HttpResponse("") else: - context["objective_form"] = objective_form - return render(request, "okr/objective_update.html", context) + context = {"objective_form": objective_form, "update": True} + + return render(request, "okr/objective_creation.html", context) @login_required @@ -196,25 +178,52 @@ def objective_delete(request, obj_id): Redirect to Objective_list_view". """ try: - objective = EmployeeObjective.objects.get(id=obj_id) - if objective.status == "Not Started" or objective.status == "Closed": + objective = Objective.objects.get(id=obj_id) + if not objective.employee_objective.exists(): objective.delete() messages.success( request, - _("Objective %(objective)s deleted") - % {"objective": objective.objective}, + _("Objective %(objective)s deleted") % {"objective": objective}, ) else: messages.warning( request, - _("You can't delete objective %(objective)s with status %(status)s") - % {"objective": objective.objective, "status": objective.status}, + _("You can't delete objective %(objective)s,related entries exists") + % {"objective": objective}, ) except EmployeeObjective.DoesNotExist: messages.error(request, _("Objective not found.")) return redirect(objective_list_view) +@login_required +@permission_required("pms.change_objective") +def objective_manager_remove(request, obj_id, manager_id): + objective = get_object_or_404(Objective, id=obj_id) + objective.managers.remove(manager_id) + return HttpResponse("") + + +@login_required +@permission_required("pms.delete_keyresult") +def key_result_remove(request, obj_id, kr_id): + objective = get_object_or_404(EmployeeObjective, id=obj_id) + objective.key_result_id.remove(kr_id) + return HttpResponse("") + + +@login_required +@permission_required("pms.delete_keyresult") +def assignees_remove(request, obj_id, emp_id): + objective = get_object_or_404(Objective, id=obj_id) + get_object_or_404( + EmployeeObjective, employee_id=emp_id, objective_id=obj_id + ).delete() + objective.assignees.remove(emp_id) + + return HttpResponse("") + + def objective_filter_pagination(request, objective_own, objective_all): """ This view takes two arguments, all_objective,own_objecitve and returns some objects. @@ -234,31 +243,29 @@ def objective_filter_pagination(request, objective_own, objective_all): request.GET or initial_data, queryset=objective_own ) objective_filer_form = objective_filter_own.form - objective_filter_own=objective_filter_own.qs + objective_filter_own = objective_filter_own.qs.order_by("-id") objective_filter_all = ObjectiveFilter( request.GET or initial_data, queryset=objective_all ).qs - if field != "" and field is not None: - objective_filter_own = group_by_queryset( - objective_filter_own, field, request.GET.get("page"), "page" - ) - objective_filter_all = group_by_queryset( - objective_filter_all, field, request.GET.get("page"), "page" - ) - + objectives = ActualObjectiveFilter(request.GET, queryset=Objective.objects.all()).qs + objectives = Paginator(objectives, get_pagination()) objective_paginator_own = Paginator(objective_filter_own, get_pagination()) objective_paginator_all = Paginator(objective_filter_all, get_pagination()) - page_number = request.GET.get("page") - objectives_own = objective_paginator_own.get_page(page_number) - objectives_all = objective_paginator_all.get_page(page_number) + own_page = request.GET.get("page") + all_page = request.GET.get("all_page") + objectives_own = objective_paginator_own.get_page(own_page) + objectives_all = objective_paginator_all.get_page(all_page) + objectives = objectives.get_page(all_page) + now = datetime.datetime.now() data_dict = parse_qs(previous_data) get_key_instances(EmployeeObjective, data_dict) context = { "superuser": "true", + "objectives": objectives, "own_objectives": objectives_own, "all_objectives": objectives_all, - "objective_filer_form": objective_filer_form, + "objective_filer_form": ActualObjectiveFilter().form, "pg": previous_data, "current_date": now, "filter_dict": data_dict, @@ -282,9 +289,7 @@ def objective_list_view(request): ) if request.user.has_perm("pms.view_employeeobjective"): - objective_own = EmployeeObjective.objects.filter( - employee_id=employee - ) | EmployeeObjective.objects.filter(emp_obj_id__employee_id=employee) + objective_own = EmployeeObjective.objects.filter(employee_id=employee) objective_own = objective_own.distinct() objective_all = EmployeeObjective.objects.all() context = objective_filter_pagination(request, objective_own, objective_all) @@ -292,25 +297,21 @@ def objective_list_view(request): elif is_manager: # if user is a manager employees_ids = [employee.id for employee in is_manager] - objective_own = EmployeeObjective.objects.filter( - employee_id=employee - ) | EmployeeObjective.objects.filter(emp_obj_id__employee_id=employee) + objective_own = EmployeeObjective.objects.filter(employee_id=employee) objective_own = objective_own.distinct() - objective_all = EmployeeObjective.objects.filter( - employee_id__in=employees_ids - ) | EmployeeObjective.objects.filter(emp_obj_id__employee_id__in=employees_ids) + objective_all = EmployeeObjective.objects.filter(employee_id__in=employees_ids) objective_all = objective_all.distinct() context = objective_filter_pagination(request, objective_own, objective_all) else: # for normal user - objective_own = EmployeeObjective.objects.filter( - employee_id=employee - ) | EmployeeObjective.objects.filter(emp_obj_id__employee_id=employee) + objective_own = EmployeeObjective.objects.filter(employee_id=employee) objective_own = objective_own.distinct() objective_all = EmployeeObjective.objects.none() context = objective_filter_pagination(request, objective_own, objective_all) if objective_all.exists() or objective_own.exists(): - template = "okr/objective_list_view.html" + # template = "okr/objective_list_view.html" + template = "okr/okr_view.html" + else: template = "okr/objective_empty.html" return render(request, template, context) @@ -336,12 +337,11 @@ def objective_list_search(request): if request.user.has_perm("pms.view_employeeobjective"): # based on permission - objective_own = EmployeeObjective.objects.filter( - employee_id=request.user.employee_get - ).filter(objective__icontains=search_val) - objective_all = EmployeeObjective.objects.filter( - objective__icontains=search_val - ) + objective_own = EmployeeObjective.objects.filter(employee_id=employee) + objective_own = objective_own.distinct() + objective_all = EmployeeObjective.objects.all() + context = objective_filter_pagination(request, objective_own, objective_all) + context = objective_filter_pagination(request, objective_own, objective_all) elif is_manager: @@ -362,7 +362,7 @@ def objective_list_search(request): ) objective_all = EmployeeObjective.objects.none() context = objective_filter_pagination(request, objective_own, objective_all) - template = "okr/objective_list.html" + template = "okr/okr_list.html" if request.GET.get("field") != "" and request.GET.get("field") is not None: template = "okr/group_by.html" return render(request, template, context) @@ -396,7 +396,7 @@ def objective_history(emp_obj_id): history_user_id = delta.new_record.history_user history_change_date = delta.new_record.history_date employee = Employee.objects.filter(employee_user_id=history_user_id).first() - key_result = delta.new_record.key_result + key_result = delta.new_record.key_result_id changed_key_results.append( { "delta": delta, @@ -419,44 +419,47 @@ def objective_history(emp_obj_id): @login_required -def objective_detailed_view(request, emp_obj_id, **kwargs): +def objective_detailed_view(request, obj_id, **kwargs): """ this function is used to update the key result of objectives args: - emp_obj_id(int) : pimarykey of EmployeeObjective + obj_id(int) : pimarykey of EmployeeObjective return: objects to objective_detailed_view """ - objective = EmployeeObjective.objects.get(id=emp_obj_id) - key_results = EmployeeKeyResult.objects.filter(employee_objective_id=objective) - comments = Comment.objects.filter(employee_objective_id=objective) - key_results_all = objective.emp_obj_id.all() + objective = Objective.objects.get(id=obj_id) + emp_objectives = EmployeeObjective.objects.filter(objective_id=objective) + # key_results = EmployeeKeyResult.objects.filter(employee_objective_id=objective) + # comments = Comment.objects.filter(employee_objective_id=emp_objectives) + # key_results_all = objective.obj_id.all() # progress of objective calculation - total_kr = key_results_all.count() - try: - progress = int( - sum(kr.progress_percentage for kr in key_results_all) / (total_kr) - ) - except (ZeroDivisionError, TypeError): - progress = 0 + # total_kr = key_results_all.count() + # try: + # progress = int( + # sum(kr.progress_percentage for kr in key_results_all) / (total_kr) + # ) + # except (ZeroDivisionError, TypeError): + # progress = 0 - objective_form = ObjectiveForm(instance=objective) - history = objective_history(emp_obj_id) + # objective_form = ObjectiveForm(instance=objective) + # history = objective_history(emp_obj_id) now = datetime.datetime.now() context = { - "employee_key_results": key_results, - "employee_objective": objective, - "comments": comments, - "historys": history, - "progress": progress, - "objective_form": objective_form, + "emp_objectives": emp_objectives, + # "employee_key_results": key_results, + "objective": objective, + # "comments": comments, + # "historys": history, + # "progress": progress, + # "objective_form": objective_form, "key_result_form": KeyResultForm, "objective_key_result_status": EmployeeKeyResult.STATUS_CHOICES, "comment_form": ObjectiveCommentForm, "current_date": now, } - return render(request, "okr/objective_detailed_view.html", context) + # return render(request, "okr/objective_detailed_view.html", context) + return render(request, "okr/okr_detailed_view.html", context) @login_required @@ -470,12 +473,37 @@ def objective_detailed_view_activity(request, id): it will return history,comment object to objective_detailed_view_activity. """ - history = objective_history(id) objective = EmployeeObjective.objects.get(id=id) + key_result_history = objective_history(id) + history = objective.tracking() comments = Comment.objects.filter(employee_objective_id=objective) + activity_list = [] + for hist in history: + hist["date"] = hist["pair"][0].history_date + activity_list.append(hist) + for com in comments: + comment = { + "type": "comment", + "comment": com, + "date": com.created_at, + } + activity_list.append(comment) + + for key in key_result_history: + key_result = { + "type": "key_result", + "key_result": key, + "date": key["changed_date"], + } + activity_list.append(key_result) + + activity_list = sorted(activity_list, key=lambda x: x["date"], reverse=True) + context = { + "objective": objective, "historys": history, "comments": comments, + "activity_list": activity_list, } return render(request, "okr/objective_detailed_view_activity.html", context) @@ -502,6 +530,19 @@ def objective_detailed_view_comment(request, id): return redirect(objective_detailed_view_activity, id) +@login_required +def kr_table_view(request, emp_objective_id): + objective_id = request.GET.get("objective_id") + emp_objectives = EmployeeObjective.objects.get(id=emp_objective_id) + krs = emp_objectives.employee_key_result.all() + context = { + "krs": krs, + "key_result_status": EmployeeKeyResult.STATUS_CHOICES, + } + template = "okr/kr_list.html" + return render(request, template, context) + + @login_required @hx_request_required def objective_detailed_view_objective_status(request, id): @@ -612,7 +653,7 @@ def objective_archive(request, id): return: redirect to objective_list_view """ - objective = EmployeeObjective.objects.get(id=id) + objective = Objective.objects.get(id=id) if objective.archive: objective.archive = False objective.save() @@ -624,6 +665,191 @@ def objective_archive(request, id): return redirect(f"/pms/objective-list-view?{request.environ['QUERY_STRING']}") +@login_required +def add_assignees(request, obj_id): + """ + this function is used to add assigneesto the objective + args: + obj_id(int) : pimarykey of Objective + return: + redirect to add assignees form + """ + objective = Objective.objects.get(id=obj_id) + form = AddAssigneesForm(instance=objective) + if request.method == "POST": + form = AddAssigneesForm(request.POST, objective) + if form.is_valid(): + objective = form.save() + assignees = form.cleaned_data["assignees"] + start_date = form.cleaned_data["start_date"] + for emp in assignees: + if EmployeeObjective.objects.filter( + employee_id=emp, objective_id=objective + ).exists(): + emp_obj = EmployeeObjective.objects.filter( + employee_id=emp, objective_id=objective + ).first() + emp_obj.start_date = start_date + else: + emp_obj = EmployeeObjective( + employee_id=emp, objective_id=objective, start_date=start_date + ) + emp_obj.save() + notify.send( + request.user.employee_get, + recipient=emp.employee_user_id, + verb="You got an OKR!.", + verb_ar="لقد حققت هدفًا ونتيجة رئيسية!", + verb_de="Du hast ein Ziel-Key-Ergebnis erreicht!", + verb_es="¡Has logrado un Resultado Clave de Objetivo!", + verb_fr="Vous avez atteint un Résultat Clé d'Objectif !", + redirect=f"/pms/objective-detailed-view/{objective.id}", + ) + messages.info( + request, + _("Objective %(objective)s Updated") % {"objective": objective}, + ) + return HttpResponse("") + context = { + "form": form, + "objective": objective, + } + return render(request, "okr/add_assignees.html", context) + + +@login_required +@manager_can_enter(perm="pms.view_employeeobjective") +def view_employee_objective(request, emp_obj_id): + """ + This function is used to render individual view of the employee objective + args: + emp_obj_id(int) : pimarykey of EmployeeObjective + return: + redirect to individual view of employee objective + """ + emp_objective = EmployeeObjective.objects.get(id=emp_obj_id) + context = { + "instance": emp_objective, + "objective_key_result_status": EmployeeObjective.STATUS_CHOICES, + } + return render(request, "okr/emp_obj_single.html", context) + + +@login_required +@manager_can_enter(perm="pms.add_employeeobjective") +def update_employee_objective(request, emp_obj_id): + """ + This function is used to update the employee objective + args: + emp_obj_id(int) : pimarykey of EmployeeObjective + return: + redirect to form of employee objective + """ + emp_objective = EmployeeObjective.objects.get(id=emp_obj_id) + form = EmployeeObjectiveForm(instance=emp_objective) + if request.method == "POST": + form = EmployeeObjectiveForm(request.POST, instance=emp_objective) + if form.is_valid: + emp_obj = form.save(commit=False) + krs = form.cleaned_data["key_result_id"] + if krs: + for kr in krs: + emp_kr = EmployeeKeyResult( + employee_objective_id=emp_objective, key_result_id=kr + ).save() + emp_obj.save() + messages.success(request, _("Employee objective Updated successfully")) + return HttpResponse("") + context = {"form": form, "k_form": KRForm()} + return render(request, "okr/emp_objective_form.html", context=context) + + +@login_required +@manager_can_enter(perm="pms.delete_employeeobjective") +def archive_employee_objective(request, emp_obj_id): + """ + This function is used to archive or unarchive the employee objective + args: + emp_obj_id(int) : pimarykey of EmployeeObjective + return: + redirect to detailed of employee objective + """ + emp_objective = EmployeeObjective.objects.get(id=emp_obj_id) + obj_id = emp_objective.objective_id.id + single_view = request.GET.get("single_view") + if emp_objective.archive: + emp_objective.archive = False + emp_objective.save() + messages.success(request, _("Objective un-archived successfully!.")) + elif not emp_objective.archive: + emp_objective.archive = True + emp_objective.save() + messages.success(request, _("Objective archived successfully!.")) + if not single_view: + return redirect(f"/pms/objective-detailed-view/{obj_id}") + else: + return HttpResponse("") + + +@login_required +@manager_can_enter(perm="pms.delete_employeeobjective") +def delete_employee_objective(request, emp_obj_id): + """ + This function is used to delete the employee objective + args: + emp_obj_id(int) : pimarykey of EmployeeObjective + return: + redirect to detailed of employee objective + """ + emp_objective = EmployeeObjective.objects.get(id=emp_obj_id) + employee = emp_objective.employee_id + objective = emp_objective.objective_id + single_view = request.GET.get("single_view") + emp_objective.delete() + objective.assignees.remove(employee) + messages.success(request, _("Objective deleted successfully!.")) + if not single_view: + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + else: + return HttpResponse("") + + +@login_required +@manager_can_enter(perm="pms.change_employeeobjective") +def change_employee_objective_status(request, emp_obj): + """ + This function is used to change status of the employee objective + args: + emp_obj_id(int) : pimarykey of EmployeeObjective + return: + a message + """ + emp_objective = EmployeeObjective.objects.get(id=emp_obj) + status = request.POST.get("status") + if emp_objective.status != status: + emp_objective.status = status + emp_objective.save() + messages.success( + request, _("The employee objective status updated successfully.") + ) + notify.send( + request.user.employee_get, + recipient=emp_objective.employee_id, + verb=f"The status of the objective '{emp_objective.objective_id}'has been changed to {emp_objective.status}.", + # verb_ar="تم تغيير حالة التذكرة.", + # verb_de="Der Status des Tickets wurde geändert.", + # verb_es="El estado del ticket ha sido cambiado.", + # verb_fr="Le statut du ticket a été modifié.", + redirect=f"/pms/objective-detailed-view/{emp_objective.objective_id.id}", + ) + else: + messages.success( + request, _("The status of the objective is the same as selected.") + ) + + return redirect(f"/pms/view-employee-objective/{emp_objective.id}/") + + @login_required @manager_can_enter(perm="pms.add_employeekeyresult") def key_result_view(request): @@ -882,7 +1108,9 @@ def feedback_creation(request): if form.is_valid(): if key_result_ids := request.POST.getlist("employee_key_results_id"): for key_result_id in key_result_ids: - key_result = EmployeeKeyResult.objects.filter(id=key_result_id).first() + key_result = EmployeeKeyResult.objects.filter( + id=key_result_id + ).first() feedback_form = form.save() feedback_form.employee_key_results_id.add(key_result) instance = form.save() @@ -2270,3 +2498,173 @@ def delete_anonymous_feedback(request, id): def view_single_anonymous_feedback(request, id): feedback = AnonymousFeedback.objects.get(id=id) return render(request, "anonymous/single_view.html", {"feedback": feedback}) + + +@login_required +@manager_can_enter(perm="pms.add_employeekeyresult") +def employee_keyresult_creation(request, emp_obj_id): + """ + This view is for employee keyresult creation , and returns a employee keyresult form. + Returns: + GET: + employee keyresult form + POST: + employee keyresult created, and returnes to employee objective details view + """ + emp_objective = EmployeeObjective.objects.get(id=emp_obj_id) + employee = emp_objective.employee_id + emp_key_result = EmployeekeyResultForm( + initial={"employee_objective_id": emp_objective} + ) + if request.method == "POST": + emp_key_result = EmployeekeyResultForm(request.POST) + if emp_key_result.is_valid(): + emp_key_result.save() + emp_objective.update_objective_progress() + kr = emp_key_result.cleaned_data["key_result_id"] + + emp_objective.key_result_id.add(kr) + # assignees = emp_key_result.cleaned_data['assignees'] + # start_date =emp_key_result.cleaned_data['start_date'] + + messages.success(request, _("Key result assigned sucessfully.")) + + notify.send( + request.user.employee_get, + recipient=employee.employee_user_id, + verb="You got an Key Result!.", + verb_ar="لقد حصلت على نتيجة رئيسية!", + verb_de="Du hast ein Schlüsselergebnis erreicht!", + verb_es="¡Has conseguido un Resultado Clave!", + verb_fr="Vous avez obtenu un Résultat Clé!", + redirect=f"/pms/objective-detailed-view/{emp_objective.objective_id.id}", + ) + return HttpResponse("") + context = { + "form": emp_key_result, + "emp_objective": emp_objective, + "k_form": KRForm(), + } + return render(request, "okr/key_result/kr_form.html", context=context) + + +@login_required +@manager_can_enter(perm="pms.add_employeekeyresult") +def employee_keyresult_update(request, kr_id): + """ + This function is for update employee keyresult, and returns a employee keyresult form. + Returns: + GET: + employee keyresult form + POST: + employee keyresult updated, and returnes to employee objective details view + """ + emp_kr = EmployeeKeyResult.objects.get(id=kr_id) + employee = emp_kr.employee_objective_id.employee_id + emp_key_result = EmployeekeyResultForm(instance=emp_kr) + if request.method == "POST": + emp_key_result = EmployeekeyResultForm(request.POST, instance=emp_kr) + if emp_key_result.is_valid(): + emp_key_result.save() + emp_kr.employee_objective_id.update_objective_progress() + + # assignees = emp_key_result.cleaned_data['assignees'] + # start_date =emp_key_result.cleaned_data['start_date'] + + messages.success(request, _("Key result Updated sucessfully.")) + + notify.send( + request.user.employee_get, + recipient=employee.employee_user_id, + verb="Your Key Result updated.", + verb_ar="تم تحديث نتيجتك الرئيسية.", + verb_de="Ihr Schlüsselergebnis wurde aktualisiert.", + verb_es="Se ha actualizado su Resultado Clave.", + verb_fr="Votre Résultat Clé a été mis à jour.", + redirect=f"/pms/objective-detailed-view/{emp_kr.employee_objective_id.objective_id.id}", + ) + return HttpResponse("") + + context = { + "form": emp_key_result, + "update": True, + } + return render(request, "okr/key_result/kr_form.html", context=context) + + +@login_required +@manager_can_enter(perm="pms.delete_employeekeyresult") +def delete_employee_keyresult(request, kr_id): + """ + This function is used to delete the employee key result + args: + kr_id(int) : pimarykey of EmployeeKeyResult + return: + redirect to detailed of employee objective + """ + emp_kr = EmployeeKeyResult.objects.get(id=kr_id) + # employee = emp_kr.employee_id + objective = emp_kr.employee_objective_id.objective_id + emp_objective = emp_kr.employee_objective_id + emp_kr.delete() + emp_objective.update_objective_progress() + # objective.assignees.remove(employee) + messages.success(request, _("Objective deleted successfully!.")) + return redirect(f"/pms/objective-detailed-view/{objective.id}") + + +@login_required +def employee_keyresult_update_status(request, kr_id): + """ + This function is used to delete the employee key result + args: + kr_id(int) : pimarykey of EmployeeKeyResult + return: + redirect to detailed of employee objective + """ + emp_kr = EmployeeKeyResult.objects.get(id=kr_id) + status = request.POST.get("key_result_status") + emp_kr.status = status + emp_kr.save() + messages.success(request, _("Key result sattus changed to {}.").format(status)) + return redirect( + f"/pms/kr-table-view/{emp_kr.employee_objective_id.id}?&objective_id={emp_kr.employee_objective_id.objective_id.id}" + ) + + +@login_required +def key_result_create(request): + """ + This method renders form and template to create Ticket type + """ + if request.method == "POST": + form = KRForm(request.POST) + if form.is_valid(): + instance = form.save() + response = { + "errors": "no_error", + "kr_id": instance.id, + "title": instance.title, + } + return JsonResponse(response) + + errors = form.errors.as_json() + return JsonResponse({"errors": errors}) + + +@login_required +def key_result_current_value_update(request): + """ + This method is used to update keyresult current value + """ + try: + current_value = eval(request.POST.get("current_value")) + emp_kr_id = eval(request.POST.get("emp_key_result_id")) + emp_kr = EmployeeKeyResult.objects.get(id=emp_kr_id) + if current_value < emp_kr.target_value: + emp_kr.current_value = current_value + emp_kr.save() + emp_kr.employee_objective_id.update_objective_progress() + return JsonResponse({"type": "sucess"}) + except: + return JsonResponse({"type": "error"})