diff --git a/base/templates/base/general_settings.html b/base/templates/base/general_settings.html index 9825ff155..3a839bc8a 100644 --- a/base/templates/base/general_settings.html +++ b/base/templates/base/general_settings.html @@ -2,9 +2,10 @@ {% include "recruitment/settings/settings.html" %} -{% include "attendance/settings/settings.html" %} {% include "offboarding/settings/settings.html" %} +{% include "attendance/settings/settings.html" %} {% include "payroll/settings/settings.html" %} {% include "announcement/expiry_day.html" %} +{% include "settings/encashment_settings.html" %} {% endblock settings %} \ No newline at end of file diff --git a/base/views.py b/base/views.py index 13a86d5f2..1ad69dcb2 100644 --- a/base/views.py +++ b/base/views.py @@ -123,7 +123,9 @@ from base.methods import ( get_key_instances, sortby, ) +from payroll.forms.forms import EncashmentGeneralSettingsForm from payroll.forms.component_forms import PayrollSettingsForm +from payroll.models.models import EncashmentGeneralSettings from payroll.models.tax_models import PayrollSettings from helpdesk.models import TicketType from helpdesk.forms import TicketTypeForm @@ -554,7 +556,11 @@ def user_group(request): return render( request, "base/auth/group.html", - {"permissions": permissions, "form": form, "groups": paginator_qry(groups,request.GET.get("page"))}, + { + "permissions": permissions, + "form": form, + "groups": paginator_qry(groups, request.GET.get("page")), + }, ) @@ -599,7 +605,11 @@ def user_group_search(request): return render( request, "base/auth/group_lines.html", - {"permissions": permissions, "form": form, "groups": paginator_qry(groups,request.GET.get("page"))}, + { + "permissions": permissions, + "form": form, + "groups": paginator_qry(groups, request.GET.get("page")), + }, ) @@ -3385,14 +3395,22 @@ def general_settings(request): """ instance = AnnouncementExpire.objects.first() form = AnnouncementExpireForm(instance=instance) + encashment_instance = EncashmentGeneralSettings.objects.first() + encashment_form = EncashmentGeneralSettingsForm(instance=encashment_instance) if request.method == "POST": form = AnnouncementExpireForm(request.POST, instance=instance) if form.is_valid(): form.save() messages.success(request, _("Settings updated.")) return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) - return render(request, "base/general_settings.html", {'form':form}) - + return render( + request, + "base/general_settings.html", + { + "form": form, + "encashment_form": encashment_form, + }, + ) @login_required @@ -3783,16 +3801,16 @@ def ticket_type_create(request): form = TicketTypeForm() if request.method == "POST": form = TicketTypeForm(request.POST) - if request.GET.get('ajax'): + if request.GET.get("ajax"): if form.is_valid(): instance = form.save() - response= { - "errors": 'no_error', - "ticket_id": instance.id, - "title": instance.title, - } + response = { + "errors": "no_error", + "ticket_id": instance.id, + "title": instance.title, + } return JsonResponse(response) - + errors = form.errors.as_json() return JsonResponse({"errors": errors}) if form.is_valid(): @@ -3808,6 +3826,7 @@ def ticket_type_create(request): }, ) + @login_required def ticket_type_update(request, t_type_id): """ diff --git a/employee/templates/settings/encashment_settings.html b/employee/templates/settings/encashment_settings.html new file mode 100644 index 000000000..7c74d20b0 --- /dev/null +++ b/employee/templates/settings/encashment_settings.html @@ -0,0 +1,51 @@ +{% load i18n %} +
+
+ {% csrf_token %} +
+

+ {% trans "Encashment redeem condition" %} +

+
+
+
+
+
+ + {{encashment_form.bonus_amount}} +
+
+ +
+
+ + {{encashment_form.leave_amount}} +
+
+
+ {% if perms.payroll.change_payrollsettings %} + + {% endif %} +
+ + +
+
diff --git a/employee/urls.py b/employee/urls.py index c93184caa..eaafab4aa 100644 --- a/employee/urls.py +++ b/employee/urls.py @@ -282,5 +282,6 @@ urlpatterns = [ path("action-type-details",policies.action_type_details,name="action-type-details",), path("disciplinary-filter-view", policies.disciplinary_filter_view, name="disciplinary-filter-view"), path("search-disciplinary", policies.search_disciplinary, name="search-disciplinary"), + path("encashment-condition-create", views.encashment_condition_create, name="encashment-condition-create"), ] diff --git a/employee/views.py b/employee/views.py index e0bfa526e..554172bee 100755 --- a/employee/views.py +++ b/employee/views.py @@ -95,8 +95,9 @@ from employee.models import ( EmployeeWorkInformation, EmployeeBankDetails, ) +from payroll.forms.forms import EncashmentGeneralSettingsForm from payroll.methods.payslip_calc import dynamic_attr -from payroll.models.models import Allowance, Contract, Deduction, Reimbursement +from payroll.models.models import Allowance, Contract, Deduction, EncashmentGeneralSettings, Reimbursement from pms.models import Feedback from recruitment.models import Candidate from horilla_documents.models import Document, DocumentRequest @@ -2745,16 +2746,20 @@ def redeem_points(request, emp_id): """ user = Employee.objects.get(id=emp_id) form = BonusPointRedeemForm() + amount_for_bonus_point = EncashmentGeneralSettings.objects.first().bonus_amount if EncashmentGeneralSettings.objects.first() else 1 if request.method == "POST": form = BonusPointRedeemForm(request.POST) if form.is_valid(): form.save(commit=False) points = form.cleaned_data["points"] + amount = amount_for_bonus_point * points + reimbursement = Reimbursement.objects.create( title=f"Bonus point Redeem for {user}", type="bonus_encashment", employee_id=user, bonus_to_encash=points, + amount = amount, description=f"{user} want to redeem {points} points", allowance_on=date.today(), ) @@ -2854,3 +2859,17 @@ def organisation_chart(request): "act_manager_id": manager.id, } return render(request, "organisation_chart/org_chart.html", context=context) + + +@login_required +def encashment_condition_create(request): + instance = EncashmentGeneralSettings.objects.first() + encashment_form = EncashmentGeneralSettingsForm(instance=instance) + if request.method == "POST": + encashment_form = EncashmentGeneralSettingsForm(request.POST,instance=instance) + if encashment_form.is_valid(): + encashment_form.save() + messages.success(request, _("Settings updated.")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + + return render(request, "settings/encashment_settings.html", {"encashment_form":encashment_form,}) diff --git a/payroll/forms/forms.py b/payroll/forms/forms.py index db96dc537..b6c61ead9 100644 --- a/payroll/forms/forms.py +++ b/payroll/forms/forms.py @@ -7,6 +7,7 @@ from django.forms import widgets from django.utils.translation import gettext_lazy as trans from django.template.loader import render_to_string from payroll.models.models import ( + EncashmentGeneralSettings, PayrollGeneralSetting, ReimbursementrequestComment, WorkRecord, @@ -154,3 +155,9 @@ class ReimbursementrequestCommentForm(ModelForm): model = ReimbursementrequestComment fields = ("comment",) + + +class EncashmentGeneralSettingsForm(ModelForm): + class Meta: + model = EncashmentGeneralSettings + fields = "__all__" diff --git a/payroll/models/models.py b/payroll/models/models.py index 37c5574bd..272229689 100644 --- a/payroll/models/models.py +++ b/payroll/models/models.py @@ -1591,6 +1591,7 @@ class Reimbursement(models.Model): def save(self, *args, **kwargs) -> None: request = getattr(thread_local_middleware._thread_locals, "request", None) + amount_for_leave = EncashmentGeneralSettings.objects.first().leave_amount if EncashmentGeneralSettings.objects.first() else 1 # Setting the created use if the used dont have the permission has_perm = request.user.has_perm("payroll.add_reimbursement") @@ -1601,6 +1602,8 @@ class Reimbursement(models.Model): elif self.type == "leave_encashment" and self.leave_type_id is None: raise ValidationError({"leave_type_id": "This field is required"}) if self.type == "leave_encashment": + if self.status == "requested": + self.amount = (self.cfd_to_encash + self.ad_to_encash) * amount_for_leave 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( @@ -1655,7 +1658,6 @@ class Reimbursement(models.Model): "The employee don't have that much leaves to encash in CFD / Available days", ) - # if self.ad if proceed: reimbursement = Allowance() @@ -1677,15 +1679,16 @@ class Reimbursement(models.Model): elif self.status == "canceled" and self.allowance_id is not None: cfd_days = self.cfd_to_encash available_days = self.ad_to_encash - if assigned_leave: - assigned_leave.available_days = ( - assigned_leave.available_days + available_days - ) - assigned_leave.carryforward_days = ( - assigned_leave.carryforward_days + cfd_days - ) - assigned_leave.save() - self.allowance_id.delete() + if self.type == "leave encashment": + if assigned_leave: + assigned_leave.available_days = ( + assigned_leave.available_days + available_days + ) + assigned_leave.carryforward_days = ( + assigned_leave.carryforward_days + cfd_days + ) + assigned_leave.save() + self.allowance_id.delete() def delete(self, *args, **kwargs): if self.allowance_id: @@ -1721,4 +1724,13 @@ class PayrollGeneralSetting(models.Model): validators=[min_zero], default=3, ) - company_id = models.ForeignKey(Company, on_delete=models.CASCADE, null=True) + company_id = models.ForeignKey(Company,on_delete=models.CASCADE,null=True) + + +class EncashmentGeneralSettings(models.Model): + """ + BonusPointGeneralSettings model + """ + bonus_amount = models.IntegerField(default = 1) + leave_amount = models.IntegerField(blank = True, null = True, verbose_name = "Amount") +