From 5cbe6160cade4236894af6bcd41f1e195145042c Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 8 Aug 2019 17:06:15 +0530 Subject: [PATCH] feat: consider carry forwarded leaves on creation of encashment --- .../leave_allocation/leave_allocation.js | 8 ++-- .../leave_allocation/leave_allocation.json | 26 ++++++++----- .../leave_allocation/leave_allocation.py | 39 ++++++++++++------- .../leave_encashment/leave_encashment.py | 7 ++-- .../leave_ledger_entry/leave_ledger_entry.py | 9 +++-- .../hr/doctype/leave_period/leave_period.py | 2 +- erpnext/hr/utils.py | 2 +- .../v12_0/generate_leave_ledger_entries.py | 13 ++++++- 8 files changed, 69 insertions(+), 37 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 8c910a200a..210a73cfe5 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -63,14 +63,14 @@ frappe.ui.form.on("Leave Allocation", { frm.trigger("calculate_total_leaves_allocated"); }, - carry_forwarded_leaves: function(frm) { + unused_leaves: function(frm) { frm.set_value("total_leaves_allocated", - flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_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.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); + flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated)); }, leave_policy: function(frm) { @@ -93,7 +93,7 @@ frappe.ui.form.on("Leave Allocation", { } }) } else if (cint(frm.doc.carry_forward) == 0) { - frm.set_value("carry_forwarded_leaves", 0); + frm.set_value("unused_leaves", 0); frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); } } diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 1618c67d3d..007497e34a 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -17,13 +17,14 @@ "section_break_6", "new_leaves_allocated", "carry_forward", - "carry_forwarded_leaves", + "unused_leaves", "total_leaves_allocated", "total_leaves_encashed", "column_break_10", "compensatory_request", "leave_period", "leave_policy", + "carry_forwarded_leaves_count", "expired", "amended_from", "notes", @@ -119,7 +120,7 @@ }, { "depends_on": "carry_forward", - "fieldname": "carry_forwarded_leaves", + "fieldname": "unused_leaves", "fieldtype": "Float", "label": "Unused leaves", "read_only": 1 @@ -167,6 +168,15 @@ "options": "Leave Policy", "read_only": 1 }, + { + "default": "0", + "fieldname": "expired", + "fieldtype": "Check", + "hidden": 1, + "in_standard_filter": 1, + "label": "Expired", + "read_only": 1 + }, { "fieldname": "amended_from", "fieldtype": "Link", @@ -194,19 +204,17 @@ "width": "300px" }, { - "default": "0", - "fieldname": "expired", - "fieldtype": "Check", - "hidden": 1, - "in_standard_filter": 1, - "label": "Expired", + "depends_on": "carry_forwarded_leaves_count", + "fieldname": "carry_forwarded_leaves_count", + "fieldtype": "Float", + "label": "Carry Forwarded Leaves", "read_only": 1 } ], "icon": "fa fa-ok", "idx": 1, "is_submittable": 1, - "modified": "2019-07-22 17:50:39.591195", + "modified": "2019-08-08 15:08:42.440909", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 2374e469a5..5e81999765 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -50,6 +50,8 @@ class LeaveAllocation(Document): def on_cancel(self): self.create_leave_ledger_entry(submit=False) + if self.carry_forward: + self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True) def validate_period(self): if date_diff(self.to_date, self.from_date) <= 0: @@ -89,24 +91,33 @@ class LeaveAllocation(Document): BackDatedAllocationError) def set_total_leaves_allocated(self): - self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, + self.unused_leaves = get_carry_forwarded_leaves(self.employee, self.leave_type, self.from_date, self.carry_forward) - self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated) - self.maintain_carry_forwarded_leaves() + self.total_leaves_allocated = flt(self.unused_leaves) + flt(self.new_leaves_allocated) + + if self.carry_forward: + self.maintain_carry_forwarded_leaves() + self.set_carry_forwarded_leaves_in_previous_allocation() if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): - frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}".format(self.leave_type))) + frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}").format(self.leave_type)) def maintain_carry_forwarded_leaves(self): - ''' reduce the carry forwarded leaves to be within the maximum allowed leaves ''' - if not self.carry_forward: - return + ''' Reduce the carry forwarded leaves to be within the maximum allowed leaves ''' + max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") if self.new_leaves_allocated <= max_leaves_allowed <= self.total_leaves_allocated: - self.carry_forwarded_leaves = max_leaves_allowed - flt(self.new_leaves_allocated) + self.unused_leaves = max_leaves_allowed - flt(self.new_leaves_allocated) self.total_leaves_allocated = flt(max_leaves_allowed) + def set_carry_forwarded_leaves_in_previous_allocation(self, on_cancel=False): + ''' Set carry forwarded leaves in previous allocation ''' + previous_allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee) + if on_cancel: + self.unused_leaves = 0.0 + frappe.db.set_value("Leave Allocation", previous_allocation.name, 'carry_forwarded_leaves_count', self.unused_leaves) + def validate_total_leaves_allocated(self): # Adding a day to include To Date in the difference date_difference = date_diff(self.to_date, self.from_date) + 1 @@ -114,10 +125,10 @@ class LeaveAllocation(Document): frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError) def create_leave_ledger_entry(self, submit=True): - if self.carry_forwarded_leaves: + if self.unused_leaves: expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carried_forward_leaves") args = dict( - leaves=self.carry_forwarded_leaves, + leaves=self.unused_leaves, from_date=self.from_date, to_date=add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date, is_carry_forward=1 @@ -142,7 +153,7 @@ def get_previous_allocation(from_date, leave_type, employee): 'docstatus': 1 }, order_by='to_date DESC', - fieldname=['name', 'from_date', 'to_date'], as_dict=1) + fieldname=['name', 'from_date', 'to_date', 'employee', 'leave_type'], as_dict=1) def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 @@ -170,13 +181,13 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): @frappe.whitelist() def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): ''' Returns carry forwarded leaves for the given employee ''' - carry_forwarded_leaves = 0.0 + unused_leaves = 0.0 previous_allocation = get_previous_allocation(date, leave_type, employee) if carry_forward and previous_allocation: validate_carry_forward(leave_type) - carry_forwarded_leaves = get_unused_leaves(employee, leave_type, previous_allocation.from_date, previous_allocation.to_date) + unused_leaves = get_unused_leaves(employee, leave_type, previous_allocation.from_date, previous_allocation.to_date) - return carry_forwarded_leaves + return unused_leaves def get_unused_leaves(employee, leave_type, from_date, to_date): ''' Returns unused leaves between the given period while skipping leave allocation expiry ''' diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 30529b6241..42f0179baf 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -64,7 +64,8 @@ class LeaveEncashment(Document): allocation = self.get_leave_allocation() - self.leave_balance = allocation.total_leaves_allocated - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) + self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\ + - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days') self.encashable_days = encashable_days if encashable_days > 0 else 0 @@ -76,9 +77,9 @@ class LeaveEncashment(Document): return True def get_leave_allocation(self): - leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated from `tabLeave Allocation` where '{0}' + leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}' between from_date and to_date and docstatus=1 and leave_type='{1}' - and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) + and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) #nosec return leave_allocation[0] if leave_allocation else None diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 8fca24127b..33e4ea0909 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -104,7 +104,7 @@ def process_expired_allocation(): `transaction_type`='Leave Allocation' AND `is_expired`=1""") - expire_allocation = frappe.get_all("Leave Ledger Entry", + expire_allocation = frappe.get_all("Leave Ledger Entry", fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'], filters={ 'to_date': ("<", today()), @@ -119,9 +119,9 @@ def process_expired_allocation(): if expire_allocation: create_expiry_ledger_entry(expire_allocation) -def create_expiry_ledger_entry(expire_allocation): +def create_expiry_ledger_entry(allocations): ''' Create ledger entry for expired allocation ''' - for allocation in expire_allocation: + for allocation in allocations: if allocation.is_carry_forward: expire_carried_forward_allocation(allocation) else: @@ -138,7 +138,7 @@ def get_remaining_leaves(allocation): @frappe.whitelist() def expire_allocation(allocation, expiry_date=None): - ''' expires allocation ''' + ''' expires non-carry forwarded allocation ''' leaves = get_remaining_leaves(allocation) expiry_date = expiry_date if expiry_date else allocation.to_date @@ -146,6 +146,7 @@ def expire_allocation(allocation, expiry_date=None): args = dict( leaves=flt(leaves) * -1, transaction_name=allocation.name, + transaction_type='Leave Allocation', from_date=expiry_date, to_date=expiry_date, is_carry_forward=0, diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index 7b0f882035..a7de7185c9 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -22,7 +22,7 @@ class LeavePeriod(Document): condition_str = " and " + " and ".join(conditions) if len(conditions) else "" - employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" + employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" #nosec .format(condition=condition_str), tuple(values))) return employees diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index bf09de86e7..4fe9f6b285 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -318,7 +318,7 @@ def create_earned_leave_ledger_entry(allocation, earned_leaves, date): ''' Create leave ledger entry based on the earned leave frequency ''' allocation.new_leaves_allocated = earned_leaves allocation.from_date = date - allocation.carry_forwarded_leaves = 0 + allocation.unused_leaves = 0 allocation.create_leave_ledger_entry() def check_frequency_hit(from_date, to_date, frequency): diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 7dfdcc162b..c1e1b4f3b2 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -13,11 +13,22 @@ def execute(): if frappe.db.a_row_exists("Leave Ledger Entry"): return + if not frappe.get_meta("Leave Allocation").has_field("unused_leaves"): + frappe.reload_doc("HR", "doctype", "Leave Allocation") + update_leave_allocation_fieldname() + generate_allocation_ledger_entries() generate_application_leave_ledger_entries() generate_encashment_leave_ledger_entries() generate_expiry_allocation_ledger_entries() +def update_leave_allocation_fieldname(): + ''' maps data from old field to the new field ''' + frappe.db.sql(""" + UPDATE `tabLeave Allocation` + SET `unused_leaves` = `carry_forwarded_leaves` + """) + def generate_allocation_ledger_entries(): ''' fix ledger entries for missing leave allocation transaction ''' allocation_list = get_allocation_records() @@ -64,7 +75,7 @@ def get_allocation_records(): employee, leave_type, new_leaves_allocated, - carry_forwarded_leaves, + unused_leaves, from_date, to_date, carry_forward