[ADD] PMS: Added bulk feedback create feature

This commit is contained in:
Horilla
2025-05-08 16:58:31 +05:30
parent d9a4dbc703
commit 9fec5d1c19
7 changed files with 294 additions and 18 deletions

View File

@@ -8,16 +8,18 @@ from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _trans
from base.methods import filter_own_and_subordinate_recordes, is_reportingmanager
from employee.forms import EmployeeForm
from employee.models import Employee
from horilla import horilla_middlewares
from horilla.decorators import login_required, permission_required
from horilla.decorators import login_required, owner_can_enter, permission_required
from horilla_views.generic.cbv import views
from pms import models
from pms.filters import BonusPointSettingFilter, EmployeeBonusPointFilter
from pms.forms import (
BonusPointSettingForm,
BulkFeedbackForm,
EmployeeBonusPointForm,
EmployeeFeedbackForm,
FeedbackForm,
)
from pms.methods import check_duplication
@@ -275,10 +277,7 @@ class EmployeeBonusPointListView(views.HorillaListView):
####################### Feedback ########################################
# @method_decorator(login_required, name="dispatch")
# @method_decorator(
# permission_required("pms.change_feedback"), name="dispatch"
# )
@method_decorator(login_required, name="dispatch")
class FeedbackEmployeeFormView(views.HorillaFormView):
"""
Feedback other employee form View
@@ -301,7 +300,7 @@ class FeedbackEmployeeFormView(views.HorillaFormView):
)
return super().form_invalid(form)
def form_valid(self, form: EmployeeForm) -> views.HttpResponse:
def form_valid(self, form: EmployeeFeedbackForm) -> views.HttpResponse:
if form.is_valid():
message = "Feedback request sent."
other_employees = check_duplication(
@@ -312,3 +311,87 @@ class FeedbackEmployeeFormView(views.HorillaFormView):
messages.success(self.request, _trans(message))
return self.HttpResponse("<script>window.location.reload()</script>")
return super().form_valid(form)
@method_decorator(login_required, name="dispatch")
@method_decorator(permission_required("pms.add_feedback"), name="dispatch")
class BulkFeedbackFormView(views.HorillaFormView):
"""
Feedback other employee form View
"""
form_class = BulkFeedbackForm
model = models.Feedback
view_id = "BulkFeedbackForm"
new_display_title = _trans("Bulk Feedback request ")
template_name = "feedback/bulk_feedback_form.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
def form_invalid(self, form: Any) -> HttpResponse:
if not form.is_valid():
errors = form.errors.as_data()
return render(
self.request, self.template_name, {"form": form, "errors": errors}
)
return super().form_invalid(form)
def form_valid(self, form: BulkFeedbackForm) -> views.HttpResponse:
if form.is_valid():
message = "Bulk Feedback request sent."
cleaned_data = form.cleaned_data
employees = cleaned_data["employee_ids"]
for employee in employees:
reporting_manager = employee.get_reporting_manager()
manager_id = (
reporting_manager if cleaned_data["include_manager"] else None
)
data = {
"review_cycle": f"{cleaned_data['title']}-{employee} feedback",
"employee_id": employee,
"manager_id": manager_id,
"question_template_id": cleaned_data["question_template_id"],
"start_date": cleaned_data["start_date"],
"end_date": cleaned_data["end_date"],
"cyclic_feedback": cleaned_data["cyclic_feedback"],
"cyclic_feedback_days_count": cleaned_data[
"cyclic_feedback_days_count"
],
"cyclic_feedback_period": cleaned_data["cyclic_feedback_period"],
"status": cleaned_data["status"],
}
feedback_form = FeedbackForm(data)
if feedback_form.is_valid():
feedback = feedback_form.save()
if cleaned_data["include_keyresult"]:
keyresults = models.EmployeeKeyResult.objects.filter(
employee_objective_id__employee_id=employee.id
)
feedback.employee_key_results_id.add(*keyresults)
if cleaned_data["include_colleagues"]:
department = employee.get_department()
# employee ids to exclude from collegue list
exclude_ids = [employee.id]
if reporting_manager:
exclude_ids.append(reporting_manager.id)
# Get employees in the same department as the employee
colleagues = Employee.objects.filter(
is_active=True, employee_work_info__department_id=department
).exclude(id__in=exclude_ids)
feedback.colleague_id.add(*colleagues)
if cleaned_data["include_subordinates"]:
subordinates = Employee.objects.filter(
is_active=True,
employee_work_info__reporting_manager_id=employee,
)
feedback.subordinate_id.add(*subordinates)
other_employees = check_duplication(
feedback, cleaned_data["other_employees"]
)
feedback.others_id.add(*other_employees)
messages.success(self.request, _trans(message))
return self.HttpResponse("<script>window.location.reload()</script>")
return super().form_valid(form)

View File

@@ -1201,3 +1201,103 @@ class EmployeeFeedbackForm(HorillaModelForm):
cleaned_data["others_id"] = employee_data
return cleaned_data
class BulkFeedbackForm(HorillaModelForm):
"""Form for creating feedback in bulk"""
title = forms.CharField(required=True, label=_("Title"))
employee_ids = forms.ModelMultipleChoiceField(
queryset=Employee.objects.filter(is_active=True), required=True
)
other_employees = forms.ModelMultipleChoiceField(
queryset=Employee.objects.filter(is_active=True),
required=False,
label=_("Other employees"),
help_text=_("Employees need to sent feedback request."),
)
include_manager = forms.BooleanField(
initial=True,
required=False,
)
include_subordinates = forms.BooleanField(
initial=True, required=False, label=_("Include all subordinates")
)
include_colleagues = forms.BooleanField(
initial=True, required=False, label=_("Include all colleagues")
)
include_keyresult = forms.BooleanField(
initial=True,
required=False,
label=_("Include all keyresults"),
help_text=_("Include all keyresults assigned to the employee."),
)
period = forms.ModelChoiceField(
queryset=Period.objects.all(),
label=_("Period"),
required=False,
widget=forms.Select(
attrs={
"onchange": "periodChange($(this))",
}
),
)
class Meta:
model = Feedback
fields = [
"title",
"employee_ids",
"status",
"other_employees",
"include_manager",
"include_subordinates",
"include_colleagues",
"include_keyresult",
"question_template_id",
"cyclic_feedback",
"cyclic_feedback_period",
"cyclic_feedback_days_count",
"period",
"start_date",
"end_date",
]
widgets = {
"start_date": forms.DateInput(
attrs={"type": "date", "class": "oh-input w-100"}
),
"end_date": forms.DateInput(
attrs={"type": "date", "class": "oh-input w-100"}
),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["employee_ids"] = HorillaMultiSelectField(
queryset=Employee.objects.filter(employee_work_info__isnull=False),
widget=HorillaMultiSelectWidget(
filter_route_name="employee-widget-filter",
filter_class=EmployeeFilter,
filter_instance_contex_name="f",
filter_template_path="employee_filters.html",
form=self,
instance=self.instance,
required=True,
),
label=_("Employees"),
)
self.fields["status"].initial = "Not Started"
self.fields["cyclic_feedback"].widget.attrs["onchange"] = "cyclicFeedback()"
def clean(self):
cleaned_data = super().clean()
if isinstance(self.fields["employee_ids"], HorillaMultiSelectField):
self.errors.pop("employee_ids", None)
employee_data = self.fields["employee_ids"].queryset.filter(
id__in=self.data.getlist("employee_ids")
)
cleaned_data["employee_ids"] = employee_data
return cleaned_data

View File

@@ -14,7 +14,7 @@ var unarchiveMessages = {
fr: "Voulez-vous vraiment désarchiver tous les retours sélectionnés?",
};
var deleteMessages = {
var deleteFeedbackMessages = {
ar: "هل ترغب حقاً في حذف كل التعليقات المحددة؟",
de: "Möchten Sie wirklich alle ausgewählten Rückmeldungen löschen?",
es: "¿Realmente quieres eliminar todas las retroalimentaciones seleccionadas?",
@@ -219,7 +219,7 @@ $("#deleteFeedback").click(function (e) {
var languageCode = null;
getCurrentLanguageCode(function (code) {
languageCode = code;
var confirmMessage = deleteMessages[languageCode];
var confirmMessage = deleteFeedbackMessages[languageCode];
var textMessage = norowMessages[languageCode];
var checkedRows = $(".feedback-checkbox").filter(":checked");
if (checkedRows.length === 0) {

View File

@@ -0,0 +1,50 @@
{% load static %}
{% include "generic/horilla_form.html" %}
<script>
$(document).ready(function(){
$('#id_cyclic_feedback_period_parent_div').addClass('d-none')
$('#id_cyclic_feedback_days_count_parent_div').addClass('d-none')
})
// this function is used to generate csrf token
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + "=")) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function periodChange(element){
period_id = element.val()
$.ajax({
url: '/pms/period-change',
type: "POST",
dataType: "json",
data: JSON.stringify(period_id),
headers: {
"X-Requested-With": "XMLHttpRequest",
"X-CSRFToken": getCookie("csrftoken"),
},
success: (data) => {
// Adding data to start and end date
$('#BulkFeedbackForm #id_start_date').val(data.start_date)
$('#BulkFeedbackForm #id_end_date').val(data.end_date);
},
error: (error) => {
console.log('Error', error);
}
});
}
function cyclicFeedback(){
$('#id_cyclic_feedback_period_parent_div').toggleClass('d-none')
$('#id_cyclic_feedback_days_count_parent_div').toggleClass('d-none')
}
</script>

View File

@@ -21,7 +21,37 @@
<div class="oh-main__titlebar oh-main__titlebar--right">
<div class="oh-main__titlebar-button-container">
{% if perms.pms.add_feedback %}
<div class="oh-btn-group ml-2">
<div class="oh-dropdown" x-data="{open: false}">
<button
onclick="event.preventDefault()"
class="oh-btn oh-btn--dropdown oh-btn oh-btn--shadow"
@click="open = !open"
@click.outside="open = false"
>
{% trans "Actions" %}
</button>
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open"
style="display: none;"
>
<ul class="oh-dropdown__items">
<li class="oh-dropdown__item">
<a
data-toggle="oh-modal-toggle"
data-target="#objectCreateModal"
hx-get="{% url "bulk-feedback-create" %}"
hx-target="#objectCreateModalTarget"
class="oh-dropdown__link"
id="bulkfeedback"
>{% trans "Bulk Feedback" %}</a
>
</li>
</ul>
</div>
</div>
</div>
{% endif %}
<!-- checking user permission for objective creation -->
{% if perms.pms.add_feedback or request.user|filtersubordinates %}
<div class="oh-btn-group ml-2">

View File

@@ -146,6 +146,19 @@
style="display: none;"
>
<ul class="oh-dropdown__items">
{% if perms.pms.add_feedback %}
<li class="oh-dropdown__item">
<a
data-toggle="oh-modal-toggle"
data-target="#objectCreateModal"
hx-get="{% url "bulk-feedback-create" %}"
hx-target="#objectCreateModalTarget"
class="oh-dropdown__link"
id="bulkfeedback"
>{% trans "Bulk Feedback" %}</a
>
</li>
{% endif %}
<li class="oh-dropdown__item">
<a href="#" class="oh-dropdown__link " id="archiveFeedback"
@@ -231,9 +244,9 @@
<script src="{% static '/base/filter.js' %}"></script>
<script>
$(".fil").click(function() {
$("#filterCount").text("(1)");
});
$(".fil").click(function() {
$("#filterCount").text("(1)");
});
</script>

View File

@@ -125,11 +125,11 @@ urlpatterns = [
"feedback-list-search", views.feedback_list_search, name="feedback-list-search"
),
path("feedback-creation", views.feedback_creation, name="feedback-creation"),
# path(
# "feedback-creation-ajax",
# views.feedback_creation_ajax,
# name="feedback-creation-ajax",
# ),
path(
"bulk-feedback-create",
cbvs.BulkFeedbackFormView.as_view(),
name="bulk-feedback-create",
),
path("feedback-update/<int:id>", views.feedback_update, name="feedback-update"),
path("feedback-delete/<int:id>", views.feedback_delete, name="feedback-delete"),
path("feedback-archive/<int:id>", views.feedback_archive, name="feedback-archive"),