fix: remove all leaves via scheduler
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
index 7c3e1e4..8c910a2 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
@@ -35,8 +35,11 @@
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 67f30b5..2374e46 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 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 @@
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 @@
)
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 @@
@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 dfa64db..4f4e0ab 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 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 @@
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 8f02ec0..36a4145 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -198,7 +198,7 @@
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 135b750..d19e15c 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 @@
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 @@
is_lwp=0
)
ledger.update(args)
+
if submit:
frappe.get_doc(ledger).submit()
else:
@@ -91,40 +94,70 @@
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 f2a798e..7dfdcc1 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_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("""