Merge branch 'develop' into leave-management
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 53da013..28dd4e8 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -264,6 +264,7 @@
 		"erpnext.projects.doctype.project.project.update_project_sales_billing",
 		"erpnext.projects.doctype.project.project.send_project_status_email_to_users",
 		"erpnext.quality_management.doctype.quality_review.quality_review.review",
+		"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation"
 		"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status"
 	],
 	"daily_long": [
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
index 4b4bfaf..1fc1d89 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
@@ -66,4 +66,4 @@
 			frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated));
 		}
 	}
-})
+});
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 6d61fe3..568182d 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -312,7 +312,7 @@
   {
    "allow_bulk_edit": 0,
    "allow_in_quick_entry": 0,
-   "allow_on_submit": 1,
+   "allow_on_submit": 0,
    "bold": 1,
    "collapsible": 0,
    "columns": 0,
@@ -677,7 +677,7 @@
  "issingle": 0,
  "istable": 0,
  "max_attachments": 0,
- "modified": "2019-01-30 11:28:09.360525",
+ "modified": "2019-05-22 11:28:09.360525",
  "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 dc270db..6fe8fdf 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -3,11 +3,12 @@
 
 from __future__ import unicode_literals
 import frappe
-from frappe.utils import flt, date_diff, formatdate
+from frappe.utils import flt, date_diff, formatdate, add_days
 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_application.leave_application import get_approved_leaves_for_period
+from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
 
 class OverlapError(frappe.ValidationError): pass
 class BackDatedAllocationError(frappe.ValidationError): pass
@@ -40,14 +41,12 @@
 				frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")\
 				.format(self.leave_type, self.employee))
 
-	def on_update_after_submit(self):
-		self.validate_new_leaves_allocated_value()
-		self.set_total_leaves_allocated()
+	def on_submit(self):
+		self.expire_previous_allocation()
+		self.create_leave_ledger_entry()
 
-		frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves))
-		frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated))
-
-		self.validate_against_leave_applications()
+	def on_cancel(self):
+		self.create_leave_ledger_entry(submit=False)
 
 	def validate_period(self):
 		if date_diff(self.to_date, self.from_date) <= 0:
@@ -91,25 +90,58 @@
 			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()
 
 		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)))
 
+	def maintain_carry_forwarded_leaves(self):
+		''' reduce the carry forwarded leaves to be within the maximum allowed leaves '''
+		if not self.carry_forward:
+			return
+		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.total_leaves_allocated = flt(max_leaves_allowed)
+
 	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
 		if date_difference < self.total_leaves_allocated:
 			frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError)
 
-	def validate_against_leave_applications(self):
-		leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
-			self.from_date, self.to_date)
+	def create_leave_ledger_entry(self, submit=True):
+		if self.carry_forwarded_leaves:
+			expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "carry_forward_leave_expiry")
+			args = dict(
+				leaves=self.carry_forwarded_leaves,
+				from_date=self.from_date,
+				to_date=add_days(self.from_date, expiry_days) if expiry_days else self.to_date,
+				is_carry_forward=1
+			)
+			create_leave_ledger_entry(self, args, submit)
 
-		if flt(leaves_taken) > flt(self.total_leaves_allocated):
-			if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
-				frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken))
-			else:
-				frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError)
+		args = dict(
+			leaves=self.new_leaves_allocated,
+			from_date=self.from_date,
+			to_date=self.to_date,
+			is_carry_forward=0
+		)
+		create_leave_ledger_entry(self, args, submit)
+
+	def expire_previous_allocation(self):
+		''' expire previous allocation leaves '''
+		leaves = get_remaining_leaves(self.employee, self.leave_type, self.from_date)
+
+		if flt(leaves) > 0:
+			args = dict(
+				leaves=leaves * -1,
+				from_date=self.to_date,
+				to_date=self.to_date,
+				is_carry_forward=0,
+				is_expired=1
+			)
+			create_leave_ledger_entry(self, args)
 
 def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
 	leave_allocated = 0
@@ -137,24 +169,20 @@
 @frappe.whitelist()
 def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None):
 	carry_forwarded_leaves = 0
-
 	if carry_forward:
 		validate_carry_forward(leave_type)
-
-		previous_allocation = frappe.db.sql("""
-			select name, from_date, to_date, total_leaves_allocated
-			from `tabLeave Allocation`
-			where employee=%s and leave_type=%s and docstatus=1 and to_date < %s
-			order by to_date desc limit 1
-		""", (employee, leave_type, date), as_dict=1)
-		if previous_allocation:
-			leaves_taken = get_approved_leaves_for_period(employee, leave_type,
-				previous_allocation[0].from_date, previous_allocation[0].to_date)
-
-			carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken)
+		carry_forwarded_leaves = get_remaining_leaves(employee, leave_type, date)
 
 	return carry_forwarded_leaves
 
+def get_remaining_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,
+			}, fieldname=['SUM(leaves)'])
+
 def validate_carry_forward(leave_type):
 	if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
-		frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
+		frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 3b22eb2..308f224 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -1,12 +1,14 @@
 from __future__ import unicode_literals
 import frappe
 import unittest
-from frappe.utils import getdate
+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
 
 class TestLeaveAllocation(unittest.TestCase):
 	def test_overlapping_allocation(self):
 		frappe.db.sql("delete from `tabLeave Allocation`")
-				
+
 		employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
 		leaves = [
 			{
@@ -18,7 +20,7 @@
 				"from_date": getdate("2015-10-01"),
 				"to_date": getdate("2015-10-31"),
 				"new_leaves_allocated": 5,
-				"docstatus": 1			
+				"docstatus": 1
 			},
 			{
 				"doctype": "Leave Allocation",
@@ -28,17 +30,17 @@
 				"leave_type": "_Test Leave Type",
 				"from_date": getdate("2015-09-01"),
 				"to_date": getdate("2015-11-30"),
-				"new_leaves_allocated": 5			
+				"new_leaves_allocated": 5
 			}
 		]
 
 		frappe.get_doc(leaves[0]).save()
 		self.assertRaises(frappe.ValidationError, frappe.get_doc(leaves[1]).save)
-		
-	def test_invalid_period(self):		
+
+	def test_invalid_period(self):
 		employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
-		
-		d = frappe.get_doc({
+
+		doc = frappe.get_doc({
 			"doctype": "Leave Allocation",
 			"__islocal": 1,
 			"employee": employee.name,
@@ -46,15 +48,15 @@
 			"leave_type": "_Test Leave Type",
 			"from_date": getdate("2015-09-30"),
 			"to_date": getdate("2015-09-1"),
-			"new_leaves_allocated": 5			
+			"new_leaves_allocated": 5
 		})
-		
+
 		#invalid period
-		self.assertRaises(frappe.ValidationError, d.save)
-	
+		self.assertRaises(frappe.ValidationError, doc.save)
+
 	def test_allocated_leave_days_over_period(self):
 		employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
-		d = frappe.get_doc({
+		doc = frappe.get_doc({
 			"doctype": "Leave Allocation",
 			"__islocal": 1,
 			"employee": employee.name,
@@ -62,10 +64,103 @@
 			"leave_type": "_Test Leave Type",
 			"from_date": getdate("2015-09-1"),
 			"to_date": getdate("2015-09-30"),
-			"new_leaves_allocated": 35			
+			"new_leaves_allocated": 35
 		})
-		
-		#allocated leave more than period 
-		self.assertRaises(frappe.ValidationError, d.save)
-		
+		#allocated leave more than period
+		self.assertRaises(frappe.ValidationError, doc.save)
+
+	def test_carry_forward_calculation(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		frappe.db.sql("delete from `tabLeave Ledger Entry`")
+		leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1)
+		leave_type.submit()
+
+		# initial leave allocation
+		leave_allocation = create_leave_allocation(
+			leave_type="_Test_CF_leave",
+			from_date=add_months(nowdate(), -12),
+			to_date=add_months(nowdate(), -1),
+			carry_forward=1)
+		leave_allocation.submit()
+
+		# leave allocation with carry forward from previous allocation
+		leave_allocation_1 = create_leave_allocation(
+			leave_type="_Test_CF_leave",
+			carry_forward=1)
+		leave_allocation_1.submit()
+
+		self.assertEquals(leave_allocation.total_leaves_allocated, leave_allocation_1.carry_forwarded_leaves)
+
+	def test_carry_forward_leaves_expiry(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		frappe.db.sql("delete from `tabLeave Ledger Entry`")
+		leave_type = create_leave_type(
+			leave_type_name="_Test_CF_leave_expiry",
+			is_carry_forward=1,
+			carry_forward_leave_expiry=90)
+		leave_type.submit()
+
+		# initial leave allocation
+		leave_allocation = create_leave_allocation(
+			leave_type="_Test_CF_leave_expiry",
+			from_date=add_months(nowdate(), -24),
+			to_date=add_months(nowdate(), -12),
+			carry_forward=1)
+		leave_allocation.submit()
+
+		leave_allocation = create_leave_allocation(
+			leave_type="_Test_CF_leave_expiry",
+			from_date=add_days(nowdate(), -90),
+			to_date=add_days(nowdate(), 100),
+			carry_forward=1)
+		leave_allocation.submit()
+
+		# expires all the carry forwarded leaves after 90 days
+		process_expired_allocation()
+
+		# leave allocation with carry forward of only new leaves allocated
+		leave_allocation_1 = create_leave_allocation(
+			leave_type="_Test_CF_leave_expiry",
+			carry_forward=1,
+			from_date=add_months(nowdate(), 6),
+			to_date=add_months(nowdate(), 12))
+		leave_allocation_1.submit()
+
+		self.assertEquals(leave_allocation_1.carry_forwarded_leaves, leave_allocation.new_leaves_allocated)
+
+	def test_creation_of_leave_ledger_entry_on_submit(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+
+		leave_allocation = create_leave_allocation()
+		leave_allocation.submit()
+
+		leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name))
+
+		self.assertEquals(len(leave_ledger_entry), 1)
+		self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee)
+		self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type)
+		self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated)
+
+		# check if leave ledger entry is deleted on cancellation
+		leave_allocation.cancel()
+		self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name}))
+
+def create_leave_allocation(**args):
+	args = frappe._dict(args)
+
+	employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
+	leave_allocation = frappe.get_doc({
+		"doctype": "Leave Allocation",
+		"__islocal": 1,
+		"employee": args.employee or employee.name,
+		"employee_name": args.employee_name or employee.employee_name,
+		"leave_type": args.leave_type or "_Test Leave Type",
+		"from_date": args.from_date or nowdate(),
+		"new_leaves_allocated": args.new_leaves_created or 15,
+		"carry_forward": args.carry_forward or 0,
+		"carry_forwarded_leaves": args.carry_forwarded_leaves or 0,
+		"to_date": args.to_date or add_months(nowdate(), 12)
+	})
+	return leave_allocation
+
 test_dependencies = ["Employee", "Leave Type"]
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index 5bce348..0b0a69f 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -143,7 +143,8 @@
 				method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on",
 				args: {
 					employee: frm.doc.employee,
-					date: frm.doc.from_date,
+					from_date: frm.doc.from_date,
+					to_date: frm.doc.to_date,
 					leave_type: frm.doc.leave_type,
 					consider_all_leaves_in_the_allocation_period: true
 				},
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index f542fa1..8e8e47e 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -10,6 +10,7 @@
 from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
+from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
 
 class LeaveDayBlockedError(frappe.ValidationError): pass
 class OverlapError(frappe.ValidationError): pass
@@ -50,6 +51,7 @@
 
 		# notify leave applier about approval
 		self.notify_employee()
+		self.create_leave_ledger_entry()
 		self.reload()
 
 	def on_cancel(self):
@@ -57,6 +59,7 @@
 		# notify leave applier about cancellation
 		self.notify_employee()
 		self.cancel_attendance()
+		self.create_leave_ledger_entry(submit=False)
 
 	def validate_applicable_after(self):
 		if self.leave_type:
@@ -192,7 +195,7 @@
 				frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave."))
 
 			if not is_lwp(self.leave_type):
-				self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, docname=self.name,
+				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 frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
@@ -346,6 +349,48 @@
 			except frappe.OutgoingEmailError:
 				pass
 
+	def create_leave_ledger_entry(self, submit=True):
+		expiry_date = get_allocation_expiry(self.employee, self.leave_type,
+			self.to_date, self.from_date)
+
+		if expiry_date:
+			self.create_ledger_entry_for_intermediate_expiry(expiry_date, submit)
+		else:
+			args = dict(
+				leaves=self.total_leave_days * -1,
+				from_date=self.from_date,
+				to_date=self.to_date,
+			)
+			create_leave_ledger_entry(self, args, submit)
+
+	def create_ledger_entry_for_intermediate_expiry(self, expiry_date, submit):
+		''' splits leave application into two ledger entries to consider expiry '''
+		args = dict(
+			from_date=self.from_date,
+			to_date=self.to_date,
+			leaves=date_diff(expiry_date, self.from_date) * -1
+		)
+		create_leave_ledger_entry(self, args, submit)
+		start_date = add_days(expiry_date, 1)
+		args.update(dict(
+			from_date=start_date,
+			to_date=self.to_date,
+			leaves=date_diff(self.to_date, start_date) * -1
+		))
+		create_leave_ledger_entry(self, args, submit)
+
+def get_allocation_expiry(employee, leave_type, to_date, from_date):
+	return frappe.db.sql("""
+		SELECT
+			to_date
+		FROM `tabLeave Ledger Entry`
+		WHERE
+			employee='%s'
+			AND leave_type='%s'
+			AND transaction_type='Leave Allocation'
+			AND (to_date BETWEEN %s AND %s)
+		""" %(employee, leave_type, from_date, to_date), as_dict=1)
+
 @frappe.whitelist()
 def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None):
 	number_of_days = 0
@@ -367,10 +412,11 @@
 	leave_allocation = {}
 	for d in allocation_records:
 		allocation = allocation_records.get(d, frappe._dict())
+		remaining_leaves = get_leave_balance_on(employee, d, date)
 		date = allocation.to_date
 		leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date, status="Approved")
 		leaves_pending = get_leaves_for_period(employee, d, allocation.from_date, date, status="Open")
-		remaining_leaves = allocation.total_leaves_allocated - leaves_taken - leaves_pending
+
 		leave_allocation[d] = {
 			"total_leaves": allocation.total_leaves_allocated,
 			"leaves_taken": leaves_taken,
@@ -385,20 +431,44 @@
 	return ret
 
 @frappe.whitelist()
-def get_leave_balance_on(employee, leave_type, date, allocation_records=None, docname=None,
-		consider_all_leaves_in_the_allocation_period=False, consider_encashed_leaves=True):
+def get_leave_balance_on(employee, leave_type, from_date, to_date=nowdate(), allocation_records=None, docname=None,
+		consider_all_leaves_in_the_allocation_period=False):
 
 	if allocation_records == None:
-		allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict())
+		allocation_records = get_leave_allocation_records(from_date, employee).get(employee, frappe._dict())
 	allocation = allocation_records.get(leave_type, frappe._dict())
-	if consider_all_leaves_in_the_allocation_period:
-		date = allocation.to_date
-	leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, date, status="Approved", docname=docname)
-	leaves_encashed = 0
-	if frappe.db.get_value("Leave Type", leave_type, 'allow_encashment') and consider_encashed_leaves:
-		leaves_encashed = flt(allocation.total_leaves_encashed)
 
-	return flt(allocation.total_leaves_allocated) - (flt(leaves_taken) + flt(leaves_encashed))
+	end_date = allocation.to_date if consider_all_leaves_in_the_allocation_period else from_date
+	expiry = get_allocation_expiry(employee, leave_type, to_date, from_date)
+
+	leaves_taken = get_leaves_taken(employee, leave_type, allocation.from_date, end_date)
+	return get_remaining_leaves(allocation, leaves_taken, from_date, expiry)
+
+def get_remaining_leaves(allocation, leaves_taken, date, expiry):
+	''' Returns leaves remaining after comparing with remaining days for allocation expiry '''
+	def _get_remaining_leaves(allocated_leaves, end_date):
+		remaining_leaves = flt(allocated_leaves) + flt(leaves_taken)
+
+		if remaining_leaves > 0:
+			remaining_days = date_diff(end_date, date) + 1
+			remaining_leaves = min(remaining_days, remaining_leaves)
+		return remaining_leaves
+
+	if expiry:
+		remaining_leaves = _get_remaining_leaves(allocation.carry_forwarded_leaves, expiry)
+		return flt(allocation.new_leaves_allocated) + flt(remaining_leaves)
+	else:
+		return _get_remaining_leaves(allocation.total_leaves_allocated, allocation.to_date)
+
+def get_leaves_taken(employee, leave_type, from_date, to_date):
+	''' Returns leaves taken based on leave application/encashment '''
+	return frappe.db.get_value("Leave Ledger Entry", filters={
+		'Employee':employee,
+		'leave_type':leave_type,
+		'leaves': ("<", 0),
+		'to_date':("<=", to_date),
+		'from_date': (">=", from_date)},
+		fieldname=['SUM(leaves)'])
 
 def get_total_allocated_leaves(employee, leave_type, date):
 	filters= {
@@ -450,9 +520,20 @@
 	conditions = (" and employee='%s'" % employee) if employee else ""
 
 	leave_allocation_records = frappe.db.sql("""
-		select employee, leave_type, total_leaves_allocated, total_leaves_encashed, from_date, to_date
-		from `tabLeave Allocation`
-		where %s between from_date and to_date and docstatus=1 {0}""".format(conditions), (date), as_dict=1)
+		SELECT
+			employee,
+			leave_type,
+			total_leaves_allocated,
+			carry_forwarded_leaves,
+			new_leaves_allocated,
+			carry_forward,
+			from_date,
+			to_date
+		FROM
+			`tabLeave Allocation`
+		WHERE
+			%s between from_date and to_date
+			AND docstatus=1 {0}""".format(conditions), (date), as_dict=1) #nosec
 
 	allocated_leaves = frappe._dict()
 	for d in leave_allocation_records:
@@ -460,7 +541,10 @@
 			"from_date": d.from_date,
 			"to_date": d.to_date,
 			"total_leaves_allocated": d.total_leaves_allocated,
-			"total_leaves_encashed":d.total_leaves_encashed
+			"carry_forward": d.carry_forward,
+			"carry_forwarded_leaves": d.carry_forwarded_leaves,
+			"new_leaves": d.new_leaves_allocated,
+			"leave_type": d.leave_type
 		}))
 	return allocated_leaves
 
@@ -641,4 +725,4 @@
 
 	if department:
 		return frappe.db.get_value('Department Approver', {'parent': department,
-			'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
+			'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index d3dcca1..4dea413 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -8,6 +8,8 @@
 from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError, NotAnOptionalHoliday, get_leave_balance_on
 from frappe.permissions import clear_user_permissions_for_doctype
 from frappe.utils import add_days, nowdate, now_datetime, getdate
+from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
+from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
 
 test_dependencies = ["Leave Allocation", "Leave Block List"]
 
@@ -274,7 +276,7 @@
 		))
 
 		# can only apply on optional holidays
-		self.assertTrue(NotAnOptionalHoliday, leave_application.insert)
+		self.assertRaises(NotAnOptionalHoliday, leave_application.insert)
 
 		leave_application.from_date = today
 		leave_application.to_date = today
@@ -287,6 +289,8 @@
 
 
 	def test_leaves_allowed(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		frappe.db.sql("delete from `tabLeave Ledger Entry`")
 		employee = get_employee()
 		leave_period = get_leave_period()
 		frappe.delete_doc_if_exists("Leave Type", "Test Leave Type", force=1)
@@ -301,7 +305,7 @@
 		allocate_leaves(employee, leave_period, leave_type.name, 5)
 
 		leave_application = frappe.get_doc(dict(
-		doctype = 'Leave Application',
+			doctype = 'Leave Application',
 			employee = employee.name,
 			leave_type = leave_type.name,
 			from_date = date,
@@ -310,15 +314,14 @@
 			docstatus = 1,
             status = "Approved"
 		))
-
-		self.assertTrue(leave_application.insert())
+		leave_application.submit()
 
 		leave_application = frappe.get_doc(dict(
 			doctype = 'Leave Application',
 			employee = employee.name,
 			leave_type = leave_type.name,
 			from_date = add_days(date, 4),
-			to_date = add_days(date, 7),
+			to_date = add_days(date, 8),
 			company = "_Test Company",
 			docstatus = 1,
             status = "Approved"
@@ -457,6 +460,38 @@
 		leave_application.submit()
 		self.assertEqual(leave_application.docstatus, 1)
 
+	def test_creation_of_leave_ledger_entry_on_submit(self):
+		frappe.db.sql("delete from `tabLeave Allocation`")
+		employee = get_employee()
+
+		leave_type = create_leave_type(leave_type_name = 'Test Leave Type 1')
+		leave_type.save()
+
+		leave_allocation = create_leave_allocation(employee=employee.name, employee_name=employee.employee_name,
+			leave_type=leave_type.name)
+		leave_allocation.submit()
+
+		leave_application = frappe.get_doc(dict(
+			doctype = 'Leave Application',
+			employee = employee.name,
+			leave_type = leave_type.name,
+			from_date = add_days(nowdate(), 1),
+			to_date = add_days(nowdate(), 4),
+			company = "_Test Company",
+			docstatus = 1,
+            status = "Approved"
+		))
+		leave_application.submit()
+		leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
+
+		self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee)
+		self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type)
+		self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1)
+
+		# check if leave ledger entry is deleted on cancellation
+		leave_application.cancel()
+		self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_application.name}))
+
 def make_allocation_record(employee=None, leave_type=None):
 	frappe.db.sql("delete from `tabLeave Allocation`")
 
@@ -513,4 +548,4 @@
 		"docstatus": 1
 	}).insert()
 
-	allocate_leave.submit()
+	allocate_leave.submit()
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index 9944bc5..c59ce63 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -10,6 +10,7 @@
 from erpnext.hr.utils import set_employee_name
 from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
 from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
+from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
 
 class LeaveEncashment(Document):
 	def validate(self):
@@ -40,6 +41,8 @@
 		frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
 				frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') + self.encashable_days)
 
+		self.create_leave_ledger_entry()
+
 	def on_cancel(self):
 		if self.additional_salary:
 			frappe.get_doc("Additional Salary", self.additional_salary).cancel()
@@ -48,6 +51,7 @@
 		if self.leave_allocation:
 			frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
 				frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days)
+		self.create_leave_ledger_entry(submit=False)
 
 	def get_leave_details_for_encashment(self):
 		salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate()))
@@ -75,3 +79,12 @@
 		and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee))
 
 		return leave_allocation[0][0] if leave_allocation else None
+
+	def create_leave_ledger_entry(self, submit=True):
+		args = frappe._dict(
+			leaves=self.encashable_days * -1,
+			from_date=self.encashment_date,
+			to_date=self.encashable_days,
+			is_carry_forward=0
+		)
+		create_leave_ledger_entry(self, args, submit)
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
index ef5c2aa..cfed780 100644
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
@@ -9,41 +9,39 @@
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure
 from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
+from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy\
 
 test_dependencies = ["Leave Type"]
 
 class TestLeaveEncashment(unittest.TestCase):
 	def setUp(self):
 		frappe.db.sql('''delete from `tabLeave Period`''')
-	def test_leave_balance_value_and_amount(self):
-		employee = "test_employee_encashment@salary.com"
-		leave_type = "_Test Leave Type Encashment"
+		frappe.db.sql('''delete from `tabLeave Allocation`''')
 
 		# create the leave policy
-		leave_policy = frappe.get_doc({
-			"doctype": "Leave Policy",
-			"leave_policy_details": [{
-				"leave_type": leave_type,
-				"annual_allocation": 10
-			}]
-		}).insert()
+		leave_policy = create_leave_policy(
+			leave_type="_Test Leave Type Encashment",
+			annual_allocation=10)
 		leave_policy.submit()
 
 		# create employee, salary structure and assignment
-		employee = make_employee(employee)
-		frappe.db.set_value("Employee", employee, "leave_policy", leave_policy.name) 
-		salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", employee,
+		self.employee = make_employee("test_employee_encashment@example.com")
+		frappe.db.set_value("Employee", "test_employee_encashment@example.com", "leave_policy", leave_policy.name)
+		salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
 			other_details={"leave_encashment_amount_per_day": 50})
 
 		# create the leave period and assign the leaves
-		leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
-		leave_period.grant_leave_allocation(employee=employee)
+		self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
+		self.leave_period.grant_leave_allocation(employee=self.employee)
 
+
+	def test_leave_balance_value_and_amount(self):
+		frappe.db.sql('''delete from `tabLeave Encashment`''')
 		leave_encashment = frappe.get_doc(dict(
 			doctype = 'Leave Encashment',
-			employee = employee,
-			leave_type = leave_type,
-			leave_period = leave_period.name,
+			employee = self.employee,
+			leave_type = "_Test Leave Type Encashment",
+			leave_period = self.leave_period.name,
 			payroll_date = today()
 		)).insert()
 
@@ -53,3 +51,26 @@
 
 		leave_encashment.submit()
 		self.assertTrue(frappe.db.get_value("Leave Encashment", leave_encashment.name, "additional_salary"))
+
+	def test_creation_of_leave_ledger_entry_on_submit(self):
+		frappe.db.sql('''delete from `tabLeave Encashment`''')
+		leave_encashment = frappe.get_doc(dict(
+			doctype = 'Leave Encashment',
+			employee = self.employee,
+			leave_type = "_Test Leave Type Encashment",
+			leave_period = self.leave_period.name,
+			payroll_date = today()
+		)).insert()
+
+		leave_encashment.submit()
+
+		leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name))
+
+		self.assertEquals(len(leave_ledger_entry), 1)
+		self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee)
+		self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type)
+		self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days *  -1)
+
+		# check if leave ledger entry is deleted on cancellation
+		leave_encashment.cancel()
+		self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_encashment.name}))
diff --git a/erpnext/hr/doctype/leave_ledger_entry/__init__.py b/erpnext/hr/doctype/leave_ledger_entry/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/leave_ledger_entry/__init__.py
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js
new file mode 100644
index 0000000..c68d518
--- /dev/null
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Leave Ledger Entry', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
new file mode 100644
index 0000000..ba33d75
--- /dev/null
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
@@ -0,0 +1,153 @@
+{
+ "creation": "2019-05-09 15:47:39.760406",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+  "employee",
+  "employee_name",
+  "leave_type",
+  "transaction_type",
+  "transaction_name",
+  "leaves",
+  "from_date",
+  "to_date",
+  "is_carry_forward",
+  "is_expired",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "employee",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Employee",
+   "options": "Employee"
+  },
+  {
+   "fieldname": "employee_name",
+   "fieldtype": "Data",
+   "label": "Employee Name"
+  },
+  {
+   "fieldname": "leave_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Leave Type",
+   "options": "Leave Type"
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Leave Ledger Entry",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "transaction_type",
+   "fieldtype": "Link",
+   "label": "Transaction Type",
+   "options": "DocType"
+  },
+  {
+   "fieldname": "transaction_name",
+   "fieldtype": "Dynamic Link",
+   "label": "Transaction Name",
+   "options": "transaction_type"
+  },
+  {
+   "fieldname": "leaves",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Leaves"
+  },
+  {
+   "fieldname": "from_date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "From Date"
+  },
+  {
+   "fieldname": "to_date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "To Date"
+  },
+  {
+   "fieldname": "is_carry_forward",
+   "fieldtype": "Check",
+   "label": "Is Carry Forward"
+  },
+  {
+   "fieldname": "is_expired",
+   "fieldtype": "Check",
+   "label": "Is Expired"
+  }
+ ],
+ "in_create": 1,
+ "is_submittable": 1,
+ "modified": "2019-05-27 03:25:47.805142",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Leave Ledger Entry",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "if_owner": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "All",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
new file mode 100644
index 0000000..e85d5ce
--- /dev/null
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+from frappe import _
+from frappe.utils import add_days, today, flt
+
+class LeaveLedgerEntry(Document):
+	def validate_entries(self):
+		total_leaves = frappe.get_all('Leave Ledger Entry', ['SUM(leaves)'])
+		if total_leaves < 0:
+			frappe.throw(_("Invalid Ledger Entry"))
+
+	def on_cancel(self):
+		# allow cancellation of expiry leaves
+		if not self.is_expired:
+			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.get_all("Leave Ledger Entry",
+		filters={
+			'employee': ledger.employee,
+			'leave_type': ledger.leave_type,
+			'transaction_type': 'Leave Application',
+			'from_date': (">=", ledger.from_date),
+			'to_date': ('<=', ledger.to_date)
+		}, fields=['transaction_name'])
+
+	if leave_application_records:
+		frappe.throw(_("Leave allocation %s is linked with leave application %s"
+			% (ledger.transaction_name, ', '.join(leave_application_records))))
+
+def create_leave_ledger_entry(ref_doc, args, submit=True):
+	ledger = frappe._dict(
+		doctype='Leave Ledger Entry',
+		employee=ref_doc.employee,
+		employee_name=ref_doc.employee_name,
+		leave_type=ref_doc.leave_type,
+		transaction_type=ref_doc.doctype,
+		transaction_name=ref_doc.name,
+		is_carry_forward=0,
+		is_expired=0
+	)
+	ledger.update(args)
+	if submit:
+		frappe.get_doc(ledger).submit()
+	else:
+		delete_ledger_entry(ledger)
+
+def delete_ledger_entry(ledger):
+	''' Delete ledger entry on cancel of leave application/allocation/encashment '''
+
+	if ledger.transaction_type == "Leave Allocation":
+		validate_leave_allocation_against_leave_application(ledger)
+
+	frappe.db.sql("""DELETE
+		FROM `tabLeave Ledger Entry`
+		WHERE
+			`transaction_name`=%s""", (ledger.transaction_name))
+
+def process_expired_allocation():
+	''' Check if a carry forwarded allocation has expired and create a expiry ledger entry '''
+
+	# fetch leave type records that has carry forwarded leaves expiry
+	leave_type_records = frappe.db.get_values("Leave Type", filters={
+			'carry_forward_leave_expiry': (">", 0)
+		}, fieldname=['name'])
+
+	if leave_type_records:
+		leave_type = [record[0] for record in leave_type_records]
+		expired_allocation = frappe.get_all("Leave Ledger Entry",
+			filters={
+				'to_date': today(),
+				'transaction_type': 'Leave Allocation',
+				'is_carry_forward': 1,
+				'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 '''
+	for allocation in expired_allocation:
+
+		leaves_taken = get_leaves_taken(allocation)
+		leaves = flt(allocation.leaves) + flt(leaves_taken)
+
+		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):
+	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
diff --git a/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py
new file mode 100644
index 0000000..6f7725c
--- /dev/null
+++ b/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestLeaveLedgerEntry(unittest.TestCase):
+	pass
diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py
index 15fa8d6..91cc9b8 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.py
+++ b/erpnext/hr/doctype/leave_period/leave_period.py
@@ -5,9 +5,10 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import getdate, cstr
+from frappe.utils import getdate, cstr, add_days
 from frappe.model.document import Document
 from erpnext.hr.utils import validate_overlap, get_employee_leave_policy
+from erpnext.hr.doctype.leave_allocation.leave_allocation import get_carry_forwarded_leaves
 from frappe.utils.background_jobs import enqueue
 from six import iteritems
 
@@ -39,8 +40,8 @@
 			employee=None, carry_forward_leaves=0):
 		employees = self.get_employees({
 			"grade": grade,
-			"department": department, 
-			"designation": designation, 
+			"department": department,
+			"designation": designation,
 			"name": employee
 		})
 
@@ -57,7 +58,7 @@
 	leave_allocations = []
 	existing_allocations_for = get_existing_allocations(employees, leave_period.name)
 	leave_type_details = get_leave_type_details()
-	count=0
+	count = 0
 	for employee in employees:
 		if employee in existing_allocations_for:
 			continue
@@ -77,8 +78,14 @@
 
 def get_existing_allocations(employees, leave_period):
 	leave_allocations = frappe.db.sql_list("""
-		select distinct employee from `tabLeave Allocation` 
-		where leave_period=%s and employee in (%s) and docstatus=1
+		SELECT DISTINCT
+			employee
+		FROM `tabLeave Allocation`
+		WHERE
+			leave_period=%s
+			AND employee in (%s)
+			AND carry_forward=0
+			AND docstatus=1
 	""" % ('%s', ', '.join(['%s']*len(employees))), [leave_period] + employees)
 	if leave_allocations:
 		frappe.msgprint(_("Skipping Leave Allocation for the following employees, as Leave Allocation records already exists against them. {0}")
@@ -87,7 +94,8 @@
 
 def get_leave_type_details():
 	leave_type_details = frappe._dict()
-	leave_types = frappe.get_all("Leave Type", fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward"])
+	leave_types = frappe.get_all("Leave Type",
+		fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "carry_forward_leave_expiry"])
 	for d in leave_types:
 		leave_type_details.setdefault(d.name, d)
 	return leave_type_details
@@ -109,6 +117,4 @@
 			allocation.carry_forward = carry_forward_leaves
 	allocation.save(ignore_permissions = True)
 	allocation.submit()
-	return allocation.name
-
-
+	return allocation.name
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
index 2c6f1d0..fc868ea 100644
--- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py
+++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
@@ -12,16 +12,20 @@
 		if random_leave_type:
 			random_leave_type = random_leave_type[0]
 			leave_type = frappe.get_doc("Leave Type", random_leave_type.name)
-			old_max_leaves_allowed = leave_type.max_leaves_allowed
 			leave_type.max_leaves_allowed = 2
 			leave_type.save()
 
-			leave_policy_details = {
-				"doctype": "Leave Policy",
-				"leave_policy_details": [{
-					"leave_type": leave_type.name,
-					"annual_allocation": leave_type.max_leaves_allowed + 1
-				}]
-			}
+		leave_policy = create_leave_policy(leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1)
 
-			self.assertRaises(frappe.ValidationError, frappe.get_doc(leave_policy_details).insert)
+		self.assertRaises(frappe.ValidationError, leave_policy.insert)
+
+def create_leave_policy(**args):
+	''' Returns an object of leave policy '''
+	args = frappe._dict(args)
+	return frappe.get_doc({
+		"doctype": "Leave Policy",
+		"leave_policy_details": [{
+			"leave_type": args.leave_type or "_Test Leave Type",
+			"annual_allocation": args.annual_allocation or 10
+		}]
+	})
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json
index 6a7a80a..0b8e38e 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.json
+++ b/erpnext/hr/doctype/leave_type/leave_type.json
@@ -1,5 +1,6 @@
 {
  "allow_copy": 0,
+ "allow_events_in_timeline": 0,
  "allow_guest_to_view": 0,
  "allow_import": 1,
  "allow_rename": 1,
@@ -14,10 +15,12 @@
  "fields": [
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "leave_type_name",
    "fieldtype": "Data",
    "hidden": 0,
@@ -42,15 +45,17 @@
    "search_index": 0,
    "set_only_once": 0,
    "translatable": 0,
-   "unique": 0
+   "unique": 1
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
    "depends_on": "",
+   "fetch_if_empty": 0,
    "fieldname": "max_leaves_allowed",
    "fieldtype": "Int",
    "hidden": 0,
@@ -78,10 +83,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "applicable_after",
    "fieldtype": "Int",
    "hidden": 0,
@@ -109,10 +116,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "max_continuous_days_allowed",
    "fieldtype": "Int",
    "hidden": 0,
@@ -141,10 +150,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "column_break_3",
    "fieldtype": "Column Break",
    "hidden": 0,
@@ -171,10 +182,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "is_carry_forward",
    "fieldtype": "Check",
    "hidden": 0,
@@ -203,10 +216,13 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "depends_on": "",
+   "fetch_if_empty": 0,
    "fieldname": "is_lwp",
    "fieldtype": "Check",
    "hidden": 0,
@@ -233,10 +249,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "is_optional_leave",
    "fieldtype": "Check",
    "hidden": 0,
@@ -264,10 +282,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "allow_negative",
    "fieldtype": "Check",
    "hidden": 0,
@@ -294,10 +314,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "include_holiday",
    "fieldtype": "Check",
    "hidden": 0,
@@ -324,10 +346,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "is_compensatory",
    "fieldtype": "Check",
    "hidden": 0,
@@ -355,10 +379,82 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 1,
    "columns": 0,
+   "depends_on": "eval: doc.is_carry_forward == 1",
+   "fetch_if_empty": 0,
+   "fieldname": "carry_forward_section",
+   "fieldtype": "Section Break",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Carry Forward",
+   "length": 0,
+   "no_copy": 0,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 0,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 0,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "default": "1",
+   "depends_on": "",
+   "description": "calculated in days",
+   "fetch_if_empty": 0,
+   "fieldname": "carry_forward_leave_expiry",
+   "fieldtype": "Int",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Carry Forward Leave Expiry",
+   "length": 0,
+   "no_copy": 0,
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 0,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 0,
+   "search_index": 0,
+   "set_only_once": 0,
+   "translatable": 0,
+   "unique": 0
+  },
+  {
+   "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 1,
+   "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "encashment",
    "fieldtype": "Section Break",
    "hidden": 0,
@@ -386,10 +482,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "allow_encashment",
    "fieldtype": "Check",
    "hidden": 0,
@@ -417,11 +515,13 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
    "depends_on": "allow_encashment",
+   "fetch_if_empty": 0,
    "fieldname": "encashment_threshold_days",
    "fieldtype": "Int",
    "hidden": 0,
@@ -449,11 +549,13 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
    "depends_on": "allow_encashment",
+   "fetch_if_empty": 0,
    "fieldname": "earning_component",
    "fieldtype": "Link",
    "hidden": 0,
@@ -482,10 +584,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 1,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "earned_leave",
    "fieldtype": "Section Break",
    "hidden": 0,
@@ -513,10 +617,12 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
+   "fetch_if_empty": 0,
    "fieldname": "is_earned_leave",
    "fieldtype": "Check",
    "hidden": 0,
@@ -544,11 +650,13 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
    "depends_on": "is_earned_leave",
+   "fetch_if_empty": 0,
    "fieldname": "earned_leave_frequency",
    "fieldtype": "Select",
    "hidden": 0,
@@ -577,12 +685,14 @@
   },
   {
    "allow_bulk_edit": 0,
+   "allow_in_quick_entry": 0,
    "allow_on_submit": 0,
    "bold": 0,
    "collapsible": 0,
    "columns": 0,
    "default": "0.5",
    "depends_on": "is_earned_leave",
+   "fetch_if_empty": 0,
    "fieldname": "rounding",
    "fieldtype": "Select",
    "hidden": 0,
@@ -611,17 +721,15 @@
   }
  ],
  "has_web_view": 0,
- "hide_heading": 0,
  "hide_toolbar": 0,
  "icon": "fa fa-flag",
  "idx": 1,
- "image_view": 0,
  "in_create": 0,
  "is_submittable": 0,
  "issingle": 0,
  "istable": 0,
  "max_attachments": 0,
- "modified": "2018-06-03 18:32:51.803472",
+ "modified": "2019-04-11 15:38:39.334283",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Type",
@@ -687,8 +795,8 @@
  ],
  "quick_entry": 0,
  "read_only": 0,
- "read_only_onload": 0,
  "show_name_in_global_search": 0,
  "track_changes": 0,
- "track_seen": 0
-}
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py
index e0127e5..dcae5fe 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.py
+++ b/erpnext/hr/doctype/leave_type/leave_type.py
@@ -2,9 +2,19 @@
 # License: GNU General Public License v3. See license.txt
 
 from __future__ import unicode_literals
+import calendar
 import frappe
+from datetime import datetime
+from frappe import _
 
 from frappe.model.document import Document
 
 class LeaveType(Document):
-	pass
\ No newline at end of file
+	def validate(self):
+		if self.is_carry_forward:
+			self.validate_carry_forward()
+
+	def validate_carry_forward(self):
+		max_days = 367 if calendar.isleap(datetime.now().year) else 366
+		if not (0 <= self.carry_forward_leave_expiry <= max_days):
+			frappe.throw(_('Invalid entry!! Carried forward days need to expire within a year'))
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py
index b844e49..1006550 100644
--- a/erpnext/hr/doctype/leave_type/test_leave_type.py
+++ b/erpnext/hr/doctype/leave_type/test_leave_type.py
@@ -2,6 +2,25 @@
 # License: GNU General Public License v3. See license.txt
 from __future__ import unicode_literals
 
-
 import frappe
-test_records = frappe.get_test_records('Leave Type')
\ No newline at end of file
+from frappe import _
+
+test_records = frappe.get_test_records('Leave Type')
+
+def create_leave_type(**args):
+    args = frappe._dict(args)
+    if frappe.db.exists("Leave Type", args.leave_type_name):
+        return frappe.get_doc("Leave Type", args.leave_type_name)
+    leave_type = frappe.get_doc({
+        "doctype": "Leave Type",
+        "leave_type_name": args.leave_type_name or "_Test Leave Type",
+        "include_holiday": args.include_holidays or 1,
+        "allow_encashment": args.allow_encashment or 0,
+        "is_earned_leave": args.is_earned_leave or 0,
+        "is_lwp": args.is_lwp or 0,
+        "is_carry_forward": args.is_carry_forward or 0,
+        "carry_forward_leave_expiry": args.carry_forward_leave_expiry or 0,
+        "encashment_threshold_days": args.encashment_threshold_days or 5,
+        "earning_component": "Leave Encashment"
+    })
+    return leave_type
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index fb1e4fc..31f407a 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -602,4 +602,5 @@
 erpnext.patches.v11_1.rename_depends_on_lwp
 execute:frappe.delete_doc("Report", "Inactive Items")
 erpnext.patches.v11_1.delete_scheduling_tool
-erpnext.patches.v12_0.make_custom_fields_for_bank_remittance
\ No newline at end of file
+erpnext.patches.v12_0.generate_leave_ledger_entries
+erpnext.patches.v12_0.make_custom_fields_for_bank_remittance
diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py
new file mode 100644
index 0000000..9c638ab
--- /dev/null
+++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py
@@ -0,0 +1,101 @@
+# Copyright (c) 2018, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+    """ Generates leave ledger entries for leave allocation/application/encashment
+        for last allocation """
+    if frappe.db.a_row_exists("Leave Ledger Entry"):
+        return
+
+    allocation_list = get_allocation_records()
+    generate_allocation_ledger_entries(allocation_list)
+    generate_application_leave_ledger_entries(allocation_list)
+    generate_encashment_leave_ledger_entries(allocation_list)
+
+def generate_allocation_ledger_entries(allocation_list):
+    ''' fix ledger entries for missing leave allocation transaction '''
+    from erpnext.hr.doctype.leave_allocation.leave_allocation import LeaveAllocation
+
+    for allocation in allocation_list:
+        if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation',
+            'transaction_name': allocation.name}):
+            LeaveAllocation.create_leave_ledger_entry(allocation)
+
+def generate_application_leave_ledger_entries(allocation_list):
+    ''' fix ledger entries for missing leave application transaction '''
+    from erpnext.hr.doctype.leave_application.leave_application import LeaveApplication
+
+    leave_applications = get_leaves_application_records(allocation_list)
+
+    for record in leave_applications:
+        if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application',
+            'transaction_name': record.name}):
+            LeaveApplication.create_leave_ledger_entry(record)
+
+def generate_encashment_leave_ledger_entries(allocation_list):
+    ''' fix ledger entries for missing leave encashment transaction '''
+    from erpnext.hr.doctype.leave_encashment.leave_encashment import LeaveEncashment
+
+    leave_encashments = get_leave_encashment_records(allocation_list)
+
+    for record in leave_encashments:
+        if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment',
+            'transaction_name': record.name}):
+            LeaveEncashment.create_leave_ledger_entry(record)
+
+def get_allocation_records():
+    return frappe.db.sql("""
+        SELECT
+            DISTINCT name,
+            employee,
+            leave_type,
+            new_leaves_allocated,
+            carry_forwarded_leaves,
+            from_date,
+            to_date,
+            carry_forward,
+            RANK() OVER(PARTITION BY employee, leave_type ORDER BY to_date DESC) as allocation
+        FROM `tabLeave Allocation`
+        WHERE
+            allocation=1
+    """, as_dict=1)
+
+def get_leaves_application_records(allocation_list):
+    leave_applications = []
+    for allocation in allocation_list:
+        leave_applications.append(frappe.db.sql("""
+            SELECT
+                DISTINCT name,
+                employee,
+                leave_type,
+                total_leave_days,
+                from_date,
+                to_date
+            FROM `tabLeave Application`
+            WHERE
+                from_date >= %s
+                leave_type = %s
+                employee = %s
+        """, (allocation.from_date, allocation.leave_type, allocation.employee)))
+    return leave_applications
+
+def get_leave_encashment_records(allocation_list):
+    leave_encashments = []
+    for allocation in allocation_list:
+        leave_encashments.append(frappe.db.sql("""
+            SELECT
+                DISTINCT name,
+                employee,
+                leave_type,
+                encashable_days,
+                from_date,
+                to_date
+            FROM `tabLeave Encashment`
+            WHERE
+                leave_type = %s
+                employee = %s
+        """, (allocation.leave_type, allocation.employee)))
+    return leave_encashments
\ No newline at end of file