[UPDT] LEAVE: Added htmx for available days message display in leave request

This commit is contained in:
Horilla
2024-08-22 17:39:42 +05:30
parent 358a1581f0
commit aa6f72a6ff
11 changed files with 195 additions and 351 deletions

View File

@@ -320,14 +320,19 @@ class LeaveRequestCreationForm(ModelForm):
unique_dates = list(set(month_year))
if f"{today.month}-{today.year}" in unique_dates:
unique_dates.remove(f"{today.strftime('%m')}-{today.year}")
forcasted_leaves = available_leave.forcasted_leaves()
if leave_type_id.reset_based == "monthly":
if f"{today.year}-{today.strftime('%m')}" not in unique_dates:
for item in unique_dates:
try:
total_leave_days += forcasted_leaves[item]
except:
pass
forcated_days = available_leave.forcasted_leaves(start_date)
total_leave_days = (
available_leave.leave_type_id.carryforward_max
if available_leave.leave_type_id.carryforward_type
in ["carryforward", "carryforward expire"]
and available_leave.leave_type_id.carryforward_max < total_leave_days
else total_leave_days
)
if available_leave.leave_type_id.carryforward_type == "no carryforward":
total_leave_days = 0
total_leave_days += forcated_days
if not effective_requested_days <= total_leave_days:
raise forms.ValidationError(_("Employee doesn't have enough leave days.."))
@@ -338,19 +343,27 @@ class LeaveRequestCreationForm(ModelForm):
super().__init__(*args, **kwargs)
self.fields["leave_type_id"].widget.attrs.update(
{
"onchange": "empleavetypeChange($(this))",
"hx-include": "#id_employee_id, #id_start_date",
"hx-target": "#availableLeaveCount",
"hx-swap": "outerHTML",
"hx-trigger": "change",
"hx-get": "/leave/employee-available-leave-count",
}
)
self.fields["employee_id"].widget.attrs.update(
{
"hx-target": "#id_leave_type_id_parent_div",
"hx-trigger": "change",
"hx-get": "/leave/get-employee-leave-types",
"hx-get": "/leave/get-employee-leave-types?form=LeaveRequestCreationForm",
}
)
self.fields["start_date"].widget.attrs.update(
{
"onchange": "dateChange($(this))",
"hx-include": "#id_employee_id, #id_leave_type_id",
"hx-target": "#availableLeaveCount",
"hx-swap": "outerHTML",
"hx-trigger": "change",
"hx-get": "/leave/employee-available-leave-count",
}
)
@@ -431,14 +444,19 @@ class LeaveRequestUpdationForm(ModelForm):
unique_dates = list(set(month_year))
if f"{today.month}-{today.year}" in unique_dates:
unique_dates.remove(f"{today.strftime('%m')}-{today.year}")
forcasted_leaves = available_leave.forcasted_leaves()
if leave_type_id.reset_based == "monthly":
if f"{today.year}-{today.strftime('%m')}" not in unique_dates:
for item in unique_dates:
try:
total_leave_days += forcasted_leaves[item]
except:
pass
forcated_days = available_leave.forcasted_leaves(start_date)
total_leave_days = (
available_leave.leave_type_id.carryforward_max
if available_leave.leave_type_id.carryforward_type
in ["carryforward", "carryforward expire"]
and available_leave.leave_type_id.carryforward_max < total_leave_days
else total_leave_days
)
if available_leave.leave_type_id.carryforward_type == "no carryforward":
total_leave_days = 0
total_leave_days += forcated_days
if not effective_requested_days <= total_leave_days:
raise forms.ValidationError(_("Employee doesn't have enough leave days.."))
@@ -449,19 +467,30 @@ class LeaveRequestUpdationForm(ModelForm):
super().__init__(*args, **kwargs)
self.fields["leave_type_id"].widget.attrs.update(
{
"onchange": "empleavetypeChange($(this))",
"id": "id_request_update_leave_type_id",
"hx-include": "#id_request_update_employee_id, #id_request_udpate_start_date",
"hx-target": "#assinedLeaveAvailableCount",
"hx-swap": "outerHTML",
"hx-trigger": "change",
"hx-get": "/leave/employee-available-leave-count",
}
)
self.fields["employee_id"].widget.attrs.update(
{
"id": "id_request_update_employee_id",
"hx-target": "#id_leave_type_id_parent_div",
"hx-trigger": "change",
"hx-get": "/leave/get-employee-leave-types",
"hx-get": "/leave/get-employee-leave-types?form=LeaveRequestUpdationForm",
}
)
self.fields["start_date"].widget.attrs.update(
{
"onchange": "dateChange($(this))",
"id": "id_request_udpate_start_date",
"hx-include": "#id_request_update_employee_id, #id_request_update_leave_type_id",
"hx-target": "#assinedLeaveAvailableCount",
"hx-swap": "outerHTML",
"hx-trigger": "change",
"hx-get": "/leave/employee-available-leave-count",
}
)

View File

@@ -223,6 +223,51 @@ class LeaveType(HorillaModel):
url = self.icon.url
return url
def leave_type_next_reset_date(self):
today = datetime.now().date()
if not self.reset:
return None
def get_reset_day(month, day):
return (
calendar.monthrange(today.year, month)[1]
if day == "last day"
else int(day)
)
if self.reset_based == "yearly":
month, day = int(self.reset_month), get_reset_day(
int(self.reset_month), self.reset_day
)
reset_date = datetime(
today.year + (datetime(today.year, month, day).date() < today),
month,
day,
).date()
elif self.reset_based == "monthly":
month = today.month
reset_date = datetime(
today.year, month, get_reset_day(month, self.reset_day)
).date()
if reset_date < today:
month = (month % 12) + 1
year = today.year + (month == 1)
reset_date = datetime(
year, month, get_reset_day(month, self.reset_day)
).date()
elif self.reset_based == "weekly":
target_weekday = WEEK_DAYS[self.reset_day]
days_until_reset = (target_weekday - today.weekday()) % 7 or 7
reset_date = today + timedelta(days=days_until_reset)
else:
reset_date = None
return reset_date
def clean(self, *args, **kwargs):
if self.is_compensatory_leave:
if LeaveType.objects.filter(is_compensatory_leave=True).count() >= 1:
@@ -321,25 +366,14 @@ class AvailableLeave(HorillaModel):
def __str__(self):
return f"{self.employee_id} | {self.leave_type_id}"
def forcasted_leaves(self):
forecasted_leave = {}
if self.leave_type_id.reset_based == "monthly":
today = datetime.now()
for i in range(1, 7): # Calculate for the next 6 months
next_month = today + relativedelta(months=i)
if self.leave_type_id.carryforward_max:
forecasted_leave[next_month.strftime("%Y-%m")] = (
self.available_days
+ min(
self.leave_type_id.carryforward_max,
(self.leave_type_id.total_days * i),
)
)
else:
forecasted_leave[next_month.strftime("%Y-%m")] = (
self.available_days + (self.leave_type_id.total_days * i)
)
return forecasted_leave
def forcasted_leaves(self, date):
if isinstance(date, str):
date = datetime.strptime(date, "%Y-%m-%d").date()
next_reset_date = self.leave_type_id.leave_type_next_reset_date()
if next_reset_date <= date:
return self.leave_type_id.total_days
return 0
# Resetting carryforward days

View File

@@ -0,0 +1,53 @@
{% load i18n %}
{% load static %}
<style>
ion-icon {
color: blue;
}
</style>
<div id="{{hx_target}}"
style="height: 40px; {% if not available_leave and not leave_type_id %}display:none;{% endif %}">
{% if not leave_type_id %}
<div id="messageDiv" style="display:none;"></div>
{% elif not available_leave %}
<div id="messageDiv" style="
background-color: rgba(229, 79, 56, 0.17);
border: 2px solid hsl(8, 77%, 56%);
border-radius: 18px;
padding: 10px;
font-weight: bold;
width: auto;
">
{% trans "Leave type is not assigned for the selected employee." %}
</div>
{% elif total_leave_days == 0.0 %}
<div id="messageDiv" style="
background-color: rgba(229, 79, 56, 0.17);
border: 2px solid hsl(8, 77%, 56%);
border-radius: 18px;
padding: 10px;
font-weight: bold;
width: 35%;
">
{% trans "Available Leaves" %} : {{ total_leave_days }}
</div>
{% else %}
<div id="messageDiv" style="
background-color: #dff0d8;
border: 2px solid #3c763d;
border-radius: 18px;
padding: 10px;
font-weight: bold;
width: 35%;
display: flex;
">
{% trans "Available Leaves" %} : {{ total_leave_days }}
{% if forcated_days and forcated_days > 0 %}
<img style="width: 20px; height: 20px; cursor: pointer;" src="{% static 'images/ui/info.png' %}" class="ml-2"
title="{% trans 'Forecasted available days added to this Available Days (Carryforward Max. + Forcasted Days)' %}" />
{% endif %}
</div>
{% endif %}
</div>

View File

@@ -28,6 +28,7 @@
>
<ion-icon name="close-outline"></ion-icon>
</button>
<div id="availableLeaveCount" style="height: 40px;display: none;"></div>
</div>
<div class="oh-modal__dialog-body">
<form

View File

@@ -6,3 +6,11 @@
>{% trans form.leave_type_id.label %}</label
>
{{form.leave_type_id}}
<script>
$(document).ready(function () {
$("select").on("select2:select", function (e) {
$(this).closest("select")[0].dispatchEvent(new Event("change"));
});
});
</script>

