feat: create party link from customer/supplier (#28387)

* feat: create party link from customer/supplier

* test: create_party_link method
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index aa132a0..7451917 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -19,6 +19,9 @@
 		frappe.db.set_default("add_taxes_from_item_tax_template",
 			self.get("add_taxes_from_item_tax_template", 0))
 
+		frappe.db.set_default("enable_common_party_accounting",
+			self.get("enable_common_party_accounting", 0))
+
 		self.validate_stale_days()
 		self.enable_payment_schedule_in_print()
 		self.toggle_discount_accounting_fields()
diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py
index daf667c..e9f813c 100644
--- a/erpnext/accounts/doctype/party_link/party_link.py
+++ b/erpnext/accounts/doctype/party_link/party_link.py
@@ -25,3 +25,17 @@
 		if existing_party_link:
 			frappe.throw(_('{} {} is already linked with another {}')
 				.format(self.primary_role, self.primary_party, existing_party_link[0]))
+
+
+@frappe.whitelist()
+def create_party_link(primary_role, primary_party, secondary_party):
+	party_link = frappe.new_doc('Party Link')
+	party_link.primary_role = primary_role
+	party_link.primary_party = primary_party
+	party_link.secondary_role = 'Customer' if primary_role == 'Supplier' else 'Supplier'
+	party_link.secondary_party = secondary_party
+
+	party_link.save(ignore_permissions=True)
+
+	return party_link
+
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 02e2416..b5453ac 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2300,6 +2300,7 @@
 		from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
 			make_customer,
 		)
+		from erpnext.accounts.doctype.party_link.party_link import create_party_link
 		from erpnext.buying.doctype.supplier.test_supplier import create_supplier
 
 		# create a customer
@@ -2308,13 +2309,7 @@
 		supplier = create_supplier(supplier_name="_Test Common Supplier").name
 
 		# create a party link between customer & supplier
-		# set primary role as supplier
-		party_link = frappe.new_doc("Party Link")
-		party_link.primary_role = "Supplier"
-		party_link.primary_party = supplier
-		party_link.secondary_role = "Customer"
-		party_link.secondary_party = customer
-		party_link.save()
+		party_link = create_party_link("Supplier", supplier, customer)
 
 		# enable common party accounting
 		frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 7ee9196..f0899b0 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -83,6 +83,12 @@
 				frm.trigger("get_supplier_group_details");
 			}, __('Actions'));
 
+			if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
+				frm.add_custom_button(__('Link with Customer'), function () {
+					frm.trigger('show_party_link_dialog');
+				}, __('Actions'));
+			}
+
 			// indicators
 			erpnext.utils.set_party_dashboard_indicators(frm);
 		}
@@ -128,5 +134,42 @@
 		else {
 			frm.toggle_reqd("represents_company", false);
 		}
+	},
+	show_party_link_dialog: function(frm) {
+		const dialog = new frappe.ui.Dialog({
+			title: __('Select a Customer'),
+			fields: [{
+				fieldtype: 'Link', label: __('Customer'),
+				options: 'Customer', fieldname: 'customer', reqd: 1
+			}],
+			primary_action: function({ customer }) {
+				frappe.call({
+					method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link',
+					args: {
+						primary_role: 'Supplier',
+						primary_party: frm.doc.name,
+						secondary_party: customer
+					},
+					freeze: true,
+					callback: function() {
+						dialog.hide();
+						frappe.msgprint({
+							message: __('Successfully linked to Customer'),
+							alert: true
+						});
+					},
+					error: function() {
+						dialog.hide();
+						frappe.msgprint({
+							message: __('Linking to Customer Failed. Please try again.'),
+							title: __('Linking Failed'),
+							indicator: 'red'
+						});
+					}
+				});
+			},
+			primary_action_label: __('Create Link')
+		});
+		dialog.show();
 	}
 });
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 4b0bbd5..107e4a4 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -134,6 +134,12 @@
 				frm.trigger("get_customer_group_details");
 			}, __('Actions'));
 
+			if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
+				frm.add_custom_button(__('Link with Supplier'), function () {
+					frm.trigger('show_party_link_dialog');
+				}, __('Actions'));
+			}
+
 			// indicator
 			erpnext.utils.set_party_dashboard_indicators(frm);
 
@@ -158,5 +164,42 @@
 			}
 		});
 
+	},
+	show_party_link_dialog: function(frm) {
+		const dialog = new frappe.ui.Dialog({
+			title: __('Select a Supplier'),
+			fields: [{
+				fieldtype: 'Link', label: __('Supplier'),
+				options: 'Supplier', fieldname: 'supplier', reqd: 1
+			}],
+			primary_action: function({ supplier }) {
+				frappe.call({
+					method: 'erpnext.accounts.doctype.party_link.party_link.create_party_link',
+					args: {
+						primary_role: 'Customer',
+						primary_party: frm.doc.name,
+						secondary_party: supplier
+					},
+					freeze: true,
+					callback: function() {
+						dialog.hide();
+						frappe.msgprint({
+							message: __('Successfully linked to Supplier'),
+							alert: true
+						});
+					},
+					error: function() {
+						dialog.hide();
+						frappe.msgprint({
+							message: __('Linking to Supplier Failed. Please try again.'),
+							title: __('Linking Failed'),
+							indicator: 'red'
+						});
+					}
+				});
+			},
+			primary_action_label: __('Create Link')
+		});
+		dialog.show();
 	}
 });