diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index b5d5136cb1f..a0d2bce1647 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -2406,6 +2406,83 @@ class TestDeliveryNote(FrappeTestCase): if row.item_code == serial_item.name: self.assertTrue(row.serial_no) +<<<<<<< HEAD +======= + if row.item_code == batch_serial_item.name: + self.assertTrue(row.batch_no) + self.assertTrue(row.serial_no) + + def test_delivery_note_return_for_batch_item_with_different_warehouse(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + batch_item = make_item( + "_Test Delivery Note Return Valuation WITH Batch Item", + properties={ + "has_batch_no": 1, + "create_new_batch": 1, + "is_stock_item": 1, + "batch_number_series": "BRTN-DNN-BIW-.#####", + }, + ).name + + batches = [] + for qty, rate in {5: 300}.items(): + se = make_stock_entry( + item_code=batch_item, target="_Test Warehouse - _TC", qty=qty, basic_rate=rate + ) + batches.append(get_batch_from_bundle(se.items[0].serial_and_batch_bundle)) + + warehouse = create_warehouse("Sales Return Test Warehouse 1", company="_Test Company") + + dn = create_delivery_note( + item_code=batch_item, + qty=5, + rate=1000, + use_serial_batch_fields=1, + batch_no=batches[0], + do_not_submit=True, + ) + + self.assertEqual(dn.items[0].warehouse, "_Test Warehouse - _TC") + + dn.save() + dn.submit() + dn.reload() + + batch_no_valuation = defaultdict(float) + + for row in dn.items: + if row.serial_and_batch_bundle: + bundle_data = frappe.get_all( + "Serial and Batch Entry", + filters={"parent": row.serial_and_batch_bundle}, + fields=["incoming_rate", "serial_no", "batch_no"], + ) + + for d in bundle_data: + if d.batch_no: + batch_no_valuation[d.batch_no] = d.incoming_rate + + return_entry = make_sales_return(dn.name) + return_entry.items[0].warehouse = warehouse + + return_entry.save() + return_entry.submit() + return_entry.reload() + + for row in return_entry.items: + self.assertEqual(row.warehouse, warehouse) + bundle_data = frappe.get_all( + "Serial and Batch Entry", + filters={"parent": row.serial_and_batch_bundle}, + fields=["incoming_rate", "batch_no"], + ) + + for d in bundle_data: + self.assertEqual(d.incoming_rate, batch_no_valuation[d.batch_no]) + +>>>>>>> 3a2e816759 (fix: incorrect valuation for sales return with different warhouse) def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 00076e8ca16..ff29b38a827 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -251,7 +251,7 @@ class SerialandBatchBundle(Document): return if return_against := self.get_return_against(parent=parent): - self.set_valuation_rate_for_return_entry(return_against, save) + self.set_valuation_rate_for_return_entry(return_against, row, save) elif self.type_of_transaction == "Outward": self.set_incoming_rate_for_outward_transaction( row, save, allow_negative_stock=allow_negative_stock @@ -259,7 +259,7 @@ class SerialandBatchBundle(Document): else: self.set_incoming_rate_for_inward_transaction(row, save) - def set_valuation_rate_for_return_entry(self, return_against, save=False): + def set_valuation_rate_for_return_entry(self, return_against, row, save=False): if valuation_details := self.get_valuation_rate_for_return_entry(return_against): for row in self.entries: if valuation_details: @@ -281,6 +281,9 @@ class SerialandBatchBundle(Document): } ) + elif self.type_of_transaction == "Inward": + self.set_incoming_rate_for_inward_transaction(row, save) + def validate_returned_serial_batch_no(self, return_against, row, original_inv_details): if row.serial_no and row.serial_no not in original_inv_details["serial_nos"]: self.throw_error_message( @@ -297,6 +300,9 @@ class SerialandBatchBundle(Document): ) def get_valuation_rate_for_return_entry(self, return_against): + if not self.voucher_detail_no: + return {} + valuation_details = frappe._dict( { "serial_nos": defaultdict(float), @@ -304,6 +310,29 @@ class SerialandBatchBundle(Document): } ) + field = { + "Sales Invoice": "sales_invoice_item", + "Purchase Invoice": "purchase_invoice_item", + "Delivery Note": "dn_detail", + "Purchase Receipt": "purchase_receipt_item", + }.get(self.voucher_type) + + return_against_voucher_detail_no = frappe.db.get_value( + self.child_table, self.voucher_detail_no, field + ) + + filters = [ + ["Serial and Batch Bundle", "voucher_no", "=", return_against], + ["Serial and Batch Entry", "docstatus", "=", 1], + ["Serial and Batch Bundle", "is_cancelled", "=", 0], + ["Serial and Batch Bundle", "item_code", "=", self.item_code], + ["Serial and Batch Bundle", "voucher_detail_no", "=", return_against_voucher_detail_no], + ] + + if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"]: + # Added to handle rejected warehouse case + filters.append(["Serial and Batch Entry", "warehouse", "=", self.warehouse]) + bundle_data = frappe.get_all( "Serial and Batch Bundle", fields=[ @@ -311,13 +340,7 @@ class SerialandBatchBundle(Document): "`tabSerial and Batch Entry`.`batch_no`", "`tabSerial and Batch Entry`.`incoming_rate`", ], - filters=[ - ["Serial and Batch Bundle", "voucher_no", "=", return_against], - ["Serial and Batch Entry", "docstatus", "=", 1], - ["Serial and Batch Bundle", "is_cancelled", "=", 0], - ["Serial and Batch Bundle", "item_code", "=", self.item_code], - ["Serial and Batch Bundle", "warehouse", "=", self.warehouse], - ], + filters=filters, order_by="`tabSerial and Batch Bundle`.`creation`, `tabSerial and Batch Entry`.`idx`", )