[ADD] PAYROLL: Comment section for reimbursement request/leave encashment request views

This commit is contained in:
Horilla
2024-01-15 11:48:16 +05:30
parent 42b6aa4249
commit 51e64fbc32
9 changed files with 253 additions and 8 deletions

View File

@@ -11,7 +11,8 @@ from payroll.models.models import (
Payslip,
WorkRecord,
LoanAccount,
Reimbursement
Reimbursement,
ReimbursementrequestComment
)
from payroll.models.tax_models import (
PayrollSettings,
@@ -29,3 +30,4 @@ admin.site.register(Payslip)
admin.site.register(PayrollSettings)
admin.site.register(LoanAccount)
admin.site.register(Reimbursement)
admin.site.register(ReimbursementrequestComment)

View File

@@ -5,7 +5,7 @@ from django import forms
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 WorkRecord
from payroll.models.models import ReimbursementrequestComment, WorkRecord
from payroll.models.models import Contract
from base.methods import reload_queryset
@@ -116,3 +116,17 @@ class WorkRecordForm(ModelForm):
fields = "__all__"
model = WorkRecord
class ReimbursementrequestCommentForm(ModelForm):
"""
ReimbursementrequestCommentForm form
"""
class Meta:
"""
Meta class for additional options
"""
model = ReimbursementrequestComment
fields = ('comment',)

View File

@@ -1557,3 +1557,21 @@ class Reimbursement(models.Model):
if self.allowance_id:
self.allowance_id.delete()
return super().delete(*args, **kwargs)
class ReimbursementrequestComment(models.Model):
"""
ReimbursementrequestComment Model
"""
request_id = models.ForeignKey(Reimbursement, on_delete=models.CASCADE)
employee_id = models.ForeignKey(Employee, on_delete=models.CASCADE)
comment = models.TextField(null=True, verbose_name=_("Comment"))
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name=_("Created At"),
null=True,
)
def __str__(self) -> str:
return f"{self.comment}"

View File

@@ -0,0 +1,56 @@
{% load basefilters %}
{% load i18n %}
{% if no_comments %}
<div class="oh-timeoff-modal__profile-content">
<div class="">
<div>
<span class="oh-timeoff-title fw-bold" style="display: block;">{% trans "There is no comments to show." %}</span>
<img style="display: block;width: 100px;margin: 20px auto ;" src="/static/images/ui/comment.png" class="">
</div>
</div>
</div>
{% else %}
{% for comment in comments %}
<div class="oh-timeoff-modal__profile-content">
<div class="oh-profile ms-4">
<div>
<span class="oh-timeoff-modal__stat-title fw-bold">{% trans "Comment" %}
<a href='{% url "payroll-request-delete-comment" comment.id %}' title='Delete Comment'>
<ion-icon name="close-circle-outline" class="text-danger ms-3 mt-1" aria-label="close outline"></ion-icon></a>
</span>
<div>
<span class="oh-timeoff-title fw-bold">{{comment.comment}}</span>
</div>
</div>
</div>
</div>
<div class="oh-modal__dialog-body">
<div class="oh-timeoff-modal__stats-container">
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title fw-bold">{% trans "By" %}</span>
<a style="text-decoration:none;"
href ="{% url 'employee-view-individual' comment.employee_id.id %}">
<span class="oh-timeoff-modal__stat-count">{{ comment.employee_id }}</span></a>
</div>
<div class="oh-timeoff-modal__stat" style="margin-left: 20px;">
<span class="oh-timeoff-modal__stat-title fw-bold">{% trans "Date & Time" %}</span>
<span class="oh-timeoff-modal__stat-count">
{% trans "on" %} &nbsp<span class="dateformat_changer">{{ comment.created_at|date:"F j, Y" }}</span> &nbsp
{% trans "at" %} &nbsp <span class="timeformat_changer">{{ comment.created_at|time:"g:i A" }}</span>
</span>
</div>
</div>
<hr>
</div>
{% endfor %}
{% endif %}

