feat:  Leave policy assignment (#23112)

* feat: Leave Policy Assignment

* feat: linking with leave allocation and valiations

* style: removed old code from leave period

* feat: Bulk Leave policy Assignment and grant Leaves

* fix: overlap validation

* feat: earned leaves based on joining date

* feat: automatic grant leave based on leave policy

* patch: create leave policy assignment based on employee current leave policy

* fix: dependent test cases

* test: Leave policy assignment

* fix: some enhancement

* style: break large function into small function

* fix:requested Changes

* fix(patch): Handled old Leave allocatioln

* fix:codacy

* fix: travis and sider,codacy

* fix: codacy

* fix: codacy

* fix: requested changes and sider

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 4cabe97..4f1c04f 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -57,7 +57,6 @@
   "column_break_45",
   "shift_request_approver",
   "attendance_and_leave_details",
-  "leave_policy",
   "attendance_device_id",
   "column_break_44",
   "holiday_list",
@@ -412,14 +411,6 @@
    "options": "Branch"
   },
   {
-   "fetch_from": "grade.default_leave_policy",
-   "fetch_if_empty": 1,
-   "fieldname": "leave_policy",
-   "fieldtype": "Link",
-   "label": "Leave Policy",
-   "options": "Leave Policy"
-  },
-  {
    "description": "Applicable Holiday List",
    "fieldname": "holiday_list",
    "fieldtype": "Link",
@@ -822,7 +813,7 @@
  "idx": 24,
  "image_field": "image",
  "links": [],
- "modified": "2020-10-16 14:41:10.580897",
+ "modified": "2020-10-16 15:02:04.283657",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee",
diff --git a/erpnext/hr/doctype/employee_grade/employee_grade.json b/erpnext/hr/doctype/employee_grade/employee_grade.json
index e63ffae..88b061a 100644
--- a/erpnext/hr/doctype/employee_grade/employee_grade.json
+++ b/erpnext/hr/doctype/employee_grade/employee_grade.json
@@ -1,167 +1,69 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 1, 
- "allow_rename": 1, 
- "autoname": "Prompt", 
- "beta": 0, 
- "creation": "2018-04-13 16:14:24.174138", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "Prompt",
+ "creation": "2018-04-13 16:14:24.174138",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "default_salary_structure"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "default_leave_policy",
-   "fieldtype": "Link",
-   "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": "Default Leave Policy",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Leave Policy",
-   "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,
    "fieldname": "default_salary_structure",
    "fieldtype": "Link",
-   "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": "Default Salary Structure",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Salary Structure",
-   "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
+   "options": "Salary Structure"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-09-18 17:17:45.617624",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-08-26 13:12:07.815330",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Grade",
- "name_case": "",
  "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "System Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "HR Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "HR User",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   }
  ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
  "sort_field": "modified",
  "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index 4374d29..f999635 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -21,6 +21,7 @@
   "show_leaves_of_all_department_members_in_calendar",
   "auto_leave_encashment",
   "restrict_backdated_leave_application",
+  "automatically_allocate_leaves_based_on_leave_policy",
   "hiring_settings",
   "check_vacancies"
  ],
@@ -41,7 +42,7 @@
    "description": "Employee records are created using the selected field",
    "fieldname": "emp_created_by",
    "fieldtype": "Select",
-   "label": "Employee Records to Be Created By",
+   "label": "Employee Records to be created by",
    "options": "Naming Series\nEmployee Number\nFull Name"
   },
   {
@@ -117,7 +118,7 @@
    "default": "0",
    "fieldname": "restrict_backdated_leave_application",
    "fieldtype": "Check",
-   "label": "Restrict Backdated Leave Applications"
+   "label": "Restrict Backdated Leave Application"
   },
   {
    "depends_on": "eval:doc.restrict_backdated_leave_application == 1",
@@ -125,13 +126,19 @@
    "fieldtype": "Link",
    "label": "Role Allowed to Create Backdated Leave Application",
    "options": "Role"
+  },
+  {
+   "default": "0",
+   "fieldname": "automatically_allocate_leaves_based_on_leave_policy",
+   "fieldtype": "Check",
+   "label": "Automatically Allocate Leaves Based On Leave Policy"
   }
  ],
  "icon": "fa fa-cog",
  "idx": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-10-13 11:49:46.168027",
+ "modified": "2020-08-27 14:30:28.995324",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "HR Settings",
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 007497e..4b31501 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-02-20 19:10:38",
@@ -24,6 +25,7 @@
   "compensatory_request",
   "leave_period",
   "leave_policy",
+  "leave_policy_assignment",
   "carry_forwarded_leaves_count",
   "expired",
   "amended_from",
@@ -160,9 +162,10 @@
    "read_only": 1
   },
   {
-   "fetch_from": "employee.leave_policy",
+   "fetch_from": "leave_policy_assignment.leave_policy",
    "fieldname": "leave_policy",
    "fieldtype": "Link",
+   "hidden": 1,
    "in_standard_filter": 1,
    "label": "Leave Policy",
    "options": "Leave Policy",
@@ -209,12 +212,21 @@
    "fieldtype": "Float",
    "label": "Carry Forwarded Leaves",
    "read_only": 1
+  },
+  {
+   "fieldname": "leave_policy_assignment",
+   "fieldtype": "Link",
+   "label": "Leave Policy Assignment",
+   "options": "Leave Policy Assignment",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-ok",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
- "modified": "2019-08-08 15:08:42.440909",
+ "links": [],
+ "modified": "2020-08-20 14:25:10.314323",
  "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 03fe3fa..a09cd2e 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -51,9 +51,19 @@
 
 	def on_cancel(self):
 		self.create_leave_ledger_entry(submit=False)
+		if self.leave_policy_assignment:
+			self.update_leave_policy_assignments_when_no_allocations_left()
 		if self.carry_forward:
 			self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
 
+	def update_leave_policy_assignments_when_no_allocations_left(self):
+		allocations = frappe.db.get_list("Leave Allocation", filters = {
+			"docstatus": 1,
+			"leave_policy_assignment": self.leave_policy_assignment
+		})
+		if len(allocations) == 0:
+			frappe.db.set_value("Leave Policy Assignment", self.leave_policy_assignment ,"leaves_allocated", 0)
+
 	def validate_period(self):
 		if date_diff(self.to_date, self.from_date) <= 0:
 			frappe.throw(_("To date cannot be before from date"))
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 6e909c3..53b7a39 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -10,6 +10,7 @@
 from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months
 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
+from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
 
 test_dependencies = ["Leave Allocation", "Leave Block List"]
 
@@ -410,25 +411,39 @@
 		self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8)), 21)
 
 	def test_earned_leaves_creation(self):
+
+		frappe.db.sql('''delete from `tabLeave Period`''')
+		frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
+		frappe.db.sql('''delete from `tabLeave Allocation`''')
+		frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
+
 		leave_period = get_leave_period()
 		employee = get_employee()
 		leave_type = 'Test Earned Leave Type'
-		if not frappe.db.exists('Leave Type', leave_type):
-			frappe.get_doc(dict(
-				leave_type_name = leave_type,
-				doctype = 'Leave Type',
-				is_earned_leave = 1,
-				earned_leave_frequency = 'Monthly',
-				rounding = 0.5,
-				max_leaves_allowed = 6
-			)).insert()
+		frappe.delete_doc_if_exists("Leave Type", 'Test Earned Leave Type', force=1)
+		frappe.get_doc(dict(
+			leave_type_name = leave_type,
+			doctype = 'Leave Type',
+			is_earned_leave = 1,
+			earned_leave_frequency = 'Monthly',
+			rounding = 0.5,
+			max_leaves_allowed = 6
+		)).insert()
+
 		leave_policy = frappe.get_doc({
 			"doctype": "Leave Policy",
 			"leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}]
 		}).insert()
-		frappe.db.set_value("Employee", employee.name, "leave_policy", leave_policy.name)
 
-		allocate_leaves(employee, leave_period, leave_type, 0, eligible_leaves = 12)
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+
+		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+
+		frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
 
 		from erpnext.hr.utils import allocate_earned_leaves
 		i = 0
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
index 99f6463..bbee18b 100644
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
@@ -9,6 +9,7 @@
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.payroll.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_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
 from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy\
 
 test_dependencies = ["Leave Type"]
@@ -16,6 +17,7 @@
 class TestLeaveEncashment(unittest.TestCase):
 	def setUp(self):
 		frappe.db.sql('''delete from `tabLeave Period`''')
+		frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
 		frappe.db.sql('''delete from `tabLeave Allocation`''')
 		frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
 		frappe.db.sql('''delete from `tabAdditional Salary`''')
@@ -29,14 +31,22 @@
 		# create employee, salary structure and assignment
 		self.employee = make_employee("test_employee_encashment@example.com")
 
-		frappe.db.set_value("Employee", self.employee, "leave_policy", leave_policy.name)
+		self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
+
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": self.leave_period.name
+		}
+
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee], frappe._dict(data))
 
 		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
-		self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
-		self.leave_period.grant_leave_allocation(employee=self.employee)
+		#grant Leaves
+		frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
+
 
 	def test_leave_balance_value_and_amount(self):
 		frappe.db.sql('''delete from `tabLeave Encashment`''')
diff --git a/erpnext/hr/doctype/leave_period/leave_period.js b/erpnext/hr/doctype/leave_period/leave_period.js
index bad2b87..0e88bc1 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.js
+++ b/erpnext/hr/doctype/leave_period/leave_period.js
@@ -2,14 +2,6 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Leave Period', {
-	refresh: (frm)=>{
-		frm.set_df_property("grant_leaves", "hidden", frm.doc.__islocal ? 1:0);
-		if(!frm.is_new()) {
-			frm.add_custom_button(__('Grant Leaves'), function () {
-				frm.trigger("grant_leaves");
-			});
-		}
-	},
 	from_date: (frm)=>{
 		if (frm.doc.from_date && !frm.doc.to_date) {
 			var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12);
@@ -22,73 +14,7 @@
 				"filters": {
 					"company": frm.doc.company,
 				}
-			}
-		})
-	},
-	grant_leaves: function(frm) {
-		var d = new frappe.ui.Dialog({
-			title: __('Grant Leaves'),
-			fields: [
-				{
-					"label": "Filter Employees By (Optional)",
-					"fieldname": "sec_break",
-					"fieldtype": "Section Break",
-				},
-				{
-					"label": "Employee Grade",
-					"fieldname": "grade",
-					"fieldtype": "Link",
-					"options": "Employee Grade"
-				},
-				{
-					"label": "Department",
-					"fieldname": "department",
-					"fieldtype": "Link",
-					"options": "Department"
-				},
-				{
-					"fieldname": "col_break",
-					"fieldtype": "Column Break",
-				},
-				{
-					"label": "Designation",
-					"fieldname": "designation",
-					"fieldtype": "Link",
-					"options": "Designation"
-				},
-				{
-					"label": "Employee",
-					"fieldname": "employee",
-					"fieldtype": "Link",
-					"options": "Employee"
-				},
-				{
-					"fieldname": "sec_break",
-					"fieldtype": "Section Break",
-				},
-				{
-					"label": "Add unused leaves from previous allocations",
-					"fieldname": "carry_forward",
-					"fieldtype": "Check"
-				}
-			],
-			primary_action: function() {
-				var data = d.get_values();
-
-				frappe.call({
-					doc: frm.doc,
-					method: "grant_leave_allocation",
-					args: data,
-					callback: function(r) {
-						if(!r.exc) {
-							d.hide();
-							frm.reload_doc();
-						}
-					}
-				});
-			},
-			primary_action_label: __('Grant')
+			};
 		});
