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:
Enter your SO quantity, delivered, in-production, and material availability to see the exact breakdown of what can and cannot be planned for production.
- - 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:
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.
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:
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.
# 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 itemsRun 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:
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.
The starting point. A submitted Sales Order with pending delivery quantity. If status is Completed/Cancelled/Closed, it won't appear in Production Plan.
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.