[ADD] PMS: Employee bonus point feature
This commit is contained in:
@@ -10,7 +10,9 @@ from simple_history.admin import SimpleHistoryAdmin
|
||||
from .models import (
|
||||
AnonymousFeedback,
|
||||
Answer,
|
||||
BonusPointSetting,
|
||||
Comment,
|
||||
EmployeeBonusPoint,
|
||||
EmployeeKeyResult,
|
||||
EmployeeObjective,
|
||||
Feedback,
|
||||
@@ -37,3 +39,5 @@ admin.site.register(Objective)
|
||||
admin.site.register(KeyResultFeedback)
|
||||
admin.site.register(Meetings)
|
||||
admin.site.register(Comment, SimpleHistoryAdmin)
|
||||
admin.site.register(BonusPointSetting)
|
||||
admin.site.register(EmployeeBonusPoint)
|
||||
|
||||
@@ -23,3 +23,11 @@ class PmsConfig(AppConfig):
|
||||
path("pms/", include("pms.urls")),
|
||||
)
|
||||
super().ready()
|
||||
try:
|
||||
from pms.signals import start_automation
|
||||
|
||||
start_automation()
|
||||
except:
|
||||
"""
|
||||
Migrations are not affected yet
|
||||
"""
|
||||
|
||||
245
pms/cbvs.py
Normal file
245
pms/cbvs.py
Normal file
@@ -0,0 +1,245 @@
|
||||
|
||||
|
||||
from typing import Any
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from horilla_views.generic.cbv import views
|
||||
from pms import models
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _trans
|
||||
from django.contrib import messages
|
||||
|
||||
from pms.filters import BonusPointSettingFilter,EmployeeBonusPointFilter
|
||||
from pms.forms import BonusPointSettingForm,EmployeeBonusPointForm
|
||||
|
||||
#================Models for BonusPointSetting==============
|
||||
class BonusPointSettingSectionView(views.HorillaSectionView):
|
||||
"""
|
||||
BonusPointSetting SectionView
|
||||
"""
|
||||
|
||||
nav_url = reverse_lazy("bonus-point-setting-nav")
|
||||
view_url = reverse_lazy("bonus-point-setting-list-view")
|
||||
view_container_id = "listContainer"
|
||||
|
||||
# script_static_paths = [
|
||||
# "static/automation/automation.js",
|
||||
# ]
|
||||
|
||||
template_name = "bonus/bonus_point_setting_section.html"
|
||||
|
||||
|
||||
|
||||
class BonusPointSettingNavView(views.HorillaNavView):
|
||||
"""
|
||||
BonusPointSetting nav view
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.create_attrs = f"""
|
||||
hx-get="{reverse_lazy("create-bonus-point-setting")}"
|
||||
hx-target="#genericModalBody"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
"""
|
||||
|
||||
nav_title = _trans("Bonus Point Setting")
|
||||
search_url = reverse_lazy("bonus-point-setting-list-view")
|
||||
search_swap_target = "#listContainer"
|
||||
|
||||
|
||||
class BonusPointSettingFormView(views.HorillaFormView):
|
||||
"""
|
||||
BonusPointSettingForm View
|
||||
"""
|
||||
|
||||
form_class = BonusPointSettingForm
|
||||
model = models.BonusPointSetting
|
||||
new_display_title = _trans("Create Bonus Point Setting")
|
||||
# template_name = "bonus/bonus_form.html"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
instance = models.BonusPointSetting.objects.filter(pk=self.kwargs["pk"]).first()
|
||||
kwargs["instance"] = instance
|
||||
return kwargs
|
||||
|
||||
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: BonusPointSettingForm) -> views.HttpResponse:
|
||||
if form.is_valid():
|
||||
message = "Bonus Point Setting added"
|
||||
if form.instance.pk:
|
||||
message = "Bonus Point Setting updated"
|
||||
form.save()
|
||||
|
||||
messages.success(self.request, _trans(message))
|
||||
return self.HttpResponse()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class BonusPointSettingListView(views.HorillaListView):
|
||||
"""
|
||||
BnusPointSetting list view
|
||||
"""
|
||||
model = models.BonusPointSetting
|
||||
search_url = reverse_lazy("bonus-point-setting-list-view")
|
||||
filter_class = BonusPointSettingFilter
|
||||
action_method = "action_template"
|
||||
# actions = [
|
||||
# {
|
||||
# "action": "Edit",
|
||||
# "icon": "create-outline",
|
||||
# "attrs": """
|
||||
# class="oh-btn oh-btn--light-bkg w-100"
|
||||
# hx-get="{edit_url}?instance_ids={ordered_ids}"
|
||||
# hx-target="#genericModalBody"
|
||||
# data-target="#genericModal"
|
||||
# data-toggle="oh-modal-toggle"
|
||||
# """,
|
||||
# },
|
||||
# {
|
||||
# "action": "Delete",
|
||||
# "icon": "trash-outline",
|
||||
# "attrs": """
|
||||
# class="oh-btn oh-btn--light-bkg w-100 tex-danger"
|
||||
# onclick="
|
||||
# event.stopPropagation();
|
||||
# confirm('Do you want to delete the bonus point setting?','{delete_url}')
|
||||
# "
|
||||
# """,
|
||||
# },
|
||||
# ]
|
||||
|
||||
columns = [
|
||||
("Model", "get_model_display"),
|
||||
("Bonus For", "get_bonus_for_display"),
|
||||
("Condition", "get_condition"),
|
||||
("Points", 'points'),
|
||||
("Is Active",'is_active'),
|
||||
]
|
||||
|
||||
|
||||
#================Models for EmployeeBonusPoint==============
|
||||
|
||||
class EmployeeBonusPointSectionView(views.HorillaSectionView):
|
||||
"""
|
||||
EmployeeBonusPoint SectionView
|
||||
"""
|
||||
|
||||
nav_url = reverse_lazy("employee-bonus-point-nav")
|
||||
view_url = reverse_lazy("employee-bonus-point-list-view")
|
||||
view_container_id = "listContainer"
|
||||
|
||||
# script_static_paths = [
|
||||
# "static/automation/automation.js",
|
||||
# ]
|
||||
|
||||
template_name = "bonus/employee_bonus_point_section.html"
|
||||
|
||||
|
||||
|
||||
class EmployeeBonusPointNavView(views.HorillaNavView):
|
||||
"""
|
||||
BonusPoint nav view
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.create_attrs = f"""
|
||||
hx-get="{reverse_lazy("create-employee-bonus-point")}"
|
||||
hx-target="#genericModalBody"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
"""
|
||||
|
||||
nav_title = _trans("Employee Bonus Point ")
|
||||
search_url = reverse_lazy("employee-bonus-point-list-view")
|
||||
search_swap_target = "#listContainer"
|
||||
|
||||
|
||||
class EmployeeBonusPointFormView(views.HorillaFormView):
|
||||
"""
|
||||
BonusPointForm View
|
||||
"""
|
||||
|
||||
form_class = EmployeeBonusPointForm
|
||||
model = models.EmployeeBonusPoint
|
||||
new_display_title = _trans("Create Employee Bonus Point ")
|
||||
# template_name = "bonus/bonus_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: EmployeeBonusPointForm) -> views.HttpResponse:
|
||||
if form.is_valid():
|
||||
message = "Bonus Point added"
|
||||
if form.instance.pk:
|
||||
message = "Bonus Point updated"
|
||||
form.save()
|
||||
|
||||
messages.success(self.request, _trans(message))
|
||||
return self.HttpResponse()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class EmployeeBonusPointListView(views.HorillaListView):
|
||||
"""
|
||||
BnusPoint list view
|
||||
"""
|
||||
model = models.EmployeeBonusPoint
|
||||
search_url = reverse_lazy("employee-bonus-point-list-view")
|
||||
filter_class = EmployeeBonusPointFilter
|
||||
# actions = [
|
||||
# {
|
||||
# "action": "Edit",
|
||||
# "icon": "create-outline",
|
||||
# "attrs": """
|
||||
# class="oh-btn oh-btn--light-bkg w-100"
|
||||
# hx-get="{edit_url}?instance_ids={ordered_ids}"
|
||||
# hx-target="#genericModalBody"
|
||||
# data-target="#genericModal"
|
||||
# data-toggle="oh-modal-toggle"
|
||||
# """,
|
||||
# },
|
||||
# {
|
||||
# "action": "Delete",
|
||||
# "icon": "trash-outline",
|
||||
# "attrs": """
|
||||
# class="oh-btn oh-btn--light-bkg w-100 tex-danger"
|
||||
# onclick="
|
||||
# event.stopPropagation();
|
||||
# confirm('Do you want to delete the automation?','{delete_url}')
|
||||
# "
|
||||
# """,
|
||||
# },
|
||||
# ]
|
||||
|
||||
columns = [
|
||||
("Employee", "employee_id"),
|
||||
("Bonus Point", "bonus_point"),
|
||||
("Based On",'based_on'),
|
||||
|
||||
]
|
||||
@@ -15,6 +15,8 @@ from django_filters import DateFilter
|
||||
from base.filters import FilterSet
|
||||
from base.methods import reload_queryset
|
||||
from pms.models import (
|
||||
BonusPointSetting,
|
||||
EmployeeBonusPoint,
|
||||
EmployeeKeyResult,
|
||||
EmployeeObjective,
|
||||
Feedback,
|
||||
@@ -407,3 +409,75 @@ class MeetingsFilter(FilterSet):
|
||||
# q_objects |= Q(**{key: value})
|
||||
# return queryset.filter(q_objects)
|
||||
# return super().filter_queryset(queryset)
|
||||
|
||||
|
||||
class BonusPointSettingFilter(FilterSet):
|
||||
"""
|
||||
Filter through BonusPointSetting model
|
||||
"""
|
||||
|
||||
# search = django_filters.CharFilter(method="search_method")
|
||||
# start_date_from = django_filters.DateFilter(
|
||||
# field_name="start_date",
|
||||
# lookup_expr="gte",
|
||||
# widget=forms.DateInput(attrs={"type": "date"}),
|
||||
# )
|
||||
# start_date_till = django_filters.DateFilter(
|
||||
# field_name="start_date",
|
||||
# lookup_expr="lte",
|
||||
# widget=forms.DateInput(attrs={"type": "date"}),
|
||||
# )
|
||||
# end_date_from = django_filters.DateFilter(
|
||||
# field_name="end_date",
|
||||
# lookup_expr="gte",
|
||||
# widget=forms.DateInput(attrs={"type": "date"}),
|
||||
# )
|
||||
# end_date_till = django_filters.DateFilter(
|
||||
# field_name="end_date",
|
||||
# lookup_expr="lte",
|
||||
# widget=forms.DateInput(attrs={"type": "date"}),
|
||||
# )
|
||||
|
||||
class Meta:
|
||||
model = BonusPointSetting
|
||||
fields = "__all__"
|
||||
|
||||
def search_method(self, queryset, _, value: str):
|
||||
"""
|
||||
This method is used to search employees and objective
|
||||
"""
|
||||
values = value.split(" ")
|
||||
empty = queryset.model.objects.none()
|
||||
for split in values:
|
||||
empty = (
|
||||
empty
|
||||
| (queryset.filter(employee_id__employee_first_name__icontains=split))
|
||||
| (queryset.filter(employee_id__employee_last_name__icontains=split))
|
||||
)
|
||||
|
||||
return empty.distinct()
|
||||
|
||||
|
||||
class EmployeeBonusPointFilter(FilterSet):
|
||||
"""
|
||||
Filter through BonusPointSetting model
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = EmployeeBonusPoint
|
||||
fields = "__all__"
|
||||
|
||||
def search_method(self, queryset, _, value: str):
|
||||
"""
|
||||
This method is used to search employees and objective
|
||||
"""
|
||||
values = value.split(" ")
|
||||
empty = queryset.model.objects.none()
|
||||
for split in values:
|
||||
empty = (
|
||||
empty
|
||||
| (queryset.filter(employee_id__employee_first_name__icontains=split))
|
||||
| (queryset.filter(employee_id__employee_last_name__icontains=split))
|
||||
)
|
||||
|
||||
return empty.distinct()
|
||||
|
||||
78
pms/forms.py
78
pms/forms.py
@@ -18,6 +18,7 @@ from django.template.loader import render_to_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from base.forms import ModelForm as BaseForm
|
||||
from base.forms import ModelForm as MF
|
||||
from base.methods import reload_queryset
|
||||
from employee.filters import EmployeeFilter
|
||||
from employee.models import Department, JobPosition
|
||||
@@ -25,8 +26,10 @@ from horilla_widgets.widgets.horilla_multi_select_field import HorillaMultiSelec
|
||||
from horilla_widgets.widgets.select_widgets import HorillaMultiSelectWidget
|
||||
from pms.models import (
|
||||
AnonymousFeedback,
|
||||
BonusPointSetting,
|
||||
Comment,
|
||||
Employee,
|
||||
EmployeeBonusPoint,
|
||||
EmployeeKeyResult,
|
||||
EmployeeObjective,
|
||||
Feedback,
|
||||
@@ -1069,3 +1072,78 @@ class MeetingsForm(BaseForm):
|
||||
self.fields["answer_employees"].queryset = employees
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class BonusPointSettingForm(MF):
|
||||
"""
|
||||
BonusPointSetting form
|
||||
"""
|
||||
|
||||
# condition_html = forms.CharField(widget=forms.HiddenInput())
|
||||
# condition_querystring = forms.CharField(widget=forms.HiddenInput())
|
||||
|
||||
# cols = {"template_attachments": 12}
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
|
||||
# if not self.data:
|
||||
# mail_to = []
|
||||
|
||||
# initial = []
|
||||
# mail_details_choice = []
|
||||
# if self.instance.pk:
|
||||
# mail_to = generate_choices(self.instance.model)[0]
|
||||
# mail_details_choice = generate_choices(self.instance.model)[1]
|
||||
# self.fields["mail_to"] = forms.MultipleChoiceField(choices=mail_to)
|
||||
# self.fields["mail_details"] = forms.ChoiceField(
|
||||
# choices=mail_details_choice,
|
||||
# help_text="Fill mail template details(reciever/instance, `self` will be the person who trigger the automation)",
|
||||
# )
|
||||
# self.fields["mail_to"].initial = initial
|
||||
# attrs = self.fields["mail_to"].widget.attrs
|
||||
# attrs["class"] = "oh-select oh-select-2 w-100"
|
||||
# attrs = self.fields["model"].widget.attrs
|
||||
|
||||
# attrs["onchange"] = "getToMail($(this))"
|
||||
# self.fields["mail_template"].empty_label = None
|
||||
# attrs = attrs.copy()
|
||||
# del attrs["onchange"]
|
||||
# self.fields["mail_details"].widget.attrs = attrs
|
||||
# if self.instance.pk:
|
||||
# self.fields["condition"].initial = self.instance.condition_html
|
||||
# self.fields["condition_html"].initial = self.instance.condition_html
|
||||
# self.fields["condition_querystring"].initial = (
|
||||
# self.instance.condition_querystring
|
||||
# )
|
||||
|
||||
class Meta:
|
||||
model = BonusPointSetting
|
||||
fields = "__all__"
|
||||
|
||||
# def as_p(self):
|
||||
# """
|
||||
# Render the form fields as HTML table rows with Bootstrap styling.
|
||||
# """
|
||||
# context = {"form": self}
|
||||
# table_html = render_to_string("horilla_form.html", context)
|
||||
# return table_html
|
||||
# def save(self, commit: bool = ...) -> Any:
|
||||
# self.instance: MailAutomation = self.instance
|
||||
# condition_querystring = self.cleaned_data["condition_querystring"]
|
||||
# condition_html = self.cleaned_data["condition_html"]
|
||||
# mail_to = self.data.getlist("mail_to")
|
||||
# self.instance.mail_to = str(mail_to)
|
||||
# self.instance.mail_details = self.data["mail_details"]
|
||||
# self.instance.condition_querystring = condition_querystring
|
||||
# self.instance.condition_html = condition_html
|
||||
# return super().save(commit)
|
||||
|
||||
|
||||
class EmployeeBonusPointForm(MF):
|
||||
"""
|
||||
EmployeeBonusPoint form
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = EmployeeBonusPoint
|
||||
fields = "__all__"
|
||||
|
||||
163
pms/models.py
163
pms/models.py
@@ -1,8 +1,15 @@
|
||||
import operator
|
||||
from datetime import date
|
||||
from typing import Iterable
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django import forms
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_delete, post_save, pre_save
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from base.horilla_company_manager import HorillaCompanyManager
|
||||
@@ -11,6 +18,8 @@ from employee.models import Employee
|
||||
from horilla.models import HorillaModel
|
||||
from horilla_audit.methods import get_diff
|
||||
from horilla_audit.models import HorillaAuditInfo, HorillaAuditLog
|
||||
from horilla_automations.methods.methods import get_model_class
|
||||
from horilla_views.cbv_methods import render_template
|
||||
|
||||
"""Objectives and key result section"""
|
||||
|
||||
@@ -723,6 +732,160 @@ class MeetingsAnswer(models.Model):
|
||||
return f"{self.employee_id.employee_first_name} - {self.answer}"
|
||||
|
||||
|
||||
class EmployeeBonusPoint(models.Model):
|
||||
employee_id = models.ForeignKey(
|
||||
Employee,
|
||||
on_delete=models.DO_NOTHING,
|
||||
related_name="employe_bonus_point",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Employee",
|
||||
)
|
||||
bonus_point = models.IntegerField(default=0)
|
||||
based_on = models.CharField(max_length=150)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.employee_id.employee_first_name} - {self.bonus_point}"
|
||||
|
||||
|
||||
class BonusPointSetting(models.Model):
|
||||
MODEL_CHOICES = [
|
||||
("pms.models.EmployeeObjective", "Objective"),
|
||||
("pms.models.EmployeeKeyResult", "Key Result"),
|
||||
]
|
||||
if apps.is_installed("project"):
|
||||
MODEL_CHOICES += [
|
||||
("project.models.Task", "Task"),
|
||||
("project.models.Project", "Project"),
|
||||
]
|
||||
BONUS_FOR = [
|
||||
("completed", "Completing"),
|
||||
]
|
||||
CONDITIONS = [
|
||||
("=", "="),
|
||||
(">", ">"),
|
||||
("<", "<"),
|
||||
("<=", "<="),
|
||||
(">=", ">="),
|
||||
]
|
||||
FIELD_1 = [
|
||||
("complition_date", "Completion Date"),
|
||||
]
|
||||
FIELD_2 = [
|
||||
("end_date", "End Date"),
|
||||
]
|
||||
model = models.CharField(max_length=100, choices=MODEL_CHOICES, null=False)
|
||||
bonus_for = models.CharField(max_length=25, choices=BONUS_FOR)
|
||||
field_1 = models.CharField(max_length=25, choices=FIELD_1, null=True, blank=True)
|
||||
conditions = models.CharField(
|
||||
max_length=25, choices=CONDITIONS, null=True, blank=True
|
||||
)
|
||||
field_2 = models.CharField(max_length=25, choices=FIELD_2, null=True, blank=True)
|
||||
points = models.IntegerField(default=0, validators=[MinValueValidator(0)])
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
def get_model_display(self):
|
||||
"""
|
||||
Display model
|
||||
"""
|
||||
return dict(BonusPointSetting.MODEL_CHOICES).get(self.model)
|
||||
|
||||
def get_bonus_for_display(self):
|
||||
"""
|
||||
Display bonus_for
|
||||
"""
|
||||
return dict(BonusPointSetting.BONUS_FOR).get(self.bonus_for)
|
||||
|
||||
def get_field_1_display(self):
|
||||
"""
|
||||
Display field_1
|
||||
"""
|
||||
return dict(BonusPointSetting.FIELD_1).get(self.field_1)
|
||||
|
||||
def get_field_2_display(self):
|
||||
"""
|
||||
Display field_2
|
||||
"""
|
||||
return dict(BonusPointSetting.FIELD_2).get(self.field_2)
|
||||
|
||||
def get_condition(self):
|
||||
"""
|
||||
Get the condition for bonus
|
||||
"""
|
||||
return f" {dict(BonusPointSetting.FIELD_1).get(self.field_1)} {self.conditions} {dict(BonusPointSetting.FIELD_2).get(self.field_2)}"
|
||||
|
||||
def action_template(self):
|
||||
"""
|
||||
This method for get custom column for managers.
|
||||
"""
|
||||
|
||||
return render_template(
|
||||
path="bonus/bonus_seetting_action.html",
|
||||
context={"instance": self},
|
||||
)
|
||||
|
||||
def create_employee_bonus(self, employee, field_1, field_2):
|
||||
"""
|
||||
For creating employee bonus
|
||||
"""
|
||||
operator_mapping = {
|
||||
"=": operator.eq,
|
||||
"!=": operator.ne,
|
||||
"<": operator.lt,
|
||||
">": operator.gt,
|
||||
"<=": operator.le,
|
||||
">=": operator.ge,
|
||||
}
|
||||
if operator_mapping[self.conditions](field_1, field_2):
|
||||
EmployeeBonusPoint(
|
||||
employee_id=employee,
|
||||
based_on=(f"{self.get_bonus_for_display} {self.model}"),
|
||||
bonus_point=self.points,
|
||||
).save()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
model_class = get_model_class(self.model)
|
||||
|
||||
def create_signal_handler(name, bonus_point_setting):
|
||||
def signal_handler(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Signal handler for post-save events of the model instances.
|
||||
"""
|
||||
# request = getattr(_thread_locals, "request", None)
|
||||
# previous_record = getattr(_thread_locals, "previous_record", None)
|
||||
# previous_instance = None
|
||||
# if previous_record:
|
||||
# previous_instance = previous_record["instance"]
|
||||
|
||||
# if BonusPointSetting.objects.filter(model='Task').exists():
|
||||
# bonus_point_settings = BonusPointSetting.objects.filter(model='Task')
|
||||
# for bs in bonus_point_settings:
|
||||
|
||||
field_1 = date.today()
|
||||
field_2 = instance.end_date
|
||||
if bonus_point_setting.bonus_for == instance.status:
|
||||
|
||||
for employee in instance.task_members.all():
|
||||
bonus_point_setting.create_employee_bonus(
|
||||
employee, field_1, field_2
|
||||
)
|
||||
|
||||
signal_handler.__name__ = name
|
||||
signal_handler.model_class = model_class
|
||||
signal_handler.bonus_point_setting = bonus_point_setting
|
||||
return signal_handler
|
||||
|
||||
# Create and connect the signal handler
|
||||
handler_name = f"{self.id}_signal_handler"
|
||||
dynamic_signal_handler = create_signal_handler(handler_name, self)
|
||||
# SIGNAL_HANDLERS.append(dynamic_signal_handler)
|
||||
post_save.connect(
|
||||
dynamic_signal_handler, sender=dynamic_signal_handler.model_class
|
||||
)
|
||||
|
||||
|
||||
def manipulate_existing_data():
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ SUBMENUS = [
|
||||
"redirect": reverse("view-key-result"),
|
||||
"accessibility": "pms.sidebar.key_result_accessibility",
|
||||
},
|
||||
{
|
||||
"menu": trans("Employee Bonus Point"),
|
||||
"redirect": reverse("employee-bonus-point"),
|
||||
},
|
||||
{
|
||||
"menu": trans("Period"),
|
||||
"redirect": reverse("period-view"),
|
||||
|
||||
233
pms/signals.py
Normal file
233
pms/signals.py
Normal file
@@ -0,0 +1,233 @@
|
||||
"""
|
||||
pms/signals.py
|
||||
"""
|
||||
import copy
|
||||
import logging
|
||||
import threading
|
||||
import types
|
||||
from django.db.models.signals import post_delete, post_save, pre_save
|
||||
from django.dispatch import receiver
|
||||
from horilla.horilla_middlewares import _thread_locals
|
||||
from pms.models import BonusPointSetting
|
||||
from datetime import date
|
||||
|
||||
|
||||
|
||||
|
||||
SIGNAL_HANDLERS = []
|
||||
INSTANCE_HANDLERS = []
|
||||
|
||||
def start_automation():
|
||||
"""
|
||||
Automation signals
|
||||
"""
|
||||
from horilla_automations.methods.methods import get_model_class, split_query_string
|
||||
|
||||
|
||||
@receiver(post_delete, sender=BonusPointSetting)
|
||||
@receiver(post_save, sender=BonusPointSetting)
|
||||
def automation_pre_create(sender, instance, **kwargs):
|
||||
"""
|
||||
signal method to handle automation post save
|
||||
"""
|
||||
start_connection()
|
||||
track_previous_instance()
|
||||
|
||||
def clear_connection():
|
||||
"""
|
||||
Method to clear signals handlers
|
||||
"""
|
||||
for handler in SIGNAL_HANDLERS:
|
||||
post_save.disconnect(handler, sender=handler.model_class)
|
||||
SIGNAL_HANDLERS.clear()
|
||||
|
||||
def create_post_bulk_update_handler(automation, model_class, query_strings):
|
||||
def post_bulk_update_handler(sender, queryset, *args, **kwargs):
|
||||
def _bulk_update_thread_handler(
|
||||
queryset, previous_queryset_copy, automation
|
||||
):
|
||||
request = getattr(queryset, "request", None)
|
||||
|
||||
if request:
|
||||
for index, instance in enumerate(queryset):
|
||||
previous_instance = previous_queryset_copy[index]
|
||||
send_automated_mail(
|
||||
request,
|
||||
False,
|
||||
automation,
|
||||
query_strings,
|
||||
instance,
|
||||
previous_instance,
|
||||
)
|
||||
previous_bulk_record = getattr(_thread_locals, "previous_bulk_record", None)
|
||||
previous_queryset = None
|
||||
if previous_bulk_record:
|
||||
previous_queryset = previous_bulk_record["queryset"]
|
||||
previous_queryset_copy = previous_bulk_record["queryset_copy"]
|
||||
|
||||
bulk_thread = threading.Thread(
|
||||
target=_bulk_update_thread_handler,
|
||||
args=(queryset, previous_queryset_copy, automation),
|
||||
)
|
||||
bulk_thread.start()
|
||||
|
||||
func_name = f"{automation.method_title}_post_bulk_signal_handler"
|
||||
|
||||
# Dynamically create a function with a unique name
|
||||
handler = types.FunctionType(
|
||||
post_bulk_update_handler.__code__,
|
||||
globals(),
|
||||
name=func_name,
|
||||
argdefs=post_bulk_update_handler.__defaults__,
|
||||
closure=post_bulk_update_handler.__closure__,
|
||||
)
|
||||
|
||||
# Set additional attributes on the function
|
||||
handler.model_class = model_class
|
||||
handler.automation = automation
|
||||
|
||||
return handler
|
||||
|
||||
def start_connection():
|
||||
"""
|
||||
Method to start signal connection accordingly to the automation
|
||||
"""
|
||||
clear_connection()
|
||||
bonus_point_settings = BonusPointSetting.objects.filter(is_active=True)
|
||||
for bonus_point_setting in bonus_point_settings:
|
||||
|
||||
# condition_querystring = bonus_point_setting.condition_querystring.replace(
|
||||
# "automation_multiple_", ""
|
||||
# )
|
||||
|
||||
# query_strings = split_query_string(condition_querystring)
|
||||
# model_path should me in the form of pms.models.Objective
|
||||
model_path = bonus_point_setting.model
|
||||
model_class = get_model_class(model_path)
|
||||
|
||||
# handler = create_post_bulk_update_handler(
|
||||
# bonus_point_setting, model_class, query_strings
|
||||
# )
|
||||
# SIGNAL_HANDLERS.append(handler)
|
||||
# post_bulk_update.connect(handler, sender=model_class)
|
||||
|
||||
def create_signal_handler(name, bonus_point_setting):
|
||||
def signal_handler(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Signal handler for post-save events of the model instances.
|
||||
"""
|
||||
# request = getattr(_thread_locals, "request", None)
|
||||
# previous_record = getattr(_thread_locals, "previous_record", None)
|
||||
# previous_instance = None
|
||||
# if previous_record:
|
||||
# previous_instance = previous_record["instance"]
|
||||
|
||||
# if BonusPointSetting.objects.filter(model='Task').exists():
|
||||
# bonus_point_settings = BonusPointSetting.objects.filter(model='Task')
|
||||
# for bs in bonus_point_settings:
|
||||
|
||||
field_1 = date.today()
|
||||
field_2 = instance.end_date
|
||||
|
||||
if bonus_point_setting.bonus_for == instance.status :
|
||||
|
||||
for employee in instance.task_members.all():
|
||||
bonus_point_setting.create_employee_bonus(employee,field_1,field_2)
|
||||
|
||||
signal_handler.__name__ = name
|
||||
signal_handler.model_class = model_class
|
||||
signal_handler.bonus_point_setting = bonus_point_setting
|
||||
return signal_handler
|
||||
|
||||
# Create and connect the signal handler
|
||||
handler_name = f"{bonus_point_setting.id}_signal_handler"
|
||||
dynamic_signal_handler = create_signal_handler(
|
||||
handler_name, bonus_point_setting
|
||||
)
|
||||
SIGNAL_HANDLERS.append(dynamic_signal_handler)
|
||||
post_save.connect(
|
||||
dynamic_signal_handler, sender=dynamic_signal_handler.model_class
|
||||
)
|
||||
|
||||
def create_pre_bulk_update_handler(automation, model_class):
|
||||
def pre_bulk_update_handler(sender, queryset, *args, **kwargs):
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
if request:
|
||||
queryset_copy = queryset.none()
|
||||
if queryset.count():
|
||||
queryset_copy = QuerySet.from_list(copy.deepcopy(list(queryset)))
|
||||
_thread_locals.previous_bulk_record = {
|
||||
"automation": automation,
|
||||
"queryset": queryset,
|
||||
"queryset_copy": queryset_copy,
|
||||
}
|
||||
|
||||
func_name = f"{automation.method_title}_pre_bulk_signal_handler"
|
||||
|
||||
# Dynamically create a function with a unique name
|
||||
handler = types.FunctionType(
|
||||
pre_bulk_update_handler.__code__,
|
||||
globals(),
|
||||
name=func_name,
|
||||
argdefs=pre_bulk_update_handler.__defaults__,
|
||||
closure=pre_bulk_update_handler.__closure__,
|
||||
)
|
||||
|
||||
# Set additional attributes on the function
|
||||
handler.model_class = model_class
|
||||
handler.automation = automation
|
||||
|
||||
return handler
|
||||
|
||||
def track_previous_instance():
|
||||
"""
|
||||
method to add signal to track the automations model previous instances
|
||||
"""
|
||||
|
||||
def clear_instance_signal_connection():
|
||||
"""
|
||||
Method to clear instance handler signals
|
||||
"""
|
||||
|
||||
for handler in INSTANCE_HANDLERS:
|
||||
|
||||
pre_save.disconnect(handler, sender=handler.model_class)
|
||||
pre_bulk_update.disconnect(handler, sender=handler.model_class)
|
||||
INSTANCE_HANDLERS.clear()
|
||||
|
||||
clear_instance_signal_connection()
|
||||
bonus_point_settings = BonusPointSetting.objects.filter(is_active=True)
|
||||
for bonus_setting in bonus_point_settings:
|
||||
model_class = get_model_class(bonus_setting.model)
|
||||
|
||||
# handler = create_pre_bulk_update_handler(bonus_setting, model_class)
|
||||
# INSTANCE_HANDLERS.append(handler)
|
||||
# pre_bulk_update.connect(handler, sender=model_class)
|
||||
|
||||
@receiver(pre_save, sender=model_class)
|
||||
def instance_handler(sender, instance, **kwargs):
|
||||
"""
|
||||
Signal handler for pres-save events of the model instances.
|
||||
"""
|
||||
# prevented storing the scheduled activities
|
||||
request = getattr(_thread_locals, "request", None)
|
||||
if instance.pk:
|
||||
# to get the previous instance
|
||||
instance = model_class.objects.filter(id=instance.pk).first()
|
||||
if request:
|
||||
_thread_locals.previous_record = {
|
||||
"bonus_setting": bonus_setting,
|
||||
"instance": instance,
|
||||
}
|
||||
instance_handler.__name__ = (
|
||||
f"{bonus_setting.id}_instance_handler"
|
||||
)
|
||||
return instance_handler
|
||||
|
||||
instance_handler.model_class = model_class
|
||||
instance_handler.bonus_setting = bonus_setting
|
||||
|
||||
INSTANCE_HANDLERS.append(instance_handler)
|
||||
|
||||
track_previous_instance()
|
||||
start_connection()
|
||||
44
pms/templates/bonus/bonus_form.html
Normal file
44
pms/templates/bonus/bonus_form.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<div id="formContainer">
|
||||
{% include "generic/horilla_form.html" %}
|
||||
</div>
|
||||
|
||||
{% comment %} {% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="objectCreateModalLabel"
|
||||
>{% trans "Bonus Point Settings" %}</span
|
||||
>
|
||||
<button
|
||||
class="oh-modal__close--custom"
|
||||
onclick="$(this).closest('.oh-modal--show').removeClass('oh-modal--show');"
|
||||
aria-label="Close"
|
||||
{% if close_hx_url and messages %}
|
||||
hx-get="{{close_hx_url}}"
|
||||
hx-target="{{close_hx_target}}"
|
||||
{% endif %}
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body" id="formBody">
|
||||
<form hx-post="{% url 'create-bonus-setting' %}?instance_id={{form.instance.id}}" hx-target="#objectCreateModalTarget">
|
||||
{{form.as_p}}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<!-- $(document).ready(function () {
|
||||
$("#id_employee_id").closest(".col-md-6").removeClass("col-md-6")
|
||||
$("#id_employee_id").on("change", function () {
|
||||
var employees = $(this).find("option:selected");
|
||||
values = $("#id_answer_employees").val();
|
||||
$("#id_answer_employees").empty();
|
||||
employees.each(function () {
|
||||
var value = $(this).val();
|
||||
var text = $(this).text();
|
||||
var option = `<option value="${value}"> ${text}</option>`;
|
||||
$("#id_answer_employees").append(option);
|
||||
});
|
||||
$("#id_answer_employees").val(values).change();
|
||||
});
|
||||
}); -->
|
||||
</script> {% endcomment %}
|
||||
49
pms/templates/bonus/bonus_point_setting_section.html
Normal file
49
pms/templates/bonus/bonus_point_setting_section.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends 'settings.html' %}
|
||||
{% block settings %}
|
||||
{% load i18n %} {% load static %}
|
||||
|
||||
|
||||
{% for path in style_path %}
|
||||
<link rel="stylesheet" href="{{path}}"/>
|
||||
{% endfor %}
|
||||
{% for path in script_static_paths %}
|
||||
<script src="{{path}}"></script>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% include "generic/components.html" %}
|
||||
|
||||
<div
|
||||
class="oh-checkpoint-badge mb-2"
|
||||
id="selectedInstances"
|
||||
data-ids="[]"
|
||||
data-clicked=""
|
||||
style="display: none"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
hx-get="{{nav_url}}?{{request.GET.urlencode}}"
|
||||
hx-trigger="load"
|
||||
>
|
||||
<div
|
||||
class="mt-5 oh-wrapper animated-background"
|
||||
style="height:80px;"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-wrapper"
|
||||
hx-get="{{view_url}}?{{request.GET.urlencode}}"
|
||||
hx-trigger="load"
|
||||
id="{{view_container_id}}"
|
||||
>
|
||||
<div
|
||||
class="mt-4 animated-background"
|
||||
style="height:600px;"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock settings %}
|
||||
27
pms/templates/bonus/bonus_seetting_action.html
Normal file
27
pms/templates/bonus/bonus_seetting_action.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% load i18n %}
|
||||
<div onclick="event.stopPropagation();" class="oh-btn-group">
|
||||
{% if perms.leave.change_holiday %}
|
||||
<button
|
||||
class="oh-btn oh-btn--light-bkg w-100"
|
||||
title="{% trans 'Edit' %}"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-get="{% url 'update-bonus-point-setting' instance.id %}?{{pd}}"
|
||||
hx-target="#genericModalBody"
|
||||
>
|
||||
<ion-icon name="create-outline"></ion-icon>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if perms.leave.delete_holiday %}
|
||||
<a
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
id="delete-link"
|
||||
hx-confirm="{% trans 'Do you want to delete the bonus point setting?' %}"
|
||||
hx-post="{% url 'delete-bonus-point-setting' instance.id %}"
|
||||
hx-target="#listContainer"
|
||||
title="{% trans 'Delete' %}"
|
||||
>
|
||||
<ion-icon name="trash-outline"></ion-icon>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
50
pms/templates/bonus/employee_bonus_point_section.html
Normal file
50
pms/templates/bonus/employee_bonus_point_section.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% load i18n %} {% load static %}
|
||||
|
||||
|
||||
{% for path in style_path %}
|
||||
<link rel="stylesheet" href="{{path}}"/>
|
||||
{% endfor %}
|
||||
{% for path in script_static_paths %}
|
||||
<script src="{{path}}"></script>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% include "generic/components.html" %}
|
||||
|
||||
<div
|
||||
class="oh-checkpoint-badge mb-2"
|
||||
id="selectedInstances"
|
||||
data-ids="[]"
|
||||
data-clicked=""
|
||||
style="display: none"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
hx-get="{{nav_url}}?{{request.GET.urlencode}}"
|
||||
hx-trigger="load"
|
||||
>
|
||||
<div
|
||||
class="mt-5 oh-wrapper animated-background"
|
||||
style="height:80px;"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-wrapper"
|
||||
hx-get="{{view_url}}?{{request.GET.urlencode}}"
|
||||
hx-trigger="load"
|
||||
id="{{view_container_id}}"
|
||||
>
|
||||
<div
|
||||
class="mt-4 animated-background"
|
||||
style="height:600px;"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
52
pms/templates/bonus/view_bonus_settings.html
Normal file
52
pms/templates/bonus/view_bonus_settings.html
Normal file
@@ -0,0 +1,52 @@
|
||||
{% extends 'settings.html' %} {% load i18n %} {% block settings %} {% load static %}
|
||||
<div class="oh-inner-sidebar-content">
|
||||
{% if perms.base.view_department %}
|
||||
<div
|
||||
class="oh-inner-sidebar-content__header d-flex justify-content-between align-items-center"
|
||||
>
|
||||
<h2 class="oh-inner-sidebar-content__title">{% trans "Bonus Point Setting" %}</h2>
|
||||
{% if perms.base.add_department %}
|
||||
<button
|
||||
class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#departmentModal"
|
||||
hx-get="{% url 'create-bonus-setting' %}"
|
||||
hx-target="#departmentForm"
|
||||
>
|
||||
<ion-icon name="add-outline" class="me-1"></ion-icon>
|
||||
{% trans "Create" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if bonous_settings %}
|
||||
<!-- {% include 'base/department/department_view.html' %} -->
|
||||
{% else %}
|
||||
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%;">
|
||||
<img style="display: block; width: 15%; margin: 20px auto; filter: opacity(0.5);" src="{% static 'images/ui/coins.png' %}" class="" alt="Page not found. 404." />
|
||||
<h5 class="oh-404__subtitle">{% trans "There is no Bonus setting at this moment." %}</h5>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="departmentModal"
|
||||
role="dialog"
|
||||
aria-labelledby="departmentModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog" id="departmentForm"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="departmentEditModal"
|
||||
role="dialog"
|
||||
aria-labelledby="departmentEditModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog" id="departmentEditForm"></div>
|
||||
</div>
|
||||
|
||||
{% endblock settings %}
|
||||
58
pms/urls.py
58
pms/urls.py
@@ -1,6 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
from base.views import object_delete
|
||||
from pms import cbvs
|
||||
|
||||
from . import models, views
|
||||
|
||||
@@ -415,4 +416,61 @@ urlpatterns = [
|
||||
views.dashboard_feedback_answer,
|
||||
name="dashboard-feedback-answer",
|
||||
),
|
||||
# ===========bonus point setting============
|
||||
# path(
|
||||
# "view-bonus-setting",
|
||||
# views.view_bonus_setting,
|
||||
# name="view-bonus-setting",
|
||||
# ),
|
||||
path(
|
||||
"bonus-point-setting",
|
||||
cbvs.BonusPointSettingSectionView.as_view(),
|
||||
name="bonus-point-setting",
|
||||
),
|
||||
path(
|
||||
"bonus-point-setting-nav",
|
||||
cbvs.BonusPointSettingNavView.as_view(),
|
||||
name="bonus-point-setting-nav",
|
||||
),
|
||||
path(
|
||||
"create-bonus-point-setting",
|
||||
cbvs.BonusPointSettingFormView.as_view(),
|
||||
name="create-bonus-point-setting",
|
||||
),
|
||||
path(
|
||||
"update-bonus-point-setting/<int:pk>/",
|
||||
cbvs.BonusPointSettingFormView.as_view(),
|
||||
name="update-bonus-point-setting",
|
||||
),
|
||||
path(
|
||||
"delete-bonus-point-setting/<int:pk>/",
|
||||
views.delete_bonus_point_setting,
|
||||
name="delete-bonus-point-setting",
|
||||
),
|
||||
path(
|
||||
"bonus-point-setting-list-view",
|
||||
cbvs.BonusPointSettingListView.as_view(),
|
||||
name="bonus-point-setting-list-view",
|
||||
),
|
||||
# ===========Employee bonus point============
|
||||
path(
|
||||
"employee-bonus-point",
|
||||
cbvs.EmployeeBonusPointSectionView.as_view(),
|
||||
name="employee-bonus-point",
|
||||
),
|
||||
path(
|
||||
"employee-bonus-point-nav",
|
||||
cbvs.EmployeeBonusPointNavView.as_view(),
|
||||
name="employee-bonus-point-nav",
|
||||
),
|
||||
path(
|
||||
"create-employee-bonus-point",
|
||||
cbvs.EmployeeBonusPointFormView.as_view(),
|
||||
name="create-employee-bonus-point",
|
||||
),
|
||||
path(
|
||||
"employee-bonus-point-list-view",
|
||||
cbvs.EmployeeBonusPointListView.as_view(),
|
||||
name="employee-bonus-point-list-view",
|
||||
),
|
||||
]
|
||||
|
||||
16
pms/views.py
16
pms/views.py
@@ -67,6 +67,7 @@ from pms.methods import (
|
||||
from pms.models import (
|
||||
AnonymousFeedback,
|
||||
Answer,
|
||||
BonusPointSetting,
|
||||
Comment,
|
||||
EmployeeKeyResult,
|
||||
EmployeeObjective,
|
||||
@@ -3575,3 +3576,18 @@ def dashboard_feedback_answer(request):
|
||||
"request_and_approve/feedback_answer.html",
|
||||
{"feedbacks": feedbacks, "current_date": datetime.date.today()},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("pms.delete_bonuspointsetting")
|
||||
def delete_bonus_point_setting(request, pk):
|
||||
"""
|
||||
Automation delete view
|
||||
"""
|
||||
try:
|
||||
BonusPointSetting.objects.get(id=pk).delete()
|
||||
messages.success(request, "Bonus Point Setting deleted")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
messages.error(request, "Something went wrong")
|
||||
return redirect(reverse("bonus-point-setting-list-view"))
|
||||
|
||||
BIN
static/images/ui/coins.png
Normal file
BIN
static/images/ui/coins.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -445,6 +445,33 @@
|
||||
</li>
|
||||
<hr>
|
||||
{% endif %}
|
||||
{% if "pms"|app_installed %}
|
||||
<li class="oh-inner-sidebar__item">
|
||||
<div class="oh-accordion ms-2">
|
||||
<div class="oh-accordion-header" style="font-size:16px;">
|
||||
<ion-icon name="analytics-outline" class="me-2"></ion-icon>
|
||||
{% trans "Performnce" %}
|
||||
</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="oh-input-group">
|
||||
{% if perms.pms.view_payslipautogenerate %}
|
||||
<a
|
||||
id="autoGeneratePayslip"
|
||||
href="{% url 'bonus-point-setting' %}"
|
||||
class="oh-inner-sidebar__link oh-dropdown__link"
|
||||
>{% trans "Bonus Point Setting" %}</a
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<hr>
|
||||
{% endif %}
|
||||
{% if "helpdesk"|app_installed %}
|
||||
<li class="oh-inner-sidebar__item">
|
||||
<div class="oh-accordion ms-2">
|
||||
|
||||
Reference in New Issue
Block a user