[ADD] EMPLOYEE: Bonus point system and view inside employee profile view
This commit is contained in:
@@ -108,3 +108,11 @@ def user_perms(perms):
|
||||
permission names return method
|
||||
"""
|
||||
return json.dumps(list(perms.values_list("codename", flat="True")))
|
||||
|
||||
|
||||
@register.filter(name="abs_value")
|
||||
def abs_value(value):
|
||||
"""
|
||||
permission names return method
|
||||
"""
|
||||
return abs(value)
|
||||
@@ -5,6 +5,7 @@ This page is used to register the model with admins site.
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from employee.models import (
|
||||
BonusPoint,
|
||||
Employee,
|
||||
EmployeeWorkInformation,
|
||||
EmployeeBankDetails,
|
||||
@@ -21,4 +22,4 @@ from simple_history.admin import SimpleHistoryAdmin
|
||||
admin.site.register(Employee)
|
||||
admin.site.register(EmployeeBankDetails)
|
||||
admin.site.register(EmployeeWorkInformation, SimpleHistoryAdmin)
|
||||
admin.site.register([EmployeeNote, EmployeeTag, PolicyMultipleFile, Policy])
|
||||
admin.site.register([EmployeeNote, EmployeeTag, PolicyMultipleFile, Policy, BonusPoint])
|
||||
|
||||
@@ -28,6 +28,7 @@ from django.forms import DateInput, TextInput
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy as trans
|
||||
from employee.models import (
|
||||
BonusPoint,
|
||||
Employee,
|
||||
EmployeeWorkInformation,
|
||||
EmployeeBankDetails,
|
||||
@@ -478,3 +479,19 @@ class PolicyForm(ModelForm):
|
||||
if commit:
|
||||
instance.attachments.add(*multiple_attachment_ids)
|
||||
return instance, attachemnts
|
||||
|
||||
|
||||
class BonusPointAddForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = BonusPoint
|
||||
fields = ["points", "reason"]
|
||||
widgets = {
|
||||
'reason': forms.TextInput(attrs={'required': 'required'}),
|
||||
}
|
||||
|
||||
class BonusPointRedeemForm(ModelForm):
|
||||
class Meta:
|
||||
model = BonusPoint
|
||||
fields = ["points"]
|
||||
|
||||
@@ -7,10 +7,14 @@ This module is used to register models for employee app
|
||||
import datetime as dtime
|
||||
from datetime import date, datetime
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
from django.utils.translation import gettext_lazy as trans
|
||||
from django.utils.translation import gettext as _
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -574,3 +578,53 @@ class Policy(models.Model):
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
self.attachments.all().delete()
|
||||
|
||||
|
||||
class BonusPoint(models.Model):
|
||||
CONDITIONS =[
|
||||
('==',_('equals')),
|
||||
('>',_('grater than')),
|
||||
('<',_('less than')),
|
||||
('>=',_('greater than or equal')),
|
||||
('<=',_('less than or equal')),
|
||||
]
|
||||
employee_id = models.OneToOneField(Employee, on_delete=models.PROTECT ,blank=True, null=True, related_name='bonus_point')
|
||||
points = models.IntegerField(default=0, help_text="Use negative numbers to reduce points.")
|
||||
encashment_condition = models.CharField(max_length=100,choices = CONDITIONS,blank=True, null=True)
|
||||
redeeming_points = models.IntegerField(blank=True, null=True)
|
||||
reason = models.TextField(blank=True, null=True)
|
||||
history = HorillaAuditLog(
|
||||
related_name="history_set",
|
||||
bases=[
|
||||
HorillaAuditInfo,
|
||||
],
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.employee_id} - {self.points} Points"
|
||||
|
||||
def tracking(self):
|
||||
"""
|
||||
This method is used to return the tracked history of the instance
|
||||
"""
|
||||
return get_diff(self)
|
||||
|
||||
@receiver(post_save, sender=Employee)
|
||||
def bonus_post_save(sender, instance, **_kwargs):
|
||||
if not BonusPoint.objects.filter(employee_id__id = instance.id).exists():
|
||||
BonusPoint.objects.create(
|
||||
employee_id = instance
|
||||
)
|
||||
|
||||
class BonusPointThreading(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
time.sleep(5)
|
||||
employees = Employee.objects.all()
|
||||
for employee in employees:
|
||||
if not BonusPoint.objects.filter(employee_id__id = employee.id).exists():
|
||||
BonusPoint.objects.create(
|
||||
employee_id = employee
|
||||
)
|
||||
|
||||
BonusPointThreading().start()
|
||||
@@ -214,6 +214,17 @@
|
||||
>{% trans "Performance" %}</a
|
||||
>
|
||||
</li>
|
||||
<li class="oh-general__tab">
|
||||
<a
|
||||
hx-get={% url 'bonus-points-tab' employee.id %}
|
||||
hx-target="#bonus_points_target"
|
||||
data-action="general-tab"
|
||||
data-target="#bonus_points_target"
|
||||
class="oh-general__tab-link"
|
||||
role="button"
|
||||
>{% trans "Bonus Points" %}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
@@ -233,6 +244,12 @@
|
||||
>
|
||||
{% include 'tabs/payroll-tab.html' %}
|
||||
</div>
|
||||
<div
|
||||
class="oh-general__tab-target oh-profile__info-tab mb-4 d-none"
|
||||
id="bonus_points_target"
|
||||
>
|
||||
{% include "tabs/bonus_points.html" %}
|
||||
</div>
|
||||
<div
|
||||
class="oh-general__tab-target oh-profile__info-tab mb-4 d-none"
|
||||
id="allowance_deduction"
|
||||
|
||||
@@ -289,6 +289,19 @@
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.employee.view_employeenote or request.user|check_manager:employee %}
|
||||
<li class="oh-general__tab">
|
||||
<a
|
||||
hx-get={% url 'bonus-points-tab' employee.id %}
|
||||
hx-target="#bonus_points_target"
|
||||
data-action="general-tab"
|
||||
data-target="#bonus_points_target"
|
||||
class="oh-general__tab-link"
|
||||
role="button"
|
||||
>{% trans "Bonus Points" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<div
|
||||
@@ -303,6 +316,12 @@
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-general__tab-target oh-profile__info-tab mb-4 d-none"
|
||||
id="bonus_points_target"
|
||||
>
|
||||
{% include "tabs/bonus_points.html" %}
|
||||
</div>
|
||||
<div
|
||||
class="oh-general__tab-target oh-profile__info-tab mb-4 d-none"
|
||||
id="note_target"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
{% endfor %}
|
||||
{% if perms.employee.add_policy %}
|
||||
<input type="hidden" name="csrfmiddlewaretoken"/>
|
||||
{% csrf_token %}
|
||||
<input type="file" name="files" class="d-none" multiple="true" id="addFile_18" onchange="submitForm(this)" />
|
||||
<input type="submit" class="d-none add_more_submit" value="save" />
|
||||
<label for="addFile_18" title="Add Files" onclick="console.log($(this).closest('[type=file]'));"
|
||||
|
||||
112
employee/templates/tabs/bonus_points.html
Normal file
112
employee/templates/tabs/bonus_points.html
Normal file
@@ -0,0 +1,112 @@
|
||||
{% load i18n %} {% load basefilters %}
|
||||
<div class="oh-wrapper d-flex justify-content-between mt-4">
|
||||
<div class="oh-faq-cards">
|
||||
|
||||
<div class="oh-faq-card">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h3 class="oh-faq-card__title">{% trans "Bonus Points" %}</h3>
|
||||
{% if perms.employee.add_bonuspoint or request.user|check_manager:employee %}
|
||||
<div class="oh-dropdown">
|
||||
<button
|
||||
class="oh-btn oh-btn--secondary-outline oh-stop-prop oh-accordion-meta__btn p-2"
|
||||
title="Add points"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#addPointsModal"
|
||||
hx-get="{% url 'add-bonus-points' employee.id %}"
|
||||
hx-target="#addPointTarget"
|
||||
>
|
||||
<ion-icon
|
||||
name="add-outline"
|
||||
role="img"
|
||||
class="md hydrated"
|
||||
aria-label="ellipsis vertical"
|
||||
></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__profile-content">
|
||||
<div class="oh-profile mb-2">
|
||||
<div class="oh-profile__avatar">
|
||||
<img src="{{employee.get_avatar}}" class="oh-profile__image me-2" />
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__profile-info">
|
||||
<span class="oh-timeoff-modal__user m-0 fw-bold">{{employee}}</span>
|
||||
<span class="oh-timeoff-modal__user m-0" style="font-size: 12px; color: #4d4a4a">
|
||||
{{employee.get_department}} / {{employee.get_job_position}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body d-flex justify-content-between align-items-center p-3" style="height: 100px;">
|
||||
<h4 style="font-size: 16px; color: #4f5153; width: 60%;"> {% trans "Balance points to redeem:" %} </h4>
|
||||
<h4 class="float-end fw-bold">{{points.points}}</h4>
|
||||
</div>
|
||||
<a
|
||||
hx-get="{% url 'redeem-points' employee.id %}"
|
||||
hx-target="#redeemModalTarget"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#redeemModal"
|
||||
class="oh-btn oh-btn--secondary oh-btn--block"
|
||||
>{% trans "Redeem Now" %}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-faq-card" style="width: 60%;">
|
||||
{% for activity in activity_list %}
|
||||
<div class="oh-helpdesk__chat-update pb-0">
|
||||
{% if activity.type == 'Bonus point created' %}
|
||||
<span> --> {% trans "Bonus Account created" %} </span>
|
||||
<span class="dateformat_changer">{{ activity.date|date:"d N Y"}}</span>
|
||||
{% else %}
|
||||
<span
|
||||
>{% if activity.reason == 'bonus points has been redeemed.' %} <strong> --> {{activity.points|abs_value}} </strong> {% else %}<strong>--> {{activity.user}}</strong> {% trans "Added " %}
|
||||
<strong>{{activity.points}} </strong> {% trans "bonus points for " %}{% endif %} <a title="{{activity.reason}}">{{activity.reason|truncatechars:40}}</a> </span
|
||||
>
|
||||
<span class="dateformat_changer">{{ activity.date|date:"d N Y"}}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="addPointsModal"
|
||||
role="dialog"
|
||||
aria-labelledby="addPointsModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addPointsModalLabel">
|
||||
<h5>{% trans "Add Bonus Points" %}</h5>
|
||||
</span>
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body" id="addPointTarget"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="redeemModal"
|
||||
role="dialog"
|
||||
aria-labelledby="redeemModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog oh-modal__dialog--timeoff oh-timeoff-modal">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="">{% trans "Redeem bonus points" %}</h2>
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="oh-modal__dialog-body oh-modal__dialog-relative oh-timeoff-modal__body"
|
||||
id="redeemModalTarget"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
40
employee/templates/tabs/forms/add_points.html
Normal file
40
employee/templates/tabs/forms/add_points.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% load i18n %}
|
||||
<form
|
||||
class="oh-general__tab-target oh-profile-section"
|
||||
action="{% url 'add-bonus-points' emp_id %}"
|
||||
method="post"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-4">
|
||||
<label
|
||||
class="oh-label"
|
||||
for="id_points"
|
||||
title="{{form.points.help_text|safe}}"
|
||||
>{% trans "Points :" %}</label
|
||||
>
|
||||
<div
|
||||
class="w-100 d-flex"
|
||||
style="align-items: center; justify-content: center !important"
|
||||
>
|
||||
{{form.points}} {{form.points.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-8">
|
||||
<label class="oh-label" for="id_reason"
|
||||
>{% trans "Reason :" %}</label
|
||||
>
|
||||
<div
|
||||
class="w-100 d-flex"
|
||||
style="align-items: center; justify-content: center !important"
|
||||
>
|
||||
{{form.reason}} {{form.reason.errors}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<button class="oh-btn oh-btn--secondary pr-4 pl-4" type="submit">
|
||||
{% trans "Add" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
29
employee/templates/tabs/forms/redeem_points_form.html
Normal file
29
employee/templates/tabs/forms/redeem_points_form.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% load i18n %}
|
||||
<form
|
||||
class="oh-general__tab-target oh-profile-section"
|
||||
action="{% url 'redeem-points' employee.id %}"
|
||||
method="post"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-12">
|
||||
<label
|
||||
class="oh-label"
|
||||
for="id_points"
|
||||
title="{{form.points.help_text|safe}}"
|
||||
>{% trans "Points :" %}</label
|
||||
>
|
||||
<div
|
||||
class="w-100 d-flex"
|
||||
style="align-items: center; justify-content: center !important"
|
||||
>
|
||||
{{form.points}} {{form.points.errors}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<button class="oh-btn oh-btn--secondary pr-4 pl-4" type="submit">
|
||||
{% trans "Add" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -211,6 +211,9 @@ urlpatterns = [
|
||||
name="contract-tab",
|
||||
kwargs={"model": Employee},
|
||||
),
|
||||
path("bonus-points-tab/<int:emp_id>", views.bonus_points_tab, name="bonus-points-tab"),
|
||||
path("add-bonus-points/<int:emp_id>", views.add_bonus_points, name="add-bonus-points"),
|
||||
path("redeem-points/<int:emp_id>", views.redeem_points, name="redeem-points"),
|
||||
path("employee-select/", views.employee_select, name="employee-select"),
|
||||
path(
|
||||
"employee-select-filter/",
|
||||
|
||||
@@ -69,6 +69,8 @@ from base.methods import (
|
||||
)
|
||||
from employee.filters import EmployeeFilter, EmployeeReGroup
|
||||
from employee.forms import (
|
||||
BonusPointAddForm,
|
||||
BonusPointRedeemForm,
|
||||
BulkUpdateFieldForm,
|
||||
EmployeeExportExcelForm,
|
||||
EmployeeForm,
|
||||
@@ -79,9 +81,15 @@ from employee.forms import (
|
||||
EmployeeBankDetailsUpdateForm,
|
||||
excel_columns,
|
||||
)
|
||||
from employee.models import Employee, EmployeeNote, EmployeeWorkInformation, EmployeeBankDetails
|
||||
from employee.models import (
|
||||
BonusPoint,
|
||||
Employee,
|
||||
EmployeeNote,
|
||||
EmployeeWorkInformation,
|
||||
EmployeeBankDetails,
|
||||
)
|
||||
from payroll.methods.payslip_calc import dynamic_attr
|
||||
from payroll.models.models import Allowance, Contract, Deduction
|
||||
from payroll.models.models import Allowance, Contract, Deduction, Reimbursement
|
||||
from pms.models import Feedback
|
||||
from recruitment.models import Candidate
|
||||
|
||||
@@ -445,7 +453,7 @@ def allowances_deductions_tab(request, emp_id):
|
||||
"active_contracts": active_contracts,
|
||||
"allowances": employee_allowances if employee_allowances else None,
|
||||
"deductions": employee_deductions if employee_deductions else None,
|
||||
"employee":employee,
|
||||
"employee": employee,
|
||||
}
|
||||
return render(request, "tabs/allowance_deduction-tab.html", context=context)
|
||||
|
||||
@@ -1100,7 +1108,7 @@ def employee_filter_view(request):
|
||||
previous_data = request.GET.urlencode()
|
||||
field = request.GET.get("field")
|
||||
queryset = Employee.objects.filter()
|
||||
employees = EmployeeFilter(request.GET,queryset=queryset).qs
|
||||
employees = EmployeeFilter(request.GET, queryset=queryset).qs
|
||||
if request.GET.get("is_active") != "False":
|
||||
employees = employees.filter(is_active=True)
|
||||
page_number = request.GET.get("page")
|
||||
@@ -1342,7 +1350,9 @@ def employee_archive(request, obj_id):
|
||||
messages.success(request, message)
|
||||
else:
|
||||
related_models = ", ".join(model for model in result.get("related_models"))
|
||||
messages.warning(request, _(f"Can't archive.Employee assigned as {related_models}"))
|
||||
messages.warning(
|
||||
request, _(f"Can't archive.Employee assigned as {related_models}")
|
||||
)
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
|
||||
|
||||
|
||||
@@ -2132,6 +2142,7 @@ def employee_select_filter(request):
|
||||
|
||||
return JsonResponse(context)
|
||||
|
||||
|
||||
@login_required
|
||||
@manager_can_enter(perm="employee.view_employeenote")
|
||||
def note_tab(request, emp_id):
|
||||
@@ -2147,7 +2158,7 @@ def note_tab(request, emp_id):
|
||||
"""
|
||||
# employee = Employee.objects.get(id=emp_id)
|
||||
employee_obj = Employee.objects.get(id=emp_id)
|
||||
notes = EmployeeNote.objects.filter(employee_id = emp_id)
|
||||
notes = EmployeeNote.objects.filter(employee_id=emp_id)
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -2173,9 +2184,7 @@ def add_note(request, emp_id=None):
|
||||
note.updated_by = request.user.employee_get
|
||||
note.save()
|
||||
messages.success(request, _("Note added successfully.."))
|
||||
response = render(
|
||||
request, "tabs/add_note.html", {"form": form}
|
||||
)
|
||||
response = render(request, "tabs/add_note.html", {"form": form})
|
||||
return HttpResponse(
|
||||
response.content.decode("utf-8") + "<script>location.reload();</script>"
|
||||
)
|
||||
@@ -2221,6 +2230,7 @@ def employee_note_update(request, note_id):
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@manager_can_enter(perm="employee.delete_employeenote")
|
||||
def employee_note_delete(request, note_id):
|
||||
@@ -2235,3 +2245,101 @@ def employee_note_delete(request, note_id):
|
||||
messages.success(request, _("Note deleted successfully..."))
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||
|
||||
|
||||
@login_required
|
||||
@manager_can_enter(perm="employee.view_bonuspoint")
|
||||
def bonus_points_tab(request, emp_id):
|
||||
"""
|
||||
This function is used to view performance tab of an employee in employee individual & profile view.
|
||||
|
||||
Parameters:
|
||||
request (HttpRequest): The HTTP request object.
|
||||
emp_id (int): The id of the employee.
|
||||
|
||||
Returns: return note-tab template
|
||||
|
||||
"""
|
||||
employee_obj = Employee.objects.get(id=emp_id)
|
||||
points = BonusPoint.objects.get(employee_id=emp_id)
|
||||
trackings = points.tracking()
|
||||
|
||||
activity_list = []
|
||||
for history in trackings:
|
||||
activity_list.append(
|
||||
{
|
||||
"type":history["type"],
|
||||
"date": history["pair"][0].history_date,
|
||||
"points": history["pair"][0].points - history["pair"][1].points,
|
||||
"user":getattr(User.objects.filter(id = history["pair"][0].history_user_id).first(),"employee_get",None),
|
||||
"reason": history["pair"][0].reason,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
return render(
|
||||
request,
|
||||
"tabs/bonus_points.html",
|
||||
{"employee": employee_obj, "points": points, "activity_list": activity_list},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@manager_can_enter(perm="employee.add_bonuspoint")
|
||||
def add_bonus_points(request, emp_id):
|
||||
bonus_point = BonusPoint.objects.get(employee_id=emp_id)
|
||||
form = BonusPointAddForm()
|
||||
if request.method == "POST":
|
||||
form = BonusPointAddForm(
|
||||
request.POST,
|
||||
request.FILES,
|
||||
)
|
||||
if form.is_valid():
|
||||
form.save(commit=False)
|
||||
bonus_point.points += form.cleaned_data["points"]
|
||||
bonus_point.reason = form.cleaned_data["reason"]
|
||||
bonus_point.save()
|
||||
messages.success(
|
||||
request,
|
||||
_("Added {} points to the bonus account").format(
|
||||
form.cleaned_data["points"]
|
||||
),
|
||||
)
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||
|
||||
return render(
|
||||
request,
|
||||
"tabs/forms/add_points.html",
|
||||
{
|
||||
"form": form,
|
||||
"emp_id": emp_id,
|
||||
},
|
||||
)
|
||||
|
||||
@login_required
|
||||
@owner_can_enter("employee.view_bonuspoint", Employee)
|
||||
def redeem_points(request,emp_id):
|
||||
user = Employee.objects.get(id=emp_id)
|
||||
form = BonusPointRedeemForm()
|
||||
if request.method == 'POST':
|
||||
form = BonusPointRedeemForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save(commit=False)
|
||||
points = form.cleaned_data['points']
|
||||
reimbursement = Reimbursement.objects.create(
|
||||
title = f"Bonus point Redeem for {user}",
|
||||
type = "bonus_encashment",
|
||||
employee_id = user,
|
||||
bonus_to_encash =points,
|
||||
description = f"{user} want to redeem {points} points",
|
||||
allowance_on = date.today(),
|
||||
)
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||
return render(
|
||||
request,
|
||||
"tabs/forms/redeem_points_form.html",
|
||||
{
|
||||
"form": form,
|
||||
"employee": user,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -106,7 +106,10 @@ def get_diff(instance):
|
||||
}
|
||||
)
|
||||
if create_history:
|
||||
updated_by = create_history.history_user.employee_get
|
||||
try:
|
||||
updated_by = create_history.history_user.employee_get
|
||||
except:
|
||||
updated_by = Bot()
|
||||
delta_changes.append(
|
||||
{
|
||||
"type": f"{create_history.instance.__class__._meta.verbose_name.capitalize()} created",
|
||||
|
||||
@@ -477,7 +477,7 @@ class MultipleFileField(forms.FileField):
|
||||
result = [single_file_clean(d, initial) for d in data]
|
||||
else:
|
||||
result = [single_file_clean(data, initial)]
|
||||
return result[0]
|
||||
return result[0] if result else None
|
||||
|
||||
|
||||
class ReimbursementForm(ModelForm):
|
||||
@@ -535,7 +535,7 @@ class ReimbursementForm(ModelForm):
|
||||
type = self.data["type"]
|
||||
elif self.instance is not None:
|
||||
type = self.instance.type
|
||||
|
||||
print(type)
|
||||
if not request.user.has_perm("payroll.add_reimbursement"):
|
||||
exclude_fields.append("employee_id")
|
||||
|
||||
@@ -544,11 +544,21 @@ class ReimbursementForm(ModelForm):
|
||||
"leave_type_id",
|
||||
"cfd_to_encash",
|
||||
"ad_to_encash",
|
||||
"bonus_to_encash",
|
||||
]
|
||||
elif self.instance.pk or self.data.get("type") == "leave_encashment":
|
||||
elif self.instance.pk and type == "leave_encashment" or self.data.get("type") == "leave_encashment":
|
||||
exclude_fields = exclude_fields + [
|
||||
"attachment",
|
||||
"amount",
|
||||
"bonus_to_encash",
|
||||
]
|
||||
elif self.instance.pk and type == "bonus_encashment" or self.data.get("type") == "bonus_encashment":
|
||||
exclude_fields = exclude_fields + [
|
||||
"attachment",
|
||||
"amount",
|
||||
"leave_type_id",
|
||||
"cfd_to_encash",
|
||||
"ad_to_encash",
|
||||
]
|
||||
if self.instance.pk:
|
||||
exclude_fields = exclude_fields + ["type", "employee_id"]
|
||||
|
||||
@@ -18,7 +18,7 @@ from django.db.models.signals import pre_save, pre_delete
|
||||
from django.http import QueryDict
|
||||
from asset.models import Asset
|
||||
from base import thread_local_middleware
|
||||
from employee.models import EmployeeWorkInformation
|
||||
from employee.models import BonusPoint, EmployeeWorkInformation
|
||||
from employee.models import Employee, Department, JobPosition
|
||||
from base.models import Company, EmployeeShift, WorkType, JobRole
|
||||
from base.horilla_company_manager import HorillaCompanyManager
|
||||
@@ -1497,6 +1497,7 @@ class Reimbursement(models.Model):
|
||||
reimbursement_types = [
|
||||
("reimbursement", "Reimbursement"),
|
||||
("leave_encashment", "Leave Encashment"),
|
||||
("bonus_encashment", "Bonus Point Encashment"),
|
||||
]
|
||||
status_types = [
|
||||
("requested", "Requested"),
|
||||
@@ -1526,6 +1527,11 @@ class Reimbursement(models.Model):
|
||||
help_text="Carry Forward Days to encash",
|
||||
verbose_name="Carry forward days",
|
||||
)
|
||||
bonus_to_encash = models.IntegerField(
|
||||
default=0,
|
||||
help_text="Bonus points to encash",
|
||||
verbose_name="Bonus points",
|
||||
)
|
||||
amount = models.FloatField(default=0)
|
||||
status = models.CharField(
|
||||
max_length=10, choices=status_types, default="requested", editable=False
|
||||
@@ -1556,16 +1562,34 @@ class Reimbursement(models.Model):
|
||||
raise ValidationError({"attachment": "This field is required"})
|
||||
elif self.type == "leave_encashment" and self.leave_type_id is None:
|
||||
raise ValidationError({"leave_type_id": "This field is required"})
|
||||
self.cfd_to_encash = max((round(self.cfd_to_encash * 2) / 2), 0)
|
||||
self.ad_to_encash = max((round(self.ad_to_encash * 2) / 2), 0)
|
||||
assigned_leave = self.leave_type_id.employee_available_leave.filter(
|
||||
employee_id=self.employee_id
|
||||
).first()
|
||||
if self.type == "leave_encashment":
|
||||
self.cfd_to_encash = max((round(self.cfd_to_encash * 2) / 2), 0)
|
||||
self.ad_to_encash = max((round(self.ad_to_encash * 2) / 2), 0)
|
||||
assigned_leave = self.leave_type_id.employee_available_leave.filter(
|
||||
employee_id=self.employee_id
|
||||
).first()
|
||||
if self.status != "approved" or self.allowance_id is None:
|
||||
super().save(*args, **kwargs)
|
||||
if self.status == "approved" and self.allowance_id is None:
|
||||
if self.type == "reimbursement":
|
||||
proceed = True
|
||||
elif self.type == "bonus_encashment":
|
||||
proceed = False
|
||||
bonus_points = BonusPoint.objects.get(employee_id=self.employee_id)
|
||||
if bonus_points.points >= self.bonus_to_encash:
|
||||
proceed = True
|
||||
bonus_points.points -= self.bonus_to_encash
|
||||
bonus_points.reason = "bonus points has been redeemed."
|
||||
bonus_points.save()
|
||||
else:
|
||||
request = getattr(
|
||||
thread_local_middleware._thread_locals, "request", None
|
||||
)
|
||||
if request:
|
||||
messages.info(
|
||||
request,
|
||||
"The employee don't have that much bonus points to encash.",
|
||||
)
|
||||
else:
|
||||
proceed = False
|
||||
if assigned_leave:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
hx-encoding="multipart/form-data">
|
||||
{{form.as_p}}
|
||||
</form>
|
||||
<table class="oh-table oh-table--sortable" {% if not form.instance.id or form.instance.type == "reimbursement" %}
|
||||
<table class="oh-table oh-table--sortable" {% if not form.instance.id or form.instance.type == "reimbursement" or form.instance.type == "bonus_encashment" %}
|
||||
id="availableTable" style="display: none;" {% endif %}>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -80,6 +80,15 @@
|
||||
<p class="oh-faq-card__desc" style="height: 150px;">
|
||||
<iframe id="iframe_pdf" src="{{ req.attachment.url }}" style="width: 100%" frameborder="0"></iframe>
|
||||
</p>
|
||||
{% elif req.type == 'bonus_encashment' %}
|
||||
<p class="oh-faq-card__desc" style="max-height: 180px;">
|
||||
<i>
|
||||
{% trans 'Requsted for' %} <span class="text-danger"> {{ req.bonus_to_encash }} </span>
|
||||
{% trans 'Bonus points to encash.' %}
|
||||
</i>
|
||||
<input style="display: block;padding: 10px;margin-top: 10px;" type="number" required default="0" min="0" placeholder="Amount"
|
||||
name="amount" class="mt-2"{% if req.status == "approved" %}disabled{% endif %} {% if req.amount %} value="{{req.amount}}"{% endif %} />
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="oh-faq-card__desc" style="max-height: 180px;">
|
||||
<i>
|
||||
|
||||
@@ -42,21 +42,32 @@
|
||||
if (element.val() == 'reimbursement') {
|
||||
$('#reimbursementModalBody [name=attachment]').parent().show()
|
||||
$('#reimbursementModalBody [name=attachment]').attr("required",true)
|
||||
|
||||
$('#reimbursementModalBody [name=leave_type_id]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=cfd_to_encash]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=ad_to_encash]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=amount]').parent().show().attr("required",true)
|
||||
$('#reimbursementModalBody #availableTable').hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=leave_type_id]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=cfd_to_encash]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=ad_to_encash]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=amount]').parent().show().attr("required",true)
|
||||
$('#reimbursementModalBody #availableTable').hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=bonus_to_encash]').parent().hide().attr("required",false)
|
||||
|
||||
} else if(element.val() == 'leave_encashment') {
|
||||
$('#reimbursementModalBody [name=attachment]').parent().hide()
|
||||
$('#reimbursementModalBody [name=attachment]').attr("required",false)
|
||||
$('#reimbursementModalBody [name=leave_type_id]').parent().show().attr("required",true)
|
||||
$('#reimbursementModalBody [name=cfd_to_encash]').parent().show().attr("required",true)
|
||||
$('#reimbursementModalBody [name=ad_to_encash]').parent().show().attr("required",true)
|
||||
$('#reimbursementModalBody [name=amount]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody #availableTable').show().attr("required",true)
|
||||
$('#reimbursementModalBody [name=leave_type_id]').parent().show().attr("required",true)
|
||||
$('#reimbursementModalBody [name=cfd_to_encash]').parent().show().attr("required",true)
|
||||
$('#reimbursementModalBody [name=ad_to_encash]').parent().show().attr("required",true)
|
||||
$('#reimbursementModalBody [name=amount]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody #availableTable').show().attr("required",true)
|
||||
$('#reimbursementModalBody [name=bonus_to_encash]').parent().hide().attr("required",false)
|
||||
|
||||
} else if(element.val() == 'bonus_encashment') {
|
||||
$('#reimbursementModalBody [name=attachment]').parent().hide()
|
||||
$('#reimbursementModalBody [name=attachment]').attr("required",false)
|
||||
$('#reimbursementModalBody [name=leave_type_id]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=cfd_to_encash]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=ad_to_encash]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=amount]').parent().hide().attr("required",false)
|
||||
$('#reimbursementModalBody #availableTable').hide().attr("required",false)
|
||||
$('#reimbursementModalBody [name=bonus_to_encash]').parent().show().attr("required",true)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1099,6 +1099,9 @@ def approve_reimbursements(request):
|
||||
for reimbursement in reimbursements:
|
||||
if reimbursement.type == "leave_encashment":
|
||||
reimbursement.amount = amount
|
||||
elif reimbursement.type == "bonus_encashment" :
|
||||
reimbursement.amount = amount
|
||||
|
||||
emp = reimbursement.employee_id
|
||||
reimbursement.status = status
|
||||
reimbursement.save()
|
||||
|
||||
Reference in New Issue
Block a user