Merge branch 'develop' into project-template-and-tasks
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 5bbd29c..04a0fb6 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -55,16 +55,34 @@
 
 			# create tasks from template
 			for task in template.tasks:
-				frappe.get_doc(dict(
-					doctype = 'Task',
-					subject = task.subject,
-					project = self.name,
-					status = 'Open',
-					exp_start_date = add_days(self.expected_start_date, task.start),
-					exp_end_date = add_days(self.expected_start_date, task.start + task.duration),
-					description = task.description,
-					task_weight = task.task_weight
-				)).insert()
+				template_task_details = frappe.get_doc("Task", task.task)
+				project_task = self.create_task_from_template(template_task_details)
+				
+				if template_task_details.depends_on:
+					for child_task in template_task_details.depends_on:
+						child_task_details = frappe.get_doc("Task",child_task.task)
+						self.create_task_from_template(child_task_details, project_task)
+
+	def create_task_from_template(self, task_details, project_task=None):
+		doc = frappe.get_doc(dict(
+				doctype = 'Task',
+				subject = task_details.subject,
+				project = self.name,
+				status = 'Open',
+				exp_start_date = add_days(self.expected_start_date, task_details.start),
+				exp_end_date = add_days(self.expected_start_date, task_details.start + task_details.duration),
+				description = task_details.description,
+				task_weight = task_details.task_weight,
+				type = task_details.type,
+				issue = task_details.issue,
+				is_group = task_details.is_group
+			))
+		if task_details.parent_task and project_task:
+			doc.parent_task = project_task.name
+		if not task_details.is_group:
+			doc.depends_on = task_details.depends_on
+		doc.insert()
+		return doc
 
 	def is_row_updated(self, row, existing_task_data, fields):
 		if self.get("__islocal") or not existing_task_data: return True
diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py
index 0c4f6f1..52f877b 100644
--- a/erpnext/projects/doctype/project/test_project.py
+++ b/erpnext/projects/doctype/project/test_project.py
@@ -7,33 +7,86 @@
 test_records = frappe.get_test_records('Project')
 test_ignore = ["Sales Order"]
 
-from erpnext.projects.doctype.project_template.test_project_template import get_project_template, make_project_template
+from erpnext.projects.doctype.project_template.test_project_template import make_project_template
 from erpnext.projects.doctype.project.project import set_project_status
-
-from frappe.utils import getdate
+from erpnext.projects.doctype.task.test_task import create_task
+from frappe.utils import getdate, nowdate, add_days
 
 class TestProject(unittest.TestCase):
-	def test_project_with_template(self):
-		frappe.db.sql('delete from tabTask where project = "Test Project with Template"')
-		frappe.delete_doc('Project', 'Test Project with Template')
+	def test_project_with_template_having_no_parent_and_depend_tasks(self):
+		""" 
+		Test Action: Basic Test of a Project created from template. The template has a single task.
+		"""
+		frappe.db.sql('delete from tabTask where project = "Test Project with Templ - no parent and dependend tasks"')
+		frappe.delete_doc('Project', 'Test Project with Templ - no parent and dependend tasks')
 
-		project = get_project('Test Project with Template')
+		if not frappe.db.exists("Task", "Test Temp Task with no parent and dependency"):
+			task1 = create_task(subject="Test Temp Task with no parent and dependency", is_template=1, begin=0, duration=3)
 
+		template = make_project_template("Test Project Template - no parent and dependend tasks", [task1])
+		project = get_project("Test Project with Templ - no parent and dependend tasks", template)
 		tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc')
 
-		task1 = tasks[0]
-		self.assertEqual(task1.subject, 'Task 1')
-		self.assertEqual(task1.description, 'Task 1 description')
-		self.assertEqual(getdate(task1.exp_start_date), getdate('2019-01-01'))
-		self.assertEqual(getdate(task1.exp_end_date), getdate('2019-01-04'))
+		self.assertEqual(tasks[0].subject, 'Test Temp Task with no parent and dependency')
+		self.assertEqual(getdate(tasks[0].exp_end_date), add_days(nowdate() + 0 + 3))
+		self.assertEqual(len(tasks), 1)
 
-		self.assertEqual(len(tasks), 4)
-		task4 = tasks[3]
-		self.assertEqual(task4.subject, 'Task 4')
-		self.assertEqual(getdate(task4.exp_end_date), getdate('2019-01-06'))
+	def test_project_template_having_parent_child_tasks(self):
 
