diff --git a/base/translator.py b/base/translator.py
index 46f6f2283..3653d7108 100644
--- a/base/translator.py
+++ b/base/translator.py
@@ -285,4 +285,8 @@ _("mail-server-conf"),
_("configuration"),
_("multiple-approval-condition"),
_("skill-zone-view"),
-_("view-mail-templates"),
\ No newline at end of file
+_("view-mail-templates"),
+_("view-loan"),
+_("view-reimbursement"),
+_("department-manager-view"),
+_("date-settings"),
\ No newline at end of file
diff --git a/base/urls.py b/base/urls.py
index 608cf58eb..162bb8f43 100644
--- a/base/urls.py
+++ b/base/urls.py
@@ -524,7 +524,7 @@ urlpatterns = [
name="delete-notifications",
),
path("settings/currency/", views.settings, name="currency-settings"),
- path("settings/date/", views.date_settings, name="date-settings"),
+ path("settings/date-settings/", views.date_settings, name="date-settings"),
path("settings/save-date/", views.save_date_format, name="save_date_format"),
path("settings/get-date-format/", views.get_date_format, name="get-date-format"),
path("settings/save-time/", views.save_time_format, name="save_time_format"),
diff --git a/horilla_crumbs/context_processors.py b/horilla_crumbs/context_processors.py
index 658eded40..59642c5c3 100644
--- a/horilla_crumbs/context_processors.py
+++ b/horilla_crumbs/context_processors.py
@@ -88,6 +88,12 @@ sidebar_urls = [
"ticket-type-view",
"mail-server-conf",
"multiple-approval-condition",
+ "skill-zone-view",
+ "view-mail-templates",
+ "view-loan",
+ "view-reimbursement",
+ "department-manager-view",
+ "date-settings",
]
remove_urls = [
"objective-detailed-view",
diff --git a/pms/admin.py b/pms/admin.py
index ab00def42..5cc9c0ce6 100644
--- a/pms/admin.py
+++ b/pms/admin.py
@@ -5,7 +5,7 @@ This page is used to register PMS models with admins site.
"""
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
-from .models import Comment, EmployeeKeyResult, Period, EmployeeObjective
+from .models import AnonymousFeedback, Comment, EmployeeKeyResult, Period, EmployeeObjective
from .models import (
Question,
QuestionTemplate,
diff --git a/pms/forms.py b/pms/forms.py
index ebb5a8102..0e1916438 100644
--- a/pms/forms.py
+++ b/pms/forms.py
@@ -13,7 +13,9 @@ from django.forms.utils import ErrorList
from django.utils.translation import gettext_lazy as _
from employee.models import Department, JobPosition
from django.forms import ModelForm
+from base.forms import ModelForm as BaseForm
from pms.models import (
+ AnonymousFeedback,
Question,
EmployeeObjective,
EmployeeKeyResult,
@@ -681,3 +683,10 @@ class PeriodForm(ModelForm):
start_date = cleaned_data.get("start_date")
end_date = cleaned_data.get("end_date")
validate_date(start_date, end_date)
+
+
+class AnonymousFeedbackForm(BaseForm):
+ class Meta:
+ model = AnonymousFeedback
+ fields = "__all__"
+ exclude = ["status", "archive"]
diff --git a/pms/migrations/__init__.py b/pms/migrations/__init__.py
index 139597f9c..e69de29bb 100644
--- a/pms/migrations/__init__.py
+++ b/pms/migrations/__init__.py
@@ -1,2 +0,0 @@
-
-
diff --git a/pms/models.py b/pms/models.py
index 4da2ab045..e6ef62208 100644
--- a/pms/models.py
+++ b/pms/models.py
@@ -1,7 +1,7 @@
from django.db import models
from django.forms import ValidationError
from django.utils.translation import gettext_lazy as _
-from base.models import Company
+from base.models import Company, Department, JobPosition
from base.horilla_company_manager import HorillaCompanyManager
# importing simple history
@@ -146,8 +146,10 @@ class EmployeeKeyResult(models.Model):
if self.employee_id is None:
self.employee_id = self.employee_objective_id.employee_id
if self.target_value != 0:
- self.progress_percentage = (int(self.current_value)/int(self.target_value))*100
-
+ self.progress_percentage = (
+ int(self.current_value) / int(self.target_value)
+ ) * 100
+
super().save(*args, **kwargs)
@@ -261,12 +263,93 @@ class Feedback(models.Model):
EmployeeKeyResult,
blank=True,
)
- objects = HorillaCompanyManager("employee_id__employee_work_info__company_id+")
+ objects = HorillaCompanyManager("employee_id__employee_work_info__company_id")
def __str__(self):
return f"{self.employee_id.employee_first_name} - {self.review_cycle}"
+class AnonymousFeedback(models.Model):
+ """feedback model for creating feedback"""
+
+ STATUS_CHOICES = (
+ ("On Track", _("On Track")),
+ ("Behind", _("Behind")),
+ ("Closed", _("Closed")),
+ ("At Risk", _("At Risk")),
+ ("Not Started", _("Not Started")),
+ )
+ BASED_ON_CHOICES = (
+ ("general", _("General")),
+ ("employee", _("Employee")),
+ ("department", _("Department")),
+ ("job_position", _("Job Position")),
+ )
+ feedback_subject = models.CharField(max_length=100, null=False, blank=False)
+ based_on = models.CharField(
+ max_length=50, choices=BASED_ON_CHOICES, default="general"
+ )
+ employee_id = models.ForeignKey(
+ Employee,
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ verbose_name=_("Employee"),
+ )
+ department_id = models.ForeignKey(
+ Department,
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ verbose_name=_("Department"),
+ )
+ job_position_id = models.ForeignKey(
+ JobPosition,
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
+ verbose_name=_("Job Position"),
+ )
+ status = models.CharField(
+ max_length=50, choices=STATUS_CHOICES, default="Not Started"
+ )
+ created_at = models.DateField(auto_now_add=True)
+ archive = models.BooleanField(null=True, blank=True, default=False)
+ anonymous_feedback_id = models.CharField(
+ max_length=10, null=True, blank=False, editable=False
+ )
+ feedback_description = models.TextField(null=True, blank=True)
+
+ def __str__(self) -> str:
+ return f"Feedback based on a {self.based_on}"
+
+ def clean(self, *args, **kwargs):
+ if self.based_on == "employee":
+ self._validate_required_field("employee_id", "Employee")
+ self.department_id = None
+ self.job_position_id = None
+ elif self.based_on == "department":
+ self._validate_required_field("department_id", "Department")
+ self.employee_id = None
+ self.job_position_id = None
+ elif self.based_on == "job_position":
+ self._validate_required_field("job_position_id", "Job Position")
+ self.employee_id = None
+ self.department_id = None
+
+ return super().clean(*args, **kwargs)
+
+ def _validate_required_field(self, field_name, field_label):
+ if not getattr(self, field_name):
+ raise ValidationError(
+ {
+ field_name: _(
+ f"The {field_label} field is required when the 'Based on' field is set to '{field_label}'."
+ )
+ }
+ )
+
+
class Answer(models.Model):
"""feedback answer model"""
diff --git a/pms/templates/anonymous/anonymous_feedback_form.html b/pms/templates/anonymous/anonymous_feedback_form.html
new file mode 100644
index 000000000..4bb7943a9
--- /dev/null
+++ b/pms/templates/anonymous/anonymous_feedback_form.html
@@ -0,0 +1,72 @@
+{% load i18n %}
+
+
diff --git a/pms/templates/anonymous/single_view.html b/pms/templates/anonymous/single_view.html
new file mode 100644
index 000000000..7635c72a6
--- /dev/null
+++ b/pms/templates/anonymous/single_view.html
@@ -0,0 +1,92 @@
+{% load i18n %}
+
diff --git a/pms/templates/feedback/feedback_list.html b/pms/templates/feedback/feedback_list.html
index e727ae0e4..2242cb8ae 100644
--- a/pms/templates/feedback/feedback_list.html
+++ b/pms/templates/feedback/feedback_list.html
@@ -15,6 +15,13 @@
{% endfor %}
{% endif %}
+
{% include 'filter_tags.html' %}
@@ -57,7 +64,15 @@
{{all_feedbacks|length}}
{% endif %}
-
+
+ {% trans "Anonymous Feedback" %}
+
+ {{requested_feedback|length}}
+
+
+
@@ -477,6 +492,95 @@
{% endif %}
+
+ {% if anonymous_feedback %}
+
+
+
+
+
+
{% trans "Subject" %}
+
{% trans "Based on" %}
+
{% trans "Create At" %}
+
+
+
+
+
+ {% for feedback in anonymous_feedback %}
+
+
+
+ {{feedback.feedback_subject}}
+
+
+ {% trans "Based on" %} :
+ {% if feedback.based_on == "employee" %}
+ {{feedback.employee_id}}
+ {% elif feedback.based_on == "department" %}
+ {{feedback.department_id}}
+ {% elif feedback.based_on == "job_position" %}
+ {{feedback.job_position_id}}
+ {% else %}
+ {{feedback.get_based_on_display}}
+ {% endif %}
+
+
+ {{feedback.created_at}}
+
+
+
+
+
+ {% if perms.pms.delete_feedback %}
+
+ {% endif %}
+
+
+
+ {% endfor%}
+
+
+
+ {% else %}
+
+
{% trans "There are no Anonymous Feedbacks available." %}
+
+ {% endif %}
+
diff --git a/pms/templates/feedback/feedback_list_view.html b/pms/templates/feedback/feedback_list_view.html
index 8700e6333..62125c531 100644
--- a/pms/templates/feedback/feedback_list_view.html
+++ b/pms/templates/feedback/feedback_list_view.html
@@ -192,10 +192,38 @@
-
-
{% include 'feedback/feedback_list.html' %}
+
+
diff --git a/pms/urls.py b/pms/urls.py
index 8eb90e889..c0fe74110 100644
--- a/pms/urls.py
+++ b/pms/urls.py
@@ -208,4 +208,25 @@ urlpatterns = [
views.objective_select_filter,
name="objective-select-filter",
),
+ path(
+ "add-anonymous-feedback",
+ views.anonymous_feedback_add,
+ name="add-anonymous-feedback",
+ ),
+ path(
+ "edit-anonymous-feedback//",
+ views.edit_anonymous_feedback,
+ name="edit-anonymous-feedback",
+ ),
+ path("archive-anonymous-feedback//",views.archive_anonymous_feedback,name="archive-anonymous-feedback"),
+ path(
+ "delete-anonymous-feedback//",
+ views.delete_anonymous_feedback,
+ name="delete-anonymous-feedback",
+ ),
+ path(
+ "single-anonymous-feedback-view//",
+ views.view_single_anonymous_feedback,
+ name="single-anonymous-feedback-view",
+ ),
]
diff --git a/pms/views.py b/pms/views.py
index d10a5d7de..009bd1902 100644
--- a/pms/views.py
+++ b/pms/views.py
@@ -7,6 +7,7 @@ from django.db.utils import IntegrityError
from django.db.models import Q
from django.forms import modelformset_factory
from django.contrib import messages
+from django.contrib.auth.models import User
from django.core.paginator import Paginator
from django.utils.translation import gettext_lazy as _
from django.shortcuts import get_object_or_404, render, redirect
@@ -24,6 +25,7 @@ from pms.filters import (
)
from django.db.models import ProtectedError
from pms.models import (
+ AnonymousFeedback,
EmployeeKeyResult,
EmployeeObjective,
Comment,
@@ -36,6 +38,7 @@ from pms.models import (
KeyResultFeedback,
)
from .forms import (
+ AnonymousFeedbackForm,
QuestionForm,
ObjectiveForm,
KeyResultForm,
@@ -965,7 +968,7 @@ def feedback_update(request, id):
@login_required
def filter_pagination_feedback(
- request, self_feedback, requested_feedback, all_feedback
+ request, self_feedback, requested_feedback, all_feedback, anonymous_feedback
):
"""
This view is used to filter or search the feedback object ,
@@ -990,7 +993,7 @@ def filter_pagination_feedback(
feedback_filter_all = FeedbackFilter(
request.GET or initial_data, queryset=all_feedback
)
-
+ anonymous_feedback = anonymous_feedback
feedback_paginator_own = Paginator(feedback_filter_own.qs, 50)
feedback_paginator_requested = Paginator(feedback_filter_requested.qs, 50)
feedback_paginator_all = Paginator(feedback_filter_all.qs, 50)
@@ -1006,6 +1009,7 @@ def filter_pagination_feedback(
"superuser": "true",
"self_feedback": feedbacks_own,
"requested_feedback": feedbacks_requested,
+ "anonymous_feedback": anonymous_feedback,
"all_feedbacks": feedbacks_all,
"feedback_filter_form": feedback_filter_own.form,
"pg": previous_data,
@@ -1047,11 +1051,16 @@ def feedback_list_search(request):
review_cycle__icontains=feedback
)
all_feedback = Feedback.objects.all().filter(review_cycle__icontains=feedback)
+ anonymous_feedback = (
+ AnonymousFeedback.objects.filter(employee_id=employee_id)
+ if not request.user.has_perm("pms.view_feedback")
+ else AnonymousFeedback.objects.all()
+ )
reporting_manager_to = employee_id.reporting_manager.all()
if request.user.has_perm("pms.view_feedback"):
context = filter_pagination_feedback(
- request, self_feedback, requested_feedback, all_feedback
+ request, self_feedback, requested_feedback, all_feedback, anonymous_feedback
)
elif reporting_manager_to:
employees_id = [i.id for i in reporting_manager_to]
@@ -1059,12 +1068,12 @@ def feedback_list_search(request):
review_cycle__icontains=feedback
)
context = filter_pagination_feedback(
- request, self_feedback, requested_feedback, all_feedback
+ request, self_feedback, requested_feedback, all_feedback, anonymous_feedback
)
else:
all_feedback = Feedback.objects.none()
context = filter_pagination_feedback(
- request, self_feedback, requested_feedback, all_feedback
+ request, self_feedback, requested_feedback, all_feedback, anonymous_feedback
)
return render(request, "feedback/feedback_list.html", context)
@@ -1081,9 +1090,9 @@ def feedback_list_view(request):
user = request.user
employee = Employee.objects.filter(employee_user_id=user).first()
feedback_requested_ids = Feedback.objects.filter(
- Q(manager_id=employee,manager_id__is_active=True)
- | Q(colleague_id=employee,colleague_id__is_active=True)
- | Q(subordinate_id=employee,subordinate_id__is_active=True)
+ Q(manager_id=employee, manager_id__is_active=True)
+ | Q(colleague_id=employee, colleague_id__is_active=True)
+ | Q(subordinate_id=employee, subordinate_id__is_active=True)
).values_list("id", flat=True)
feedback_own = Feedback.objects.filter(employee_id=employee).filter(
archive=False, employee_id__is_active=True
@@ -1094,24 +1103,32 @@ def feedback_list_view(request):
feedback_all = Feedback.objects.all().filter(
archive=False, employee_id__is_active=True
)
+ anonymous_feedback = (
+ AnonymousFeedback.objects.filter(employee_id=employee, archive=False)
+ if not request.user.has_perm("pms.view_feedback")
+ else AnonymousFeedback.objects.filter(archive=False)
+ )
+ anonymous_feedback = anonymous_feedback | AnonymousFeedback.objects.filter(
+ anonymous_feedback_id=request.user.id, archive=False
+ )
employees = Employee.objects.filter(
employee_work_info__reporting_manager_id=employee, is_active=True
) # checking the user is reporting manager or not
feedback_available = Feedback.objects.all()
if request.user.has_perm("pms.view_feedback"):
context = filter_pagination_feedback(
- request, feedback_own, feedback_requested, feedback_all
+ request, feedback_own, feedback_requested, feedback_all, anonymous_feedback
)
elif employees:
# based on the reporting manager
feedback_all = Feedback.objects.filter(employee_id__in=employees)
context = filter_pagination_feedback(
- request, feedback_own, feedback_requested, feedback_all
+ request, feedback_own, feedback_requested, feedback_all, anonymous_feedback
)
else:
feedback_all = Feedback.objects.none()
context = filter_pagination_feedback(
- request, feedback_own, feedback_requested, feedback_all
+ request, feedback_own, feedback_requested, feedback_all, anonymous_feedback
)
if feedback_available.exists():
template = "feedback/feedback_list_view.html"
@@ -2072,9 +2089,7 @@ def objective_select(request):
page_number = request.GET.get("page")
table = request.GET.get("tableName")
user = request.user.employee_get
- employees = EmployeeObjective.objects.filter(
- employee_id=user, archive=False
- )
+ employees = EmployeeObjective.objects.filter(employee_id=user, archive=False)
if page_number == "all":
if table == "all":
if request.user.has_perm("pms.view_employeeobjective"):
@@ -2114,13 +2129,14 @@ def objective_select_filter(request):
if table == "all":
if request.user.has_perm("pms.view_employeeobjective"):
employee_filter = ObjectiveFilter(
- filters, queryset=EmployeeObjective.objects.all()
- )
+ filters, queryset=EmployeeObjective.objects.all()
+ )
else:
employee_filter = ObjectiveFilter(
- filters, queryset=EmployeeObjective.objects.filter(
+ filters,
+ queryset=EmployeeObjective.objects.filter(
employee_id__employee_work_info__reporting_manager_id__employee_user_id=request.user
- )
+ ),
)
else:
employee_filter = ObjectiveFilter(
@@ -2135,3 +2151,99 @@ def objective_select_filter(request):
context = {"employee_ids": employee_ids, "total_count": total_count}
return JsonResponse(context)
+
+
+@login_required
+def anonymous_feedback_add(request):
+ if request.method == "POST":
+ form = AnonymousFeedbackForm(request.POST)
+ anonymous_id = request.user.id
+
+ if form.is_valid():
+ feedback = form.save(commit=False)
+ feedback.anonymous_feedback_id = anonymous_id
+ feedback.save()
+ if feedback.based_on == "employee":
+ try:
+ notify.send(
+ User.objects.filter(username="Horilla Bot").first(),
+ recipient=feedback.employee_id.employee_user_id,
+ verb="You received an anonymous feedback!",
+ verb_ar="لقد تلقيت تقييمًا مجهولًا!",
+ verb_de="Sie haben anonymes Feedback erhalten!",
+ verb_es="¡Has recibido un comentario anónimo!",
+ verb_fr="Vous avez reçu un feedback anonyme!",
+ redirect="/pms/feedback-view/",
+ icon="bag-check",
+ )
+ except:
+ pass
+ return HttpResponse("")
+ else:
+ form = AnonymousFeedbackForm()
+
+ context = {"form": form, "create": True}
+ return render(request, "anonymous/anonymous_feedback_form.html", context)
+
+
+@login_required
+def edit_anonymous_feedback(request, id):
+ feedback = AnonymousFeedback.objects.get(id=id)
+ form = AnonymousFeedbackForm(instance=feedback)
+ anonymous_id = request.user.id
+ if request.method == "POST":
+ form = AnonymousFeedbackForm(request.POST, instance=feedback)
+ if form.is_valid():
+ feedback = form.save(commit=False)
+ feedback.anonymous_feedback_id = anonymous_id
+ feedback.save()
+ return HttpResponse("")
+ context = {"form": form, "create": False}
+ return render(request, "anonymous/anonymous_feedback_form.html", context)
+
+
+@login_required
+def archive_anonymous_feedback(request, id):
+ """
+ this function is used to archive the feedback for employee
+ args:
+ id(int): primarykey of feedback
+ """
+
+ feedback = AnonymousFeedback.objects.get(id=id)
+ if feedback.archive:
+ feedback.archive = False
+ feedback.save()
+ messages.info(request, _("Feedback un-archived successfully!."))
+ elif not feedback.archive:
+ feedback.archive = True
+ feedback.save()
+ messages.info(request, _("Feedback archived successfully!."))
+ return redirect(feedback_list_view)
+
+
+@login_required
+def delete_anonymous_feedback(request, id):
+ try:
+ feedback = AnonymousFeedback.objects.get(id=id)
+ feedback.delete()
+ messages.success(request, _("Feedback deleted successfully!"))
+
+ except IntegrityError:
+ messages.error(
+ request, _("Failed to delete feedback: Feedback template is in use.")
+ )
+
+ except AnonymousFeedback.DoesNotExist:
+ messages.error(request, _("Feedback not found."))
+
+ except ProtectedError:
+ messages.error(request, _("Related entries exists"))
+
+ return redirect(feedback_list_view)
+
+
+@login_required
+def view_single_anonymous_feedback(request, id):
+ feedback = AnonymousFeedback.objects.get(id=id)
+ return render(request, "anonymous/single_view.html", {"feedback": feedback})
diff --git a/templates/notification/notification_items.html b/templates/notification/notification_items.html
index 67c7eb630..6052947a2 100644
--- a/templates/notification/notification_items.html
+++ b/templates/notification/notification_items.html
@@ -32,7 +32,7 @@
{% else %}
{{ notification.verb }}
{% endif %}
- {{ notification.timesince }} {% trans "ago by" %} {{ notification.actor.employee_first_name }}
+ {{ notification.timesince }} {% trans "ago by" %} {% if notification.actor.employee_first_name %}{{ notification.actor.employee_first_name }}{% else %}{% trans "Anonymous" %}{% endif %}