From 52546af1a03398c709e820b21c371d61867612c4 Mon Sep 17 00:00:00 2001 From: Horilla Date: Tue, 26 Mar 2024 14:11:47 +0530 Subject: [PATCH] [ADD] PMS: Keyresult dynamic creation in objective form, and its validations --- pms/forms.py | 56 +++- pms/models.py | 8 +- .../okr/key_result/key_result_form.html | 50 +++ pms/templates/okr/objective_creation.html | 28 +- pms/templates/okr/okr_view.html | 57 +++- pms/urls.py | 18 +- pms/views.py | 298 ++++++++++-------- 7 files changed, 369 insertions(+), 146 deletions(-) create mode 100644 pms/templates/okr/key_result/key_result_form.html diff --git a/pms/forms.py b/pms/forms.py index 1614159b5..2f3f483b1 100644 --- a/pms/forms.py +++ b/pms/forms.py @@ -89,6 +89,18 @@ class ObjectiveForm(BaseForm): widget=forms.DateInput(attrs={"class": "oh-input w-100", "type": "date"}), ) add_assignees = forms.BooleanField(required=False) + # default_key_results = forms.ModelMultipleChoiceField( + # queryset=KeyResult.objects.all(), + # required=False, + # widget=forms.SelectMultiple( + # attrs={ + # "class": "oh-select oh-select-2 select2-hidden-accessible", + # "onchange": "keyResultChange($(this))", + # } + # ), + # # widget=forms.SelectMultiple(attrs={'style': 'display:none;'}) + # ) + # archive = forms.BooleanField() class Meta: @@ -102,6 +114,7 @@ class ObjectiveForm(BaseForm): "managers", "description", "duration", + 'key_result_id', "add_assignees", "assignees", # 'period', @@ -109,14 +122,20 @@ class ObjectiveForm(BaseForm): # 'end_date', # 'archive', ] - # widgets = { + widgets = { + "key_result_id":forms.SelectMultiple( + attrs={ + "class": "oh-select oh-select-2 select2-hidden-accessible", + "onchange": "keyResultChange($(this))", + } + ), # "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): """ @@ -142,6 +161,13 @@ class ObjectiveForm(BaseForm): label="Assignees", ) 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") + ) + # self.fields['start_date'].widget.attrs.update({"style":"display:none;"}) # self.fields['assignees'].widget.attrs.update({"style":"display:none;"}) @@ -384,7 +410,33 @@ class KRForm(MF): context = {"form": self} table_html = render_to_string("common_form.html", context) return table_html + + def clean(self): + cleaned_data = super().clean() + duration = cleaned_data.get('duration') + target_value = cleaned_data.get('target_value') + progress_type = cleaned_data.get('progress_type') + if duration is None or duration == '': + raise ValidationError({ + 'duration':'This field is required' + }) + if target_value is None or target_value == '': + raise ValidationError({ + 'target_value':'This field is required' + }) + if duration <= 0: + raise ValidationError({ + 'duration':'Duration cannot be less than or equal to zero' + }) + if target_value <= 0: + raise ValidationError({ + 'target_value':'Duration cannot be less than or equal to zero' + }) + if progress_type == '%' and target_value > 100 : + raise ValidationError({ + 'target_value':'Target value cannot be greater than zero for progress type "percentage"' + }) class KeyResultForm(ModelForm): """ diff --git a/pms/models.py b/pms/models.py index 50098ad0b..87343ba1a 100644 --- a/pms/models.py +++ b/pms/models.py @@ -44,9 +44,9 @@ class KeyResult(models.Model): blank=False, null=False, max_length=255, verbose_name="Description" ) progress_type = models.CharField( - max_length=60, null=True, blank=True, choices=PROGRESS_CHOICES + max_length=60, default='%', choices=PROGRESS_CHOICES ) - target_value = models.IntegerField(null=True, blank=True, default=0) + target_value = models.IntegerField(null=True, blank=True, default=100) duration = models.IntegerField(null=True, blank=True) history = HorillaAuditLog(bases=[HorillaAuditInfo]) company_id = models.ForeignKey( @@ -84,7 +84,7 @@ class Objective(models.Model): KeyResult, blank=True, related_name="objective", - verbose_name="Key results", + verbose_name="Default Key results", ) duration = models.IntegerField(default=1, validators=[MinValueValidator(0)]) created_at = models.DateField(auto_now_add=True) @@ -320,7 +320,7 @@ class EmployeeKeyResult(models.Model): # if self.employee_id is None: # self.employee_id = self.employee_objective_id.employee_id # if self.target_value != 0: - if not self.pk: + if not self.pk and not self.current_value: self.current_value = self.start_value self.update_kr_progress() super().save(*args, **kwargs) diff --git a/pms/templates/okr/key_result/key_result_form.html b/pms/templates/okr/key_result/key_result_form.html new file mode 100644 index 000000000..16e01b987 --- /dev/null +++ b/pms/templates/okr/key_result/key_result_form.html @@ -0,0 +1,50 @@ +{% load static %}{% load i18n %} + +{% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} + +
+{% endif %} + + +
+ +
+

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

