From 72b91eb08fb683b0a37cc1cd967be6d67b5992f4 Mon Sep 17 00:00:00 2001 From: krantheman Date: Mon, 4 Dec 2023 18:33:43 +0530 Subject: [PATCH 01/11] feat: send mail to HR Managers for failure of automatic allocation of Earned Leaves --- hrms/hr/utils.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/hrms/hr/utils.py b/hrms/hr/utils.py index ec8a7a787e..060ff00ce2 100644 --- a/hrms/hr/utils.py +++ b/hrms/hr/utils.py @@ -6,6 +6,7 @@ from frappe.model.document import Document from frappe.utils import ( add_days, + comma_and, cstr, flt, format_datetime, @@ -338,6 +339,7 @@ def allocate_earned_leaves(): e_leave_types = get_earned_leaves() today = frappe.flags.current_date or getdate() + failed_allocations = [] for e_leave_type in e_leave_types: leave_allocations = get_leave_allocations(today, e_leave_type.name) @@ -368,7 +370,29 @@ def allocate_earned_leaves(): if check_effective_date( from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.allocate_on_day ): - update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, date_of_joining) + try: + update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, date_of_joining) + except Exception: + failed_allocations.append(allocation.name) + if failed_allocations: + allocations = comma_and([get_link_to_form("Leave Allocation", x) for x in failed_allocations]) + + hr_managers = frappe.db.sql_list( + """ + SELECT DISTINCT(has_role.parent) + FROM `tabHas Role` has_role LEFT JOIN `tabUser` user + ON has_role.parent = user.name + WHERE has_role.parenttype = 'User' AND user.enabled = 1 AND has_role.role = %s + """, + "HR Manager", + ) + frappe.sendmail( + recipients=hr_managers, + subject=_("Failure of Automatic Allocation of Earned Leaves"), + message=_("Automatic Leave Allocation has failed for the following Earned Leaves: {0}.").format( + allocations + ), + ) def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, date_of_joining): From 7122dd48d7f2eddb4da4f6196ffe21e69f579a34 Mon Sep 17 00:00:00 2001 From: krantheman Date: Tue, 5 Dec 2023 18:36:15 +0530 Subject: [PATCH 02/11] feat: add Allocate Manual Leaves dialog --- .../leave_allocation/leave_allocation.js | 211 ++++++++++++++---- .../leave_allocation_dashboard.html | 30 +++ hrms/hr/utils.py | 1 + 3 files changed, 194 insertions(+), 48 deletions(-) create mode 100644 hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index 4bd60bfb67..43326e059b 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -1,138 +1,253 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.add_fetch('employee', 'employee_name', 'employee_name'); +cur_frm.add_fetch("employee", "employee_name", "employee_name"); frappe.ui.form.on("Leave Allocation", { - onload: function(frm) { + onload: function (frm) { // Ignore cancellation of doctype on cancel all. frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"]; - if (!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today()); + if (!frm.doc.from_date) + frm.set_value("from_date", frappe.datetime.get_today()); - frm.set_query("employee", function() { + frm.set_query("employee", function () { return { - query: "erpnext.controllers.queries.employee_query" + query: "erpnext.controllers.queries.employee_query", }; }); - frm.set_query("leave_type", function() { + frm.set_query("leave_type", function () { return { filters: { - is_lwp: 0 - } + is_lwp: 0, + }, }; }); }, - refresh: function(frm) { + make_dashboard: async function (frm) { + response = await frappe.db.get_value("Leave Type", frm.doc.leave_type, [ + "is_earned_leave", + "earned_leave_frequency", + ]); + leave_type = response.message; + if ( + !leave_type.is_earned_leave || + leave_type.earned_leave_frequency != "Monthly" + ) + return; + $("div").remove(".form-dashboard-section.custom"); + + frm.dashboard.add_section( + frappe.render_template("leave_allocation_dashboard", { + data: leave_details, + }), + __("Allocated Leaves") + ); + frm.dashboard.show(); + }, + + refresh: function (frm) { if (frm.doc.docstatus === 1 && frm.doc.expired) { - var valid_expiry = moment(frappe.datetime.get_today()).isBetween(frm.doc.from_date, frm.doc.to_date); + var valid_expiry = moment(frappe.datetime.get_today()).isBetween( + frm.doc.from_date, + frm.doc.to_date + ); if (valid_expiry) { // expire current allocation - frm.add_custom_button(__('Expire Allocation'), function() { + frm.add_custom_button(__("Expire Allocation"), function () { frm.trigger("expire_allocation"); }); } } if (!frm.doc.__islocal && frm.doc.leave_policy_assignment) { - frappe.db.get_value("Leave Type", frm.doc.leave_type, "is_earned_leave", (r) => { - if (cint(r?.is_earned_leave)) + frappe.db.get_value( + "Leave Type", + frm.doc.leave_type, + ["is_earned_leave", "earned_leave_frequency", "rounding"], + (r) => { + if (!cint(r?.is_earned_leave)) return; frm.set_df_property("new_leaves_allocated", "read_only", 1); - }); + frm.frequency = r?.earned_leave_frequency; + frm.rounding = r?.rounding; + + frm.add_custom_button(__("Allocate Leaves Manually"), function () { + const dialog = new frappe.ui.Dialog({ + title: "Enter details", + fields: [ + { + label: "New Leaves Allocated", + fieldname: "new_leaves_allocated", + fieldtype: "Data", + }, + ], + primary_action_label: "Allocate", + primary_action() { + dialog.hide(); + }, + }); + dialog.show(); + }); + } + ); } }, - expire_allocation: function(frm) { + expire_allocation: function (frm) { frappe.call({ - method: 'hrms.hr.doctype.leave_ledger_entry.leave_ledger_entry.expire_allocation', + method: + "hrms.hr.doctype.leave_ledger_entry.leave_ledger_entry.expire_allocation", args: { - 'allocation': frm.doc, - 'expiry_date': frappe.datetime.get_today() + allocation: frm.doc, + expiry_date: frappe.datetime.get_today(), }, freeze: true, - callback: function(r) { + callback: function (r) { if (!r.exc) { frappe.msgprint(__("Allocation Expired!")); } frm.refresh(); - } + }, }); }, - employee: function(frm) { + employee: function (frm) { frm.trigger("calculate_total_leaves_allocated"); }, - leave_type: function(frm) { + leave_type: function (frm) { frm.trigger("leave_policy"); frm.trigger("calculate_total_leaves_allocated"); + frm.trigger("make_dashboard"); }, - carry_forward: function(frm) { + carry_forward: function (frm) { frm.trigger("calculate_total_leaves_allocated"); }, - unused_leaves: function(frm) { - frm.set_value("total_leaves_allocated", - flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated)); + unused_leaves: function (frm) { + frm.set_value( + "total_leaves_allocated", + flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated) + ); }, - new_leaves_allocated: function(frm) { - frm.set_value("total_leaves_allocated", - flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated)); + new_leaves_allocated: function (frm) { + frm.set_value( + "total_leaves_allocated", + flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated) + ); }, - leave_policy: function(frm) { + leave_policy: function (frm) { if (frm.doc.leave_policy && frm.doc.leave_type) { - frappe.db.get_value("Leave Policy Detail", { - 'parent': frm.doc.leave_policy, - 'leave_type': frm.doc.leave_type - }, 'annual_allocation', (r) => { - if (r && !r.exc) frm.set_value("new_leaves_allocated", flt(r.annual_allocation)); - }, "Leave Policy"); + frappe.db.get_value( + "Leave Policy Detail", + { + parent: frm.doc.leave_policy, + leave_type: frm.doc.leave_type, + }, + "annual_allocation", + (r) => { + if (r && !r.exc) + frm.set_value("new_leaves_allocated", flt(r.annual_allocation)); + }, + "Leave Policy" + ); } }, - calculate_total_leaves_allocated: function(frm) { - if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) { + calculate_total_leaves_allocated: function (frm) { + if ( + cint(frm.doc.carry_forward) == 1 && + frm.doc.leave_type && + frm.doc.employee + ) { return frappe.call({ method: "set_total_leaves_allocated", doc: frm.doc, - callback: function() { + callback: function () { frm.refresh_fields(); - } + }, }); } else if (cint(frm.doc.carry_forward) == 0) { frm.set_value("unused_leaves", 0); - frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); + frm.set_value( + "total_leaves_allocated", + flt(frm.doc.new_leaves_allocated) + ); } - } + }, + get_monthly_earned_leave: async function (frm) { + await frappe.run_serially([ + () => + frappe.db + .get_value("Employee", frm.doc.employee, "date_of_joining") + .then((r) => (frm.doj = r.message.date_of_joining)), + () => + frappe.db.get_value( + "Leave Policy Detail", + { + parent: frm.doc.leave_policy, + leave_type: frm.doc.leave_type, + }, + "annual_allocation", + (r) => { + frm.annual_leaves = r.annual_allocation; + }, + "Leave Policy" + ), + () => + frappe.call({ + method: "hrms.hr.utils.get_monthly_earned_leave", + args: { + date_of_joining: frm.doj, + annual_leaves: frm.annual_leaves, + frequency: frm.frequency, + rounding: frm.rounding, + }, + callback: function (r) { + frm.monthly_earned_leave = r.message; + }, + }), + ]); + }, }); frappe.tour["Leave Allocation"] = [ { fieldname: "employee", title: "Employee", - description: __("Select the Employee for which you want to allocate leaves.") + description: __( + "Select the Employee for which you want to allocate leaves." + ), }, { fieldname: "leave_type", title: "Leave Type", - description: __("Select the Leave Type like Sick leave, Privilege Leave, Casual Leave, etc.") + description: __( + "Select the Leave Type like Sick leave, Privilege Leave, Casual Leave, etc." + ), }, { fieldname: "from_date", title: "From Date", - description: __("Select the date from which this Leave Allocation will be valid.") + description: __( + "Select the date from which this Leave Allocation will be valid." + ), }, { fieldname: "to_date", title: "To Date", - description: __("Select the date after which this Leave Allocation will expire.") + description: __( + "Select the date after which this Leave Allocation will expire." + ), }, { fieldname: "new_leaves_allocated", title: "New Leaves Allocated", - description: __("Enter the number of leaves you want to allocate for the period.") - } + description: __( + "Enter the number of leaves you want to allocate for the period." + ), + }, ]; diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html b/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html new file mode 100644 index 0000000000..c5615a0afb --- /dev/null +++ b/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html @@ -0,0 +1,30 @@ + +{% if not jQuery.isEmptyObject(data) %} + + + + + + + + + + + + + {% for(const [key, value] of Object.entries(data)) { %} + {% let color = cint(value["remaining_leaves"]) > 0 ? "green" : "red" %} + + + + + + + + + {% } %} + +
{{ __("Leave Type") }}{{ __("Total Allocated Leaves") }}{{ __("Expired Leaves") }}{{ __("Used Leaves") }}{{ __("Leaves Pending Approval") }}{{ __("Available Leaves") }}
{%= key %} {%= value["total_leaves"] %} {%= value["expired_leaves"] %} {%= value["leaves_taken"] %} {%= value["leaves_pending_approval"] %} {%= value["remaining_leaves"] %}
+{% else %} +

No Leave has been allocated.

+{% endif %} diff --git a/hrms/hr/utils.py b/hrms/hr/utils.py index 060ff00ce2..d1f4984430 100644 --- a/hrms/hr/utils.py +++ b/hrms/hr/utils.py @@ -435,6 +435,7 @@ def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type allocation.add_comment(comment_type="Info", text=text) +@frappe.whitelist() def get_monthly_earned_leave( date_of_joining, annual_leaves, From ac67eb33b5f38b5b63ee55032ebbfa2adcb90d95 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 7 Dec 2023 17:02:34 +0530 Subject: [PATCH 03/11] feat: default New Leaves in Allocate Leaves Manually to monthly earned leaves --- hrms/hr/doctype/leave_allocation/leave_allocation.js | 6 ++++-- hrms/www/jobs/__init__.py | 0 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 hrms/www/jobs/__init__.py diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index 43326e059b..5ab00ad977 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -71,14 +71,15 @@ frappe.ui.form.on("Leave Allocation", { frm.set_df_property("new_leaves_allocated", "read_only", 1); frm.frequency = r?.earned_leave_frequency; frm.rounding = r?.rounding; + frm.trigger("get_monthly_earned_leave"); frm.add_custom_button(__("Allocate Leaves Manually"), function () { const dialog = new frappe.ui.Dialog({ title: "Enter details", fields: [ { - label: "New Leaves Allocated", - fieldname: "new_leaves_allocated", + label: "New Leaves to be Allocated", + fieldname: "new_leaves", fieldtype: "Data", }, ], @@ -87,6 +88,7 @@ frappe.ui.form.on("Leave Allocation", { dialog.hide(); }, }); + dialog.fields_dict.new_leaves.set_value(frm.monthly_earned_leave); dialog.show(); }); } diff --git a/hrms/www/jobs/__init__.py b/hrms/www/jobs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 8fd4da45d44caf45056ba81ddec4634f934580c3 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 7 Dec 2023 18:25:56 +0530 Subject: [PATCH 04/11] feat: create leave ledger entry and leave comment in document timeline on Allocating Leaves Manually --- .../leave_allocation/leave_allocation.js | 21 +++++++++++++++++-- hrms/hr/utils.py | 12 +++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index 5ab00ad977..e732b6e135 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -81,14 +81,18 @@ frappe.ui.form.on("Leave Allocation", { label: "New Leaves to be Allocated", fieldname: "new_leaves", fieldtype: "Data", + onchange: function () { + frm.new_leaves = this.value; + }, }, ], primary_action_label: "Allocate", primary_action() { + frm.trigger("allocate_leaves_manually"); dialog.hide(); }, }); - dialog.fields_dict.new_leaves.set_value(frm.monthly_earned_leave); + dialog.fields_dict.new_leaves.set_value(frm.new_leaves); dialog.show(); }); } @@ -209,11 +213,24 @@ frappe.ui.form.on("Leave Allocation", { rounding: frm.rounding, }, callback: function (r) { - frm.monthly_earned_leave = r.message; + frm.new_leaves = r.message; }, }), ]); }, + + allocate_leaves_manually: function (frm) { + frappe.call({ + method: "hrms.hr.utils.allocate_leaves_manually", + args: { + allocation_name: frm.doc.name, + new_leaves: frm.new_leaves, + }, + callback: function () { + frm.refresh(); + }, + }); + }, }); frappe.tour["Leave Allocation"] = [ diff --git a/hrms/hr/utils.py b/hrms/hr/utils.py index d1f4984430..510aeaa2a3 100644 --- a/hrms/hr/utils.py +++ b/hrms/hr/utils.py @@ -505,6 +505,18 @@ def get_earned_leaves(): ) +@frappe.whitelist() +def allocate_leaves_manually(allocation_name, new_leaves): + allocation = frappe.get_doc("Leave Allocation", allocation_name) + today_date = frappe.flags.current_date or getdate() + + create_additional_leave_ledger_entry(allocation, new_leaves, today_date) + text = _("{0} leaves were manually allocated by {1} on {2}").format( + frappe.bold(new_leaves), frappe.session.user, frappe.bold(formatdate(today_date)) + ) + allocation.add_comment(comment_type="Info", text=text) + + def create_additional_leave_ledger_entry(allocation, leaves, date): """Create leave ledger entry for leave types""" allocation.new_leaves_allocated = leaves From 078bc6dec08118b87125a8db4154478c02df31a0 Mon Sep 17 00:00:00 2001 From: krantheman Date: Fri, 8 Dec 2023 18:02:08 +0530 Subject: [PATCH 05/11] feat: Allocated Leaves Scedule on form dashboard --- .../leave_allocation/leave_allocation.js | 61 ++++++++++++------- .../leave_allocation_dashboard.html | 28 +++------ 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index e732b6e135..d91830464d 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -16,6 +16,7 @@ frappe.ui.form.on("Leave Allocation", { query: "erpnext.controllers.queries.employee_query", }; }); + frm.set_query("leave_type", function () { return { filters: { @@ -25,28 +26,6 @@ frappe.ui.form.on("Leave Allocation", { }); }, - make_dashboard: async function (frm) { - response = await frappe.db.get_value("Leave Type", frm.doc.leave_type, [ - "is_earned_leave", - "earned_leave_frequency", - ]); - leave_type = response.message; - if ( - !leave_type.is_earned_leave || - leave_type.earned_leave_frequency != "Monthly" - ) - return; - $("div").remove(".form-dashboard-section.custom"); - - frm.dashboard.add_section( - frappe.render_template("leave_allocation_dashboard", { - data: leave_details, - }), - __("Allocated Leaves") - ); - frm.dashboard.show(); - }, - refresh: function (frm) { if (frm.doc.docstatus === 1 && frm.doc.expired) { var valid_expiry = moment(frappe.datetime.get_today()).isBetween( @@ -163,6 +142,7 @@ frappe.ui.form.on("Leave Allocation", { ); } }, + calculate_total_leaves_allocated: function (frm) { if ( cint(frm.doc.carry_forward) == 1 && @@ -184,6 +164,7 @@ frappe.ui.form.on("Leave Allocation", { ); } }, + get_monthly_earned_leave: async function (frm) { await frappe.run_serially([ () => @@ -213,7 +194,9 @@ frappe.ui.form.on("Leave Allocation", { rounding: frm.rounding, }, callback: function (r) { + frm.monthly_leaves = r.message; frm.new_leaves = r.message; + frm.trigger("make_dashboard"); }, }), ]); @@ -231,6 +214,40 @@ frappe.ui.form.on("Leave Allocation", { }, }); }, + + make_dashboard: async function (frm) { + response = await frappe.db.get_value("Leave Type", frm.doc.leave_type, [ + "is_earned_leave", + "earned_leave_frequency", + ]); + leave_type = response.message; + if ( + !leave_type.is_earned_leave || + leave_type.earned_leave_frequency != "Monthly" + ) + return; + $("div").remove(".form-dashboard-section.custom"); + + const from_date_array = frm.doc.from_date.split("-"); + const from_month = from_date_array[1] - 1; + const to_date_array = frm.doc.to_date.split("-"); + let to_month = to_date_array[1] - 1; + if (to_date_array[0] > from_date_array[0]) to_month += 12; + + const months = []; + for (let i = from_month; i <= to_month; i++) { + months.push(moment().month(i).format("MMMM")); + } + + frm.dashboard.add_section( + frappe.render_template("leave_allocation_dashboard", { + months: months, + monthly_leaves: frm.monthly_leaves, + }), + __("Allocated Leaves") + ); + frm.dashboard.show(); + }, }); frappe.tour["Leave Allocation"] = [ diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html b/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html index c5615a0afb..9798e7b1e4 100644 --- a/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html +++ b/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html @@ -1,30 +1,20 @@ - -{% if not jQuery.isEmptyObject(data) %} +{% if monthly_leaves %} - - - - - - + + - {% for(const [key, value] of Object.entries(data)) { %} - {% let color = cint(value["remaining_leaves"]) > 0 ? "green" : "red" %} - - - - - - - - + {% for(const value of months) { %} {% %} + + + + {% } %}
{{ __("Leave Type") }}{{ __("Total Allocated Leaves") }}{{ __("Expired Leaves") }}{{ __("Used Leaves") }}{{ __("Leaves Pending Approval") }}{{ __("Available Leaves") }}{{ __("Month") }}{{ __("No. of Leaves") }}
{%= key %} {%= value["total_leaves"] %} {%= value["expired_leaves"] %} {%= value["leaves_taken"] %} {%= value["leaves_pending_approval"] %} {%= value["remaining_leaves"] %}
{%= value %}{%= monthly_leaves %}
{% else %} -

No Leave has been allocated.

+

No Leave has been allocated.

{% endif %} From 378f4be7859a2fe80d5caa4f7d7865d42388b96a Mon Sep 17 00:00:00 2001 From: krantheman Date: Tue, 12 Dec 2023 13:25:21 +0530 Subject: [PATCH 06/11] chore: qb instead of raw sql --- .../leave_allocation/leave_allocation.js | 2 +- hrms/hr/utils.py | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index d91830464d..14832f49c3 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -1,7 +1,7 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.add_fetch("employee", "employee_name", "employee_name"); +frm.add_fetch("employee", "employee_name", "employee_name"); frappe.ui.form.on("Leave Allocation", { onload: function (frm) { diff --git a/hrms/hr/utils.py b/hrms/hr/utils.py index 510aeaa2a3..a179fa045b 100644 --- a/hrms/hr/utils.py +++ b/hrms/hr/utils.py @@ -4,6 +4,7 @@ import frappe from frappe import _ from frappe.model.document import Document +from frappe.query_builder import DocType from frappe.utils import ( add_days, comma_and, @@ -377,15 +378,18 @@ def allocate_earned_leaves(): if failed_allocations: allocations = comma_and([get_link_to_form("Leave Allocation", x) for x in failed_allocations]) - hr_managers = frappe.db.sql_list( - """ - SELECT DISTINCT(has_role.parent) - FROM `tabHas Role` has_role LEFT JOIN `tabUser` user - ON has_role.parent = user.name - WHERE has_role.parenttype = 'User' AND user.enabled = 1 AND has_role.role = %s - """, - "HR Manager", + User = DocType("User") + HasRole = DocType("Has Role") + query = ( + frappe.qb.from_(HasRole) + .left_join(User) + .on(HasRole.parent == User.name) + .select(HasRole.parent) + .distinct() + .where((HasRole.parenttype == "User") & (User.enabled == 1) & (HasRole.role == "HR Manager")) ) + hr_managers = query.run(pluck=True) + frappe.sendmail( recipients=hr_managers, subject=_("Failure of Automatic Allocation of Earned Leaves"), From 36585a2fa43e51774537cf9b6204610ce691d222 Mon Sep 17 00:00:00 2001 From: krantheman Date: Tue, 12 Dec 2023 17:03:48 +0530 Subject: [PATCH 07/11] refactor: date instead of month in dashboard table --- .../leave_allocation/leave_allocation.js | 43 ++++++++++--------- .../leave_allocation_dashboard.html | 10 ++--- hrms/hr/utils.py | 39 +++++++++++++++++ 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index 14832f49c3..d90f946938 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -1,9 +1,10 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frm.add_fetch("employee", "employee_name", "employee_name"); - frappe.ui.form.on("Leave Allocation", { + setup: function (frm) { + frm.add_fetch("employee", "employee_name", "employee_name"); + }, onload: function (frm) { // Ignore cancellation of doctype on cancel all. frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"]; @@ -228,25 +229,25 @@ frappe.ui.form.on("Leave Allocation", { return; $("div").remove(".form-dashboard-section.custom"); - const from_date_array = frm.doc.from_date.split("-"); - const from_month = from_date_array[1] - 1; - const to_date_array = frm.doc.to_date.split("-"); - let to_month = to_date_array[1] - 1; - if (to_date_array[0] > from_date_array[0]) to_month += 12; - - const months = []; - for (let i = from_month; i <= to_month; i++) { - months.push(moment().month(i).format("MMMM")); - } - - frm.dashboard.add_section( - frappe.render_template("leave_allocation_dashboard", { - months: months, - monthly_leaves: frm.monthly_leaves, - }), - __("Allocated Leaves") - ); - frm.dashboard.show(); + frappe.call({ + method: "hrms.hr.utils.get_monthly_allocations", + args: { + employee: frm.doc.employee, + leave_type: frm.doc.leave_type, + from_date: frm.doc.from_date, + to_date: frm.doc.to_date, + leave_policy: frm.doc.leave_policy, + }, + callback: function (r) { + frm.dashboard.add_section( + frappe.render_template("leave_allocation_dashboard", { + allocations: r.message + }), + __("Leaves Allocated") + ); + frm.dashboard.show(); + }, + }); }, }); diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html b/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html index 9798e7b1e4..4baed7ebf8 100644 --- a/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html +++ b/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html @@ -1,16 +1,16 @@ -{% if monthly_leaves %} +{% if allocations.length %} - + - {% for(const value of months) { %} {% %} + {% for(const i of allocations) { %} {% %} - - + + {% } %} diff --git a/hrms/hr/utils.py b/hrms/hr/utils.py index a179fa045b..7b727c7b95 100644 --- a/hrms/hr/utils.py +++ b/hrms/hr/utils.py @@ -7,6 +7,7 @@ from frappe.query_builder import DocType from frappe.utils import ( add_days, + add_months, comma_and, cstr, flt, @@ -521,6 +522,44 @@ def allocate_leaves_manually(allocation_name, new_leaves): allocation.add_comment(comment_type="Info", text=text) +@frappe.whitelist() +def get_monthly_allocations(employee, leave_type, from_date, to_date, leave_policy): + annual_allocation = frappe.db.get_value( + "Leave Policy Detail", {"parent": leave_policy, "leave_type": leave_type}, "annual_allocation" + ) + date_of_joining = frappe.db.get_value("Employee", employee, "date_of_joining") + allocate_on_day, rounding = frappe.db.get_value( + "Leave Type", leave_type, ["allocate_on_day", "rounding"] + ) + monthly_earned_leave = get_monthly_earned_leave( + date_of_joining, annual_allocation, "Monthly", rounding, pro_rated=False + ) + + allocations = [] + date = get_monthly_allocation_date(from_date, allocate_on_day) + total_leaves = 0 + if date < from_date: + date = add_months(date, 1) + date = get_monthly_allocation_date(date, allocate_on_day) + while date <= to_date and total_leaves <= annual_allocation: + allocations.append({"date": date, "leaves": monthly_earned_leave}) + date = add_months(date, 1) + date = get_monthly_allocation_date(date, allocate_on_day) + total_leaves += monthly_earned_leave + return allocations + + +def get_monthly_allocation_date(date, allocate_on_day): + allocation_date = { + "First Day": get_first_day(date), + "Last Day": get_last_day(date), + "Date of Joining": date[:-2] + "15", + }[allocate_on_day] + return ( + allocation_date if allocate_on_day == "Date of Joining" else allocation_date.strftime("%Y-%m-%d") + ) + + def create_additional_leave_ledger_entry(allocation, leaves, date): """Create leave ledger entry for leave types""" allocation.new_leaves_allocated = leaves From 1b75334144b367cea2ae190d2a2f6b9147d8a3ad Mon Sep 17 00:00:00 2001 From: krantheman Date: Tue, 12 Dec 2023 18:38:50 +0530 Subject: [PATCH 08/11] refactor: use single monthly_earned_leave --- .../leave_allocation/leave_allocation.js | 9 ++++--- .../leave_allocation_dashboard.html | 8 +++--- hrms/hr/utils.py | 25 ++++++++----------- hrms/www/jobs/__init__.py | 0 4 files changed, 20 insertions(+), 22 deletions(-) delete mode 100644 hrms/www/jobs/__init__.py diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index d90f946938..bff38e46f3 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -195,7 +195,7 @@ frappe.ui.form.on("Leave Allocation", { rounding: frm.rounding, }, callback: function (r) { - frm.monthly_leaves = r.message; + frm.monthly_earned_leave = r.message; frm.new_leaves = r.message; frm.trigger("make_dashboard"); }, @@ -230,18 +230,19 @@ frappe.ui.form.on("Leave Allocation", { $("div").remove(".form-dashboard-section.custom"); frappe.call({ - method: "hrms.hr.utils.get_monthly_allocations", + method: "hrms.hr.utils.get_monthly_allocation_dates", args: { - employee: frm.doc.employee, leave_type: frm.doc.leave_type, from_date: frm.doc.from_date, to_date: frm.doc.to_date, leave_policy: frm.doc.leave_policy, + monthly_earned_leave: frm.monthly_earned_leave, }, callback: function (r) { frm.dashboard.add_section( frappe.render_template("leave_allocation_dashboard", { - allocations: r.message + allocation_dates: r.message, + monthly_earned_leave: frm.monthly_earned_leave, }), __("Leaves Allocated") ); diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html b/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html index 4baed7ebf8..2b2ab83f47 100644 --- a/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html +++ b/hrms/hr/doctype/leave_allocation/leave_allocation_dashboard.html @@ -1,4 +1,4 @@ -{% if allocations.length %} +{% if allocation_dates.length %}
{{ __("Month") }}{{ __("Date") }} {{ __("No. of Leaves") }}
{%= value %}{%= monthly_leaves %}{%= i.date %}{%= i.leaves %}
@@ -7,10 +7,10 @@ - {% for(const i of allocations) { %} {% %} + {% for(const date of allocation_dates) { %} {% %} - - + + {% } %} diff --git a/hrms/hr/utils.py b/hrms/hr/utils.py index 7b727c7b95..f26c1ec548 100644 --- a/hrms/hr/utils.py +++ b/hrms/hr/utils.py @@ -523,33 +523,30 @@ def allocate_leaves_manually(allocation_name, new_leaves): @frappe.whitelist() -def get_monthly_allocations(employee, leave_type, from_date, to_date, leave_policy): +def get_monthly_allocation_dates( + leave_type, from_date, to_date, leave_policy, monthly_earned_leave +): annual_allocation = frappe.db.get_value( "Leave Policy Detail", {"parent": leave_policy, "leave_type": leave_type}, "annual_allocation" ) - date_of_joining = frappe.db.get_value("Employee", employee, "date_of_joining") - allocate_on_day, rounding = frappe.db.get_value( - "Leave Type", leave_type, ["allocate_on_day", "rounding"] - ) - monthly_earned_leave = get_monthly_earned_leave( - date_of_joining, annual_allocation, "Monthly", rounding, pro_rated=False - ) + allocate_on_day = frappe.db.get_value("Leave Type", leave_type, "allocate_on_day") + date = get_month_allocation_date(from_date, allocate_on_day) allocations = [] - date = get_monthly_allocation_date(from_date, allocate_on_day) - total_leaves = 0 + monthly_earned_leave = float(monthly_earned_leave) + total_leaves = monthly_earned_leave if date < from_date: date = add_months(date, 1) - date = get_monthly_allocation_date(date, allocate_on_day) + date = get_month_allocation_date(date, allocate_on_day) while date <= to_date and total_leaves <= annual_allocation: - allocations.append({"date": date, "leaves": monthly_earned_leave}) + allocations.append(date) date = add_months(date, 1) - date = get_monthly_allocation_date(date, allocate_on_day) + date = get_month_allocation_date(date, allocate_on_day) total_leaves += monthly_earned_leave return allocations -def get_monthly_allocation_date(date, allocate_on_day): +def get_month_allocation_date(date, allocate_on_day): allocation_date = { "First Day": get_first_day(date), "Last Day": get_last_day(date), diff --git a/hrms/www/jobs/__init__.py b/hrms/www/jobs/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From fa89c0425bd58253c009e79640dd66eee6fbe422 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 13 Dec 2023 13:21:12 +0530 Subject: [PATCH 09/11] fix: missing arguments while rendering table --- hrms/hr/doctype/leave_allocation/leave_allocation.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index bff38e46f3..8f86e3fa58 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -224,7 +224,13 @@ frappe.ui.form.on("Leave Allocation", { leave_type = response.message; if ( !leave_type.is_earned_leave || - leave_type.earned_leave_frequency != "Monthly" + leave_type.earned_leave_frequency != "Monthly" || + !( + frm.doc.from_date && + frm.doc.to_date && + frm.doc.leave_policy && + frm.monthly_earned_leave + ) ) return; $("div").remove(".form-dashboard-section.custom"); From 546d486179c5a9e7c892a98d4785c8cdc49852d5 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 13 Dec 2023 13:24:58 +0530 Subject: [PATCH 10/11] fix: doc reload on manual allocation --- hrms/hr/doctype/leave_allocation/leave_allocation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index 8f86e3fa58..77b9c94afe 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -211,7 +211,7 @@ frappe.ui.form.on("Leave Allocation", { new_leaves: frm.new_leaves, }, callback: function () { - frm.refresh(); + frm.reload_doc(); }, }); }, From d27268183dde56ccc5b064a25ab5bf319f3ec7d8 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 13 Dec 2023 15:54:26 +0530 Subject: [PATCH 11/11] fix: schedult section title --- hrms/hr/doctype/leave_allocation/leave_allocation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index 77b9c94afe..af606e4afb 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -250,7 +250,7 @@ frappe.ui.form.on("Leave Allocation", { allocation_dates: r.message, monthly_earned_leave: frm.monthly_earned_leave, }), - __("Leaves Allocated") + __("Leave Allocation Schedule") ); frm.dashboard.show(); },
{%= i.date %}{%= i.leaves %}{%= date %}{%= monthly_earned_leave %}