mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-16 07:58:38 +00:00
feat(selling): add shipping contact person to sales order, delivery note and sales invoice (#58159)
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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[
|
||||
"",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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[
|
||||
|
||||
@@ -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")):
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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[
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user