[ADD] PMS: Keyresult dynamic creation in objective form, and its validations

This commit is contained in:
Horilla
2024-03-26 14:11:47 +05:30
parent 109063dffa
commit 52546af1a0
7 changed files with 369 additions and 146 deletions

View File

@@ -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):
"""

View File

@@ -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)

View File

@@ -0,0 +1,50 @@
{% load static %}{% load i18n %}
<!-- start of messages -->
{% if messages %}
<div class="oh-wrapper">
{% for message in messages %}
<div class="oh-alert-container">
<div class="oh-alert oh-alert--animated {{message.tags}}">
{{ message }}
</div>
</div>
{% endfor %}
<script>
setTimeout(function () {
$('#kRModal').find(".oh-modal__close--custom").click();
}, 1000);
</script>
</div>
{% endif %}
<!-- end of messages -->
<div class="oh-modal__dialog">
<!-- for creating key result -->
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" >
{% trans "Create Key Result" %}
</h2>
<button type="button" class="oh-modal__close--custom"
onclick="removeCreateKR();$('#kRModal').hide();"
{% if messages %}
hx-get="{{redirect_url}}"
hx-target="#objectivesTarget"
{% endif %}
>
<ion-icon name="close-outline" role="img" aria-label="close outline"></ion-icon>
</button>
</div>
<div class="oh-modal__dialog-body" >
<form
hx-target = "#kRModal"
hx-post="{% url 'key-result-creation' %}"
id ="krForm"
>
<input type="hidden" name="dyanamic_create" id="">
{% csrf_token %}
{{k_form.as_p}}
</form>
</div>
</div>

View File

@@ -1,6 +1,24 @@
{% load static i18n%}
{% load i18n %}
<!-- start of messages -->
{% if messages %}
<div class="oh-wrapper">
{% for message in messages %}
<div class="oh-alert-container">
<div class="oh-alert oh-alert--animated {{message.tags}}">
{{ message }}
</div>
</div>
{% endfor %}
{% comment %} <script>
setTimeout(function () {
$('#kRModal').find(".oh-modal__close--custom").click();
}, 1000);
</script> {% endcomment %}
</div>
{% endif %}
<!-- end of messages -->
{% if objective_form.non_field_errors %}
<!-- form non field errors -->
@@ -43,8 +61,7 @@
hx-target="#objectivesTarget"
id="objectiveForm"
>
{% endif %}
{% endif %}
{% csrf_token %}
{{objective_form.as_p}}
</form>
@@ -72,11 +89,18 @@
>
{% trans "Save" %}
</button>
</form>
</div>
</div>
</div>
<!-- end of period modal -->
<!-- key result modal -->
<div class="oh-modal" id="kRModal" role="dialog" aria-labelledby="editKeyResultModal" aria-hidden="true">
{% include "okr/key_result/key_result_form.html" %}
</div>
<!-- end of key result modal -->
<script src="{% static 'src/okr/objective_creation.js' %}"></script>
<script src="{% static 'src/period/period.js' %}"></script>

View File

@@ -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 = $('<option selected></option>').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();
}
</script>
{% endblock content %}

View File

@@ -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/<int:obj_id>", views.objective_update, name="objective-update"
),
path("add-assignees/<int:obj_id>", 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/<int:obj_id>", views.objective_update, name="objective-update"
),
path(
"objective-delete/<int:obj_id>", views.objective_delete, name="objective-delete"
),
@@ -284,7 +292,6 @@ urlpatterns = [
views.change_employee_objective_status,
name="change-employee-objective-status",
),
path("add-assignees/<int:obj_id>", views.add_assignees, name="add-assignees"),
path(
"employee-key-result-creation/<int:emp_obj_id>",
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,

View File

@@ -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("<script>window.location.reload()</script>")
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("<script>window.location.reload()</script>")
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("<script>window.location.reload()</script>")
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("<script>window.location.reload()</script>")
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):