Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow user to bulk allocate leaves manually #2539

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 87 additions & 39 deletions hrms/hr/doctype/leave_allocation/leave_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,50 +323,15 @@ def allocate_leaves_manually(self, new_leaves, from_date=None):
title=_("Invalid Dates"),
)

new_allocation = flt(self.total_leaves_allocated) + flt(new_leaves)
new_allocation_without_cf = flt(
flt(self.get_existing_leave_count()) + flt(new_leaves),
self.precision("total_leaves_allocated"),
)

max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
if new_allocation > max_leaves_allowed and max_leaves_allowed > 0:
new_allocation = max_leaves_allowed

annual_allocation = frappe.db.get_value(
"Leave Policy Detail",
{"parent": self.leave_policy, "leave_type": self.leave_type},
"annual_allocation",
)
annual_allocation = flt(annual_allocation, self.precision("total_leaves_allocated"))

if (
new_allocation != self.total_leaves_allocated
# annual allocation as per policy should not be exceeded
and new_allocation_without_cf <= annual_allocation
):
self.db_set("total_leaves_allocated", new_allocation, update_modified=False)

date = from_date or frappe.flags.current_date or getdate()
create_additional_leave_ledger_entry(self, new_leaves, date)

text = _("{0} leaves were manually allocated by {1} on {2}").format(
frappe.bold(new_leaves), frappe.session.user, frappe.bold(formatdate(date))
)
self.add_comment(comment_type="Info", text=text)
try:
self.add_leaves(new_leaves, from_date)
frappe.msgprint(
_("{0} leaves allocated successfully").format(frappe.bold(new_leaves)),
indicator="green",
alert=True,
)

else:
msg = _("Total leaves allocated cannot exceed annual allocation of {0}.").format(
frappe.bold(_(annual_allocation))
)
msg += "<br><br>"
msg += _("Reference: {0}").format(get_link_to_form("Leave Policy", self.leave_policy))
frappe.throw(msg, title=_("Annual Allocation Exceeded"))
except frappe.ValidationError as e:
frappe.throw(str(e), title=_("Allocation Error"))

@frappe.whitelist()
def get_monthly_earned_leave(self):
Expand All @@ -392,6 +357,49 @@ def get_monthly_earned_leave(self):

return _get_monthly_earned_leave(doj, annual_allocation, frequency, rounding)

def add_leaves(self, new_leaves, date=None):
date = date or frappe.flags.current_date or getdate()
new_allocation = flt(self.total_leaves_allocated) + flt(new_leaves)
new_allocation_without_cf = flt(
flt(self.get_existing_leave_count()) + flt(new_leaves),
self.precision("total_leaves_allocated"),
)

max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
if new_allocation > max_leaves_allowed and max_leaves_allowed > 0:
new_allocation = max_leaves_allowed

annual_allocation = 0
if self.leave_policy:
annual_allocation = frappe.db.get_value(
"Leave Policy Detail",
{"parent": self.leave_policy, "leave_type": self.leave_type},
"annual_allocation",
)
annual_allocation = flt(annual_allocation, self.precision("total_leaves_allocated"))

if new_allocation != self.total_leaves_allocated and (
not self.leave_policy or new_allocation_without_cf <= annual_allocation
):
self.db_set("total_leaves_allocated", new_allocation, update_modified=False)
create_additional_leave_ledger_entry(self, new_leaves, date)

text = _("{0} leaves were manually allocated by {1} on {2}").format(
frappe.bold(new_leaves), frappe.session.user, frappe.bold(formatdate(date))
)
self.add_comment(comment_type="Info", text=text)

return {
"status": "success",
"employee": self.employee,
"doc": get_link_to_form("Leave Allocation", self.name),
}

else:
raise frappe.ValidationError(
_("Total leaves allocated cannot exceed allocation limit or annual allocation.")
)


def get_previous_allocation(from_date, leave_type, employee):
"""Returns document properties of previous allocation"""
Expand Down Expand Up @@ -477,3 +485,43 @@ def get_unused_leaves(employee, leave_type, from_date, to_date):
def validate_carry_forward(leave_type):
if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))


@frappe.whitelist()
def bulk_allocate_leaves(allocations, new_leaves):
allocations = frappe.parse_json(allocations)
if len(allocations) <= 30:
return _bulk_allocate_leaves(allocations, new_leaves)

