From f2a77d178dcea70771e1bb385e08effd1cc3f88d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 16 Feb 2026 19:19:30 +0530 Subject: [PATCH] fix: cancel SABB if SLE cancelled from LCV (cherry picked from commit f23a49a25ee0f7f7a479c2bb6ced9fc197e3fa44) --- erpnext/controllers/buying_controller.py | 21 +++- .../purchase_receipt/test_purchase_receipt.py | 96 +++++++++++++++++++ .../serial_and_batch_bundle.py | 3 + 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 8c8692d0be3..06a8f25b186 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -626,7 +626,9 @@ class BuyingController(SubcontractingController): or self.is_return or (self.is_internal_transfer() and self.docstatus == 2) else self.get_package_for_target_warehouse( - d, type_of_transaction=type_of_transaction + d, + type_of_transaction=type_of_transaction, + via_landed_cost_voucher=via_landed_cost_voucher, ) ), }, @@ -714,7 +716,22 @@ class BuyingController(SubcontractingController): via_landed_cost_voucher=via_landed_cost_voucher, ) - def get_package_for_target_warehouse(self, item, warehouse=None, type_of_transaction=None) -> str: + def get_package_for_target_warehouse( + self, item, warehouse=None, type_of_transaction=None, via_landed_cost_voucher=None + ) -> str: + if via_landed_cost_voucher and item.get("warehouse"): + if sabb := frappe.db.get_value( + "Serial and Batch Bundle", + { + "voucher_detail_no": item.name, + "warehouse": item.get("warehouse"), + "docstatus": 1, + "is_cancelled": 0, + }, + "name", + ): + return sabb + if not item.serial_and_batch_bundle: return "" diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c91b9e22632..2070b264f8f 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1015,6 +1015,102 @@ class TestPurchaseReceipt(FrappeTestCase): pr.cancel() + def test_lcv_for_internal_transfer(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( + make_landed_cost_voucher, + ) + + prepare_data_for_internal_transfer() + + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + item_code = make_item( + "Test Item For LCV in Internal Transfer", + {"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "TEST-SBATCH.###"}, + ).name + + pr1 = make_purchase_receipt( + item_code=item_code, + qty=10, + rate=100, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + ) + + dn1 = create_delivery_note( + item_code=pr1.items[0].item_code, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=500, + warehouse="Stores - TCP1", + target_warehouse="Work In Progress - TCP1", + ) + + pr = make_inter_company_purchase_receipt(dn1.name) + pr.items[0].from_warehouse = "Work In Progress - TCP1" + pr.items[0].warehouse = "Stores - TCP1" + pr.submit() + + sle_entries = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, + fields=["serial_and_batch_bundle", "actual_qty"], + ) + self.assertEqual(len(sle_entries), 2) + + inward_sabb = frappe.get_all( + "Serial and Batch Bundle", + filters={ + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "total_qty": (">", 0), + "docstatus": 1, + }, + pluck="name", + ) + self.assertEqual(len(inward_sabb), 1) + + original_cost = frappe.db.get_value("Serial and Batch Bundle", inward_sabb[0], "total_amount") + + make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=100, + distribute_charges_based_on="Qty", + expense_account="Expenses Included In Valuation - TCP1", + ) + + sle_entries = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0}, + fields=["serial_and_batch_bundle", "actual_qty"], + ) + self.assertEqual(len(sle_entries), 2) + + new_inward_sabb = frappe.get_all( + "Serial and Batch Bundle", + filters={ + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "total_qty": (">", 0), + "docstatus": 1, + }, + pluck="name", + ) + self.assertEqual(len(new_inward_sabb), 1) + + new_cost = frappe.db.get_value("Serial and Batch Bundle", new_inward_sabb[0], "total_amount") + self.assertEqual(new_cost, original_cost + 100) + + self.assertTrue(new_inward_sabb[0] == inward_sabb[0]) + def test_stock_transfer_from_purchase_receipt_with_valuation(self): from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt from erpnext.stock.doctype.delivery_note.test_delivery_note import create_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 f0abd507173..93807ee7c0b 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 @@ -1541,6 +1541,9 @@ class SerialandBatchBundle(Document): return query.run(as_dict=True) def validate_voucher_no_docstatus(self): + if self.is_cancelled: + return + if self.voucher_type == "POS Invoice": return