From 8fb600162e1f93c0adaffcbff567b4821d853635 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 31 Jul 2019 19:31:26 +0530 Subject: [PATCH] fix: remove all leaves via scheduler --- .../leave_allocation/leave_allocation.js | 7 +- .../leave_allocation/leave_allocation.py | 96 +++++++------------ .../leave_allocation/test_leave_allocation.py | 4 +- .../leave_application/leave_application.py | 2 +- .../leave_ledger_entry/leave_ledger_entry.py | 91 ++++++++++++------ .../v12_0/generate_leave_ledger_entries.py | 3 +- 6 files changed, 107 insertions(+), 96 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 7c3e1e44fa..8c910a200a 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -35,8 +35,11 @@ frappe.ui.form.on("Leave Allocation", { expire_allocation: function(frm) { frappe.call({ - method: 'expire_current_allocation', - doc: frm.doc, + method: 'erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.expire_allocation', + args: { + 'allocation': frm.doc, + 'expiry_date': frappe.datetime.get_today() + }, freeze: true, callback: function(r){ if(!r.exc){ diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 67f30b5816..2374e469a5 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -7,7 +7,7 @@ from frappe.utils import flt, date_diff, formatdate, add_days, today from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, get_leave_period -from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry class OverlapError(frappe.ValidationError): pass class BackDatedAllocationError(frappe.ValidationError): pass @@ -42,7 +42,11 @@ class LeaveAllocation(Document): def on_submit(self): self.create_leave_ledger_entry() - self.expire_previous_allocation() + + # expire all unused leaves in the ledger on creation of carry forward allocation + allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee) + if self.carry_forward and allocation: + expire_allocation(allocation) def on_cancel(self): self.create_leave_ledger_entry(submit=False) @@ -128,55 +132,17 @@ class LeaveAllocation(Document): ) create_leave_ledger_entry(self, args, submit) - def expire_current_allocation(self): - ''' expires allocation ''' - date = self.to_date - leaves = get_unused_leaves(self.employee, self.leave_type, date) - ref_name = self.name - - if leaves: - expiry_date = today() - args = dict( - leaves=flt(leaves) * -1, - transaction_name=ref_name, - from_date=expiry_date, - to_date=expiry_date, - is_carry_forward=0, - is_expired=1 - ) - create_leave_ledger_entry(self, args) - - frappe.db.set_value("Leave Allocation", self.name, "expired", 1) - - def expire_previous_allocation(self): - date = self.from_date - leaves = get_unused_leaves(self.employee, self.leave_type, date) - ref_name = self.get_previous_allocation() - - if leaves: - expiry_date = add_days(self.from_date, -1) - args = dict( - leaves=flt(leaves) * -1, - transaction_name=ref_name, - from_date=expiry_date, - to_date=expiry_date, - is_carry_forward=0, - is_expired=1 - ) - create_leave_ledger_entry(self, args) - - frappe.db.set_value("Leave Allocation", ref_name, "expired", 1) - - def get_previous_allocation(self): - return frappe.db.get_value("Leave Allocation", - filters={ - 'to_date': ("<", self.from_date), - 'leave_type': self.leave_type, - 'employee': self.employee, - 'docstatus': 1 - }, - order_by='to_date DESC', - fieldname=['name']) +def get_previous_allocation(from_date, leave_type, employee): + ''' Returns document properties of previous allocation ''' + return frappe.db.get_value("Leave Allocation", + filters={ + 'to_date': ("<", from_date), + 'leave_type': leave_type, + 'employee': employee, + 'docstatus': 1 + }, + order_by='to_date DESC', + fieldname=['name', 'from_date', 'to_date'], as_dict=1) def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 @@ -203,21 +169,27 @@ 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): - carry_forwarded_leaves = 0 - if carry_forward: + ''' Returns carry forwarded leaves for the given employee ''' + carry_forwarded_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, date) + carry_forwarded_leaves = get_unused_leaves(employee, leave_type, previous_allocation.from_date, previous_allocation.to_date) return carry_forwarded_leaves -def get_unused_leaves(employee, leave_type, date): - return frappe.db.get_value("Leave Ledger Entry", filters={ - "to_date": ("<=", date), - "employee": employee, - "docstatus": 1, - "leave_type": leave_type, - "is_lwp": 0 - }, fieldname=['SUM(leaves)']) +def get_unused_leaves(employee, leave_type, from_date, to_date): + ''' Returns unused leaves between the given period while skipping leave allocation expiry ''' + leaves = frappe.get_all("Leave Ledger Entry", filters={ + 'employee': employee, + 'leave_type': leave_type, + 'from_date': ('>=', from_date), + 'to_date': ('<=', to_date) + }, or_filters={ + 'is_expired': 0, + 'is_carry_forward': 1 + }, fields=['sum(leaves) as leaves']) + return flt(leaves[0]['leaves']) def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index dfa64db416..4f4e0ab1fc 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -3,7 +3,7 @@ import frappe import unittest from frappe.utils import nowdate, add_months, getdate, add_days from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type -from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation class TestLeaveAllocation(unittest.TestCase): def test_overlapping_allocation(self): @@ -108,6 +108,8 @@ class TestLeaveAllocation(unittest.TestCase): carry_forward=1) leave_allocation.submit() + expire_allocation(leave_allocation) + leave_allocation = create_leave_allocation( leave_type="_Test_CF_leave_expiry", from_date=add_days(nowdate(), -90), diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 8f02ec0f92..36a4145467 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -198,7 +198,7 @@ class LeaveApplication(Document): if not is_lwp(self.leave_type): self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, self.to_date, consider_all_leaves_in_the_allocation_period=True) - if self.status != "Rejected" and self.leave_balance < self.total_leave_days: + if self.status != "Rejected" and (self.leave_balance < self.total_leave_days or not self.leave_balance): if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}") .format(self.leave_type)) 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 135b750bf9..d19e15cf1a 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -6,29 +6,31 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import add_days, today, flt, DATE_FORMAT +from frappe.utils import add_days, today, flt, DATE_FORMAT, getdate class LeaveLedgerEntry(Document): def validate(self): - if self.from_date > self.to_date: + if getdate(self.from_date) > getdate(self.to_date): frappe.throw(_("To date needs to be before from date")) def on_cancel(self): # allow cancellation of expiry leaves - if not self.is_expired: + if self.is_expired: + frappe.db.set_value("Leave Allocation", self.transaction_name, "expired", 0) + else: frappe.throw(_("Only expired allocation can be cancelled")) def validate_leave_allocation_against_leave_application(ledger): ''' Checks that leave allocation has no leave application against it ''' leave_application_records = frappe.db.sql_list(""" SELECT transaction_name - FROM `tabLeave Application` + FROM `tabLeave Ledger Entry` WHERE - employee=%s, - leave_type=%s, - transaction_type='Leave Application', - from_date>=%s, - to_date<=%s + employee=%s + AND leave_type=%s + AND transaction_type='Leave Application' + AND from_date>=%s + AND to_date<=%s """, (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date)) if leave_application_records: @@ -48,6 +50,7 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): is_lwp=0 ) ledger.update(args) + if submit: frappe.get_doc(ledger).submit() else: @@ -91,40 +94,70 @@ def process_expired_allocation(): if leave_type_records: leave_type = [record[0] for record in leave_type_records] - expired_allocation = frappe.get_all("Leave Ledger Entry", + expired_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': add_days(today(), -1), 'transaction_type': 'Leave Allocation', - 'is_carry_forward': 1, + }, + or_filters={ + 'is_carry_forward': 0, 'leave_type': ('in', leave_type) - }, fields=['leaves', 'to_date', 'employee', 'leave_type']) + }) if expired_allocation: create_expiry_ledger_entry(expired_allocation) def create_expiry_ledger_entry(expired_allocation): - ''' Create expiry ledger entry for carry forwarded leaves ''' + ''' Create ledger entry for expired allocation ''' for allocation in expired_allocation: - leaves_taken = get_leaves_taken(allocation) - leaves = flt(allocation.leaves) + flt(leaves_taken) + if allocation.is_carry_forward: + expire_carried_forward_allocation(allocation) + else: + expire_allocation(allocation) - if leaves > 0: - args = frappe._dict( - leaves=allocation.leaves * -1, - to_date=allocation.to_date, - is_carry_forward=1, - is_expired=1, - from_date=allocation.to_date - ) - create_leave_ledger_entry(allocation, args) - -def get_leaves_taken(allocation): +def get_remaining_leaves(allocation): + ''' Returns remaining leaves from the given allocation ''' return frappe.db.get_value("Leave Ledger Entry", filters={ 'employee': allocation.employee, 'leave_type': allocation.leave_type, - 'from_date': ('>=', allocation.from_date), 'to_date': ('<=', allocation.to_date), - 'transaction_type': 'Leave application' - }, fieldname=['SUM(leaves)']) \ No newline at end of file + }, fieldname=['SUM(leaves)']) + +@frappe.whitelist() +def expire_allocation(allocation, expiry_date=None): + ''' expires allocation ''' + leaves = get_remaining_leaves(allocation) + expiry_date = expiry_date if expiry_date else allocation.to_date + + if leaves: + args = dict( + leaves=flt(leaves) * -1, + transaction_name=allocation.name, + from_date=expiry_date, + to_date=expiry_date, + is_carry_forward=0, + is_expired=1 + ) + create_leave_ledger_entry(allocation, args) + + frappe.db.set_value("Leave Allocation", allocation.name, "expired", 1) + +def expire_carried_forward_allocation(allocation): + ''' Expires remaining leaves in the on carried forward allocation ''' + from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period + leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date) + leaves = flt(allocation.leaves) + flt(leaves_taken) + if leaves > 0: + args = frappe._dict( + transaction_name=allocation.name, + transaction_type="Leave Allocation", + leaves=allocation.leaves * -1, + is_carry_forward=allocation.is_carry_forward, + is_expired=1, + from_date=allocation.to_date, + to_date=allocation.to_date + ) + create_leave_ledger_entry(allocation, args) \ No newline at end of file diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index f2a798e1f8..7dfdcc162b 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -48,13 +48,14 @@ def generate_encashment_leave_ledger_entries(): def generate_expiry_allocation_ledger_entries(): ''' fix ledger entries for missing leave allocation transaction ''' + from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation allocation_list = get_allocation_records() for allocation in allocation_list: if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name, 'is_expired': 1}): allocation.update(dict(doctype="Leave Allocation")) allocation_obj = frappe.get_doc(allocation) - allocation_obj.expire_previous_allocation() + expire_allocation(allocation_obj) def get_allocation_records(): return frappe.db.sql("""