From 0aed8c04c685b05cb349d6522f0999b738c95503 Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Wed, 5 Nov 2025 14:03:45 +0000 Subject: [PATCH 1/3] fix: add validation for company linked address fields (cherry picked from commit 800a44a65ffe9a3ec6516a55c9515b4c86bbea7e) --- erpnext/controllers/accounts_controller.py | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index f60fe003b0d..ee5b4f03a68 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -308,6 +308,31 @@ class AccountsController(TransactionBase): self.set_default_letter_head() self.validate_company_in_accounting_dimension() self.validate_party_address_and_contact() + self.validate_company_linked_addresses() + + def validate_company_linked_addresses(self): + address_fields = [] + if self.doctype in ("Quotation", "Sales Order", "Delivery Note", "Sales Invoice"): + address_fields = ["dispatch_address_name", "company_address"] + elif self.doctype in ("Purchase Order", "Purchase Receipt", "Purchase Invoice", "Supplier Quotation"): + address_fields = ["billing_address", "shipping_address"] + + for field in address_fields: + address = self.get(field) + if address and not frappe.db.exists( + "Dynamic Link", + { + "parent": address, + "parenttype": "Address", + "link_doctype": "Company", + "link_name": self.company, + }, + ): + frappe.throw( + _("{0} does not belong to the {1}.").format( + _(self.meta.get_label(field)), bold(self.company) + ) + ) def set_default_letter_head(self): if hasattr(self, "letter_head") and not self.letter_head: From 1d4b97c6193da6930ea597c2453285a20350e30b Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Wed, 5 Nov 2025 15:02:57 +0000 Subject: [PATCH 2/3] test: add test for company linked address fields (cherry picked from commit e10007c6468c545f09e394f5dea7010fd5772669) --- .../tests/test_accounts_controller.py | 121 +++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 899ec45c169..158a4acc6bd 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -16,9 +16,16 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.party import get_party_account -from erpnext.buying.doctype.purchase_order.test_purchase_order import prepare_data_for_internal_transfer +from erpnext.buying.doctype.purchase_order.test_purchase_order import ( + create_purchase_order, + prepare_data_for_internal_transfer, +) from erpnext.projects.doctype.project.test_project import make_project +from erpnext.selling.doctype.quotation.test_quotation import make_quotation +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt def make_customer(customer_name, currency=None): @@ -2224,6 +2231,30 @@ class TestAccountsController(FrappeTestCase): supplier_shipping.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"}) supplier_shipping.save() + company_address = make_address( + address_title="Company", address_type="Shipping", address_line1="100", city="Mumbai" + ) + company_address.append("links", {"link_doctype": "Company", "link_name": "_Test Company"}) + company_address.save() + + q = make_quotation(do_not_save=True, qty=10) + q.company_address = supplier_shipping.name + self.assertRaises(frappe.ValidationError, q.save) + q.company_address = company_address.name + q.save() + + so = make_sales_order(do_not_save=True) + so.company_address = supplier_shipping.name + self.assertRaises(frappe.ValidationError, so.save) + so.company_address = company_address.name + so.save() + + so.dispatch_address_name = supplier_shipping.name + self.assertRaises(frappe.ValidationError, so.save) + so.dispatch_address_name = company_address.name + so.reload() + so.save() + si = create_sales_invoice(do_not_save=True) si.customer_address = supplier_billing.name self.assertRaises(frappe.ValidationError, si.save) @@ -2236,12 +2267,100 @@ class TestAccountsController(FrappeTestCase): si.reload() si.save() + si.company_address = supplier_shipping.name + self.assertRaises(frappe.ValidationError, si.save) + si.company_address = company_address.name + si.reload() + si.save() + + si.dispatch_address_name = supplier_shipping.name + self.assertRaises(frappe.ValidationError, si.save) + si.dispatch_address_name = company_address.name + si.reload() + si.save() + + dn = create_delivery_note(do_not_save=True, qty=10) + dn.company_address = supplier_shipping.name + self.assertRaises(frappe.ValidationError, dn.save) + dn.company_address = company_address.name + dn.save() + + dn.dispatch_address_name = supplier_shipping.name + self.assertRaises(frappe.ValidationError, dn.save) + dn.dispatch_address_name = company_address.name + dn.reload() + dn.save() + + sq = frappe.new_doc("Supplier Quotation") + sq.naming_series = "PUR-SQTN-.YYYY.-" + sq.supplier = "_Test Supplier" + sq.company = "_Test Company" + sq.append( + "items", + { + "item_code": "_Test Item", + "item_name": "_Test Item", + "description": "_Test Item", + "warehouse": "_Test Warehouse - _TC", + "qty": 1, + "uom": "Nos", + "stock_uom": "Nos", + "rate": 100, + }, + ) + sq.shipping_address = customer_shipping.name + self.assertRaises(frappe.ValidationError, sq.save) + sq.shipping_address = company_address.name + sq.save() + + sq.billing_address = customer_shipping.name + self.assertRaises(frappe.ValidationError, sq.save) + sq.billing_address = company_address.name + sq.reload() + sq.save() + + po = create_purchase_order(do_not_save=True) + po.shipping_address = customer_shipping.name + self.assertRaises(frappe.ValidationError, po.save) + po.shipping_address = company_address.name + po.save() + + po.billing_address = supplier_billing.name + self.assertRaises(frappe.ValidationError, po.save) + po.billing_address = company_address.name + po.reload() + po.save() + pi = make_purchase_invoice(do_not_save=True) pi.supplier_address = customer_shipping.name self.assertRaises(frappe.ValidationError, pi.save) pi.supplier_address = supplier_shipping.name pi.save() + pi.billing_address = customer_shipping.name + self.assertRaises(frappe.ValidationError, pi.save) + pi.billing_address = company_address.name + pi.reload() + pi.save() + + pi.shipping_address = customer_shipping.name + self.assertRaises(frappe.ValidationError, pi.save) + pi.shipping_address = company_address.name + pi.reload() + pi.save() + + pr = make_purchase_receipt(do_not_save=True) + pr.shipping_address = customer_shipping.name + self.assertRaises(frappe.ValidationError, pr.save) + pr.shipping_address = company_address.name + pr.save() + + pr.billing_address = customer_shipping.name + self.assertRaises(frappe.ValidationError, pr.save) + pr.billing_address = company_address.name + pr.reload() + pr.save() + def test_party_contact(self): from frappe.contacts.doctype.contact.test_contact import create_contact From ef6f2389a0386e52015254be1621869f9a96ec28 Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Tue, 11 Nov 2025 13:18:05 +0000 Subject: [PATCH 3/3] test: add minimal test case (cherry picked from commit e64b6db2eb8055344c853d47fabc5ce4f9a1d46a) --- .../tests/test_accounts_controller.py | 147 ++++-------------- 1 file changed, 31 insertions(+), 116 deletions(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 158a4acc6bd..dde95899b7f 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -21,11 +21,7 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import ( prepare_data_for_internal_transfer, ) from erpnext.projects.doctype.project.test_project import make_project -from erpnext.selling.doctype.quotation.test_quotation import make_quotation -from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.item.test_item import create_item -from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt def make_customer(customer_name, currency=None): @@ -2231,30 +2227,6 @@ class TestAccountsController(FrappeTestCase): supplier_shipping.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"}) supplier_shipping.save() - company_address = make_address( - address_title="Company", address_type="Shipping", address_line1="100", city="Mumbai" - ) - company_address.append("links", {"link_doctype": "Company", "link_name": "_Test Company"}) - company_address.save() - - q = make_quotation(do_not_save=True, qty=10) - q.company_address = supplier_shipping.name - self.assertRaises(frappe.ValidationError, q.save) - q.company_address = company_address.name - q.save() - - so = make_sales_order(do_not_save=True) - so.company_address = supplier_shipping.name - self.assertRaises(frappe.ValidationError, so.save) - so.company_address = company_address.name - so.save() - - so.dispatch_address_name = supplier_shipping.name - self.assertRaises(frappe.ValidationError, so.save) - so.dispatch_address_name = company_address.name - so.reload() - so.save() - si = create_sales_invoice(do_not_save=True) si.customer_address = supplier_billing.name self.assertRaises(frappe.ValidationError, si.save) @@ -2267,100 +2239,12 @@ class TestAccountsController(FrappeTestCase): si.reload() si.save() - si.company_address = supplier_shipping.name - self.assertRaises(frappe.ValidationError, si.save) - si.company_address = company_address.name - si.reload() - si.save() - - si.dispatch_address_name = supplier_shipping.name - self.assertRaises(frappe.ValidationError, si.save) - si.dispatch_address_name = company_address.name - si.reload() - si.save() - - dn = create_delivery_note(do_not_save=True, qty=10) - dn.company_address = supplier_shipping.name - self.assertRaises(frappe.ValidationError, dn.save) - dn.company_address = company_address.name - dn.save() - - dn.dispatch_address_name = supplier_shipping.name - self.assertRaises(frappe.ValidationError, dn.save) - dn.dispatch_address_name = company_address.name - dn.reload() - dn.save() - - sq = frappe.new_doc("Supplier Quotation") - sq.naming_series = "PUR-SQTN-.YYYY.-" - sq.supplier = "_Test Supplier" - sq.company = "_Test Company" - sq.append( - "items", - { - "item_code": "_Test Item", - "item_name": "_Test Item", - "description": "_Test Item", - "warehouse": "_Test Warehouse - _TC", - "qty": 1, - "uom": "Nos", - "stock_uom": "Nos", - "rate": 100, - }, - ) - sq.shipping_address = customer_shipping.name - self.assertRaises(frappe.ValidationError, sq.save) - sq.shipping_address = company_address.name - sq.save() - - sq.billing_address = customer_shipping.name - self.assertRaises(frappe.ValidationError, sq.save) - sq.billing_address = company_address.name - sq.reload() - sq.save() - - po = create_purchase_order(do_not_save=True) - po.shipping_address = customer_shipping.name - self.assertRaises(frappe.ValidationError, po.save) - po.shipping_address = company_address.name - po.save() - - po.billing_address = supplier_billing.name - self.assertRaises(frappe.ValidationError, po.save) - po.billing_address = company_address.name - po.reload() - po.save() - pi = make_purchase_invoice(do_not_save=True) pi.supplier_address = customer_shipping.name self.assertRaises(frappe.ValidationError, pi.save) pi.supplier_address = supplier_shipping.name pi.save() - pi.billing_address = customer_shipping.name - self.assertRaises(frappe.ValidationError, pi.save) - pi.billing_address = company_address.name - pi.reload() - pi.save() - - pi.shipping_address = customer_shipping.name - self.assertRaises(frappe.ValidationError, pi.save) - pi.shipping_address = company_address.name - pi.reload() - pi.save() - - pr = make_purchase_receipt(do_not_save=True) - pr.shipping_address = customer_shipping.name - self.assertRaises(frappe.ValidationError, pr.save) - pr.shipping_address = company_address.name - pr.save() - - pr.billing_address = customer_shipping.name - self.assertRaises(frappe.ValidationError, pr.save) - pr.billing_address = company_address.name - pr.reload() - pr.save() - def test_party_contact(self): from frappe.contacts.doctype.contact.test_contact import create_contact @@ -2547,3 +2431,34 @@ class TestAccountsController(FrappeTestCase): # Second return should only get remaining discount (100 - 60 = 40) self.assertEqual(return_si_2.discount_amount, -40) + + def test_company_linked_address(self): + from erpnext.crm.doctype.prospect.test_prospect import make_address + + company_address = make_address( + address_title="Company", address_type="Shipping", address_line1="100", city="Mumbai" + ) + company_address.append("links", {"link_doctype": "Company", "link_name": "_Test Company"}) + company_address.save() + + customer_shipping = make_address( + address_title="Customer", address_type="Shipping", address_line1="10" + ) + customer_shipping.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"}) + customer_shipping.save() + + supplier_billing = make_address(address_title="Supplier", address_line1="2", city="Ahmedabad") + supplier_billing.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"}) + supplier_billing.save() + + po = create_purchase_order(do_not_save=True) + po.shipping_address = customer_shipping.name + self.assertRaises(frappe.ValidationError, po.save) + po.shipping_address = company_address.name + po.save() + + po.billing_address = supplier_billing.name + self.assertRaises(frappe.ValidationError, po.save) + po.billing_address = company_address.name + po.reload() + po.save()