From 5f4a648dd4f91b4f8d8e076cd83ad4a92271ddae Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 15 Mar 2024 13:05:07 +0530 Subject: [PATCH 1/3] refactor: validate SO and SI references (cherry picked from commit 4d090bd3b83c25e18b3e44b1f7dd75dd38faba7f) # Conflicts: # erpnext/stock/doctype/delivery_note/delivery_note.py --- .../doctype/delivery_note/delivery_note.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 4216ca0cdc3..c0a4b4fd09b 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -130,6 +130,7 @@ class DeliveryNote(SellingController): def validate(self): self.validate_posting_time() super(DeliveryNote, self).validate() + self.validate_references() self.set_status() self.so_required() self.validate_proj_cust() @@ -195,6 +196,91 @@ class DeliveryNote(SellingController): ] ) +<<<<<<< HEAD +======= + def set_serial_and_batch_bundle_from_pick_list(self): + from erpnext.stock.serial_batch_bundle import SerialBatchCreation + + if not self.pick_list: + return + + for item in self.items: + if item.pick_list_item and not item.serial_and_batch_bundle: + filters = { + "item_code": item.item_code, + "voucher_type": "Pick List", + "voucher_no": self.pick_list, + "voucher_detail_no": item.pick_list_item, + } + + bundle_id = frappe.db.get_value("Serial and Batch Bundle", filters, "name") + + if bundle_id: + cls_obj = SerialBatchCreation( + { + "type_of_transaction": "Outward", + "serial_and_batch_bundle": bundle_id, + "item_code": item.get("item_code"), + } + ) + + cls_obj.duplicate_package() + + 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")) + +>>>>>>> 4d090bd3b8 (refactor: validate SO and SI references) def validate_proj_cust(self): """check for does customer belong to same project as entered..""" if self.project and self.customer: From f87f284616add2a3241b20713ec297b1cfd0f25a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 16 Mar 2024 14:39:57 +0530 Subject: [PATCH 2/3] test: SO reference validation (cherry picked from commit 4f396d304979a23c3265c467112aa1dbc99932bd) # Conflicts: # erpnext/selling/doctype/sales_order/test_sales_order.py --- erpnext/selling/doctype/sales_order/test_sales_order.py | 8 +++++++- .../stock/doctype/delivery_note/test_delivery_note.py | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 2de0168812d..e971b4a53cd 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2225,13 +2225,19 @@ def make_sales_order(**args): return so +<<<<<<< HEAD def create_dn_against_so(so, delivered_qty=0): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) +======= +def create_dn_against_so(so, delivered_qty=0, do_not_submit=False): + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) +>>>>>>> 4f396d3049 (test: SO reference validation) 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/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 9f2c0be75d0..6e735ea67b7 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -723,6 +723,15 @@ class TestDeliveryNote(FrappeTestCase): 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") From 9d827e84beed72f66cd2ad8ef9300fce3ceda868 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 16 Mar 2024 15:01:46 +0530 Subject: [PATCH 3/3] chore: resolve conflicts --- .../doctype/sales_order/test_sales_order.py | 5 --- .../doctype/delivery_note/delivery_note.py | 33 ------------------- 2 files changed, 38 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index e971b4a53cd..69180d23d29 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2225,13 +2225,8 @@ def make_sales_order(**args): return so -<<<<<<< HEAD -def create_dn_against_so(so, delivered_qty=0): - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) -======= def create_dn_against_so(so, delivered_qty=0, do_not_submit=False): frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) ->>>>>>> 4f396d3049 (test: SO reference validation) dn = make_delivery_note(so) dn.get("items")[0].qty = delivered_qty or 5 diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index c0a4b4fd09b..a402bb5aed2 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -196,38 +196,6 @@ class DeliveryNote(SellingController): ] ) -<<<<<<< HEAD -======= - def set_serial_and_batch_bundle_from_pick_list(self): - from erpnext.stock.serial_batch_bundle import SerialBatchCreation - - if not self.pick_list: - return - - for item in self.items: - if item.pick_list_item and not item.serial_and_batch_bundle: - filters = { - "item_code": item.item_code, - "voucher_type": "Pick List", - "voucher_no": self.pick_list, - "voucher_detail_no": item.pick_list_item, - } - - bundle_id = frappe.db.get_value("Serial and Batch Bundle", filters, "name") - - if bundle_id: - cls_obj = SerialBatchCreation( - { - "type_of_transaction": "Outward", - "serial_and_batch_bundle": bundle_id, - "item_code": item.get("item_code"), - } - ) - - cls_obj.duplicate_package() - - 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() @@ -280,7 +248,6 @@ class DeliveryNote(SellingController): if err_msg: frappe.throw(err_msg, title=_("References to Sales Invoices are Incomplete")) ->>>>>>> 4d090bd3b8 (refactor: validate SO and SI references) def validate_proj_cust(self): """check for does customer belong to same project as entered..""" if self.project and self.customer: