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("""