From d6f2ff6b87efd411249a6f5bcd1740d0bc09fe8d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:55:18 +0530 Subject: [PATCH] fix: show correct status in Serial No Ledger (backport #54567) (#54626) * refactor: extract SN status logic (cherry picked from commit cb2e6e1e2ea32e62a629c1323f5903045d4cde96) * fix: show correct status in Serial No Ledger (cherry picked from commit 2b3e047143405cb3f810553b7ecfed505779801c) --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> --- .../serial_no_ledger/serial_no_ledger.py | 3 +- erpnext/stock/serial_batch_bundle.py | 68 +++++++++++-------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py index c927f3d6d9e..b73f8dde3b2 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -7,6 +7,7 @@ import frappe from frappe import _ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_serial_nos_from_sle +from erpnext.stock.serial_batch_bundle import get_serial_no_status from erpnext.stock.stock_ledger import get_stock_ledger_entries BUYING_VOUCHER_TYPES = ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"] @@ -111,7 +112,7 @@ def get_data(filters): "posting_time": row.posting_time, "voucher_type": row.voucher_type, "voucher_no": row.voucher_no, - "status": "Active" if row.actual_qty > 0 else "Delivered", + "status": get_serial_no_status(row), "company": row.company, "warehouse": row.warehouse, "qty": 1 if row.actual_qty > 0 else -1, diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 5cfdd776240..4a47144ecf1 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -15,6 +15,45 @@ from erpnext.stock.deprecated_serial_batch import ( ) from erpnext.stock.valuation import round_off_if_near_zero +CONSUMED_SERIAL_NO_STOCK_ENTRY_PURPOSES = ( + "Manufacture", + "Material Issue", + "Repack", + "Material Consumption for Manufacture", +) +INACTIVE_SERIAL_NO_STOCK_ENTRY_PURPOSES = ("Disassemble", "Material Receipt") + + +def get_serial_no_status(sle): + warehouse = sle.warehouse if sle.actual_qty > 0 else None + if warehouse: + return "Active" + + status = get_status_for_serial_nos(sle) + if sle.voucher_type == "Stock Entry" and sle.actual_qty < 0: + purpose = frappe.get_cached_value("Stock Entry", sle.voucher_no, "purpose") + if purpose in INACTIVE_SERIAL_NO_STOCK_ENTRY_PURPOSES: + status = "Inactive" + + return status + + +def get_status_for_serial_nos(sle): + status = "Inactive" + if sle.actual_qty < 0: + status = "Delivered" + if sle.voucher_type == "Stock Entry": + purpose = frappe.get_cached_value("Stock Entry", sle.voucher_no, "purpose") + if purpose in CONSUMED_SERIAL_NO_STOCK_ENTRY_PURPOSES: + status = "Consumed" + + if sle.is_cancelled == 1 and ( + sle.voucher_type in ["Purchase Invoice", "Purchase Receipt"] or status == "Consumed" + ): + status = "Inactive" + + return status + class SerialBatchBundle: def __init__(self, **kwargs): @@ -429,25 +468,7 @@ class SerialBatchBundle: self.update_serial_no_status_warehouse(self.sle, serial_nos) def get_status_for_serial_nos(self, sle): - status = "Inactive" - if sle.actual_qty < 0: - status = "Delivered" - if sle.voucher_type == "Stock Entry": - purpose = frappe.get_cached_value("Stock Entry", sle.voucher_no, "purpose") - if purpose in [ - "Manufacture", - "Material Issue", - "Repack", - "Material Consumption for Manufacture", - ]: - status = "Consumed" - - if sle.is_cancelled == 1 and ( - sle.voucher_type in ["Purchase Invoice", "Purchase Receipt"] or status == "Consumed" - ): - status = "Inactive" - - return status + return get_status_for_serial_nos(sle) def update_serial_no_status_warehouse(self, sle, serial_nos): warehouse = sle.warehouse if sle.actual_qty > 0 else None @@ -455,19 +476,12 @@ class SerialBatchBundle: if isinstance(serial_nos, str): serial_nos = [serial_nos] - status = "Active" - if not warehouse: - status = self.get_status_for_serial_nos(sle) + status = get_serial_no_status(sle) customer = None if sle.voucher_type in ["Sales Invoice", "Delivery Note"] and sle.actual_qty < 0: customer = frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "customer") - if sle.voucher_type in ["Stock Entry"] and sle.actual_qty < 0: - purpose = frappe.get_cached_value("Stock Entry", sle.voucher_no, "purpose") - if purpose in ["Disassemble", "Material Receipt"]: - status = "Inactive" - sn_table = frappe.qb.DocType("Serial No") query = (