frappe.enqueue(_bulk_allocate_leaves, timeout=3000, allocations=allocations, new_leaves=new_leaves)
frappe.msgprint(
_("Allocation of leaves has been queued. It may take a few minutes."),
alert=True,
indicator="blue",
)


def _bulk_allocate_leaves(allocations, new_leaves):
success, failure = [], []

for allocation in allocations:
doc = frappe.get_doc("Leave Allocation", allocation)
try:
result = doc.add_leaves(new_leaves)
success.append(result)
except Exception as e:
frappe.log_error(
title=f"Bulk Allocation - Processing failed for Allocation {doc.name}.",
message=str(e),
reference_doctype="Leave Allocation",
reference_name=doc.name,
)
failure.append(doc.employee)

frappe.clear_messages()
frappe.publish_realtime(
"completed_bulk_leave_allocation",
message={"success": success, "failure": failure, "for_update": True},
doctype="Leave Allocation",
after_commit=True,
)
39 changes: 39 additions & 0 deletions hrms/hr/doctype/leave_allocation/leave_allocation_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,43 @@ frappe.listview_settings["Leave Allocation"] = {
return [__("Expired"), "gray", "expired, =, 1"];
}
},
onload: function (listview) {
listview.page.add_action_item(
__("Allocate More Leaves"),
() => {
const allocations = listview
.get_checked_items()
.map((allocation) => allocation.name);
const dialog = new frappe.ui.Dialog({
title: "Manual Leave Allocation",
fields: [
{
label: "New Leaves to be Allocated",
fieldname: "new_leaves",
fieldtype: "Float",
},
],
primary_action_label: "Allocate",
primary_action({ new_leaves }) {
frappe.call({
method: "hrms.hr.doctype.leave_allocation.leave_allocation.bulk_allocate_leaves",
args: { allocations, new_leaves },
freeze: true,
freeze_message: __("Allocating Leaves"),
});
dialog.hide();
},
});
dialog.show();
},
__("Actions"),
);
},
refresh: function (listview) {
hrms.handle_realtime_bulk_action_notification(
listview,
"completed_bulk_leave_allocation",
"Leave Allocation",
);
},
};
20 changes: 20 additions & 0 deletions hrms/hr/doctype/leave_allocation/test_leave_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from hrms.hr.doctype.leave_allocation.leave_allocation import (
BackDatedAllocationError,
OverAllocationError,
bulk_allocate_leaves,
)
from hrms.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
from hrms.hr.doctype.leave_type.test_leave_type import create_leave_type
Expand Down Expand Up @@ -614,6 +615,25 @@ def test_validation_against_leave_application_after_submit(self):
leave_allocation.total_leaves_allocated = leave_application.total_leave_days - 1
self.assertRaises(frappe.ValidationError, leave_allocation.submit)

def test_bulk_leave_allocation(self):
leave_allocation_1 = create_leave_allocation()
leave_allocation_1.submit()

leave_allocation_2 = create_leave_allocation(
leave_type="_Test Leave Type Earned",
)
leave_allocation_2.submit()

allocations = [leave_allocation_1.name, leave_allocation_2.name] # leave_allocation_2.name]

bulk_allocate_leaves(allocations, 1.5)

leave_allocation_1.reload()
leave_allocation_2.reload()

self.assertEqual(leave_allocation_1.total_leaves_allocated, 16.5)
self.assertEqual(leave_allocation_2.total_leaves_allocated, 16.5)


def create_leave_allocation(**args):
args = frappe._dict(args)
Expand Down
14 changes: 13 additions & 1 deletion hrms/public/js/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,33 @@ $.extend(hrms, {
message.failure,
message.success,
message.for_processing,
message.for_update,
);

// refresh only on complete/partial success
if (message.success) frm.refresh();
});
},

notify_bulk_action_status: (doctype, failure, success, for_processing = false) => {
notify_bulk_action_status: (
doctype,
failure,
success,
for_processing = false,
for_update = false,
) => {
let action = __("create/submit");
let action_past = __("created");
if (for_processing) {
action = __("process");
action_past = __("processed");
}

if (for_update) {
action = __("update");
action_past = __("updated");
}

let message = "";
let title = __("Success");
let indicator = "green";
Expand Down
Loading