Employee tree (#11667)

* nested set implemented

* treeview for employee

* patch added to assign lft rgt, update nsm_model on trash

* call on_trash method of super class
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 227302c..2779b1d 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -2418,6 +2418,96 @@
    "search_index": 0, 
    "set_only_once": 0, 
    "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "lft", 
+   "fieldtype": "Int", 
+   "hidden": 1, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "lft", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "rgt", 
+   "fieldtype": "Int", 
+   "hidden": 1, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "rgt", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "old_parent", 
+   "fieldtype": "Data", 
+   "hidden": 1, 
+   "ignore_user_permissions": 1, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Old Parent", 
+   "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, 
+   "unique": 0
   }
  ], 
  "has_web_view": 0, 
@@ -2432,7 +2522,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2017-10-04 11:42:02.495731", 
+ "modified": "2017-11-19 01:27:48.222343", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Employee", 
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 03626cd..7c9c112 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -10,13 +10,14 @@
 import frappe.permissions
 from frappe.model.document import Document
 from erpnext.utilities.transaction_base import delete_events
-
+from frappe.utils.nestedset import NestedSet
 
 class EmployeeUserDisabledError(frappe.ValidationError):
 	pass
 
+class Employee(NestedSet):
+	nsm_parent_field = 'reports_to'
 
-class Employee(Document):
 	def autoname(self):
 		naming_method = frappe.db.get_value("HR Settings", None, "emp_created_by")
 		if not naming_method:
@@ -52,7 +53,11 @@
 				frappe.permissions.remove_user_permission(
 					"Employee", self.name, existing_user_id)
 
+	def update_nsm_model(self):
+		frappe.utils.nestedset.update_nsm(self)
+
 	def on_update(self):
+		self.update_nsm_model()
 		if self.user_id:
 			self.update_user()
 			self.update_user_permissions()
@@ -154,13 +159,13 @@
 			throw(_("Employee cannot report to himself."))
 
 	def on_trash(self):
+		super(Employee, self).on_trash()
 		delete_events(self.doctype, self.name)
 
 	def validate_prefered_email(self):
 		if self.prefered_contact_email and not self.get(scrub(self.prefered_contact_email)):
 			frappe.msgprint(_("Please enter " + self.prefered_contact_email))
 
-
 def get_timeline_data(doctype, name):
 	'''Return timeline for attendance'''
 	return dict(frappe.db.sql('''select unix_timestamp(attendance_date), count(*)
@@ -183,7 +188,6 @@
 
 	return ret
 
-
 def validate_employee_role(doc, method):
 	# called via User hook
 	if "Employee" in [d.role for d in doc.get("roles")]:
@@ -241,7 +245,6 @@
 
 def is_holiday(employee, date=None):
 	'''Returns True if given Employee has an holiday on the given date
-
 	:param employee: Employee `name`
 	:param date: Date to check. Will check for today if None'''
 
@@ -300,4 +303,27 @@
 		if user or email:
 			employee_emails.append(user or email)
 
-	return employee_emails
\ No newline at end of file
+	return employee_emails
+
+@frappe.whitelist()
+def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
+	condition = ''
+
+	if is_root:
+		parent = ""
+	if parent and company and parent!=company:
+		condition = ' and reports_to = "{0}"'.format(frappe.db.escape(parent))
+	else:
+		condition = ' and ifnull(reports_to, "")=""'
+
+	employee = frappe.db.sql("""
+		select
+			name as value, employee_name as title,
+			exists(select name from `tabEmployee` where reports_to=emp.name) as expandable
+		from
+			`tabEmployee` emp
+		where company='{company}' {condition} order by name"""
+		.format(company=company, condition=condition),  as_dict=1)
+
+	# return employee
+	return employee
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee/employee_tree.js b/erpnext/hr/doctype/employee/employee_tree.js
new file mode 100644
index 0000000..5d3ec42
--- /dev/null
+++ b/erpnext/hr/doctype/employee/employee_tree.js
@@ -0,0 +1,36 @@
+frappe.treeview_settings['Employee'] = {
+	get_tree_nodes: "erpnext.hr.doctype.employee.employee.get_children",
+	filters: [
+		{
+			fieldname: "company",
+			fieldtype:"Select",
+			options: $.map(locals[':Company'], function(c) { return c.name; }).sort(),
+			label: __("Company"),
+			default: frappe.defaults.get_default('company') ? frappe.defaults.get_default('company') : ""
+		}
+	],
+	breadcrumb: "Hr",
+	disable_add_node: true,
+	get_tree_root: false,
+	toolbar: [
+		{ toggle_btn: true },
+		{
+			label:__("Edit"),
+			condition: function(node) {
+				return !node.is_root;
+			},
+			click: function(node) {
+				frappe.set_route("Form", "Employee", node.data.value);
+			}
+		}
+	],
+	menu_items: [
+		{
+			label: __("New Employee"),
+			action: function() {
+				frappe.new_doc("Employee", true);
+			},
+			condition: 'frappe.boot.user.can_create.indexOf("Employee") !== -1'
+		}
+	],
+};
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7725615..a2efa82 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -472,4 +472,5 @@
 erpnext.patches.v10_0.rename_schools_to_education
 erpnext.patches.v9_2.repost_reserved_qty_for_production
 erpnext.patches.v9_2.remove_company_from_patient
-erpnext.patches.v9_2.set_item_name_in_production_order
\ No newline at end of file
+erpnext.patches.v9_2.set_item_name_in_production_order
+erpnext.patches.v10_0.update_lft_rgt_for_employee
diff --git a/erpnext/patches/v10_0/update_lft_rgt_for_employee.py b/erpnext/patches/v10_0/update_lft_rgt_for_employee.py
new file mode 100644
index 0000000..82fbeaa
--- /dev/null
+++ b/erpnext/patches/v10_0/update_lft_rgt_for_employee.py
@@ -0,0 +1,8 @@
+import frappe
+from frappe.utils.nestedset import rebuild_tree
+
+def execute():
+    """ assign lft and rgt appropriately """
+    frappe.reload_doc("hr", "doctype", "employee")
+
+    rebuild_tree("Employee", "reports_to")
\ No newline at end of file