ERP

How to Handle Unfulfilled SO Quantities in ERPNext Production Planning

ERPNext Production Plan's 'Get Items from Sales Orders' misses partially delivered orders, shows wrong pending quantities, and ignores material availability. This guide fixes the three breakpoints with interactive diagnostics and working Python scripts.

MManojAugust 4, 202613 min read
#erpnext#manufacturing#production-planning#erp
Share

You have 50 open Sales Orders, each partially delivered. You open Production Plan, click "Get Items from Sales Orders", and expect to see the remaining quantities that need production. Instead, some orders show zero pending quantity. Others show the full SO quantity instead of just the unfulfilled portion. The planned_qty field on the Sales Order Item is out of sync with actual Production Plan Items. This is the most common production planning complaint on the ERPNext forum — GitHub #49819, Forum #163193, Forum #161593 all report the same breakpoints. This guide walks through the three root causes and provides working Python scripts to fix each one.

Need QI in your manufacturing workflow?

Once production is planned, quality inspection catches defects before they ship. See the Quality Inspection in Manufacturing guide for template setup, auto-triggering, and rejection workflows.

Production planning with partial deliveries is where ERPNext's manufacturing module shows its roughest edges. The "Get Items" button filters Sales Orders by status and pending quantity, but the three fields it relies on — delivered_qty, produced_qty, and planned_qty — can drift out of sync with reality when Work Orders are cancelled, Production Plans are closed without completion, or Stock Entries are amended. I am Manoj, ERP implementation lead at Mith Tech in Bengaluru, and reconciling production planning data after partial deliveries is a weekly task for our manufacturing clients.

Why "Get Items" shows the wrong quantity

The Production Plan's "Get Items from Sales Orders" button runs a query that calculates pending quantity as:

pending_qty = so_item.qty - so_item.delivered_qty - so_item.planned_qty

This formula has three failure modes:

1. planned_qty is stale. When you cancel a Production Plan or close it without completing all Work Orders, planned_qty on the SO Item may not decrement. The system thinks those quantities are still being produced. The next "Get Items" subtracts the stale planned_qty and shows less (or zero) pending quantity.

2. SO status filter excludes valid orders. "Get Items" only considers Sales Orders with status "To Deliver and Bill" or "To Deliver". If a partial payment marked the SO as "To Bill" or a custom workflow moved it to a non-standard status, the order disappears from the query.

3. No active default BOM. Items without a BOM flagged as is_active = 1 and is_default = 1 are silently skipped. This happens when someone creates a new BOM version but forgets to mark it as default, or when a BOM is deactivated during an engineering change.

Visualising the gap

The Fulfillment Gap Calculator breaks down an SO quantity into its components — what has been delivered, what is in production, what can be planned (material available), and what is blocked:

CalculatorFulfillment Gap Calculatorlink

Enter your SO quantity, delivered, in-production, and material availability to see the exact breakdown of what can and cannot be planned for production.

Unfulfilled Qty
250
500 - 150 delivered - 100 in prod
Can Be Planned
180
72% of 250 has material
Blocked by Material
70
Needs procurement before planning
Production Plan Summary
  • - Create Production Plan for 180 units (material available)
  • - Create Material Request for the remaining 70 units (shortage)
  • - Already in production: 100 | Delivered: 150

Diagnosing your specific issue

The Production Plan Debugger walks through the five checkpoints. Answer each question about your SO and it will identify which root cause applies to your situation:

InteractiveProduction Plan Debuggerlink

Five diagnostic checks to identify why Get Items from Sales Orders shows wrong or zero pending quantities. Each check explains what to look for and what the fix is.

Step 1GitHub #49819
SO has pending qty?
Check if qty_to_deliver > 0 on the Sales Order Item. If all quantities are already delivered, the SO won't appear in "Get Items".
Step 2Forum #163193
SO status is 'To Deliver and Bill' or 'To Deliver'?
Production Plan > Get Items only picks SOs with status that indicates pending delivery. Statuses like 'Completed', 'Cancelled', or 'Closed' are excluded.
Step 3
Item has an active default BOM?
Production Plan only picks items that have a BOM marked as both 'Is Active' and 'Is Default'. Without one, the item is treated as a purchase item.
Step 4Forum #161593
Warehouse filter matches?
If you set a warehouse filter on the Production Plan, only SO Items with a matching delivery warehouse appear. A mismatch means zero items.
Step 5GitHub #50809
Previously planned qty accounted for?
The planned_qty field on SO Items tracks how many units are already in a submitted Production Plan. If this value is stale or wrong, Get Items may under-count or skip items.