View File

@@ -10,14 +10,14 @@
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left md hydrated" role="img" aria-label="search outline"></ion-icon>
<input type="text" id="pipelineSearch" placeholder="Search" style="margin-right:10px" onkeyup="$('.filterButton').click()" name="search" class="oh-input oh-input__icon mr-3" aria-label="Search Input" />
</div>
<ul class="oh-view-types ml-2" style="margin-bottom: 0;">
{% comment %} <ul class="oh-view-types ml-2" style="margin-bottom: 0;">
<li class="oh-view-type" data-view="list" onclick="event.stopPropagation()">
<a class="oh-btn oh-btn--view" title="List"><ion-icon name="list-outline" role="img" class="md hydrated" aria-label="list outline"></ion-icon></a>
</li>
<li class="oh-view-type" data-view="card">
<a class="oh-btn oh-btn--view oh-btn--view-active" title="Card"><ion-icon name="grid-outline" role="img" class="md hydrated" aria-label="grid outline"></ion-icon></a>
</li>
</ul>
</ul> {% endcomment %}
{% include 'payroll/reimbursement/filter.html' %}
<div class="oh-main__titlebar-button-container">
<div class="oh-main__titlebar-button-container">

View File

@@ -0,0 +1,27 @@
{% load i18n %}
{% if form.errors %}
<!-- form errors -->
<div class="oh-wrapper">
<div class="oh-alert-container">
{% for error in form.non_field_errors %}
<div class="oh-alert oh-alert--animated oh-alert--danger">
{{ error }}
</div>
{% endfor %}
</div>
</div>
{% endif %}
<form
hx-post="{% url 'payroll-request-add-comment' request_id %}"
hx-target="#shiftRequestCommentForm"
method="post"
hx-encoding="multipart/form-data">
{% csrf_token %}
{{form.as_p}}
<button
type="submit"
class="oh-btn oh-btn--secondary mt-2 mr-0 oh-btn--w-100-resp"
>
{% trans "Save" %}
</button>
</form>

View File

@@ -56,8 +56,17 @@
</div>
<h3 class="oh-faq-card__title">{{ req.title }}</h3>
<p class="oh-faq-card__desc">{{ req.description }}.</p>
<span class="oh-card__footer--border-top pt-1" style="display: block;">
<i style="color: hsl(0,0%,45%);">{% trans 'Allowance on' %}</i> {{ req.allowance_on }}.
<span class="oh-card__footer--border-top pt-1 " style="display: block;">
<div class="mb-2">
<i><a hx-target="#shiftRequestCommentForm" hx-get="{% url 'payroll-request-add-comment' req.id %}" class="text-primary"
hx-swap="innerHTML" data-toggle="oh-modal-toggle" data-target="#shiftcommentModal" style='cursor:pointer;'>{% trans 'Add Comments' %}</a> &nbsp,
<a hx-target="#shiftRequestDetailTarget" hx-get="{% url 'payroll-request-view-comment' req.id %}" class="text-primary"
data-toggle='oh-modal-toggle' data-target = '#shiftRequestDetailModal' style='cursor:pointer;'>{% trans 'View Comments' %}</a></i>
</div>
<i style="color: hsl(0,0%,45%);">{% trans 'Allowance on' %}</i> <span class='dateformat_changer'>{{ req.allowance_on }}</span>
{% if req.type == 'reimbursement' %}
<i><a data-target="#reimbursementAttachementModal" data-toggle="oh-modal-toggle" hx-target="#reimbursementAttachementModalBody" hx-get="{% url "reimbursement-attachments" req.id %}" class="text-danger"
title="Attachments" rel="noopener noreferrer">{% trans 'View Attachments' %}</a></i>
@@ -145,6 +154,54 @@
</div>
</div>
<!-- start of comment modal -->
<div
class="oh-modal"
id="shiftcommentModal"
role="dialog"
aria-labelledby="emptagModal"
aria-hidden="true"
>
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" id="createModalTitle">
{% trans "Add Comment" %}
</h2>
<button class="oh-modal__close" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
</button>
</div>
<div class="oh-modal__dialog-body" id="shiftRequestCommentForm"></div>
</div>
</div>
<!-- end of comment modal -->
<div
class="oh-modal" style="z-index: 60;"
id="shiftRequestDetailModal"
role="dialog"
aria-labelledby="shiftRequestDetailModal"
aria-hidden="true"
>
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<h2
class="oh-modal__dialog-title"
id=""
>
{% trans "Details" %}
</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"
id="shiftRequestDetailTarget"
></div>
</div>
</div>
<script>
function reimbursementConfirm(params, target, approve = false) {
Swal.fire({

View File

@@ -126,4 +126,20 @@ urlpatterns = [
views.payslip_select_filter,
name="payslip-select-filter",
),
path(
"payroll-request-add-comment/<int:payroll_id>/",
views.create_payrollrequest_comment,
name="payroll-request-add-comment",
),
path(
"payroll-request-view-comment/<int:payroll_id>/",
views.view_payrollrequest_comment,
name="payroll-request-view-comment",
),
path(
"payroll-request-delete-comment/<int:comment_id>/",
views.delete_payrollrequest_comment,
name="payroll-request-delete-comment",
),
]

View File

@@ -19,8 +19,8 @@ from base.methods import export_data, generate_colors, get_key_instances
from employee.models import Employee, EmployeeWorkInformation
from base.methods import closest_numbers
from base.methods import generate_pdf
from payroll.models.models import Payslip, WorkRecord, Contract
from payroll.forms.forms import ContractForm, WorkRecordForm
from payroll.models.models import Payslip, Reimbursement, ReimbursementrequestComment, WorkRecord, Contract
from payroll.forms.forms import ContractForm, ReimbursementrequestCommentForm, WorkRecordForm
from payroll.models.tax_models import PayrollSettings
from payroll.forms.component_forms import ContractExportFieldForm, PayrollSettingsForm
from payroll.methods.methods import save_payslip
@@ -1186,3 +1186,58 @@ def payslip_select_filter(request):
context = {"payslip_ids": payslip_ids, "total_count": total_count}
return JsonResponse(context)
@login_required
def create_payrollrequest_comment(request, payroll_id):
"""
This method renders form and template to create Reimbursement request comments
"""
payroll = Reimbursement.objects.filter(id=payroll_id).first()
emp = request.user.employee_get
form = ReimbursementrequestCommentForm(initial={'employee_id':emp.id, 'request_id':payroll_id})
if request.method == "POST":
form = ReimbursementrequestCommentForm(request.POST )
if form.is_valid():
form.instance.employee_id = emp
form.instance.request_id = payroll
form.save()
form = ReimbursementrequestCommentForm(initial={'employee_id':emp.id, 'request_id':payroll_id})
messages.success(request, _("Comment added successfully!"))
return HttpResponse("<script>window.location.reload()</script>")
return render(
request,
"payroll/reimbursement/reimbursement_request_comment_form.html",
{
"form": form, "request_id":payroll_id
},
)
@login_required
def view_payrollrequest_comment(request, payroll_id):
"""
This method is used to show Reimbursement request comments
"""
comments = ReimbursementrequestComment.objects.filter(request_id=payroll_id).order_by('-created_at')
no_comments = False
if not comments.exists():
no_comments = True
return render(
request,
"payroll/reimbursement/comment_view.html",
{"comments": comments, 'no_comments': no_comments }
)
@login_required
def delete_payrollrequest_comment(request, comment_id):
"""
This method is used to delete Reimbursement request comments
"""
ReimbursementrequestComment.objects.get(id=comment_id).delete()
messages.success(request, _("Comment deleted successfully!"))
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))