Merge pull request #10982 from netchampfaris/multiple-users-pos-profile

[Enhancement] Allow Multiple users in POS Profile
diff --git a/.eslintrc b/.eslintrc
index c9cd552..4dd1216 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -132,6 +132,7 @@
 		"get_url_arg": true,
 		"get_server_fields": true,
 		"set_multiple": true,
-		"QUnit": true
+		"QUnit": true,
+		"Chart": true
 	}
 }
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index b245f56..2812fa7 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -4,7 +4,7 @@
 import frappe
 from erpnext.hooks import regional_overrides
 
-__version__ = '9.2.2'
+__version__ = '9.2.4'
 
 def get_default_company(user=None):
 	'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 790003c..cc35652 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -566,17 +566,26 @@
 		account = get_bank_cash_account(mode_of_payment, company).get("account")
 
 	if not account:
+		'''
+			Set the default account first. If the user hasn't set any default account then, he doesn't
+			want us to set any random account. In this case set the account only if there is single
+			account (of that type), otherwise return empty dict.
+		'''
 		if account_type=="Bank":
 			account = frappe.db.get_value("Company", company, "default_bank_account")
 			if not account:
-				account = frappe.db.get_value("Account",
-					{"company": company, "account_type": "Bank", "is_group": 0})
+				account_list = frappe.get_all("Account", filters = {"company": company,
+					"account_type": "Bank", "is_group": 0})
+				if len(account_list) == 1:
+					account = account_list[0].name
 
 		elif account_type=="Cash":
 			account = frappe.db.get_value("Company", company, "default_cash_account")
 			if not account:
-				account = frappe.db.get_value("Account",
-					{"company": company, "account_type": "Cash", "is_group": 0})
+				account_list = frappe.get_all("Account", filters = {"company": company,
+					"account_type": "Cash", "is_group": 0})
+				if len(account_list) == 1:
+					account = account_list[0].name
 
 	if account:
 		account_details = frappe.db.get_value("Account", account,
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 17ac1f7..52d8a2d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -648,13 +648,13 @@
 
 	set_difference_amount: function(frm) {
 		var unallocated_amount = 0;
+		var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
+			function(d) { return flt(d.amount) }));
+
 		if(frm.doc.party) {
 			var party_amount = frm.doc.payment_type=="Receive" ?
 				frm.doc.paid_amount : frm.doc.received_amount;
 
-			var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
-				function(d) { return flt(d.amount) }));
-
 			if(frm.doc.total_allocated_amount < party_amount) {
 				if(frm.doc.payment_type == "Receive") {
 					unallocated_amount = party_amount - (frm.doc.total_allocated_amount - total_deductions);
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 56db392..ba5b7f2 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -309,14 +309,16 @@
 
 		rows = []
 		for d in data:
-			rows.append(d[self.ageing_col_idx_start : self.ageing_col_idx_start+4])
-
-		if rows:
-			rows.insert(0, [[d.get("label")] for d in ageing_columns])
+			rows.append(
+				{
+					'values': d[self.ageing_col_idx_start : self.ageing_col_idx_start+4]
+				}
+			)
 
 		return {
 			"data": {
-				'labels': rows
+				'labels': [d.get("label") for d in ageing_columns],
+				'datasets': rows
 			},
 			"type": 'percentage'
 		}
diff --git a/erpnext/config/projects.py b/erpnext/config/projects.py
index a8514b2..b97e097 100644
--- a/erpnext/config/projects.py
+++ b/erpnext/config/projects.py
@@ -15,6 +15,7 @@
 				{
 					"type": "doctype",
 					"name": "Task",
+					"route": "Tree/Task",
 					"description": _("Project activity / task."),
 				},
 				{
diff --git a/erpnext/hr/doctype/employee_loan/employee_loan.js b/erpnext/hr/doctype/employee_loan/employee_loan.js
index 33d3a85..1f38105 100644
--- a/erpnext/hr/doctype/employee_loan/employee_loan.js
+++ b/erpnext/hr/doctype/employee_loan/employee_loan.js
@@ -87,22 +87,24 @@
 	},
 
 	employee_loan_application: function (frm) {
-		return frappe.call({
-			method: "erpnext.hr.doctype.employee_loan.employee_loan.get_employee_loan_application",
-			args: {
-				"employee_loan_application": frm.doc.employee_loan_application
-			},
-			callback: function (r) {
-				if (!r.exc && r.message) {
-					frm.set_value("loan_type", r.message.loan_type);
-					frm.set_value("loan_amount", r.message.loan_amount);
-					frm.set_value("repayment_method", r.message.repayment_method);
-					frm.set_value("monthly_repayment_amount", r.message.repayment_amount);
-					frm.set_value("repayment_periods", r.message.repayment_periods);
-					frm.set_value("rate_of_interest", r.message.rate_of_interest);
-				}
-			}
-		})
+	    if(frm.doc.employee_loan_application){
+            return frappe.call({
+                method: "erpnext.hr.doctype.employee_loan.employee_loan.get_employee_loan_application",
+                args: {
+                    "employee_loan_application": frm.doc.employee_loan_application
+                },
+                callback: function (r) {
+                    if (!r.exc && r.message) {
+                        frm.set_value("loan_type", r.message.loan_type);
+                        frm.set_value("loan_amount", r.message.loan_amount);
+                        frm.set_value("repayment_method", r.message.repayment_method);
+                        frm.set_value("monthly_repayment_amount", r.message.repayment_amount);
+                        frm.set_value("repayment_periods", r.message.repayment_periods);
+                        frm.set_value("rate_of_interest", r.message.rate_of_interest);
+                    }
+                }
+            });
+        }
 	},
 
 	repayment_method: function (frm) {
diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py
index 3216f0d..76a7f6f 100644
--- a/erpnext/hub_node/__init__.py
+++ b/erpnext/hub_node/__init__.py
@@ -37,7 +37,9 @@
 	return response
 
 @frappe.whitelist()
-def get_item_details(hub_sync_id):
+def get_item_details(hub_sync_id=None):
+	if not hub_sync_id:
+		return
 	connection = get_connection()
 	return connection.get_doc('Hub Item', hub_sync_id)
 
diff --git a/erpnext/hub_node/page/hub/hub.js b/erpnext/hub_node/page/hub/hub.js
index b065f0a..15bd97d 100644
--- a/erpnext/hub_node/page/hub/hub.js
+++ b/erpnext/hub_node/page/hub/hub.js
@@ -382,6 +382,7 @@
 			},
 			method: "erpnext.hub_node.get_item_details",
 			callback: (r) => {
+				if (!r || !r.message) return;
 				let item = r.message;
 				this.item_cache[item_code] = item;
 				this.render_item_page(item);
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js
index c3e6f20..226ebfc 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.js
+++ b/erpnext/manufacturing/doctype/production_order/production_order.js
@@ -17,6 +17,14 @@
 			}
 		});
 		