The fix — scripts and workflow

The fix depends on the root cause. The recipe below provides three Python scripts: one to query unfulfilled SOs directly (bypassing the UI), one to reconcile planned_qty, and one to check material availability:

Code recipeProduction Plan Fix Recipelink

Three-tab Python recipe: query unfulfilled SO items with pending qty and active BOM, reconcile stale planned_qty against actual Production Plan Items, and check raw material availability against BOM requirements.

Fetch Sales Order items with pending quantities and active BOMs, ready for production planning. Handles the partial-delivery edge case that trips up the standard Get Items button.

python
# Fetch Sales Orders with unfulfilled quantities
# for production planning in ERPNext v15
# See: GitHub #49819, Forum #163193

import frappe

def get_unfulfilled_so_items(
    company: str,
    from_date: str | None = None,
    item_code: str | None = None,
):
    """Return SO items where qty_to_deliver > 0
    and an active BOM exists for the item."""

    filters = {
        "docstatus": 1,
        "company": company,
        "status": ["not in", ["Completed", "Cancelled", "Closed"]],
    }
    if from_date:
        filters["transaction_date"] = [">=", from_date]

    orders = frappe.get_all(
        "Sales Order",
        filters=filters,
        fields=["name"],
    )

    items = []
    for so in orders:
        so_items = frappe.get_all(
            "Sales Order Item",
            filters={
                "parent": so.name,
                "qty": [">", 0],
            },
            fields=[
                "item_code", "qty", "delivered_qty",
                "produced_qty", "ordered_qty",
            ],
        )
        for item in so_items:
            pending = item.qty - item.delivered_qty
            if pending <= 0:
                continue
            if item_code and item.item_code != item_code:
                continue
            # Check for active BOM
            bom = frappe.db.exists(
                "BOM",
                {"item": item.item_code, "is_active": 1, "is_default": 1},
            )
            if not bom:
                continue
            items.append({
                "sales_order": so.name,
                "item_code": item.item_code,
                "so_qty": item.qty,
                "delivered_qty": item.delivered_qty,
                "produced_qty": item.produced_qty,
                "pending_qty": pending,
                "bom": bom,
            })

    return items

Run the unfulfilled SO query

Execute the get_unfulfilled_so_items() function from the bench console or a custom API endpoint. This returns all SO Items with pending quantity and an active BOM — the same items that "Get Items" should show. Compare the output against what the UI shows to identify discrepancies.

Reconcile planned_qty

Run reconcile_planned_qty(sales_order) for each SO with discrepancies. This recalculates planned_qty by summing actual submitted Production Plan Items that reference the SO. After reconciliation, "Get Items" will show the correct pending quantities.

Check material availability

For each item you plan to produce, run check_material_availability() with the required quantity and source warehouse. This returns the list of raw material shortages and the maximum quantity that can be produced with current stock. Use this to split the Work Order into a producible batch and a material-blocked batch.

Create Work Orders from corrected plan

Open Production Plan, click "Get Items from Sales Orders" again. The corrected planned_qty values should now show the right pending quantities. Create Work Orders for the plannable quantity. For material-blocked quantities, create Material Requests.

The production planning workflow — with failure points

The visual flow below shows the complete path from Sales Order to Delivery, with the specific failure points that cause production planning issues:

InteractivePlanning Workflow Diagramlink

Click through each step of the production planning workflow to see what can go wrong — from SO to Get Items to BOM check to material availability to Work Order completion to delivery.

Sales Order

The starting point. A submitted Sales Order with pending delivery quantity. If status is Completed/Cancelled/Closed, it won't appear in Production Plan.

SO may be fully delivered or closed

What to know before you commit

Run reconciliation during off-hours. The reconcile_planned_qty script updates SO Item records directly. On a system with 500+ open SOs, this takes 2–5 minutes and briefly locks each SO record. Run it during non-business hours to avoid conflicts with users creating orders.

Reconciliation is safe to repeat. The script recalculates from the source of truth (submitted Production Plan Items), so running it multiple times produces the same result. Add it to a daily scheduled job if planned_qty drift is a recurring issue.

Material availability is point-in-time. The check_material_availability function reads current stock. Between the check and Work Order creation, stock may change (other Production Plans, Purchase Receipts, Stock Entries). For accurate planning, run the check immediately before creating Work Orders.

