feat(selling): add shipping contact person to sales order, delivery note and sales invoice (#58159)

This commit is contained in:
Pandiyan P
2026-08-14 15:04:31 +05:30
committed by GitHub
parent 73957dc84b
commit 10109674a4
9 changed files with 204 additions and 5 deletions

View File

@@ -170,6 +170,10 @@
"shipping_address_section",
"shipping_address_name",
"shipping_address",
"shipping_contact_person",
"shipping_contact_display",
"shipping_contact_mobile",
"shipping_contact_email",
"shipping_addr_col_break",
"dispatch_address_name",
"dispatch_address",
@@ -589,6 +593,46 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "shipping_contact_person",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Shipping Contact Person",
"options": "Contact",
"print_hide": 1
},
{
"fetch_from": "shipping_contact_person.full_name",
"fieldname": "shipping_contact_display",
"fieldtype": "Small Text",
"hide_days": 1,
"hide_seconds": 1,
"label": "Shipping Contact",
"read_only": 1
},
{
"fetch_from": "shipping_contact_person.mobile_no",
"fieldname": "shipping_contact_mobile",
"fieldtype": "Small Text",
"hide_days": 1,
"hide_seconds": 1,
"label": "Shipping Contact Mobile No",
"options": "Phone",
"read_only": 1
},
{
"fetch_from": "shipping_contact_person.email_id",
"fieldname": "shipping_contact_email",
"fieldtype": "Data",
"hidden": 1,
"hide_days": 1,
"hide_seconds": 1,
"label": "Shipping Contact Email",
"options": "Email",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "company_address",
"fieldtype": "Link",
@@ -2361,7 +2405,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2026-08-12 12:00:00.000000",
"modified": "2026-08-14 12:43:19.480555",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -202,6 +202,10 @@ class SalesInvoice(SellingController):
set_warehouse: DF.Link | None
shipping_address: DF.TextEditor | None
shipping_address_name: DF.Link | None
shipping_contact_display: DF.SmallText | None
shipping_contact_email: DF.Data | None
shipping_contact_mobile: DF.SmallText | None
shipping_contact_person: DF.Link | None
shipping_rule: DF.Link | None
status: DF.Literal[
"",

View File

@@ -156,7 +156,7 @@ def _get_party_details(
dispatch_address,
ignore_permissions=ignore_permissions,
)
set_contact_details(party_details, party, party_type)
set_contact_details(party_details, party, party_type, doctype)
set_other_values(party_details, party, party_type)
set_price_list(party_details, party, party_type, price_list, pos_profile)
@@ -358,10 +358,22 @@ def complete_contact_details(party_details):
party_details.update(contact_details)
def set_contact_details(party_details, party, party_type):
def set_contact_details(party_details, party, party_type, doctype=None):
party_details.contact_person = get_default_contact(party_type, party.name)
complete_contact_details(party_details)
# the shipping contact is picked by the user, so it has no default to fall back on;
# blank it instead of carrying the previous party's contact over
if doctype and frappe.get_meta(doctype).has_field("shipping_contact_person"):
party_details.update(
{
"shipping_contact_person": None,
"shipping_contact_display": None,
"shipping_contact_mobile": None,
"shipping_contact_email": None,
}
)
def set_other_values(party_details, party, party_type):
# copy

View File

@@ -49,6 +49,7 @@ erpnext.sales_common = {
);
me.frm.set_query("contact_person", erpnext.queries.contact_query);
me.frm.set_query("shipping_contact_person", erpnext.queries.contact_query);
me.frm.set_query("company_contact_person", erpnext.queries.company_contact_query);
me.frm.set_query("customer_address", erpnext.queries.address_query);
me.frm.set_query("shipping_address_name", erpnext.queries.address_query);

View File

@@ -113,6 +113,10 @@
"shipping_address_column",
"shipping_address_name",
"shipping_address",
"shipping_contact_person",
"shipping_contact_display",
"shipping_contact_mobile",
"shipping_contact_email",
"column_break_93",
"dispatch_address_name",
"dispatch_address",
@@ -459,6 +463,46 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "shipping_contact_person",
"fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Shipping Contact Person",
"options": "Contact",
"print_hide": 1
},
{
"fetch_from": "shipping_contact_person.full_name",
"fieldname": "shipping_contact_display",
"fieldtype": "Small Text",
"hide_days": 1,
"hide_seconds": 1,
"label": "Shipping Contact",
"read_only": 1
},
{
"fetch_from": "shipping_contact_person.mobile_no",
"fieldname": "shipping_contact_mobile",
"fieldtype": "Small Text",
"hide_days": 1,
"hide_seconds": 1,
"label": "Shipping Contact Mobile No",
"options": "Phone",
"read_only": 1
},
{
"fetch_from": "shipping_contact_person.email_id",
"fieldname": "shipping_contact_email",
"fieldtype": "Data",
"hidden": 1,
"hide_days": 1,
"hide_seconds": 1,
"label": "Shipping Contact Email",
"options": "Email",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "customer_group",
"fieldtype": "Link",
@@ -1782,7 +1826,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2026-08-12 12:00:00.000000",
"modified": "2026-08-14 12:43:19.480555",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",

View File

@@ -146,6 +146,10 @@ class SalesOrder(SellingController):
set_warehouse: DF.Link | None
shipping_address: DF.TextEditor | None
shipping_address_name: DF.Link | None
shipping_contact_display: DF.SmallText | None
shipping_contact_email: DF.Data | None
shipping_contact_mobile: DF.SmallText | None
shipping_contact_person: DF.Link | None
shipping_rule: DF.Link | None
skip_delivery_note: DF.Check
status: DF.Literal[

View File

@@ -11,6 +11,7 @@ from frappe.query_builder.functions import Sum
from frappe.tests import change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
from erpnext.accounts.party import get_party_details
from erpnext.controllers.accounts_controller import InvalidQtyError, get_due_date, update_child_qty_rate
from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import (
make_maintenance_schedule,
@@ -3411,6 +3412,55 @@ class TestSalesOrder(ERPNextTestSuite):
finally:
frappe.db.set_value("Item", "_Test Item", "grant_commission", original)
def test_shipping_contact_person_flows_to_delivery_note_and_sales_invoice(self):
billing_contact = "_Test Contact for _Test Customer-_Test Customer"
shipping_contact = "_Test Contact 2 for _Test Customer-_Test Customer"
so = make_sales_order(customer="_Test Customer", do_not_submit=True)
so.contact_person = billing_contact
so.shipping_contact_person = shipping_contact
so.save()
# fetch_from fills the display fields off the shipping contact, not the billing one
self.assertEqual(so.shipping_contact_display, "_Test Contact 2 for _Test Customer")
self.assertEqual(so.shipping_contact_email, "test_contact_two_customer@example.com")
so.submit()
dn = make_delivery_note(so.name)
self.assertEqual(dn.contact_person, billing_contact)
self.assertEqual(dn.shipping_contact_person, shipping_contact)
si = make_sales_invoice(so.name)
self.assertEqual(si.shipping_contact_person, shipping_contact)
def test_get_party_details_blanks_shipping_contact(self):
# unlike the billing contact there is no default to fall back on, so the keys must
# still come back as None — that is what clears the previous party's contact on the form
party_details = get_party_details(
party="_Test Customer",
party_type="Customer",
company="_Test Company",
doctype="Sales Order",
)
for fieldname in (
"shipping_contact_person",
"shipping_contact_display",
"shipping_contact_mobile",
"shipping_contact_email",
):
self.assertIn(fieldname, party_details)
self.assertIsNone(party_details[fieldname])
# purchase transactions have no such field, so the keys must not be added there
purchase_details = get_party_details(
party="_Test Supplier",
party_type="Supplier",
company="_Test Company",
doctype="Purchase Order",
)
self.assertNotIn("shipping_contact_person", purchase_details)
def compare_payment_schedules(doc, doc1, doc2):
for index, schedule in enumerate(doc1.get("payment_schedule")):

View File

@@ -105,6 +105,10 @@
"shipping_address_section",
"shipping_address_name",
"shipping_address",
"shipping_contact_person",
"shipping_contact_display",
"shipping_contact_mobile",
"shipping_contact_email",
"column_break_95",
"dispatch_address_name",
"dispatch_address",
@@ -356,6 +360,38 @@
"label": "Shipping Address",
"read_only": 1
},
{
"fieldname": "shipping_contact_person",
"fieldtype": "Link",
"label": "Shipping Contact Person",
"options": "Contact",
"print_hide": 1
},
{
"fetch_from": "shipping_contact_person.full_name",
"fieldname": "shipping_contact_display",
"fieldtype": "Small Text",
"label": "Shipping Contact",
"read_only": 1
},
{
"fetch_from": "shipping_contact_person.mobile_no",
"fieldname": "shipping_contact_mobile",
"fieldtype": "Small Text",
"label": "Shipping Contact Mobile No",
"options": "Phone",
"read_only": 1
},
{
"fetch_from": "shipping_contact_person.email_id",
"fieldname": "shipping_contact_email",
"fieldtype": "Data",
"hidden": 1,
"label": "Shipping Contact Email",
"options": "Email",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
@@ -1473,7 +1509,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2026-08-12 12:00:00.000000",
"modified": "2026-08-14 12:43:19.480555",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@@ -119,6 +119,10 @@ class DeliveryNote(SellingController):
set_warehouse: DF.Link | None
shipping_address: DF.TextEditor | None
shipping_address_name: DF.Link | None
shipping_contact_display: DF.SmallText | None
shipping_contact_email: DF.Data | None
shipping_contact_mobile: DF.SmallText | None
shipping_contact_person: DF.Link | None
shipping_rule: DF.Link | None
status: DF.Literal[
"",