View File

@@ -6,6 +6,7 @@
<button class="oh-modal__close" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
</button>
<div id="assinedLeaveAvailableCount" style="height: 40px;display: none;"></div>
</div>
<div class="oh-modal__dialog-body">
<form
@@ -20,7 +21,6 @@
<script>
$(document).ready(function () {
$("select").on("select2:select", function (e) {
$(".leave-message").hide();
$(this).closest("select")[0].dispatchEvent(new Event("change"));
});
});

View File

@@ -408,63 +408,6 @@
});
});
function typeChange(selectElement) {
var selectedLeavetype =selectElement.val()
let parentForm = selectElement.parents().closest("form")
var employeeId = parentForm.find('[name = employee_id]').val()
$.ajax({
type: "post",
url: "{% url 'employee-leave-details' %}",
data: {
csrfmiddlewaretoken: getCookie("csrftoken"),
"leave_type":selectedLeavetype,
"employee_id":employeeId,
},
success: function (response) {
// Assuming parentForm is a reference to the form containing the element to update
var messageDiv = parentForm.find(".leave-message");
// Check if the messageDiv already exists, if not create it
if (!messageDiv.length) {
messageDiv = $("<div class='leave-message'></div>");
parentForm.prepend(messageDiv);
}
// Checking leave type is selected in the form or not
if (response.leave_count === ''){
messageDiv.hide()
}
else if (response.leave_count === 0.0){
messageDiv.show()
messageDiv.text("Available Leaves : " + response.leave_count);
messageDiv.css({
'background-color': 'rgb(229 79 56 / 17%)',
'border': '2px solid hsl(8,77%,56%)',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': '35%'
});
}
else{
messageDiv.show()
// Set the message content and apply styling
messageDiv.text("Available Leaves : " + response.leave_count);
messageDiv.css({
'background-color': '#dff0d8',
'border': '2px solid #3c763d',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': '35%'
});
}
}
});
}
function enlargeImage(src,$element) {
$(".enlargeImageContainer").empty()

View File

@@ -26,32 +26,33 @@ class LeaveMailSendThread(Thread):
if leave_request_id != "#":
link = int(leave_request_id)
for recipient in recipients:
html_message = render_to_string(
"base/mail_templates/leave_request_template.html",
{
"link": link,
"instance": recipient,
"host": host,
"protocol": protocol,
"subject": subject,
"content": content,
},
)
email = EmailMessage(
subject,
html_message,
email_backend.dynamic_from_email_with_display_name,
[recipient.email],
)
email.content_subtype = "html"
try:
email.send()
except:
messages.error(
self.request, f"Mail not sent to {recipient.get_full_name()}"
if recipient:
html_message = render_to_string(
"base/mail_templates/leave_request_template.html",
{
"link": link,
"instance": recipient,
"host": host,
"protocol": protocol,
"subject": subject,
"content": content,
},
)
email = EmailMessage(
subject,
html_message,
email_backend.dynamic_from_email_with_display_name,
[recipient.email],
)
email.content_subtype = "html"
try:
email.send()
except:
messages.error(
self.request, f"Mail not sent to {recipient.get_full_name()}"
)
def run(self) -> None:
super().run()
if self.type == "request":

View File

@@ -267,9 +267,9 @@ urlpatterns = [
name="user-request-select-filter",
),
path(
"employee-leave-details",
views.employee_leave_details,
name="employee-leave-details",
"employee-available-leave-count",
views.employee_available_leave_count,
name="employee-available-leave-count",
),
path(
"leave-request-add-comment/<int:leave_id>/",

BIN
static/images/ui/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -107,231 +107,6 @@ nav.after(
);
function empleavetypeChange(selectElement) {
var selectedLeavetype =selectElement.val()
let parentForm = selectElement.parents().closest("form")
var employeeId = parentForm.find('[name = employee_id]').val()
var start_date = parentForm.find('[name = start_date_id]').val()
$.ajax({
type: "post",
url: "/leave/employee-leave-details",
data: {
csrfmiddlewaretoken: getCookie("csrftoken"),
"leave_type":selectedLeavetype,
"employee_id":employeeId,
"date":start_date,
},
success: function (response) {
// Assuming parentForm is a reference to the form containing the element to update
var messageDiv = parentForm.find(".leave-message");
// Check if the messageDiv already exists, if not create it
if (!messageDiv.length) {
messageDiv = $("<div class='leave-message'></div>");
parentForm.prepend(messageDiv);
}
// Checking leave type is selected in the form or not
if (response.leave_count != '' && response.employee != ''){
messageDiv.show()
messageDiv.text("Available Leaves : " + response.leave_count);
messageDiv.css({
'background-color': '#dff0d8',
'border': '2px solid #3c763d',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': '35%'
});
}
else if ( selectedLeavetype === ''){
messageDiv.hide()
}
else if (selectedLeavetype != '' && response.leave_count === '' && response.employee != ''){
messageDiv.show()
messageDiv.text("Leave type is not assigned for selecetd employee.");
messageDiv.css({
'background-color': 'rgb(229 79 56 / 17%)',
'border': '2px solid hsl(8,77%,56%)',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': 'auto'
});
}
else if (response.leave_count === 0.0){
messageDiv.show()
messageDiv.text("Available Leaves : " + response.leave_count);
messageDiv.css({
'background-color': 'rgb(229 79 56 / 17%)',
'border': '2px solid hsl(8,77%,56%)',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': '35%'
});
}
else{
messageDiv.hide()
}
}
});
}
function employeeChange(selectElement) {
var employeeId =selectElement.val()
let parentForm = selectElement.parents().closest("form")
var leavetypeId = parentForm.find('[name = leave_type_id]').val()
var start_date = parentForm.find('[name = start_date_id]').val()
$.ajax({
type: "post",
url: "/leave/employee-leave-details",
data: {
csrfmiddlewaretoken: getCookie("csrftoken"),
"leave_type":leavetypeId,
"employee_id":employeeId,
"date":start_date,
},
success: function (response) {
// Assuming parentForm is a reference to the form containing the element to update
var messageDiv = parentForm.find(".leave-message");
// Check if the messageDiv already exists, if not create it
if (!messageDiv.length) {
messageDiv = $("<div class='leave-message'></div>");
parentForm.prepend(messageDiv);
}
// Checking leave type is selected in the form or not
if (response.leave_count != '' && response.employee != ''){
messageDiv.show()
messageDiv.text("Available Leaves : " + response.leave_count);
messageDiv.css({
'background-color': '#dff0d8',
'border': '2px solid #3c763d',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': '35%'
});
}
else if ( leavetypeId === ''){
messageDiv.hide()
}
else if (leavetypeId != '' && response.leave_count === '' && response.employee != ''){
messageDiv.show()
messageDiv.text("Leave type is not assigned for selecetd employee.");
messageDiv.css({
'background-color': 'rgb(229 79 56 / 17%)',
'border': '2px solid hsl(8,77%,56%)',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': 'auto'
});
}
else if (response.leave_count === 0.0){
messageDiv.show()
messageDiv.text("Available Leaves : " + response.leave_count);
messageDiv.css({
'background-color': 'rgb(229 79 56 / 17%)',
'border': '2px solid hsl(8,77%,56%)',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': '35%'
});
}
else{
messageDiv.hide()
}
}
});
}
function dateChange(selectElement) {
let parentForm = selectElement.parents().closest("form")
var employeeId = parentForm.find('[name = employee_id]').val()
var leavetypeId = parentForm.find('[name = leave_type_id]').val()
var start_date = selectElement.val()
$.ajax({
type: "post",
url: "/leave/employee-leave-details",
data: {
csrfmiddlewaretoken: getCookie("csrftoken"),
"leave_type":leavetypeId,
"employee_id":employeeId,
"date": start_date,
},
success: function (response) {
// Assuming parentForm is a reference to the form containing the element to update
var messageDiv = parentForm.find(".leave-message");
// Check if the messageDiv already exists, if not create it
if (!messageDiv.length) {
messageDiv = $("<div class='leave-message'></div>");
parentForm.prepend(messageDiv);
}
// Checking leave type is selected in the form or not
if (response.leave_count != '' && response.employee != '') {
messageDiv.show()
messageDiv.text("Available Leaves : " + response.leave_count);
messageDiv.css({
'background-color': '#dff0d8',
'border': '2px solid #3c763d',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': '35%'
});
}
else if ( leavetypeId === ''){
messageDiv.hide()
}
else if (leavetypeId != '' && response.leave_count === '' && response.employee != ''){
messageDiv.show()
messageDiv.text("Leave type is not assigned for selecetd employee.");
messageDiv.css({
'background-color': 'rgb(229 79 56 / 17%)',
'border': '2px solid hsl(8,77%,56%)',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': 'auto'
});
}
else if (response.leave_count === 0.0){
messageDiv.show()
messageDiv.text("Available Leaves : " + response.leave_count);
messageDiv.css({
'background-color': 'rgb(229 79 56 / 17%)',
'border': '2px solid hsl(8,77%,56%)',
'border-radius': '18px',
'padding': '10px',
'font-weight': 'bold',
'margin-bottom': '15px',
'width': '35%'
});
}
else{
messageDiv.hide()
}
}
});
}
function shiftChange(selectElement) {
var shiftId =selectElement.val()
let parentForm = selectElement.parents().closest("form")