Multi-level BOMs. The scripts above handle single-level BOMs. For multi-level (sub-assembly) BOMs, ERPNext's "Get Sub Assembly Items" button in the Production Plan handles the explosion. The planned_qty reconciliation still applies — the issue is at the top-level SO Item, not the sub-assembly level.

+Why does ERPNext Production Plan show zero pending quantity for my Sales Order?

The most common cause is stale planned_qty on the Sales Order Item. When a Production Plan is cancelled or closed without completing all Work Orders, planned_qty may not decrement. The system calculates pending as qty - delivered_qty - planned_qty, so a stale planned_qty makes the pending quantity appear as zero. Run the reconciliation script to fix it.

+How do I get items from partially delivered Sales Orders in ERPNext?

Use Production Plan → Get Items from Sales Orders. The system filters for SOs with status "To Deliver" or "To Deliver and Bill" and calculates pending quantity per item. If items are missing, check three things: (1) planned_qty is not stale, (2) the item has an active default BOM, (3) the SO status matches the expected filter. The Production Plan Debugger in this guide walks through each check.

+What is planned_qty in ERPNext Sales Order Item?

planned_qty tracks how much of the SO Item quantity has been included in submitted Production Plans. It is used by "Get Items" to avoid double-planning. The field should equal the sum of planned_qty from all submitted, non-closed Production Plan Items that reference this SO Item. When it drifts (due to cancelled plans or amendments), production planning shows incorrect pending quantities.

+How do I check material availability for production planning in ERPNext?

Use Production Plan → Get Raw Materials or run the material availability script from this guide. Both check current stock against BOM requirements for the planned production quantity. ERPNext compares the actual_qty in the source warehouse against qty_consumed_per_unit × planned_qty for each BOM item. Shortages appear as items needing Material Requests.

+Why does Get Items not show items without a BOM?

Production Plan is designed for manufactured items only — items that have a Bill of Materials defining their raw material composition. Items without an active default BOM (both is_active = 1 and is_default = 1) are silently excluded from "Get Items". This is by design, not a bug. If your manufactured item is missing, check that its BOM is both active and marked as default.

+Can I run production planning for a single Sales Order?

Yes. In the Production Plan, use the "Sales Order" filter in "Get Items from Sales Orders" to select a specific SO. Alternatively, open the Sales Order and click Menu → Create → Production Plan (available in ERPNext v15+). This pre-fills the Production Plan with items from that specific SO.

+How do I handle multi-level BOMs in production planning?

After "Get Items", click "Get Sub Assembly Items" in the Production Plan. This explodes multi-level BOMs and creates rows for each sub-assembly that needs to be manufactured. Work Orders are created bottom-up — sub-assemblies first, then the parent item. The planned_qty reconciliation applies to the top-level SO Item, not sub-assemblies.

+What ERPNext version does this guide apply to?

This guide targets ERPNext v15. The Production Plan workflow, planned_qty field behaviour, and "Get Items" logic have been consistent across v14 and v15. The reconcile_planned_qty script uses standard Frappe ORM calls that work across versions. Check for version-specific changes in the ERPNext release notes if you are on v14 or the upcoming v16.

Production planning with partial deliveries breaks when planned_qty drifts out of sync — and it drifts every time a Production Plan is cancelled, closed, or amended without completing all Work Orders. The reconciliation script above fixes the data. Run it once to clean up, then add it as a daily scheduled job if the drift recurs. The Fulfillment Gap Calculator and Production Plan Debugger help you diagnose before you script.

Production planning not matching your Sales Orders?

We fix ERPNext manufacturing workflows — production planning, Work Order management, and BOM configuration. If your Production Plan shows wrong quantities or misses partially delivered orders, we will trace the root cause and fix it.

Free · By email

Get practical ERPNext & automation guides

New implementation guides, cost breakdowns and open-source tips for Indian businesses — occasionally, straight to your inbox. No spam.

M

Written by

Manoj

Founder of Mith Tech, an open-source ERP & automation studio. Hands-on ERPNext/Frappe implementation across multi-branch, multi-warehouse Indian operations — GST/TDS/PT compliance, branch-level permissions, and custom Frappe apps that give management real-time visibility.

Keep reading

See what this looks like for your business

Book a free 30-minute audit. We'll map your workflows, find where time and money leak, and design an open-source stack you actually own — no per-user licence fees.

Book a consultation
0 0
Published on 4 August 2026

Manoj

Comments

No comments yet. Start a new discussion.

Ctrl+Enter to add comment