diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 261566ec001e..375accd65b68 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -2156,13 +2156,14 @@ def make_sales_order(**args):
return so
-def create_dn_against_so(so, delivered_qty=0):
+def create_dn_against_so(so, delivered_qty=0, do_not_submit=False):
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1)
dn = make_delivery_note(so)
dn.get("items")[0].qty = delivered_qty or 5
dn.insert()
- dn.submit()
+ if not do_not_submit:
+ dn.submit()
return dn
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index a7fcd04c634b..4c2c023b6ae3 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -251,6 +251,7 @@ def so_required(self):
def validate(self):
self.validate_posting_time()
super(DeliveryNote, self).validate()
+ self.validate_references()
self.set_status()
self.so_required()
self.validate_proj_cust()
@@ -341,6 +342,58 @@ def set_serial_and_batch_bundle_from_pick_list(self):
item.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
+ def validate_references(self):
+ self.validate_sales_order_references()
+ self.validate_sales_invoice_references()
+
+ def validate_sales_order_references(self):
+ err_msg = ""
+ for item in self.items:
+ if (item.against_sales_order and not item.so_detail) or (
+ not item.against_sales_order and item.so_detail
+ ):
+ if not item.against_sales_order:
+ err_msg += (
+ _("'Sales Order' reference ({1}) is missing in row {0}").format(
+ frappe.bold(item.idx), frappe.bold("against_sales_order")
+ )
+ + "
"
+ )
+ else:
+ err_msg += (
+ _("'Sales Order Item' reference ({1}) is missing in row {0}").format(
+ frappe.bold(item.idx), frappe.bold("so_detail")
+ )
+ + "
"
+ )
+
+ if err_msg:
+ frappe.throw(err_msg, title=_("References to Sales Orders are Incomplete"))
+
+ def validate_sales_invoice_references(self):
+ err_msg = ""
+ for item in self.items:
+ if (item.against_sales_invoice and not item.si_detail) or (
+ not item.against_sales_invoice and item.si_detail
+ ):
+ if not item.against_sales_invoice:
+ err_msg += (
+ _("'Sales Invoice' reference ({1}) is missing in row {0}").format(
+ frappe.bold(item.idx), frappe.bold("against_sales_invoice")
+ )
+ + "
"
+ )
+ else:
+ err_msg += (
+ _("'Sales Invoice Item' reference ({1}) is missing in row {0}").format(
+ frappe.bold(item.idx), frappe.bold("si_detail")
+ )
+ + "
"
+ )
+
+ if err_msg:
+ frappe.throw(err_msg, title=_("References to Sales Invoices are Incomplete"))
+
def validate_proj_cust(self):
"""check for does customer belong to same project as entered.."""
if self.project and self.customer:
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 808862949584..f9341393ad01 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -813,6 +813,15 @@ def test_closed_delivery_note(self):
dn.cancel()
self.assertEqual(dn.status, "Cancelled")
+ def test_sales_order_reference_validation(self):
+ so = make_sales_order(po_no="12345")
+ dn = create_dn_against_so(so.name, delivered_qty=2, do_not_submit=True)
+ dn.items[0].against_sales_order = None
+ self.assertRaises(frappe.ValidationError, dn.save)
+ dn.reload()
+ dn.items[0].so_detail = None
+ self.assertRaises(frappe.ValidationError, dn.save)
+
def test_dn_billing_status_case1(self):
# SO -> DN -> SI
so = make_sales_order(po_no="12345")