+		frm.set_query("source_warehouse", function() {
+			return {
+				filters: {
+					'company': frm.doc.company,
+				}
+			}
+		});
+		
 		frm.set_query("source_warehouse", "required_items", function() {
 			return {
 				filters: {
diff --git a/erpnext/manufacturing/page/production_analytics/production_analytics.js b/erpnext/manufacturing/page/production_analytics/production_analytics.js
index 39168b7..efbd0a5 100644
--- a/erpnext/manufacturing/page/production_analytics/production_analytics.js
+++ b/erpnext/manufacturing/page/production_analytics/production_analytics.js
@@ -64,7 +64,7 @@
 
 		var chart_data = this.get_chart_data ? this.get_chart_data() : null;
 
-		this.chart = new frappe.chart.FrappeChart({
+		this.chart = new Chart({
 			parent: ".chart",
 			data: chart_data,
 			type: 'line'
diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js
index df38cfe..b8f324a 100644
--- a/erpnext/projects/doctype/task/task.js
+++ b/erpnext/projects/doctype/task/task.js
@@ -19,38 +19,47 @@
 	},
 
 	refresh: function(frm) {
-		var doc = frm.doc;
-		if(doc.__islocal) {
-			if(!frm.doc.exp_end_date) {
-				frm.set_value("exp_end_date", frappe.datetime.add_days(new Date(), 7));
+		frm.fields_dict['parent_task'].get_query = function() {
+			return {
+				filters: {
+					"is_group": 1,
+				}
 			}
 		}
-
-		if(!doc.__islocal) {
-			if(frappe.model.can_read("Timesheet")) {
-				frm.add_custom_button(__("Timesheet"), function() {
-					frappe.route_options = {"project": doc.project, "task": doc.name}
-					frappe.set_route("List", "Timesheet");
-				}, __("View"), true);
-			}
-			if(frappe.model.can_read("Expense Claim")) {
-				frm.add_custom_button(__("Expense Claims"), function() {
-					frappe.route_options = {"project": doc.project, "task": doc.name}
-					frappe.set_route("List", "Expense Claim");
-				}, __("View"), true);
+		if(!frm.is_group){
+			var doc = frm.doc;
+			if(doc.__islocal) {
+				if(!frm.doc.exp_end_date) {
+					frm.set_value("exp_end_date", frappe.datetime.add_days(new Date(), 7));
+				}
 			}
 
-			if(frm.perm[0].write) {
-				if(frm.doc.status!=="Closed" && frm.doc.status!=="Cancelled") {
-					frm.add_custom_button(__("Close"), function() {
-						frm.set_value("status", "Closed");
-						frm.save();
-					});
-				} else {
-					frm.add_custom_button(__("Reopen"), function() {
-						frm.set_value("status", "Open");
-						frm.save();
-					});
+			if(!doc.__islocal) {
+				if(frappe.model.can_read("Timesheet")) {
+					frm.add_custom_button(__("Timesheet"), function() {
+						frappe.route_options = {"project": doc.project, "task": doc.name}
+						frappe.set_route("List", "Timesheet");
+					}, __("View"), true);
+				}
+				if(frappe.model.can_read("Expense Claim")) {
+					frm.add_custom_button(__("Expense Claims"), function() {
+						frappe.route_options = {"project": doc.project, "task": doc.name}
+						frappe.set_route("List", "Expense Claim");
+					}, __("View"), true);
+				}
+
+				if(frm.perm[0].write) {
+					if(frm.doc.status!=="Closed" && frm.doc.status!=="Cancelled") {
+						frm.add_custom_button(__("Close"), function() {
+							frm.set_value("status", "Closed");
+							frm.save();
+						});
+					} else {
+						frm.add_custom_button(__("Reopen"), function() {
+							frm.set_value("status", "Open");
+							frm.save();
+						});
+					}
 				}
 			}
 		}
@@ -71,6 +80,21 @@
 		}
 	},
 
+	is_group: function(frm) {
+		frappe.call({
+			method:"erpnext.projects.doctype.task.task.check_if_child_exists",
+			args: {
+				name: frm.doc.name
+			},
+			callback: function(r){
+				if(r.message){
+					frappe.msgprint(__('Cannot convert it to non-group. Child Tasks exist.'));
+					frm.reload_doc();
+				}
+			}
+		})
+	},
+
 	validate: function(frm) {
 		frm.doc.project && frappe.model.remove_from_locals("Project",
 			frm.doc.project);
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index e4ab5a7..41950a3 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -3,7 +3,7 @@
  "allow_guest_to_view": 0, 
  "allow_import": 1, 
  "allow_rename": 1, 
- "autoname": "TASK.#####", 
+ "autoname": "field:subject", 
  "beta": 0, 
  "creation": "2013-01-29 19:25:50", 
  "custom": 0, 
@@ -30,9 +30,8 @@
    "label": "Subject", 
    "length": 0, 
    "no_copy": 0, 
-   "oldfieldname": "subject", 
-   "oldfieldtype": "Data", 
    "permlevel": 0, 
+   "precision": "", 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
    "read_only": 0, 
@@ -78,6 +77,37 @@
   {
    "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
+   "bold": 1, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "default": "0", 
+   "fieldname": "is_group", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 1, 
+   "in_standard_filter": 0, 
+   "label": "Is Group", 
+   "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
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
@@ -173,9 +203,42 @@
   {
    "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
-   "bold": 0, 
+   "bold": 1, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "parent_task", 
+   "fieldtype": "Link", 
+   "hidden": 0, 
+   "ignore_user_permissions": 1, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Parent Task", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Task", 
+   "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": 1, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "collapsible_depends_on": "", 
+   "columns": 0, 
+   "depends_on": "", 
    "fieldname": "section_break_10", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -205,6 +268,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "exp_start_date", 
    "fieldtype": "Date", 
    "hidden": 0, 
@@ -237,6 +301,7 @@
    "collapsible": 0, 
    "columns": 0, 
    "default": "0", 
+   "depends_on": "", 
    "description": "", 
    "fieldname": "expected_time", 
    "fieldtype": "Float", 
@@ -269,6 +334,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "task_weight", 
    "fieldtype": "Float", 
    "hidden": 0, 
@@ -328,6 +394,7 @@
    "bold": 1, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "exp_end_date", 
    "fieldtype": "Date", 
    "hidden": 0, 
@@ -359,6 +426,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "progress", 
    "fieldtype": "Percent", 
    "hidden": 0, 
@@ -389,6 +457,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "is_milestone", 
    "fieldtype": "Check", 
    "hidden": 0, 
@@ -418,7 +487,9 @@
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "collapsible_depends_on": "", 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "section_break0", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -449,6 +520,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "description", 
    "fieldtype": "Text Editor", 
    "hidden": 0, 
@@ -481,7 +553,9 @@
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "collapsible_depends_on": "", 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "section_break", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -512,6 +586,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "depends_on", 
    "fieldtype": "Table", 
    "hidden": 0, 
@@ -543,6 +618,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "depends_on_tasks", 
    "fieldtype": "Data", 
    "hidden": 1, 
@@ -572,7 +648,9 @@
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "collapsible_depends_on": "", 
    "columns": 0, 
+   "depends_on": "", 
    "description": "", 
    "fieldname": "actual", 
    "fieldtype": "Section Break", 
@@ -606,6 +684,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "act_start_date", 
    "fieldtype": "Date", 
    "hidden": 0, 
@@ -638,6 +717,7 @@
    "collapsible": 0, 
    "columns": 0, 
    "default": "", 
+   "depends_on": "", 
    "description": "", 
    "fieldname": "actual_time", 
    "fieldtype": "Float", 
@@ -699,6 +779,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "act_end_date", 
    "fieldtype": "Date", 
    "hidden": 0, 
@@ -730,6 +811,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "section_break_17", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -759,6 +841,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "total_costing_amount", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -791,6 +874,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "total_expense_claim", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -851,6 +935,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "", 
    "fieldname": "total_billing_amount", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -1025,6 +1110,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": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
   }
  ], 
  "has_web_view": 0, 
@@ -1039,7 +1214,7 @@
  "istable": 0, 
  "max_attachments": 5, 
  "menu_index": 0, 
- "modified": "2017-05-23 11:28:28.161600", 
+ "modified": "2017-10-06 03:57:37.901446", 
  "modified_by": "Administrator", 
  "module": "Projects", 
  "name": "Task", 
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 52ae132..5b1bcaf 100644
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -5,13 +5,14 @@
 import frappe, json
 
 from frappe.utils import getdate, date_diff, add_days, cstr
-from frappe import _
-
-from frappe.model.document import Document
+from frappe import _, throw
+from frappe.utils.nestedset import NestedSet, rebuild_tree
 
 class CircularReferenceError(frappe.ValidationError): pass
 
-class Task(Document):
+class Task(NestedSet):
+	nsm_parent_field = 'parent_task'
+
 	def get_feed(self):
 		return '{0}: {1}'.format(_(self.status), self.subject)
 
@@ -59,7 +60,11 @@
 				depends_on_tasks += d.task + ","
 		self.depends_on_tasks = depends_on_tasks
 
+	def update_nsm_model(self):
+		frappe.utils.nestedset.update_nsm(self)
+
 	def on_update(self):
+		self.update_nsm_model()
 		self.check_recursion()
 		self.reschedule_dependent_tasks()
 		self.update_project()
@@ -105,16 +110,20 @@
 						frappe.throw(_("Circular Reference Error"), CircularReferenceError)
 					if b[0]:
 						task_list.append(b[0])
+
 				if count == 15:
 					break
 
 	def reschedule_dependent_tasks(self):
 		end_date = self.exp_end_date or self.act_end_date
 		if end_date:
-			for task_name in frappe.db.sql("""select name from `tabTask` as parent where parent.project = %(project)s and parent.name in \
-				(select parent from `tabTask Depends On` as child where child.task = %(task)s and child.project = %(project)s)""",
-				{'project': self.project, 'task':self.name }, as_dict=1):
-
+			for task_name in frappe.db.sql("""
+				select name from `tabTask` as parent 
+				where parent.project = %(project)s 
+					and parent.name in (
+						select parent from `tabTask Depends On` as child 
+						where child.task = %(task)s and child.project = %(project)s)
+			""", {'project': self.project, 'task':self.name }, as_dict=1):
 				task = frappe.get_doc("Task", task_name.name)
 				if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open":
 					task_duration = date_diff(task.exp_end_date, task.exp_start_date)
@@ -128,6 +137,17 @@
 		if project_user:
 			return True
 
+	def on_trash(self):
+		if check_if_child_exists(self.name):
+			throw(_("Child Task exists for this Task. You can not delete this Task."))
+
+		self.update_nsm_model()
+
+@frappe.whitelist()
+def check_if_child_exists(name):
+	return frappe.db.sql("""select name from `tabTask`
+		where parent_task = %s""", name)
+
 @frappe.whitelist()
 def get_events(start, end, filters=None):
 	"""Returns events for Gantt / Calendar view rendering.
@@ -177,4 +197,48 @@
 		and exp_end_date < CURDATE()
 		and `status` not in ('Closed', 'Cancelled')""")
 
+@frappe.whitelist()
+def get_children():
+	doctype = frappe.local.form_dict.get('doctype')
 
+	parent_field = 'parent_' + doctype.lower().replace(' ', '_')
+	parent = frappe.form_dict.get("parent") or ""
+
+	if parent == "task":
+		parent = ""
+
+	tasks = frappe.db.sql("""select name as value,
+		is_group as expandable
+		from `tab{doctype}`
+		where docstatus < 2
+		and ifnull(`{parent_field}`,'') = %s
+		order by name""".format(doctype=frappe.db.escape(doctype),
+		parent_field=frappe.db.escape(parent_field)), (parent), as_dict=1)
+
+	# return tasks
+	return tasks
+
+@frappe.whitelist()
+def add_node():
+    from frappe.desk.treeview import make_tree_args
+    args = frappe.form_dict
+    args.update({
+    	"name_field": "subject"
+    })
+    args = make_tree_args(**args)
+
+    if args.parent_task == 'task':
+        args.parent_task = None
+
+    frappe.get_doc(args).insert()
+
+@frappe.whitelist()
+def add_multiple_tasks(data, parent):
+    data = json.loads(data)['tasks']
+    tasks = data.split('\n')
+    new_doc = {'doctype': 'Task', 'parent_task': parent}
+
+    for d in tasks:
+        new_doc['subject'] = d
+        new_task = frappe.get_doc(new_doc)
+        new_task.insert()
diff --git a/erpnext/projects/doctype/task/task_tree.js b/erpnext/projects/doctype/task/task_tree.js
new file mode 100644
index 0000000..f11c34f
--- /dev/null
+++ b/erpnext/projects/doctype/task/task_tree.js
@@ -0,0 +1,59 @@
+frappe.provide("frappe.treeview_settings");
+
+frappe.treeview_settings['Task'] = {
+	get_tree_nodes: "erpnext.projects.doctype.task.task.get_children",
+	add_tree_node: "erpnext.projects.doctype.task.task.add_node",
+	filters: [
+		{
+			fieldname: "task",
+			fieldtype:"Link",
+			options: "Task",
+			label: __("Task"),
+			get_query: function(){
+				return {
+					filters: [["Task", 'is_group', '=', 1]]
+				};
+			}
+		}
+	],
+	title: "Task",
+	breadcrumb: "Projects",
+	get_tree_root: false,
+	root_label: "task",
+	ignore_fields:["parent_task"],
+	get_label: function(node) {
+		return node.data.value;
+	},
+	onload: function(me){
+		me.make_tree();
+		me.set_root = true;
+	},
+	toolbar: [
+		{
+			label:__("Add Multiple"),
+			condition: function(node) {
+				return node.expandable;
+			},
+			click: function(node) {
+				var d = new frappe.ui.Dialog({
+					'fields': [
+						{'fieldname': 'tasks', 'label': 'Tasks', 'fieldtype': 'Text'},
+					],
+					primary_action: function(){
+						d.hide();
+						return frappe.call({
+							method: "erpnext.projects.doctype.task.task.add_multiple_tasks",
+							args: {
+								data: d.get_values(),
+								parent: node.data.value
+							},
+							callback: function() { }
+						});
+					}
+				});
+				d.show();
+			}
+		}
+	],
+	extend_toolbar: true
+};
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/test_records.json b/erpnext/projects/doctype/task/test_records.json
deleted file mode 100644
index 42ca0e7..0000000
--- a/erpnext/projects/doctype/task/test_records.json
+++ /dev/null
@@ -1,15 +0,0 @@
-[
- {
-  "status": "Open", 
-  "subject": "_Test Task",
-  "name": "task001"
- }, 
- {
-  "status": "Open", 
-  "subject": "_Test Task 1"
- }, 
- {
-  "status": "Open", 
-  "subject": "_Test Task 2"
- }
-]
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py
index 2e64b73..1d94618 100644
--- a/erpnext/projects/doctype/task/test_task.py
+++ b/erpnext/projects/doctype/task/test_task.py
@@ -5,137 +5,61 @@
 import unittest
 from frappe.utils import getdate, nowdate, add_days
 
-# test_records = frappe.get_test_records('Task')
-
 from erpnext.projects.doctype.task.task import CircularReferenceError
 
 class TestTask(unittest.TestCase):
 	def test_circular_reference(self):
+		task1 = create_task("_Test Task 1", nowdate(), add_days(nowdate(), 10))
+		task2 = create_task("_Test Task 2", add_days(nowdate(), 11), add_days(nowdate(), 15), task1.name)
+		task3 = create_task("_Test Task 3", add_days(nowdate(), 11), add_days(nowdate(), 15), task2.name)
 
-		task1 =  frappe.new_doc('Task')
-		task1.update({
-			"status": "Open",
-			"subject": "_Test Task 1",
-			"project": "_Test Project",
-			"exp_start_date": "2015-1-1",
-			"exp_end_date": "2015-1-10"
-		})
-		task1.save()
-
-		task2 =  frappe.new_doc('Task')
-		task2.update({
-			"status": "Open",
-			"subject": "_Test Task 2",
-			"project": "_Test Project",
-			"exp_start_date": "2015-1-11",
-			"exp_end_date": "2015-1-15",
-			"depends_on":[
-				{
-					"task": task1.name
-				}
-			]
-		})
-		task2.save()
-
-		task3 =  frappe.new_doc('Task')
-		task3.update({
-			"status": "Open",
-			"subject": "_Test Task 2",
-			"project": "_Test Project",
-			"exp_start_date": "2015-1-11",
-			"exp_end_date": "2015-1-15",
-			"depends_on":[
-				{
-					"task": task2.name
-				}
-			]
-		})
-		task3.save()
-
+		task1.reload()
 		task1.append("depends_on", {
 			"task": task3.name
 		})
+
 		self.assertRaises(CircularReferenceError, task1.save)
 
 		task1.set("depends_on", [])
 		task1.save()
 
-		task4 =  frappe.new_doc('Task')
-		task4.update({
-			"status": "Open",
-			"subject": "_Test Task 1",
-			"exp_start_date": "2015-1-1",
-			"exp_end_date": "2015-1-15",
-			"depends_on":[
-				{
-					"task": task1.name
-				}
-			]
-		})
-		task4.save()
+		task4 = create_task("_Test Task 4", nowdate(), add_days(nowdate(), 15), task1.name)
 
 		task3.append("depends_on", {
 			"task": task4.name
 		})
 
 	def test_reschedule_dependent_task(self):
-		task1 =  frappe.new_doc('Task')
-		task1.update({
-			"status": "Open",
-			"subject": "_Test Task 1",
-			"project": "_Test Project",
-			"exp_start_date": "2015-1-1",
-			"exp_end_date": "2015-1-10"
-		})
-		task1.save()
+		task1 = create_task("_Test Task 1", nowdate(), add_days(nowdate(), 10))
 
-		task2 =  frappe.new_doc('Task')
-		task2.update({
-			"status": "Open",
-			"subject": "_Test Task 2",
-			"project": "_Test Project",
-			"exp_start_date": "2015-1-11",
-			"exp_end_date": "2015-1-15",
-			"depends_on":[
-				{
-					"task": task1.name,
-					"project": "_Test Project"
-				}
-			]
-		})
+		task2 = create_task("_Test Task 2", add_days(nowdate(), 11), add_days(nowdate(), 15), task1.name)
+		task2.get("depends_on")[0].project = "_Test Project"
 		task2.save()
 
-		task3 =  frappe.new_doc('Task')
-		task3.update({
-			"status": "Open",
-			"subject": "_Test Task 3",
-			"project": "_Test Project",
-			"exp_start_date": "2015-1-16",
-			"exp_end_date": "2015-1-18",
-			"depends_on":[
-				{
-					"task": task2.name,
-					"project": "_Test Project"
-				}
-			]
-		})
+		task3 = create_task("_Test Task 3", add_days(nowdate(), 11), add_days(nowdate(), 15), task2.name)
+		task3.get("depends_on")[0].project = "_Test Project"
 		task3.save()
 
 		task1.update({
-			"exp_end_date": "2015-1-20"
+			"exp_end_date": add_days(nowdate(), 20)
 		})
 		task1.save()
 
-		self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
-		self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
+		self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"),
+			getdate(add_days(nowdate(), 21)))
+		self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"),
+			getdate(add_days(nowdate(), 25)))
 
-		self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
-		self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
+		self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"),
+			getdate(add_days(nowdate(), 26)))
+		self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"),
+			getdate(add_days(nowdate(), 30)))
 
 	def test_close_assignment(self):
-		task = frappe.new_doc("Task")
-		task.subject = "Test Close Assignment"
-		task.insert()
+		if not frappe.db.exists("Task", "Test Close Assignment"):
+			task = frappe.new_doc("Task")
+			task.subject = "Test Close Assignment"
+			task.insert()
 
 		def assign():
 			from frappe.desk.form import assign_to
@@ -147,8 +71,10 @@
 			})
 
 		def get_owner_and_status():
-			return frappe.db.get_value("ToDo", filters={"reference_type": task.doctype, "reference_name": task.name,
-					"description": "Close this task"}, fieldname=("owner", "status"), as_dict=True)
+			return frappe.db.get_value("ToDo",
+				filters={"reference_type": task.doctype, "reference_name": task.name,
+					"description": "Close this task"},
+				fieldname=("owner", "status"), as_dict=True)
 
 		assign()
 		todo = get_owner_and_status()
@@ -164,16 +90,29 @@
 		self.assertEquals(todo.status, "Closed")
 
 	def test_overdue(self):
-		task = frappe.get_doc({
-			"doctype":"Task",
-			"subject": "Testing Overdue",
-			"status": "Open",
-			"exp_end_date": add_days(nowdate(), -1)
-		})
-
-		task.insert()
+		task = create_task("Testing Overdue", add_days(nowdate(), -10), add_days(nowdate(), -5))
 
 		from erpnext.projects.doctype.task.task import set_tasks_as_overdue
 		set_tasks_as_overdue()
 
 		self.assertEquals(frappe.db.get_value("Task", task.name, "status"), "Overdue")
+
+def create_task(subject, start=None, end=None, depends_on=None, project=None):
+	if not frappe.db.exists("Task", subject):
+		task = frappe.new_doc('Task')
+		task.status = "Open"
+		task.subject = subject
+		task.exp_start_date = start or nowdate()
+		task.exp_end_date = end or nowdate()
+		task.project = project or "_Test Project"
+		task.save()
+	else:
+		task = frappe.get_doc("Task", subject)
+
+	if depends_on:
+		task.append("depends_on", {
+			"task": depends_on
+		})
+		task.save()
+
+	return task
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/test_task.js b/erpnext/projects/doctype/task/tests/test_task.js
similarity index 100%
rename from erpnext/projects/doctype/task/test_task.js
rename to erpnext/projects/doctype/task/tests/test_task.js
diff --git a/erpnext/projects/doctype/task/tests/test_task_tree.js b/erpnext/projects/doctype/task/tests/test_task_tree.js
new file mode 100644
index 0000000..9cbcf85
--- /dev/null
+++ b/erpnext/projects/doctype/task/tests/test_task_tree.js
@@ -0,0 +1,99 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Task Tree", function (assert) {
+	let done = assert.async();
+
+	// number of asserts
+	assert.expect(5);
+
+	frappe.run_serially([
+		// insert a new Task
+		() => frappe.set_route('Tree', 'Task'),
+		() => frappe.timeout(0.5),
+
+		// Checking adding child without selecting any Node
+		() => frappe.tests.click_button('New'),
+		() => frappe.timeout(0.5),
+		() => {assert.equal($(`.msgprint`).text(), "Select a group node first.", "Error message success");},
+		() => frappe.tests.click_button('Close'),
+		() => frappe.timeout(0.5),
+
+		// Creating child nodes
+		() => frappe.tests.click_link('task'),
+		() => frappe.map_group.make('Test-1'),
+		() => frappe.map_group.make('Test-2'),
+		() => frappe.map_group.make('Test-3', 1),
+		() => frappe.timeout(1),
+		() => frappe.tests.click_link('Test-3'),
+		() => frappe.map_group.make('Test-4', 0),
+
+		// Checking Edit button
+		() => frappe.timeout(0.5),
+		() => frappe.tests.click_link('Test-1'),
+		() => frappe.tests.click_button('Edit'),
+		() => frappe.timeout(0.5),
+		() => {assert.deepEqual(frappe.get_route(), ["Form", "Task", "Test-1"], "Edit route checks");},
+
+		// Deleting child Node
+		() => frappe.set_route('Tree', 'Task'),
+		() => frappe.timeout(0.5),
+		() => frappe.tests.click_link('Test-1'),
+		() => frappe.tests.click_button('Delete'),
+		() => frappe.timeout(0.5),
+		() => frappe.tests.click_button('Yes'),
+
+		// Deleting Group Node that has child nodes in it
+		() => frappe.timeout(0.5),
+		() => frappe.tests.click_link('Test-3'),
+		() => frappe.tests.click_button('Delete'),
+		() => frappe.timeout(0.5),
+		() => frappe.tests.click_button('Yes'),
+		() => frappe.timeout(1),
+		() => {assert.equal(cur_dialog.title, 'Message', 'Error thrown correctly');},
+		() => frappe.tests.click_button('Close'),
+
+		// Renaming Child node
+		() => frappe.timeout(0.5),
+		() => frappe.tests.click_link('Test-2'),
+		() => frappe.tests.click_button('Rename'),
+		() => frappe.timeout(1),
+		() => cur_dialog.set_value('new_name', 'Test-5'),
+		() => frappe.timeout(1.5),
+		() => cur_dialog.get_primary_btn().click(),
+		() => frappe.timeout(1),
+		() => {assert.equal($(`a:contains("Test-5"):visible`).length, 1, 'Rename successfull');},
+
+		// Add multiple child tasks
+		() => frappe.tests.click_link('Test-3'),
+		() => frappe.timeout(0.5),
+		() => frappe.click_button('Add Multiple'),
+		() => frappe.timeout(1),
+		() => cur_dialog.set_value('tasks', 'Test-6\nTest-7'),
+		() => frappe.timeout(0.5),
+		() => frappe.click_button('Submit'),
+		() => frappe.timeout(2),
+		() => frappe.click_button('Expand All'),
+		() => frappe.timeout(1),
+		() => {
+			let count = $(`a:contains("Test-6"):visible`).length + $(`a:contains("Test-7"):visible`).length;
+			assert.equal(count, 2, "Multiple Tasks added successfully");
+		},
+
+		() => done()
+	]);
+});
+
+frappe.map_group = {
+	make:function(subject, is_group = 0){
+		return frappe.run_serially([
+			() => frappe.click_button('Add Child'),
+			() => frappe.timeout(1),
+			() => cur_dialog.set_value('is_group', is_group),
+			() => cur_dialog.set_value('subject', subject),
+			() => frappe.click_button('Create New'),
+			() => frappe.timeout(1.5)
+		]);
+	}
+};
diff --git a/erpnext/public/js/pos/pos_selected_item.html b/erpnext/public/js/pos/pos_selected_item.html
index 6f2772b..085e048 100644
--- a/erpnext/public/js/pos/pos_selected_item.html
+++ b/erpnext/public/js/pos/pos_selected_item.html
@@ -8,7 +8,7 @@
 		<input type="tel" class="form-control cell" disabled value="{%= price_list_rate %}"/>
 	</div>
 	<div class="pos-list-row">
-		<div class="cell">{{ __("Discount") }}:</div>
+		<div class="cell">{{ __("Discount") }}: %</div>
 		<input type="tel" class="form-control cell pos-item-disc" value="{%= discount_percentage %}">
 	</div>
 	<div class="pos-list-row">
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index efb04d1..47cda19 100644
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -38,7 +38,7 @@
 	},
 
 	stale_rate_allowed: () => {
-		return cint(frappe.boot.sysdefaults.allow_stale) || 1;
+		return cint(frappe.boot.sysdefaults.allow_stale);
 	},
 
 	setup_serial_no: function() {
diff --git a/erpnext/schools/doctype/student_applicant/student_applicant.py b/erpnext/schools/doctype/student_applicant/student_applicant.py
index 465b4e4..d0db658 100644
--- a/erpnext/schools/doctype/student_applicant/student_applicant.py
+++ b/erpnext/schools/doctype/student_applicant/student_applicant.py
@@ -13,9 +13,12 @@
 		from frappe.model.naming import set_name_by_naming_series
 		if self.student_admission:
 			if self.program:
+				# set the naming series from the student admission if provided.
 				student_admission = get_student_admission_data(self.student_admission, self.program)
 				if student_admission:
 					naming_series = student_admission.get("applicant_naming_series")
+				else:
+					naming_series = None
 			else:
 				frappe.throw(_("Select the program first"))
 
@@ -40,15 +43,16 @@
 
 	def validation_from_student_admission(self):
 		student_admission = get_student_admission_data(self.student_admission, self.program)
-		if student_admission:
-			if ((
-					student_admission.minimum_age
-					and getdate(student_admission.minimum_age) > getdate(self.date_of_birth)
-				) or (
-					student_admission.maximum_age
-					and getdate(student_admission.maximum_age) < getdate(self.date_of_birth)
-				)):
-					frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+
+		# different validation for minimum and maximum age so that either min/max can also work independently.
+		if student_admission and student_admission.minimum_age and \
+			getdate(student_admission.minimum_age) < getdate(self.date_of_birth):
+				frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+
+		if student_admission and student_admission.maximum_age and \
+			getdate(student_admission.maximum_age) > getdate(self.date_of_birth):
+				frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+
 
 	def on_payment_authorized(self, *args, **kwargs):
 		self.db_set('paid', 1)
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index fa21a3d..747a3c0 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -477,9 +477,11 @@
 		target.qty = flt(source.qty) - flt(source.delivered_qty)
 
 		item = frappe.db.get_value("Item", target.item_code, ["item_group", "selling_cost_center"], as_dict=1)
-		target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \
-			or item.selling_cost_center \
-			or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
+
+		if item:
+			target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \
+				or item.selling_cost_center \
+				or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
 
 	target_doc = get_mapped_doc("Sales Order", source_name, {
 		"Sales Order": {
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index 2080224..e940f94 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -4,6 +4,7 @@
 
 from __future__ import unicode_literals
 import frappe
+from frappe.utils import cint
 
 def boot_session(bootinfo):
 	"""boot session - send website info if guest"""
@@ -19,8 +20,8 @@
 			'territory')
 		bootinfo.sysdefaults.customer_group = frappe.db.get_single_value('Selling Settings',
 			'customer_group')
-		bootinfo.sysdefaults.allow_stale = frappe.db.get_single_value('Accounts Settings',
-			'allow_stale') or 1
+		bootinfo.sysdefaults.allow_stale = cint(frappe.db.get_single_value('Accounts Settings',
+			'allow_stale'))
 
 		bootinfo.notification_settings = frappe.get_doc("Notification Control",
 			"Notification Control")
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 4b4f15b..bf8eaba 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -57,7 +57,7 @@
 		if not self.description:
 			self.description = self.item_name
 
-		if self.is_sales_item and not self.is_item_from_hub:
+		if self.is_sales_item and not self.get('is_item_from_hub'):
 			self.publish_in_hub = 1
 
 	def after_insert(self):
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index ab73b34..6a18f11 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -32,7 +32,7 @@
 		if email_id:
 			if not self.lead:
 				self.lead = frappe.db.get_value("Lead", {"email_id": email_id})
-			if not self.contact:
+			if not self.contact and not self.customer:
 				self.contact = frappe.db.get_value("Contact", {"email_id": email_id})
 
 				if self.contact:
diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt
index e7de604..24858f3 100644
--- a/erpnext/tests/ui/tests.txt
+++ b/erpnext/tests/ui/tests.txt
@@ -133,3 +133,4 @@
 erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.js
 erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.js
 erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js
+erpnext/projects/doctype/task/tests/test_task_tree.js