feat: duplicate linked task in project (#19271)

* feat: create a duplicate project

* fix: allow duplication via form

* feat: fetch old task and link project

* fix: link task with project

* fix: parse json string as python object

* fix: avoid duplicate task based on the project template

* fix: ask user for the new project name

* fix: display a descriptive message on switching to a new route

* fix: override duplicate in menu

* fix: check for duplicate project name after submitting prompt

* fix: set the project template

* fix: minor changes

* fix: function call

* refactor: add a separate button for duplicate

* Update project.js
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 192e1bc..4a03a58 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -19,7 +19,7 @@
 					frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
 				});
 			},
-		}
+		};
 	},
 	onload: function (frm) {
 		var so = frappe.meta.get_docfield("Project", "sales_order");
@@ -28,15 +28,15 @@
 			return {
 				"customer": frm.doc.customer,
 				"project_name": frm.doc.name
-			}
-		}
+			};
+		};
 
 		frm.set_query('customer', 'erpnext.controllers.queries.customer_query');
 
 		frm.set_query("user", "users", function () {
 			return {
 				query: "erpnext.projects.doctype.project.project.get_users_for_project"
-			}
+			};
 		});
 
 		// sales order
@@ -51,7 +51,7 @@
 
 			return {
 				filters: filters
-			}
+			};
 		});
 
 		if (frappe.model.can_read("Task")) {
@@ -85,6 +85,10 @@
 
 	set_buttons: function(frm) {
 		if (!frm.is_new()) {
+			frm.add_custom_button(__('Duplicate Project with Tasks'), () => {
+				frm.events.create_duplicate(frm);
+			});
+
 			frm.add_custom_button(__('Completed'), () => {
 				frm.events.set_status(frm, 'Completed');
 			}, __('Set Status'));
@@ -95,6 +99,22 @@
 		}
 	},
 
+	create_duplicate: function(frm) {
+		return new Promise(resolve => {
+			frappe.prompt('Project Name', (data) => {
+				frappe.xcall('erpnext.projects.doctype.project.project.create_duplicate_project',
+					{
+						prev_doc: frm.doc,
+						project_name: data.value
+					}).then(() => {
+					frappe.set_route('Form', "Project", data.value);
+					frappe.show_alert(__("Duplicate project has been created"));
+				});
+				resolve();
+			});
+		});
+	},
+
 	set_status: function(frm, status) {
 		frappe.confirm(__('Set Project and all Tasks to status {0}?', [status.bold()]), () => {
 			frappe.xcall('erpnext.projects.doctype.project.project.set_project_status',
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 783bcf3..bf6e21a 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -323,6 +323,37 @@
 	if get_time(nowtime()) >= get_time(time):
 		return True
 
+
+@frappe.whitelist()
+def create_duplicate_project(prev_doc, project_name):
+	''' Create duplicate project based on the old project '''
+	import json
+	prev_doc = json.loads(prev_doc)
+
+	if project_name == prev_doc.get('name'):
+		frappe.throw(_("Use a name that is different from previous project name"))
+
+	# change the copied doc name to new project name
+	project = frappe.copy_doc(prev_doc)
+	project.name = project_name
+	project.project_template = ''
+	project.project_name = project_name
+	project.insert()
+
+	# fetch all the task linked with the old project
+	task_list = frappe.get_all("Task", filters={
+		'project': prev_doc.get('name')
+	}, fields=['name'])
+
+	# Create duplicate task for all the task
+	for task in task_list:
+		task = frappe.get_doc('Task', task)
+		new_task = frappe.copy_doc(task)
+		new_task.project = project.name
+		new_task.insert()
+
+	project.db_set('project_template', prev_doc.get('project_template'))
+
 def get_projects_for_collect_progress(frequency, fields):
 	fields.extend(["name"])