From 3194558e080ae576ab652362ae65e7db38d29797 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sat, 14 Oct 2023 23:20:38 +0530 Subject: [PATCH 1/2] fix: serial and batch no get removed on save of return DN --- erpnext/stock/doctype/delivery_note/delivery_note.py | 12 ++++++++++++ erpnext/stock/doctype/packed_item/packed_item.json | 3 +-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 115827a60cba..b18ee9943c7e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -144,6 +144,7 @@ def validate(self): from erpnext.stock.doctype.packed_item.packed_item import make_packing_list + self.set_product_bundle_reference_in_packed_items() # should be called before `make_packing_list` make_packing_list(self) if self._action != "submit" and not self.is_return: @@ -430,6 +431,17 @@ def validate_duplicate_serial_nos(self): else: serial_nos.append(serial_no) + def set_product_bundle_reference_in_packed_items(self): + if self.packed_items and ((self.is_return and self.return_against) or self.amended_from): + if items_ref_map := { + item.dn_detail or item.get("_amended_from"): item.name + for item in self.items + if item.dn_detail or item.get("_amended_from") + }: + for item in self.packed_items: + if item.parent_detail_docname in items_ref_map: + item.parent_detail_docname = items_ref_map[item.parent_detail_docname] + def update_billed_amount_based_on_so(so_detail, update_modified=True): from frappe.query_builder.functions import Sum diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json index c5fb2411c281..679c6c149e96 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.json +++ b/erpnext/stock/doctype/packed_item/packed_item.json @@ -192,7 +192,6 @@ "fieldtype": "Data", "hidden": 1, "label": "Parent Detail docname", - "no_copy": 1, "oldfieldname": "parent_detail_docname", "oldfieldtype": "Data", "print_hide": 1, @@ -259,7 +258,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-04-28 13:16:38.460806", + "modified": "2023-10-14 23:26:11.755425", "modified_by": "Administrator", "module": "Stock", "name": "Packed Item", From b538e04ce84aebf19d31c30014eb71305f43e050 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 12 Oct 2023 18:36:25 +0530 Subject: [PATCH 2/2] test: add test case for DN return with product bundle --- .../delivery_note/test_delivery_note.py | 55 +++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 093e16c1cf81..8ea87f00c5b8 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -10,6 +10,7 @@ from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.utils import get_balance_on +from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle from erpnext.selling.doctype.sales_order.test_sales_order import ( automatically_fetch_payment_terms, @@ -268,8 +269,6 @@ def test_sales_return_for_non_bundled_items_partial(self): self.assertEqual(dn.items[0].returned_qty, 2) self.assertEqual(dn.per_returned, 40) - from erpnext.controllers.sales_and_purchase_return import make_return_doc - return_dn_2 = make_return_doc("Delivery Note", dn.name) # Check if unreturned amount is mapped in 2nd return @@ -361,8 +360,6 @@ def test_delivery_note_return_valuation_on_different_warehuose(self): dn.submit() self.assertEqual(dn.items[0].incoming_rate, 150) - from erpnext.controllers.sales_and_purchase_return import make_return_doc - return_dn = make_return_doc(dn.doctype, dn.name) return_dn.items[0].warehouse = return_warehouse return_dn.save().submit() @@ -1182,7 +1179,6 @@ def test_internal_transfer_precision_gle(self): ) def test_batch_expiry_for_delivery_note(self): - from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt item = make_item( @@ -1239,6 +1235,55 @@ def test_duplicate_serial_no_in_delivery_note(self): # Test - 1: ValidationError should be raised self.assertRaises(frappe.ValidationError, dn.submit) + def test_packed_items_for_return_delivery_note(self): + # Step - 1: Create Items + product_bundle_item = make_item(properties={"is_stock_item": 0}).name + batch_item = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TEST-BATCH-.#####", + } + ).name + serial_item = make_item( + properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "TEST-SERIAL-.#####"} + ).name + + # Step - 2: Inward Stock + se1 = make_stock_entry(item_code=batch_item, target="_Test Warehouse - _TC", qty=3) + serial_nos = ( + make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=3) + .items[0] + .serial_no + ) + + # Step - 3: Create a Product Bundle + from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import ( + create_product_bundle_item, + ) + + create_product_bundle_item(product_bundle_item, packed_items=[[batch_item, 1], [serial_item, 1]]) + + # Step - 4: Create a Delivery Note for the Product Bundle + dn = create_delivery_note( + item_code=product_bundle_item, + warehouse="_Test Warehouse - _TC", + qty=3, + do_not_submit=True, + ) + dn.packed_items[1].serial_no = serial_nos + dn.save() + dn.submit() + + # Step - 5: Create a Return Delivery Note(Sales Return) + return_dn = make_return_doc(dn.doctype, dn.name) + return_dn.save() + return_dn.submit() + + self.assertEqual(return_dn.packed_items[0].batch_no, dn.packed_items[0].batch_no) + self.assertEqual(return_dn.packed_items[1].serial_no, dn.packed_items[1].serial_no) + def tearDown(self): frappe.db.rollback() frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)