-def get_project(name):
-	template = get_project_template()
+		frappe.db.sql('delete from tabTask where project = "Test Project with Templ - tasks with parent-child"')
+		frappe.delete_doc('Project', 'Test Project with Templ - tasks with parent-child')
+
+		if not frappe.db.exists("Task", "Test Temp Task parent"):
+			task1 = create_task(subject="Test Temp Task parent", is_group=1, is_template=1, begin=1, duration=1)
+
+		if not frappe.db.exists("Task", "Test Temp Task child 1"):
+			task2 = create_task(subject="Test Temp Task child 1", parent_task=task1.name, is_template=1, begin=1, duration=3)
+		
+		if not frappe.db.exists("Task", "Test Temp Task child 2"):
+			task3 = create_task(subject="Test Temp Task child 2", parent_task=task1.name, is_template=1, begin=2, duration=3)
+
+		template = make_project_template("Test Project Template  - tasks with parent-child", [task1, task2, task3])
+		project = get_project("Test Project with Templ - tasks with parent-child", template)
+		tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc')
+		print(tasks, type(tasks), len(tasks))
+		self.assertEqual(tasks[0].subject, 'Test Temp Task parent')
+		self.assertEqual(getdate(tasks[0].exp_end_date), add_days(nowdate()+ 1 + 1))
+
+		self.assertEqual(tasks[1].subject, 'Test Temp Task child 1')
+		self.assertEqual(getdate(tasks[1].exp_end_date), add_days(nowdate()+ 1 + 3))
+		self.assertEqual(tasks[1].parent_task, tasks[0].name)
+
+		self.assertEqual(tasks[2].subject, 'Test Temp Task child 2')
+		self.assertEqual(getdate(tasks[2].exp_end_date), add_days(nowdate()+ 2 + 3))
+		self.assertEqual(tasks[2].parent_task, tasks[0].name)
+
+		self.assertEqual(len(tasks), 3)
+
+	def test_project_template_having_dependent_tasks(self):
+
+		frappe.db.sql('delete from tabTask where project = "Test Project with Templ - dependent tasks"')
+		frappe.delete_doc('Project', 'Test Project with Templ - dependent tasks')
+
+		if not frappe.db.exists("Task", "Test Temp Task for dependency"):
+			task1 = create_task(subject="Test Temp Task for dependency", is_template=1, begin=3, duration=1)
+
+		if not frappe.db.exists("Task", "Test Temp Task with dependency"):
+			task2 = create_task(subject="Test Temp Task with dependency", depends_on=task1.name, is_template=1, begin=2, duration=2)
+		
+		template = make_project_template("Test Project Template  - tasks with parent-child", [task1, task2])
+		project = get_project("Test Project with Templ - tasks with parent-child", template)
+		tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc')
+
+		self.assertEqual(tasks[0].subject, 'Test Temp Task for dependency')
+		self.assertEqual(getdate(tasks[0].exp_end_date), add_days(nowdate()+ 3 + 1))
+
+		self.assertEqual(tasks[1].subject, 'Test Temp Task with dependency')
+		self.assertEqual(getdate(tasks[1].exp_end_date), add_days(nowdate()+ 2 + 2))
+		self.assertEqual(tasks[1].depends_on, tasks[0].name)
+
+		self.assertEqual(len(tasks), 2)
+
+def get_project(name, template):
 
 	project = frappe.get_doc(dict(
 		doctype = 'Project',
@@ -49,8 +102,6 @@
 	args = frappe._dict(args)
 	if args.project_template_name:
 		template = make_project_template(args.project_template_name)
-	else:
-		template = get_project_template()
 
 	project = frappe.get_doc(dict(
 		doctype = 'Project',
diff --git a/erpnext/projects/doctype/project_template/project_template.js b/erpnext/projects/doctype/project_template/project_template.js
index d7a876d..7668df3 100644
--- a/erpnext/projects/doctype/project_template/project_template.js
+++ b/erpnext/projects/doctype/project_template/project_template.js
@@ -5,4 +5,13 @@
 	// refresh: function(frm) {
 
 	// }
+	setup: function (frm) {
+		frm.set_query("task", "tasks", function () {
+			return {
+				filters: {
+					"is_template": 1
+				}
+			};
+		});
+	}
 });
diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py
index 2c5831a..379365f 100644
--- a/erpnext/projects/doctype/project_template/test_project_template.py
+++ b/erpnext/projects/doctype/project_template/test_project_template.py
@@ -5,28 +5,25 @@
 
 import frappe
 import unittest
+from erpnext.projects.doctype.task.test_task import create_task
 
 class TestProjectTemplate(unittest.TestCase):
 	pass
 
-def get_project_template():
-	if not frappe.db.exists('Project Template', 'Test Project Template'):
+def get_project_template(project_template_name="Test Project Template", project_tasks=[]):
+	if not frappe.db.exists('Project Template', project_template_name):
 		frappe.get_doc(dict(
 			doctype = 'Project Template',
-			name = 'Test Project Template',
-			tasks = [
-				dict(subject='Task 1', description='Task 1 description',
-					start=0, duration=3),
-				dict(subject='Task 2', description='Task 2 description',
-					start=0, duration=2),
-				dict(subject='Task 3', description='Task 3 description',
-					start=2, duration=4),
-				dict(subject='Task 4', description='Task 4 description',
-					start=3, duration=2),
+			name = project_template_name,
+			tasks = project_tasks or [
+				create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3),
+				create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2),
+				create_task(subject="_Test Template Task 3", is_template=1, begin=2, duration=4),
+				create_task(subject="_Test Template Task 4", is_template=1, begin=3, duration=2),
 			]
 		)).insert()
 
-	return frappe.get_doc('Project Template', 'Test Project Template')
+	return frappe.get_doc('Project Template', project_template_name)
 
 def make_project_template(project_template_name, project_tasks=[]):
 	if not frappe.db.exists('Project Template', project_template_name):
@@ -34,14 +31,8 @@
 			doctype = 'Project Template',
 			name = project_template_name,
 			tasks = project_tasks or [
-				dict(subject='Task 1', description='Task 1 description',
-					start=0, duration=3),
-				dict(subject='Task 2', description='Task 2 description',
-					start=0, duration=2),
-				dict(subject='Task 3', description='Task 3 description',
-					start=2, duration=4),
-				dict(subject='Task 4', description='Task 4 description',
-					start=3, duration=2),
+				create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3),
+				create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2),
 			]
 		)).insert()
 
diff --git a/erpnext/projects/doctype/project_template_task/project_template_task.json b/erpnext/projects/doctype/project_template_task/project_template_task.json
index 8644d89..80c510d 100644
--- a/erpnext/projects/doctype/project_template_task/project_template_task.json
+++ b/erpnext/projects/doctype/project_template_task/project_template_task.json
@@ -1,203 +1,32 @@
 {
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
  "creation": "2019-02-18 17:24:41.830096",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
- "document_type": "",
  "editable_grid": 1,
  "engine": "InnoDB",
+ "field_order": [
+  "task"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "subject",
-   "fieldtype": "Data",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
+   "fieldname": "task",
+   "fieldtype": "Link",
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Subject",
-   "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": 1,
-   "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": "start",
-   "fieldtype": "Int",
-   "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": "Begin On (Days)",
-   "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": 1,
-   "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": "duration",
-   "fieldtype": "Int",
-   "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": "Duration (Days)",
-   "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": 1,
-   "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": "task_weight",
-   "fieldtype": "Float",
-   "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": "Task Weight",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "description",
-   "fieldtype": "Text Editor",
-   "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": "Description",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Task",
+   "options": "Task",
+   "reqd": 1
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
  "istable": 1,
- "max_attachments": 0,
- "modified": "2019-02-18 18:30:22.688966",
+ "links": [],
+ "modified": "2020-12-07 13:28:40.961810",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Project Template Task",
- "name_case": "",
  "owner": "Administrator",
  "permissions": [],
  "quick_entry": 1,
- "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/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index 27f1a71..a9e3d9b 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -12,6 +12,7 @@
   "issue",
   "type",
   "is_group",
+  "is_template",
   "column_break0",
   "status",
   "priority",
@@ -22,9 +23,11 @@
   "sb_timeline",
   "exp_start_date",
   "expected_time",
+  "start",
   "column_break_11",
   "exp_end_date",
   "progress",
+  "duration",
   "is_milestone",
   "sb_details",
   "description",
@@ -360,6 +363,22 @@
    "label": "Completed By",
    "no_copy": 1,
    "options": "User"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_template",
+   "fieldtype": "Check",
+   "label": "Is Template"
+  },
+  {
+   "fieldname": "start",
+   "fieldtype": "Int",
+   "label": "Begin On (Days)"
+  },
+  {
+   "fieldname": "duration",
+   "fieldtype": "Int",
+   "label": "Duration (Days)"
   }
  ],
  "icon": "fa fa-check",
@@ -367,7 +386,7 @@
  "is_tree": 1,
  "links": [],
  "max_attachments": 5,
- "modified": "2020-07-03 12:36:04.960457",
+ "modified": "2020-12-07 13:26:53.614689",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Task",
diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py
index 47a28fd..181a2dc 100644
--- a/erpnext/projects/doctype/task/test_task.py
+++ b/erpnext/projects/doctype/task/test_task.py
@@ -97,7 +97,7 @@
 
 		self.assertEqual(frappe.db.get_value("Task", task.name, "status"), "Overdue")
 
-def create_task(subject, start=None, end=None, depends_on=None, project=None, save=True):
+def create_task(subject, start=None, end=None, depends_on=None, project=None, parent_task=None, is_group=0, is_template=0, begin=0, duration=0, save=True):
 	if not frappe.db.exists("Task", subject):
 		task = frappe.new_doc('Task')
 		task.status = "Open"
@@ -105,6 +105,10 @@
 		task.exp_start_date = start or nowdate()
 		task.exp_end_date = end or nowdate()
 		task.project = project or "_Test Project"
+		task.is_template = is_template,
+		task.start = begin
+		task.duration = duration,
+		task.is_group = is_group
 		if save:
 			task.save()
 	else: