fix: show correct status in Serial No Ledger (backport #54567) (#54625)

* refactor: extract SN status logic

(cherry picked from commit cb2e6e1e2e)

* fix: show correct status in Serial No Ledger

(cherry picked from commit 2b3e047143)

---------

Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
This commit is contained in:
mergify[bot]
2026-04-29 13:55:06 +05:30
committed by GitHub
parent d64b19416e
commit 559b31baae
2 changed files with 43 additions and 28 deletions

View File

@@ -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,

View File

@@ -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):
@@ -410,25 +449,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
@@ -436,19 +457,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 = (