From ae3453c84b68b4dedddc269f32f964f0b3cdc066 Mon Sep 17 00:00:00 2001 From: Imesha Sudasingha Date: Mon, 23 Feb 2026 20:42:08 +0530 Subject: [PATCH] Merge pull request #52544 from one-highflyer/fix/improve-reserved-serial-no-error-message fix(stock): improve error message when serial no is reserved via SRE (cherry picked from commit 71248ff40bbeb399378a0797f268f45b224f08ae) --- .../serial_and_batch_bundle.py | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) 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 e81c320adc4..c38a73a13eb 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 @@ -299,10 +299,20 @@ class SerialandBatchBundle(Document): for serial_no in serial_nos: if not serial_no_warehouse.get(serial_no) or serial_no_warehouse.get(serial_no) != self.warehouse: - self.throw_error_message( - f"Serial No {bold(serial_no)} is not present in the warehouse {bold(self.warehouse)}.", - SerialNoWarehouseError, - ) + reservation = get_serial_no_reservation(self.item_code, serial_no, self.warehouse) + if reservation: + self.throw_error_message( + f"Serial No {bold(serial_no)} is in warehouse {bold(self.warehouse)}" + f" but is reserved for {reservation.voucher_type} {bold(reservation.voucher_no)}" + f" via {get_link_to_form('Stock Reservation Entry', reservation.name)}." + f" Please use an unreserved serial number or cancel the reservation.", + SerialNoWarehouseError, + ) + else: + self.throw_error_message( + f"Serial No {bold(serial_no)} is not present in the warehouse {bold(self.warehouse)}.", + SerialNoWarehouseError, + ) def validate_serial_nos_duplicate(self): # Don't inward same serial number multiple times @@ -2585,6 +2595,32 @@ def get_reserved_serial_nos_for_sre(kwargs) -> list: return query.run(as_dict=True) +def get_serial_no_reservation(item_code: str, serial_no: str, warehouse: str) -> _dict | None: + """Returns the Stock Reservation Entry that has reserved the given serial number, if any.""" + + sre = frappe.qb.DocType("Stock Reservation Entry") + sb_entry = frappe.qb.DocType("Serial and Batch Entry") + result = ( + frappe.qb.from_(sre) + .inner_join(sb_entry) + .on(sre.name == sb_entry.parent) + .select(sre.name, sre.voucher_type, sre.voucher_no) + .where( + (sre.docstatus == 1) + & (sre.item_code == item_code) + & (sre.warehouse == warehouse) + & (sre.status.notin(["Delivered", "Cancelled", "Closed"])) + & (sre.reservation_based_on == "Serial and Batch") + & (sb_entry.serial_no == serial_no) + & (sb_entry.qty != sb_entry.delivered_qty) + ) + .limit(1) + .run(as_dict=True) + ) + + return result[0] if result else None + + def get_reserved_batches_for_pos(kwargs) -> dict: """Returns a dict of `Batch No` followed by the `Qty` reserved in POS Invoices."""