diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 081ac5dd96c..211fcfc8afe 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -240,8 +240,29 @@ class Project(Document): def after_insert(self): self.copy_from_template("after_insert") - if self.sales_order: - frappe.db.set_value("Sales Order", self.sales_order, "project", self.name) + self.link_with_sales_order() + + def link_with_sales_order(self) -> None: + """Back-link the source Sales Order to this project. + + The link is set only when the Sales Order is not already tied to another + project, so projects created concurrently for the same Sales Order cannot + overwrite each other's reference. + """ + if not self.sales_order: + return + + sales_order = frappe.get_doc("Sales Order", self.sales_order) + if sales_order.project and sales_order.project != self.name: + frappe.msgprint( + _("Sales Order {0} is already linked to Project {1}, skipping the link.").format( + self.sales_order, sales_order.project + ), + alert=True, + ) + return + + sales_order.db_set("project", self.name) def on_trash(self): frappe.db.set_value("Sales Order", {"project": self.name}, "project", "") diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index b24f0d13fe2..07960128855 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -174,6 +174,24 @@ class TestProject(ERPNextTestSuite): so.reload() self.assertFalse(so.project) + def test_sales_order_link_is_not_overwritten_by_second_project(self): + so = make_sales_order() + + first_project = make_project_from_so(so.name).save() + so.reload() + self.assertEqual(so.project, first_project.name) + + # A second project for the same sales order must not steal the link. + second_project = frappe.get_doc( + doctype="Project", + project_name="Second project for same sales order", + sales_order=so.name, + ).insert() + self.assertEqual(second_project.sales_order, so.name) + + so.reload() + self.assertEqual(so.project, first_project.name) + def test_project_with_template_tasks_having_common_name(self): # Step - 1: Create Template Parent Tasks template_parent_task1 = create_task(subject="Parent Task - 1", is_template=1, is_group=1)