-		d.show();
-	}
+	},
 });
diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py
index 0973ac7..28a33f6 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.py
+++ b/erpnext/hr/doctype/leave_period/leave_period.py
@@ -7,24 +7,10 @@
 from frappe import _
 from frappe.utils import getdate, cstr, add_days, date_diff, getdate, ceil
 from frappe.model.document import Document
-from erpnext.hr.utils import validate_overlap, get_employee_leave_policy
+from erpnext.hr.utils import validate_overlap
 from frappe.utils.background_jobs import enqueue
-from six import iteritems
 
 class LeavePeriod(Document):
-	def get_employees(self, args):
-		conditions, values = [], []
-		for field, value in iteritems(args):
-			if value:
-				conditions.append("{0}=%s".format(field))
-				values.append(value)
-
-		condition_str = " and " + " and ".join(conditions) if len(conditions) else ""
-
-		employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" #nosec
-			.format(condition=condition_str), tuple(values)))
-
-		return employees
 
 	def validate(self):
 		self.validate_dates()
@@ -33,96 +19,3 @@
 	def validate_dates(self):
 		if getdate(self.from_date) >= getdate(self.to_date):
 			frappe.throw(_("To date can not be equal or less than from date"))
-
-
-	def grant_leave_allocation(self, grade=None, department=None, designation=None,
-			employee=None, carry_forward=0):
-		employee_records = self.get_employees({
-			"grade": grade,
-			"department": department,
-			"designation": designation,
-			"name": employee
-		})
-
-		if employee_records:
-			if len(employee_records) > 20:
-				frappe.enqueue(grant_leave_alloc_for_employees, timeout=600,
-					employee_records=employee_records, leave_period=self, carry_forward=carry_forward)
-			else:
-				grant_leave_alloc_for_employees(employee_records, self, carry_forward)
-		else:
-			frappe.msgprint(_("No Employee Found"))
-
-def grant_leave_alloc_for_employees(employee_records, leave_period, carry_forward=0):
-	leave_allocations = []
-	existing_allocations_for = get_existing_allocations(list(employee_records.keys()), leave_period.name)
-	leave_type_details = get_leave_type_details()
-	count = 0
-	for employee in employee_records.keys():
-		if employee in existing_allocations_for:
-			continue
-		count +=1
-		leave_policy = get_employee_leave_policy(employee)
-		if leave_policy:
-			for leave_policy_detail in leave_policy.leave_policy_details:
-				if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
-					leave_allocation = create_leave_allocation(employee, leave_policy_detail.leave_type,
-						leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward, employee_records.get(employee))
-					leave_allocations.append(leave_allocation)
-		frappe.db.commit()
-		frappe.publish_progress(count*100/len(set(employee_records.keys()) - set(existing_allocations_for)), title = _("Allocating leaves..."))
-
-	if leave_allocations:
-		frappe.msgprint(_("Leaves has been granted sucessfully"))
-
-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 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}")
-			.format("\n".join(leave_allocations)))
-	return leave_allocations
-
-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", "expire_carry_forwarded_leaves_after_days"])
-	for d in leave_types:
-		leave_type_details.setdefault(d.name, d)
-	return leave_type_details
-
-def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward, date_of_joining):
-	''' Creates leave allocation for the given employee in the provided leave period '''
-	if carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
-		carry_forward = 0
-
-	# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
-	if getdate(date_of_joining) > getdate(leave_period.from_date):
-		remaining_period = ((date_diff(leave_period.to_date, date_of_joining) + 1) / (date_diff(leave_period.to_date, leave_period.from_date) + 1))
-		new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
-
-	# Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
-	if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1:
-		new_leaves_allocated = 0
-
-	allocation = frappe.get_doc(dict(
-		doctype="Leave Allocation",
-		employee=employee,
-		leave_type=leave_type,
-		from_date=leave_period.from_date,
-		to_date=leave_period.to_date,
-		new_leaves_allocated=new_leaves_allocated,
-		leave_period=leave_period.name,
-		carry_forward=carry_forward
-		))
-	allocation.save(ignore_permissions = True)
-	allocation.submit()
-	return allocation.name
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py
index 1762cf9..b5857bc 100644
--- a/erpnext/hr/doctype/leave_period/test_leave_period.py
+++ b/erpnext/hr/doctype/leave_period/test_leave_period.py
@@ -5,43 +5,11 @@
 
 import frappe, erpnext
 import unittest
-from frappe.utils import today, add_months
-from erpnext.hr.doctype.employee.test_employee import make_employee
-from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
 
 test_dependencies = ["Employee", "Leave Type", "Leave Policy"]
 
 class TestLeavePeriod(unittest.TestCase):
-	def setUp(self):
-		frappe.db.sql("delete from `tabLeave Period`")
-
-	def test_leave_grant(self):
-		leave_type = "_Test Leave Type"
-
-		# create the leave policy
-		leave_policy = frappe.get_doc({
-			"doctype": "Leave Policy",
-			"leave_policy_details": [{
-				"leave_type": leave_type,
-				"annual_allocation": 20
-			}]
-		}).insert()
-		leave_policy.submit()
-
-		# create employee and assign the leave period
-		employee = "test_leave_period@employee.com"
-		employee_doc_name = make_employee(employee)
-		frappe.db.set_value("Employee", employee_doc_name, "leave_policy", leave_policy.name)
-
-		# clear the already allocated leave
-		frappe.db.sql('''delete from `tabLeave Allocation` where employee=%s''', "test_leave_period@employee.com")
-
-		# create the leave period
-		leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
-
-		# test leave_allocation
-		leave_period.grant_leave_allocation(employee=employee_doc_name)
-		self.assertEqual(get_leave_balance_on(employee_doc_name, leave_type, today()), 20)
+	pass
 
 def create_leave_period(from_date, to_date, company=None):
 	leave_period = frappe.db.get_value('Leave Period',
diff --git a/erpnext/hr/doctype/leave_policy_assignment/__init__.py b/erpnext/hr/doctype/leave_policy_assignment/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/__init__.py
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js
new file mode 100644
index 0000000..7c32a0d
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Leave Policy Assignment', {
+	onload: function(frm) {
+		frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
+	},
+
+	refresh: function(frm) {
+		if (frm.doc.docstatus === 1 && frm.doc.leaves_allocated === 0) {
+			frm.add_custom_button(__("Grant Leave"), function() {
+
+				frappe.call({
+					doc: frm.doc,
+					method: "grant_leave_alloc_for_employee",
+					callback: function(r) {
+						let leave_allocations = r.message;
+						let msg = frm.events.get_success_message(leave_allocations);
+						frappe.msgprint(msg);
+						cur_frm.refresh();
+					}
+				});
+			});
+		}
+	},
+
+	get_success_message: function(leave_allocations) {
+		let msg = __("Leaves has been granted successfully");
+		msg += "<br><table class='table table-bordered'>";
+		msg += "<tr><th>"+__('Leave Type')+"</th><th>"+__("Leave Allocation")+"</th><th>"+__("Leaves Granted")+"</th><tr>";
+		for (let key in leave_allocations) {
+			msg += "<tr><th>"+key+"</th><td>"+leave_allocations[key]["name"]+"</td><td>"+leave_allocations[key]["leaves"]+"</td></tr>";
+		}
+		msg += "</table>";
+		return msg;
+	},
+
+	assignment_based_on: function(frm) {
+		if (frm.doc.assignment_based_on) {
+			frm.events.set_effective_date(frm);
+		} else {
+			frm.set_value("effective_from", '');
+			frm.set_value("effective_to", '');
+		}
+	},
+
+	leave_period: function(frm) {
+		if (frm.doc.leave_period) {
+			frm.events.set_effective_date(frm);
+		}
+	},
+
+	set_effective_date: function(frm) {
+		if (frm.doc.assignment_based_on == "Leave Period" && frm.doc.leave_period) {
+			frappe.model.with_doc("Leave Period", frm.doc.leave_period, function () {
+				let from_date = frappe.model.get_value("Leave Period", frm.doc.leave_period, "from_date");
+				let to_date = frappe.model.get_value("Leave Period", frm.doc.leave_period, "to_date");
+				frm.set_value("effective_from", from_date);
+				frm.set_value("effective_to", to_date);
+
+			});
+		} else if (frm.doc.assignment_based_on == "Joining Date" && frm.doc.employee) {
+			frappe.model.with_doc("Employee", frm.doc.employee, function () {
+				let from_date = frappe.model.get_value("Employee", frm.doc.employee, "date_of_joining");
+				frm.set_value("effective_from", from_date);
+				frm.set_value("effective_to", frappe.datetime.add_months(frm.doc.effective_from, 12));
+			});
+		}
+		frm.refresh();
+	}
+
+});
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
new file mode 100644
index 0000000..ecebb3b
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
@@ -0,0 +1,160 @@
+{
+ "actions": [],
+ "autoname": "HR-LPOL-ASSGN-.#####",
+ "creation": "2020-08-19 13:02:43.343666",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "employee",
+  "employee_name",
+  "company",
+  "leave_policy",
+  "carry_forward",
+  "column_break_5",
+  "assignment_based_on",
+  "leave_period",
+  "effective_from",
+  "effective_to",
+  "leaves_allocated",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "employee",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Employee",
+   "options": "Employee",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "employee.employee_name",
+   "fieldname": "employee_name",
+   "fieldtype": "Data",
+   "label": "Employee name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "leave_policy",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Leave Policy",
+   "options": "Leave Policy",
+   "reqd": 1
+  },
+  {
+   "fieldname": "assignment_based_on",
+   "fieldtype": "Select",
+   "label": "Assignment based on",
+   "options": "\nLeave Period\nJoining Date"
+  },
+  {
+   "depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
+   "fieldname": "leave_period",
+   "fieldtype": "Link",
+   "label": "Leave Period",
+   "mandatory_depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
+   "options": "Leave Period"
+  },
+  {
+   "fieldname": "effective_from",
+   "fieldtype": "Date",
+   "label": "Effective From",
+   "read_only_depends_on": "eval:doc.assignment_based_on",
+   "reqd": 1
+  },
+  {
+   "fieldname": "effective_to",
+   "fieldtype": "Date",
+   "label": "Effective To",
+   "read_only_depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_standard_filter": 1,
+   "label": "Company",
+   "options": "Company",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_5",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Leave Policy Assignment",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "carry_forward",
+   "fieldtype": "Check",
+   "label": "Add unused leaves from previous allocations"
+  },
+  {
+   "default": "0",
+   "fieldname": "leaves_allocated",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Leaves Allocated"
+  }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-10-15 15:18:15.227848",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Leave Policy Assignment",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR User",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
new file mode 100644
index 0000000..a5068bc
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, 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 _, bold
+from frappe.utils import getdate, date_diff, comma_and, formatdate
+from math import ceil
+import json
+from six import string_types
+
+class LeavePolicyAssignment(Document):
+
+	def validate(self):
+		self.validate_policy_assignment_overlap()
+		self.set_dates()
+
+	def set_dates(self):
+		if self.assignment_based_on == "Leave Period":
+			self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
+		elif self.assignment_based_on == "Joining Date":
+			self.effective_from = frappe.db.get_value("Employee", self.employee, "date_of_joining")
+
+	def validate_policy_assignment_overlap(self):
+		leave_policy_assignments = frappe.get_all("Leave Policy Assignment", filters = {
+			"employee": self.employee,
+			"name": ("!=", self.name),
+			"docstatus": 1,
+			"effective_to": (">=", self.effective_from),
+			"effective_from": ("<=", self.effective_to)
+		})
+
+		if len(leave_policy_assignments):
+			frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}")
+				.format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to))))
+
+	def grant_leave_alloc_for_employee(self):
+		if self.leaves_allocated:
+			frappe.throw(_("Leave already have been assigned for this Leave Policy Assignment"))
+		else:
+			leave_allocations = {}
+			leave_type_details = get_leave_type_details()
+
+			leave_policy = frappe.get_doc("Leave Policy", self.leave_policy)
+			date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
+
+			for leave_policy_detail in leave_policy.leave_policy_details:
+				if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
+					leave_allocation, new_leaves_allocated = self.create_leave_allocation(
+						leave_policy_detail.leave_type, leave_policy_detail.annual_allocation,
+						leave_type_details, date_of_joining
+					)
+
+				leave_allocations[leave_policy_detail.leave_type] = {"name": leave_allocation, "leaves": new_leaves_allocated}
+
+			self.db_set("leaves_allocated", 1)
+			return leave_allocations
+
+	def create_leave_allocation(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+		# Creates leave allocation for the given employee in the provided leave period
+		carry_forward = self.carry_forward
+		if self.carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
+			carry_forward = 0
+
+		new_leaves_allocated = self.get_new_leaves(leave_type, new_leaves_allocated,
+			leave_type_details, date_of_joining)
+
+		allocation = frappe.get_doc(dict(
+			doctype="Leave Allocation",
+			employee=self.employee,
+			leave_type=leave_type,
+			from_date=self.effective_from,
+			to_date=self.effective_to,
+			new_leaves_allocated=new_leaves_allocated,
+			leave_period=self.leave_period or None,
+			leave_policy_assignment = self.name,
+			leave_policy = self.leave_policy,
+			carry_forward=carry_forward
+			))
+		allocation.save(ignore_permissions = True)
+		allocation.submit()
+		return allocation.name, new_leaves_allocated
+
+	def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+		# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
+		if getdate(date_of_joining) > getdate(self.effective_from):
+			remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1))
+			new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
+
+		# Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
+		if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1:
+			new_leaves_allocated = 0
+
+		return new_leaves_allocated
+
+@frappe.whitelist()
+def grant_leave_for_multiple_employees(leave_policy_assignments):
+	leave_policy_assignments = json.loads(leave_policy_assignments)
+	not_granted = []
+	for assignment in leave_policy_assignments:
+		try:
+			frappe.get_doc("Leave Policy Assignment", assignment).grant_leave_alloc_for_employee()
+		except Exception:
+			not_granted.append(assignment)
+
+		if len(not_granted):
+			msg = _("Leave not Granted for Assignments:")+ bold(comma_and(not_granted)) + _(". Please Check documents")
+		else:
+			msg = _("Leave granted Successfully")
+	frappe.msgprint(msg)
+
+@frappe.whitelist()
+def create_assignment_for_multiple_employees(employees, data):
+
+	if isinstance(employees, string_types):
+		employees= json.loads(employees)
+
+	if isinstance(data, string_types):
+		data = frappe._dict(json.loads(data))
+
+	docs_name = []
+	for employee in employees:
+		assignment = frappe.new_doc("Leave Policy Assignment")
+		assignment.employee = employee
+		assignment.assignment_based_on = data.assignment_based_on or None
+		assignment.leave_policy = data.leave_policy
+		assignment.effective_from = getdate(data.effective_from) or None
+		assignment.effective_to = getdate(data.effective_to) or None
+		assignment.leave_period = data.leave_period or None
+		assignment.carry_forward = data.carry_forward
+
+		assignment.save()
+		assignment.submit()
+		docs_name.append(assignment.name)
+	return docs_name
+
+
+def automatically_allocate_leaves_based_on_leave_policy():
+	today = getdate()
+	automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_single_value(
+		'HR Settings', 'automatically_allocate_leaves_based_on_leave_policy'
+	)
+
+	pending_assignments = frappe.get_list(
+		"Leave Policy Assignment",
+		filters = {"docstatus": 1, "leaves_allocated": 0, "effective_from": today}
+	)
+
+	if len(pending_assignments) and automatically_allocate_leaves_based_on_leave_policy:
+		for assignment in pending_assignments:
+			frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
+
+
+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", "expire_carry_forwarded_leaves_after_days"])
+	for d in leave_types:
+		leave_type_details.setdefault(d.name, d)
+	return leave_type_details
+
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
new file mode 100644
index 0000000..468f243
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
@@ -0,0 +1,138 @@
+frappe.listview_settings['Leave Policy Assignment'] = {
+	onload: function (list_view) {
+		let me = this;
+		list_view.page.add_inner_button(__("Bulk Leave Policy Assignment"), function () {
+			me.dialog = new frappe.ui.form.MultiSelectDialog({
+				doctype: "Employee",
+				target: cur_list,
+				setters: {
+					company: '',
+					department: '',
+				},
+				data_fields: [{
+					fieldname: 'leave_policy',
+					fieldtype: 'Link',
+					options: 'Leave Policy',
+					label: __('Leave Policy'),
+					reqd: 1
+				},
+				{
+					fieldname: 'assignment_based_on',
+					fieldtype: 'Select',
+					options: ["", "Leave Period"],
+					label: __('Assignment Based On'),
+					onchange: () => {
+						if (cur_dialog.fields_dict.assignment_based_on.value === "Leave Period") {
+							cur_dialog.set_df_property("effective_from", "read_only", 1);
+							cur_dialog.set_df_property("leave_period", "reqd", 1);
+							cur_dialog.set_df_property("effective_to", "read_only", 1);
+						} else {
+							cur_dialog.set_df_property("effective_from", "read_only", 0);
+							cur_dialog.set_df_property("leave_period", "reqd", 0);
+							cur_dialog.set_df_property("effective_to", "read_only", 0);
+							cur_dialog.set_value("effective_from", "");
+							cur_dialog.set_value("effective_to", "");
+						}
+					}
+				},
+				{
+					fieldname: "leave_period",
+					fieldtype: 'Link',
+					options: "Leave Period",
+					label: __('Leave Period'),
+					depends_on: doc => {
+						return doc.assignment_based_on == 'Leave Period';
+					},
+					onchange: () => {
+						if (cur_dialog.fields_dict.leave_period.value) {
+							me.set_effective_date();
+						}
+					}
+				},
+				{
+					fieldtype: "Column Break"
+				},
+				{
+					fieldname: 'effective_from',
+					fieldtype: 'Date',
+					label: __('Effective From'),
+					reqd: 1
+				},
+				{
+					fieldname: 'effective_to',
+					fieldtype: 'Date',
+					label: __('Effective To'),
+					reqd: 1
+				},
+				{
+					fieldname: 'carry_forward',
+					fieldtype: 'Check',
+					label: __('Add unused leaves from previous allocations')
+				}
+				],
+				get_query() {
+					return {
+						filters: {
+							status: ['=', 'Active']
+						}
+					};
+				},
+				add_filters_group: 1,
+				primary_action_label: "Assign",
+				action(employees, data) {
+					frappe.call({
+						method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.create_assignment_for_multiple_employees',
+						async: false,
+						args: {
+							employees: employees,
+							data: data
+						}
+					});
+					cur_dialog.hide();
+				}
+			});
+		});
+
+		list_view.page.add_inner_button(__("Grant Leaves"), function () {
+			me.dialog = new frappe.ui.form.MultiSelectDialog({
+				doctype: "Leave Policy Assignment",
+				target: cur_list,
+				setters: {
+					company: '',
+					employee: '',
+				},
+				get_query() {
+					return {
+						filters: {
+							docstatus: ['=', 1],
+							leaves_allocated: ['=', 0]
+						}
+					};
+				},
+				add_filters_group: 1,
+				primary_action_label: "Grant Leaves",
+				action(leave_policy_assignments) {
+					frappe.call({
+						method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.grant_leave_for_multiple_employees',
+						async: false,
+						args: {
+							leave_policy_assignments: leave_policy_assignments
+						}
+					});
+					me.dialog.hide();
+				}
+			});
+		});
+	},
+
+	set_effective_date: function () {
+		if (cur_dialog.fields_dict.assignment_based_on.value === "Leave Period" && cur_dialog.fields_dict.leave_period.value) {
+			frappe.model.with_doc("Leave Period", cur_dialog.fields_dict.leave_period.value, function () {
+				let from_date = frappe.model.get_value("Leave Period", cur_dialog.fields_dict.leave_period.value, "from_date");
+				let to_date = frappe.model.get_value("Leave Period", cur_dialog.fields_dict.leave_period.value, "to_date");
+				cur_dialog.set_value("effective_from", from_date);
+				cur_dialog.set_value("effective_to", to_date);
+			});
+		}
+	}
+};
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
new file mode 100644
index 0000000..c7bc6fb
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+from erpnext.hr.doctype.leave_application.test_leave_application import get_leave_period, get_employee
+from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
+from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy
+
+class TestLeavePolicyAssignment(unittest.TestCase):
+
+	def setUp(self):
+		for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
+			frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
+
+	def test_grant_leaves(self):
+		leave_period = get_leave_period()
+		employee = get_employee()
+
+		# create the leave policy with leave type "_Test Leave Type", allocation = 10
+		leave_policy = create_leave_policy()
+		leave_policy.submit()
+
+
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+
+		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+
+		leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
+		leave_policy_assignment_doc.grant_leave_alloc_for_employee()
+		leave_policy_assignment_doc.reload()
+
+		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
+
+		leave_allocation = frappe.get_list("Leave Allocation", filters={
+			"employee": employee.name,
+			"leave_policy":leave_policy.name,
+			"leave_policy_assignment": leave_policy_assignments[0],
+			"docstatus": 1})[0]
+
+		leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
+
+		self.assertEqual(leave_alloc_doc.new_leaves_allocated, 10)
+		self.assertEqual(leave_alloc_doc.leave_type, "_Test Leave Type")
+		self.assertEqual(leave_alloc_doc.from_date, leave_period.from_date)
+		self.assertEqual(leave_alloc_doc.to_date, leave_period.to_date)
+		self.assertEqual(leave_alloc_doc.leave_policy, leave_policy.name)
+		self.assertEqual(leave_alloc_doc.leave_policy_assignment, leave_policy_assignments[0])
+
+	def test_allow_to_grant_all_leave_after_cancellation_of_every_leave_allocation(self):
+		leave_period = get_leave_period()
+		employee = get_employee()
+
+		# create the leave policy with leave type "_Test Leave Type", allocation = 10
+		leave_policy = create_leave_policy()
+		leave_policy.submit()
+
+
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+
+		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+
+		leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
+		leave_policy_assignment_doc.grant_leave_alloc_for_employee()
+		leave_policy_assignment_doc.reload()
+
+
+		# every leave is allocated no more leave can be granted now
+		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
+
+		leave_allocation = frappe.get_list("Leave Allocation", filters={
+			"employee": employee.name,
+			"leave_policy":leave_policy.name,
+			"leave_policy_assignment": leave_policy_assignments[0],
+			"docstatus": 1})[0]
+
+		leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
+
+		# User all allowed to grant leave when there is no allocation against assignment
+		leave_alloc_doc.cancel()
+		leave_alloc_doc.delete()
+
+		leave_policy_assignment_doc.reload()
+
+
+		# User are now allowed to grant leave
+		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 0)
+
+	def tearDown(self):
+		for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
+			frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
+
+
diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json
index 4a135e0..a209291 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.json
+++ b/erpnext/hr/doctype/leave_type/leave_type.json
@@ -33,6 +33,7 @@
   "is_earned_leave",
   "earned_leave_frequency",
   "column_break_22",
+  "based_on_date_of_joining",
   "rounding"
  ],
  "fields": [
@@ -189,6 +190,13 @@
   },
   {
    "default": "0",
+   "depends_on": "eval:doc.is_earned_leave",
+   "description": "If checked, leave will be granted on the day of joining every month.",
+   "fieldname": "based_on_date_of_joining",
+   "fieldtype": "Check",
+   "label": "Based On Date Of Joining"
+  },
+  {
    "depends_on": "eval:doc.is_lwp == 0",
    "fieldname": "is_ppl",
    "fieldtype": "Check",
@@ -205,7 +213,7 @@
  "icon": "fa fa-flag",
  "idx": 1,
  "links": [],
- "modified": "2020-08-26 14:04:54.318687",
+ "modified": "2020-10-15 15:49:47.555105",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Type",
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 8d95924..d700e7f 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -215,19 +215,6 @@
 		+ _(") for {0}").format(exists_for)
 	frappe.throw(msg)
 
-def get_employee_leave_policy(employee):
-	leave_policy = frappe.db.get_value("Employee", employee, "leave_policy")
-	if not leave_policy:
-		employee_grade = frappe.db.get_value("Employee", employee, "grade")
-		if employee_grade:
-			leave_policy = frappe.db.get_value("Employee Grade", employee_grade, "default_leave_policy")
-			if not leave_policy:
-				frappe.throw(_("Employee {0} of grade {1} have no default leave policy").format(employee, employee_grade))
-	if leave_policy:
-		return frappe.get_doc("Leave Policy", leave_policy)
-	else:
-		frappe.throw(_("Please set leave policy for employee {0} in Employee / Grade record").format(employee))
-
 def validate_duplicate_exemption_for_payroll_period(doctype, docname, payroll_period, employee):
 	existing_record = frappe.db.exists(doctype, {
 		"payroll_period": payroll_period,
@@ -300,43 +287,68 @@
 
 def allocate_earned_leaves():
 	'''Allocate earned leaves to Employees'''
-	e_leave_types = frappe.get_all("Leave Type",
-		fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding"],
-		filters={'is_earned_leave' : 1})
+	e_leave_types = get_earned_leaves()
 	today = getdate()
-	divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
 
 	for e_leave_type in e_leave_types:
-		leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where %s
-			between from_date and to_date and docstatus=1 and leave_type=%s""", (today, e_leave_type.name), as_dict=1)
+
+		leave_allocations = get_leave_allocations(today, e_leave_type.name)
+
 		for allocation in leave_allocations:
-			leave_policy = get_employee_leave_policy(allocation.employee)
-			if not leave_policy:
+
+			if not allocation.leave_policy_assignment and not allocation.leave_policy:
 				continue
-			if not e_leave_type.earned_leave_frequency == "Monthly":
-				if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency):
-					continue
+
+			leave_policy = allocation.leave_policy if allocation.leave_policy else frappe.db.get_value(
+					"Leave Policy Assignment", allocation.leave_policy_assignment, ["leave_policy"])
+
 			annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={
-				'parent': leave_policy.name,
+				'parent': leave_policy,
 				'leave_type': e_leave_type.name
 			}, fieldname=['annual_allocation'])
-			if annual_allocation:
-				earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency]
-				if e_leave_type.rounding == "0.5":
-					earned_leaves = round(earned_leaves * 2) / 2
-				else:
-					earned_leaves = round(earned_leaves)
 
-				allocation = frappe.get_doc('Leave Allocation', allocation.name)
-				new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
+			from_date=allocation.from_date
 
-				if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
-					new_allocation = e_leave_type.max_leaves_allowed
+			if e_leave_type.based_on_date_of_joining_date:
+				from_date  = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
 
-				if new_allocation == allocation.total_leaves_allocated:
-					continue
-				allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
-				create_additional_leave_ledger_entry(allocation, earned_leaves, today)
+			if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining_date):
+				update_previous_leave_allocation(allocation, annual_allocation, e_leave_type)
+
+def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type):
+	divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
+	if annual_allocation:
+		earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency]
+		if e_leave_type.rounding == "0.5":
+			earned_leaves = round(earned_leaves * 2) / 2
+		else:
+			earned_leaves = round(earned_leaves)
+
+		allocation = frappe.get_doc('Leave Allocation', allocation.name)
+		new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
+
+		if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
+			new_allocation = e_leave_type.max_leaves_allowed
+
+		if new_allocation != allocation.total_leaves_allocated:
+			allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
+			today_date = today()
+			create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
+
+
+def get_leave_allocations(date, leave_type):
+	return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy
+		from `tabLeave Allocation`
+		where
+			%s between from_date and to_date and docstatus=1
+			and leave_type=%s""",
+	(date, leave_type), as_dict=1)
+
+
+def get_earned_leaves():
+	return frappe.get_all("Leave Type",
+		fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding", "based_on_date_of_joining"],
+		filters={'is_earned_leave' : 1})
 
 def create_additional_leave_ledger_entry(allocation, leaves, date):
 	''' Create leave ledger entry for leave types '''
@@ -345,24 +357,32 @@
 	allocation.unused_leaves = 0
 	allocation.create_leave_ledger_entry()
 
-def check_frequency_hit(from_date, to_date, frequency):
-	'''Return True if current date matches frequency'''
-	from_dt = get_datetime(from_date)
-	to_dt = get_datetime(to_date)
+def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining_date):
+	import calendar
 	from dateutil import relativedelta
-	rd = relativedelta.relativedelta(to_dt, from_dt)
-	months = rd.months
-	if frequency == "Quarterly":
-		if not months % 3:
+
+	from_date = get_datetime(from_date)
+	to_date = get_datetime(to_date)
+	rd = relativedelta.relativedelta(to_date, from_date)
+	#last day of month
+	last_day =  calendar.monthrange(to_date.year, to_date.month)[1]
+
+	if (from_date.day == to_date.day and based_on_date_of_joining_date) or (not based_on_date_of_joining_date and to_date.day == last_day):
+		if frequency == "Monthly":
 			return True
-	elif frequency == "Half-Yearly":
-		if not months % 6:
+		elif frequency == "Quarterly" and rd.months % 3:
 			return True
-	elif frequency == "Yearly":
-		if not months % 12:
+		elif frequency == "Half-Yearly" and rd.months % 6:
 			return True
+		elif frequency == "Yearly" and rd.months % 12:
+			return True
+
+	if frappe.flags.in_test:
+		return True
+
 	return False
 
+
 def get_salary_assignment(employee, date):
 	assignment = frappe.db.sql("""
 		select * from `tabSalary Structure Assignment`
@@ -454,3 +474,10 @@
 	if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0:
 		total_claimed_amount = sum_of_claimed_amount[0].total_amount
 	return total_claimed_amount
+
+def grant_leaves_automatically():
+	automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_singles_value("HR Settings", "automatically_allocate_leaves_based_on_leave_policy")
+	if automatically_allocate_leaves_based_on_leave_policy:
+		lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0})
+		for assignment in lpa:
+			frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()