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 %} + +
+ {% csrf_token %} +
+ + {{form.feedback_subject}} {{form.feedback_subject.errors}} +
+
+ + {{form.based_on}} {{form.based_on.errors}} +
+
+ + {{form.employee_id}} {{form.employee_id.errors}} +
+
+ + {{form.department_id}} {{form.department_id.errors}} +
+
+ + {{form.job_position_id}} {{form.job_position_id.errors}} +
+
+ + {{form.feedback_description}} {{form.feedback_description.errors}} +
+ +
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 %} +
+ +
{% trans "Anonymous Feedback" %}
+
+ +
+
+ {% trans "Subject" %} + {{feedback.feedback_subject}} +
+
+
+
+ {% trans "Based on" %} + + {{feedback.get_based_on_display}} + +
+ {% if not feedback.based_on == "general" %} +
+ {% if feedback.based_on == "employee" %} + {% trans "Employee" %} + {{feedback.employee_id}} + {% endif %} + {% if feedback.based_on == "department" %} + {% trans "Department" %} + {{feedback.department_id}} + {% endif %} + {% if feedback.based_on == "job_position" %} + {% trans "Job Position" %} + {{feedback.job_position_id}} + {% endif %} +
+ {% endif %} +
+
+
+ + {% trans "Description" %} + + {{feedback.feedback_description}} +
+
+ {% comment %} {% if perms.payroll.change_allowance or + perms.payroll.delete_allowance %} +
+ +
+ {% endif %} {% endcomment %} +
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 feedback.archive == True %} + +
    + {% if perms.pms.delete_feedback %} +
    + {% csrf_token %} + +
    + {% 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 %}