How to Handle Batch and Serial Number Tracking in ERPNext Stock Transactions

ERPNext centralizes batch and serial number tracking through the Serial and Batch Bundle (SABB) system, which validates, creates, and links inventory identifiers to stock transactions while enforcing unique constraints and valuation rules.

ERPNext treats batch numbers and serial numbers as first-class citizens in inventory management. When you submit a stock transaction—whether a Stock Entry, Delivery Note, or Purchase Receipt—the system automatically validates that the item master has the correct tracking flags enabled ("Has Batch No" or "Has Serial No") and persists the relationships through a dedicated bundle mechanism.

The Architecture of Batch and Serial Number Tracking

ERPNext implements a transaction-agnostic pipeline that processes batch and serial data in six distinct stages before posting to the Stock Ledger.

Step 1: Input Validation Against Item Master

When a user submits a stock document containing batch_no or serial_no values in the child table items, the system first validates that the item configuration permits these identifiers. In erpnext/stock/doctype/stock_entry/stock_entry.py, the validate() method (line 151) checks the item master flags to ensure that batch or serial tracking is explicitly enabled for the specific item code.

Step 2: Serial and Batch Bundle Resolution

The core linking mechanism resides in erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py. The get_bundle() function (line 210) serves as the primary entry point for bundle creation or retrieval. This function either fetches an existing SABB document when the same serial/batch combination was previously used, or generates a new bundle with a unique identifier (format: SABB-…). The bundle acts as the immutable record connecting the stock transaction to its specific batch and serial numbers.

Step 3: Serial Number Specific Validation Rules

For serialized items, ERPNext enforces strict uniqueness constraints. The system prevents the reuse of serial numbers unless the "Allow existing Serial No to be Manufactured/Received again" setting is enabled in Stock Settings. Additionally, the code performs reservation checks via get_stock_reservation_entry (line 2730 in serial_and_batch_bundle.py) to block double-inward entries of the same serial number, ensuring that a single physical unit cannot exist in two locations simultaneously.

Step 4: Batch Processing and Valuation Logic

Batch handling includes expiration validation and valuation rate updates. If the "Allow Expired Batches" setting is disabled, the system blocks transactions involving batches past their expiry date. For valuation, when using the Moving Average method, erpnext/stock/stock_ledger.py (line 2000) automatically recalculates and updates the batch's valuation rate based on incoming stock rates during ledger posting.

Step 5: Stock Ledger and GL Posting

After bundle validation and creation, make_sl_entries() in erpnext/stock/doctype/stock_entry/stock_entry_utils.py (line 136) writes the Stock Ledger Entries. Each entry contains the batch_no and serial_no fields, ensuring that every quantity movement carries full traceability. General Ledger entries follow the standard accounting flow, while the Stock Ledger maintains the granular inventory tracking.

Step 6: Frontend Selection Interface

The user interface provides a Batch/Serial Selector component implemented in erpnext/public/js/stock_utils.js. This searchable dialog auto-populates based on the selected warehouse, item code, and FIFO/LIFO preferences, preventing manual entry errors and ensuring only available batches/serials are selected.

Practical Implementation Examples

Creating a Stock Entry with Batch and Serial Numbers

When programmatically creating stock transactions, you must instantiate the Batch and Serial No documents before referencing them in the Stock Entry.

import frappe

# Create a new batch

batch = frappe.get_doc({
    "doctype": "Batch",
    "item": "ITEM-001",
    "batch_id": "BATCH-2024-001",
    "expiry_date": "2025-12-31"
}).insert()

# Create serial numbers

serials = ["SN-0001", "SN-0002", "SN-0003"]
for sn in serials:
    frappe.get_doc({
        "doctype": "Serial No",
        "item_code": "ITEM-001",
        "serial_no": sn
    }).insert()

# Build Stock Entry with batch and serial references

se = frappe.get_doc({
    "doctype": "Stock Entry",
    "stock_entry_type": "Material Issue",
    "items": [{
        "item_code": "ITEM-001",
        "warehouse": "Stores - WH",
        "qty": 3,
        "basic_rate": 150.0,
        "batch_no": batch.name,
        "serial_no": "\n".join(serials)  # Newline-separated string

    }]
})

se.insert()
se.submit()  # Triggers validation, SABB creation, and ledger posting

When se.submit() executes, the validate() method invokes get_bundle() to create the SABB document linking BATCH-2024-001 with serial numbers SN-0001 through SN-0003.

Fetching Existing Serial-Batch Information

To programmatically query existing bundle information for validation or reporting:

from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
    get_bundle,
)

bundle = get_bundle(
    item_code="ITEM-001",
    serial_nos=["SN-0002"],
    batch_no=None,
    warehouse="Stores - WH"
)

print(f"Bundle: {bundle.name}")
print(f"Batch: {bundle.batch_no}")
print(f"Serials: {bundle.serial_nos}")

If the specified serial number already exists in another active bundle, get_bundle() raises a validation error unless the reuse setting is explicitly enabled.

Updating Batch Valuation Rates

For batches using Moving Average valuation, you can manually trigger recalculation after adjustments:

batch = frappe.get_doc("Batch", "BATCH-2024-001")
batch.update_stock_valuation_rate()
batch.save()

The update_stock_valuation_rate() method in stock_ledger.py recomputes the weighted average based on all historical Stock Ledger Entries linked to that specific batch ID.

Key Source Files and References

Understanding the batch and serial number tracking implementation requires familiarity with these specific modules:

Summary

  • ERPNext uses Serial and Batch Bundles (SABB) as the central linking mechanism between stock transactions and their respective batch/serial identifiers.

  • Validation occurs at multiple levels: item master flags prevent invalid tracking assignments, while reservation checks enforce serial number uniqueness across the system.

  • The get_bundle() function in serial_and_batch_bundle.py handles both creation and retrieval of bundles, ensuring consistent naming and relationship management.

  • Batch valuation automatically updates using Moving Average calculations during ledger posting in stock_ledger.py.

  • All stock-related DocTypes (Stock Entry, Purchase Receipt, Delivery Note) share the same validation pipeline, guaranteeing consistent behavior across inventory operations.

Frequently Asked Questions

What is a Serial and Batch Bundle (SABB) in ERPNext?

A Serial and Batch Bundle is a document type that stores the relationship between a stock transaction and its associated batch numbers and serial numbers. When you submit a stock transaction involving tracked items, ERPNext creates a SABB document (named with the prefix SABB-) that serves as the immutable record linking the transaction to specific inventory identifiers, enabling full traceability and audit trails.

How does ERPNext prevent duplicate serial numbers in stock transactions?

ERPNext enforces serial number uniqueness through the get_stock_reservation_entry check in serial_and_batch_bundle.py (line 2730). The system queries existing Serial and Batch Bundles to verify that a serial number is not already assigned to an active stock entry. If the system detects an attempt to inward a serial number that already exists in another bundle, it raises a validation error unless the "Allow existing Serial No to be Manufactured/Received again" setting is explicitly enabled in Stock Settings.

Can you reuse a serial number after it has been delivered to a customer?

By default, serial numbers cannot be reused once they have been issued through a Delivery Note or Sales Invoice. The serial number retains its history and current status (e.g., "Delivered"). To allow reuse—which is typically only appropriate for manufacturing scenarios or returns—you must enable the "Allow existing Serial No to be Manufactured/Received again" checkbox in Stock Settings. Even with this setting enabled, the system maintains a complete history of all transactions associated with that serial number.

How does batch valuation work when using the Moving Average method?

When an item uses Moving Average valuation and has batch-wise tracking enabled, ERPNext calculates the batch-specific valuation rate in stock_ledger.py (line 2000) during ledger posting. The system examines all Stock Ledger Entries for that specific batch ID, computes the weighted average of incoming rates and quantities, and updates the batch document's valuation rate. This ensures that each batch maintains its own cost basis independent of other batches of the same item, providing accurate cost of goods sold calculations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →