[Fix] While saving project getting timeout error (#14672)

diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 44b0c00..1f4bd00 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -4,7 +4,7 @@
 from __future__ import unicode_literals
 import frappe
 
-from frappe.utils import flt, getdate, get_url
+from frappe.utils import flt, getdate, get_url, now
 from frappe import _
 
 from frappe.model.document import Document
@@ -60,6 +60,7 @@
 		self.validate_weights()
 		self.sync_tasks()
 		self.tasks = []
+		self.load_tasks()
 		self.send_welcome_email()
 
 	def validate_project_name(self):
@@ -83,36 +84,68 @@
 		"""sync tasks and remove table"""
 		if self.flags.dont_sync_tasks: return
 		task_names = []
+
+		existing_task_data = {}
+		for d in frappe.get_all('Project Task',
+			fields = ["title", "status", "start_date", "end_date", "description", "task_weight", "task_id"],
+			filters = {'parent': self.name}):
+			existing_task_data.setdefault(d.task_id, d)
+
 		for t in self.tasks:
 			if t.task_id:
 				task = frappe.get_doc("Task", t.task_id)
 			else:
 				task = frappe.new_doc("Task")
 				task.project = self.name
-			task.update({
-				"subject": t.title,
-				"status": t.status,
-				"exp_start_date": t.start_date,
-				"exp_end_date": t.end_date,
-				"description": t.description,
-				"task_weight": t.task_weight
-			})
 
-			self.map_custom_fields(t, task)
+			if not t.task_id or self.is_row_updated(t, existing_task_data):
+				task.update({
+					"subject": t.title,
+					"status": t.status,
+					"exp_start_date": t.start_date,
+					"exp_end_date": t.end_date,
+					"description": t.description,
+					"task_weight": t.task_weight
+				})
 
-			task.flags.ignore_links = True
-			task.flags.from_project = True
-			task.flags.ignore_feed = True
-			task.save(ignore_permissions = True)
-			task_names.append(task.name)
+				self.map_custom_fields(t, task)
+
+				task.flags.ignore_links = True
+				task.flags.from_project = True
+				task.flags.ignore_feed = True
+
+				if t.task_id:
+					task.update({
+						"modified_by": frappe.session.user,
+						"modified": now()
+					})
+
+					task.validate()
+					task.db_update()
+				else:
+					task.save(ignore_permissions = True)
+				task_names.append(task.name)
+			else:
+				task_names.append(task.name)
 
 		# delete
 		for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}):
 			frappe.delete_doc("Task", t.name)
 
+	def update_costing_and_percentage_complete(self):
 		self.update_percent_complete()
 		self.update_costing()
 
+	def is_row_updated(self, row, existing_task_data):
+		if self.get("__islocal") or not existing_task_data: return True
+
+		d = existing_task_data.get(row.task_id)
+
+		if (d and (row.title != d.title or row.status != d.status
+			or getdate(row.start_date) != getdate(d.start_date) or getdate(row.end_date) != getdate(d.end_date)
+			or row.description != d.description or row.task_weight != d.task_weight)):
+			return True
+
 	def map_custom_fields(self, source, target):
 		project_task_custom_fields = frappe.get_all("Custom Field", {"dt": "Project Task"}, "fieldname")
 
@@ -207,6 +240,9 @@
 
 		self.total_billed_amount = total_billed_amount and total_billed_amount[0][0] or 0
 
+	def after_rename(self, old_name, new_name, merge=False):
+		if old_name == self.copied_from:
+			frappe.db.set_value('Project', new_name, 'copied_from', new_name)
 
 	def send_welcome_email(self):
 		url = get_url("/project/?name={0}".format(self.name))
@@ -227,8 +263,7 @@
 				user.welcome_email_sent=1
 
 	def on_update(self):
-		self.load_tasks()
-		self.sync_tasks()
+		self.update_costing_and_percentage_complete()
 		self.update_dependencies_on_duplicated_project()
 
 	def update_dependencies_on_duplicated_project(self):
@@ -251,9 +286,7 @@
 					continue
 
 				name = _task.name
-				depends_on_tasks = _task.depends_on_tasks
 
-				depends_on_tasks = [x for x in depends_on_tasks.split(',') if x]
 				dependency_map[task.title] = [ x['subject'] for x in frappe.get_list(
 					'Task Depends On', {"parent": name}, ['subject'])]
 
@@ -264,7 +297,8 @@
 				for dt in value:
 					dt_name = frappe.db.get_value('Task', {"subject": dt, "project": self.name })
 					task_doc.append('depends_on', {"task": dt_name})
-				task_doc.save()
+
+				task_doc.update_db()
 
 def get_timeline_data(doctype, name):
 	'''Return timeline for attendance'''
diff --git a/erpnext/projects/doctype/project_task/project_task.json b/erpnext/projects/doctype/project_task/project_task.json
index 79790e5..e5f28b7 100644
--- a/erpnext/projects/doctype/project_task/project_task.json
+++ b/erpnext/projects/doctype/project_task/project_task.json
@@ -371,7 +371,7 @@
    "remember_last_selected_value": 1, 
    "report_hide": 0, 
    "reqd": 0, 
-   "search_index": 0, 
+   "search_index": 1, 
    "set_only_once": 0, 
    "unique": 0
   }
@@ -386,7 +386,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2017-12-19 14:49:15.886339", 
+ "modified": "2018-06-26 11:46:50.890251", 
  "modified_by": "Administrator", 
  "module": "Projects", 
  "name": "Project Task", 
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index 8e72d03..cf524da 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -38,7 +38,7 @@
    "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 1, 
-   "search_index": 0, 
+   "search_index": 1, 
    "set_only_once": 0, 
    "unique": 0
   }, 
@@ -70,7 +70,7 @@
    "remember_last_selected_value": 1, 
    "report_hide": 0, 
    "reqd": 0, 
-   "search_index": 0, 
+   "search_index": 1, 
    "set_only_once": 0, 
    "unique": 0
   }, 
@@ -1214,7 +1214,7 @@
  "istable": 0, 
  "max_attachments": 5, 
  "menu_index": 0, 
- "modified": "2017-11-10 18:37:19.660293", 
+ "modified": "2018-06-26 11:46:06.678115", 
  "modified_by": "Administrator", 
  "module": "Projects", 
  "name": "Task",