From efd777a1a8731be498ac0f1424489a6947d72601 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 5 Jul 2026 15:10:03 +0530 Subject: [PATCH 1/2] fix(manufacturing): scope Production Planning arrival qty to open POs arrival_qty summed the all-time ordered qty of every submitted PO line for the item+warehouse, so it grew monotonically with purchase history. Sum the pending qty (qty - received_qty) on open POs instead, and take the earliest schedule date from the same scope. --- .../production_planning_report.py | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py index 4ce607b11a1..ac5cf67de96 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Min, Sum from pypika import Order from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses @@ -228,24 +229,26 @@ class ProductionPlanReport: self.purchase_details = {} - purchased_items = frappe.get_all( - "Purchase Order Item", - fields=[ - "item_code", - {"MIN": "schedule_date", "as": "arrival_date"}, - # qty is not in the GROUP BY, so it must be aggregated to be valid on postgres; sum the - # on-order qty per item+warehouse (the meaningful "arriving" figure) instead of MariaDB's - # arbitrary single-row pick. - {"SUM": "qty", "as": "arrival_qty"}, - "warehouse", - ], - filters={ - "item_code": ("in", self.item_codes), - "warehouse": ("in", self.warehouses), - "docstatus": 1, - }, - group_by="item_code, warehouse", - ) + po = frappe.qb.DocType("Purchase Order") + poi = frappe.qb.DocType("Purchase Order Item") + purchased_items = ( + frappe.qb.from_(po) + .join(poi) + .on(poi.parent == po.name) + .select( + poi.item_code, + Min(poi.schedule_date).as_("arrival_date"), + Sum(poi.qty - poi.received_qty).as_("arrival_qty"), + poi.warehouse, + ) + .where( + (poi.item_code.isin(self.item_codes)) + & (poi.warehouse.isin(self.warehouses)) + & (po.docstatus == 1) + & (po.status.notin(["Closed", "Completed", "Cancelled"])) + ) + .groupby(poi.item_code, poi.warehouse) + ).run(as_dict=True) for d in purchased_items: key = (d.item_code, d.warehouse) if key not in self.purchase_details: From 2a6bc517bca915963b01a7f37288f56bda27f526 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 5 Jul 2026 15:29:18 +0530 Subject: [PATCH 2/2] fix: exclude fully received PO lines from arrival qty and date --- .../production_planning_report/production_planning_report.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py index ac5cf67de96..ea423b7a40a 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py @@ -246,6 +246,7 @@ class ProductionPlanReport: & (poi.warehouse.isin(self.warehouses)) & (po.docstatus == 1) & (po.status.notin(["Closed", "Completed", "Cancelled"])) + & (poi.qty > poi.received_qty) ) .groupby(poi.item_code, poi.warehouse) ).run(as_dict=True)