+ +
+
+
+ + + {% csrf_token %} + {{k_form.as_p}} +
+
+
\ No newline at end of file diff --git a/pms/templates/okr/objective_creation.html b/pms/templates/okr/objective_creation.html index 464836f0c..5b5c032fa 100644 --- a/pms/templates/okr/objective_creation.html +++ b/pms/templates/okr/objective_creation.html @@ -1,6 +1,24 @@ {% load static i18n%} {% load i18n %} + +{% if messages %} +
+ {% for message in messages %} +
+
+ {{ message }} +
+
+ {% endfor %} + {% comment %} {% endcomment %} +
+{% endif %} + {% if objective_form.non_field_errors %} @@ -43,8 +61,7 @@ hx-target="#objectivesTarget" id="objectiveForm" > - {% endif %} - + {% endif %} {% csrf_token %} {{objective_form.as_p}} @@ -72,11 +89,18 @@ > {% trans "Save" %} + + + + + diff --git a/pms/templates/okr/okr_view.html b/pms/templates/okr/okr_view.html index ec86d5500..4ce85d59b 100644 --- a/pms/templates/okr/okr_view.html +++ b/pms/templates/okr/okr_view.html @@ -131,7 +131,62 @@ }); targetEl.classList.add("oh-tabs__content--active"); } -} + } + + //create key result dynamically + function keyResultChange(element) { + + var kr = $(element).val(); + // Check if 'create_new_key_result' exists in the list + if (kr.includes('create_new_key_result')) { + var objData = $('#objectiveForm').serialize() + $("[name=dyanamic_create]").val(objData) + $("#kRModal").show(); + } + + + } + {% comment %} function saveKeyResult(){ + var title=$('#kRModal').find('#id_title').val() + var description = $('#kRModal').find("#id_description").val() + var progress_type = $('#kRModal').find("#id_progress_type").val() + var target_value = $('#kRModal').find("#id_target_value").val() + var duration =$('#kRModal').find('#id_duration').val() + $.ajax({ + type: "post", + url: "{% url 'key-result-creation' %}", + data: { + csrfmiddlewaretoken: getCookie("csrftoken"), + "title": title, + "description":description, + 'progress_type':progress_type, + "target_value":target_value, + 'duration':duration + }, + success: function (response) { + if (response.errors === "no_error") { + var newOption = $('').val(response.kr_id).text(response.title) + $("#kRModal").hide(); + $("#id_key_result_id option[value='create_new_key_result']").before(newOption); + $("#id_key_result_id option[value='create_new_key_result']").prop('selected',false) + }else{ + + } + } + }); + } {% endcomment %} + function removeCreateKR(){ + $("#id_key_result_id option[value='create_new_key_result']").prop('selected',false) + // Select the li element + var listItem = $('li.select2-selection__choice[title="Create new Key result"]'); + + // Select the remove span within the li element + var removeSpan = listItem.find('span.select2-selection__choice__remove'); + + // Trigger a click event on the remove span + removeSpan.click(); + + } {% endblock content %} diff --git a/pms/urls.py b/pms/urls.py index 6e88c9708..cd1ff54ae 100644 --- a/pms/urls.py +++ b/pms/urls.py @@ -4,16 +4,24 @@ from . import models urlpatterns = [ + + # objectives + path("objective-list-view/", views.objective_list_view, name="objective-list-view"), path("objective-creation/", views.objective_creation, name="objective-creation"), + path( + "objective-update/", views.objective_update, name="objective-update" + ), + path("add-assignees/", views.add_assignees, name="add-assignees"), + + # key results + path("key-result-creation", views.key_result_create, name="key-result-creation"), + path( "objective-list-search", views.objective_list_search, name="objective-list-search", ), - path("objective-list-view/", views.objective_list_view, name="objective-list-view"), - path( - "objective-update/", views.objective_update, name="objective-update" - ), + path( "objective-delete/", views.objective_delete, name="objective-delete" ), @@ -284,7 +292,6 @@ urlpatterns = [ 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, @@ -305,7 +312,6 @@ urlpatterns = [ 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, diff --git a/pms/views.py b/pms/views.py index 4deeeec6b..6a9febd53 100644 --- a/pms/views.py +++ b/pms/views.py @@ -56,6 +56,75 @@ from .forms import ( QuestionTemplateForm, ) +# objectives +@login_required +def objective_list_view(request): + """ + This view is used to show all the objectives and returns some objects. + Returns: + Objects based on userlevel. + """ + user = request.user + employee = Employee.objects.filter(employee_user_id=user).first() + is_manager = Employee.objects.filter( + employee_work_info__reporting_manager_id=employee + ) + template = "okr/okr_view.html" + objective_own = EmployeeObjective.objects.filter(employee_id=employee) + objective_own = objective_own.distinct() + if request.user.has_perm("pms.view_employeeobjective"): + # 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) + + elif is_manager: + # if user is a manager + employees_ids = [employee.id for employee in is_manager] + 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) + # objective_own = objective_own.distinct() + objective_all = EmployeeObjective.objects.none() + context = objective_filter_pagination(request, objective_own, objective_all) + return render(request, template, context) + +def obj_form_save(request,objective_form): + """ + This view is used to save objective form + """ + objective = objective_form.save() + assignees = objective_form.cleaned_data["assignees"] + start_date = objective_form.cleaned_data["start_date"] + default_krs = objective_form.cleaned_data["key_result_id"] + + 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() + # assiging default key result + if default_krs: + for kr in default_krs: + emp_kr=EmployeeKeyResult( + employee_objective_id=emp_objective,key_result_id=kr,progress_type=kr.progress_type,target_value=kr.target_value + ) + emp_kr.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}", + ) @login_required @manager_can_enter(perm="pms.add_employeeobjective") @@ -70,35 +139,42 @@ def objective_creation(request): """ employee = request.user.employee_get objective_form = ObjectiveForm(employee=employee) + + if request.GET.get('key_result_id') is not None: + objective_form = ObjectiveForm(request.GET) + if request.method == "POST": 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}", - ) + obj_form_save(request,objective_form) return HttpResponse("") - context = {"objective_form": objective_form, "p_form": PeriodForm()} + context = {"objective_form": objective_form, "p_form": PeriodForm(),"k_form": KRForm()} return render(request, "okr/objective_creation.html", context=context) +@login_required +def key_result_create(request): + """ + This method renders form and template to create Ticket type + """ + form = KRForm() + redirect_url = None + if request.method == "POST": + form = KRForm(request.POST) + if form.is_valid(): + instance = form.save() + obj_data = request.POST.get("dyanamic_create") + obj_data = obj_data.replace("create_new_key_result", str(instance.id)) + messages.success( + request, + _("Key result %(key_result)s created successfully") % {"key_result": instance}, + ) + # Redirect to the desired URL with encoded query parameters + redirect_url = f'/pms/objective-creation?{obj_data}' + form = KRForm() + return render(request,'okr/key_result/key_result_form.html',{'k_form':form,'redirect_url':redirect_url}) + + @login_required @hx_request_required @manager_can_enter(perm="pms.change_employeeobjective") @@ -112,13 +188,13 @@ def objective_update(request, obj_id): """ instance = Objective.objects.get(id=obj_id) objective_form = ObjectiveForm(instance=instance) - context = {"objective_form": objective_form, "update": True} if request.method == "POST": objective_form = ObjectiveForm(request.POST, instance=instance) if objective_form.is_valid(): objective = objective_form.save() assignees = objective_form.cleaned_data["assignees"] start_date = objective_form.cleaned_data["start_date"] + default_krs = objective_form.cleaned_data["key_result_id"] new_emp = [assignee for assignee in assignees] delete_list = [] @@ -146,6 +222,17 @@ def objective_update(request, obj_id): employee_id=emp, objective_id=objective, start_date=start_date ) emp_obj.save() + # assiging default key result + if default_krs: + for kr in default_krs: + if not EmployeeKeyResult.objects.filter(employee_objective_id=emp_obj, key_result_id=kr).exists(): + emp_kr = EmployeeKeyResult.objects.create( + employee_objective_id=emp_obj, + key_result_id=kr, + progress_type=kr.progress_type, + target_value=kr.target_value + ) + notify.send( request.user.employee_get, recipient=emp.employee_user_id, @@ -161,11 +248,66 @@ def objective_update(request, obj_id): _("Objective %(objective)s Updated") % {"objective": instance}, ) return HttpResponse("") - else: - context = {"objective_form": objective_form, "update": True} + context = {"objective_form": objective_form,"k_form": KRForm(), "update": True} return render(request, "okr/objective_creation.html", context) +@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, instance=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 not EmployeeObjective.objects.filter( + employee_id=emp, objective_id=objective + ).exists(): + emp_obj = EmployeeObjective( + employee_id=emp, objective_id=objective, start_date=start_date + ) + emp_obj.save() + # assiging default key result + default_krs = objective.key_result_id.all() + if default_krs: + for kr in default_krs: + if not EmployeeKeyResult.objects.filter(employee_objective_id=emp_obj, key_result_id=kr).exists(): + emp_kr = EmployeeKeyResult.objects.create( + employee_objective_id=emp_obj, + key_result_id=kr, + progress_type=kr.progress_type, + target_value=kr.target_value + ) + 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.delete_employeeobjective") @@ -275,42 +417,6 @@ def objective_filter_pagination(request, objective_own, objective_all): return context -@login_required -def objective_list_view(request): - """ - This view is used to show all the objectives and returns some objects. - Returns: - Objects based on userlevel. - """ - user = request.user - employee = Employee.objects.filter(employee_user_id=user).first() - is_manager = Employee.objects.filter( - employee_work_info__reporting_manager_id=employee - ) - template = "okr/okr_view.html" - - if request.user.has_perm("pms.view_employeeobjective"): - 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) - - 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) - objective_own = objective_own.distinct() - 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) - objective_own = objective_own.distinct() - objective_all = EmployeeObjective.objects.none() - context = objective_filter_pagination(request, objective_own, objective_all) - return render(request, template, context) - @login_required # @hx_request_required @@ -660,57 +766,6 @@ 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, instance=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") @@ -2621,25 +2676,6 @@ def employee_keyresult_update_status(request, kr_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):