From eed7c98b30107ef582658ec070602270efbe3ce0 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 17 Jul 2026 13:54:08 +0530 Subject: [PATCH] fix: replace column-literal work order filter with server-side query The work_order link filter in Stock Entry passed the string `tabWork Order`.produced_qty as a filter value. It was never a real column comparison: db_query coerces string values on numeric fields with flt(), so the condition silently degraded to qty > 0, and on backends that don't coerce text to numeric (postgres) such filters fail with InvalidTextRepresentation. Move the condition into a whitelisted search query that compares the columns properly (qty > produced_qty), mirroring pick_list's get_pending_work_orders. --- .../stock/doctype/stock_entry/stock_entry.js | 9 ++++--- .../stock/doctype/stock_entry/stock_entry.py | 22 +++++++++++++++++ .../doctype/stock_entry/test_stock_entry.py | 24 ++++++++++++++++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 650eeea9891..0c95e192032 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -21,11 +21,10 @@ frappe.ui.form.on("Stock Entry", { frm.set_query("work_order", function () { return { - filters: [ - ["Work Order", "docstatus", "=", 1], - ["Work Order", "qty", ">", "`tabWork Order`.produced_qty"], - ["Work Order", "company", "=", frm.doc.company], - ], + query: "erpnext.stock.doctype.stock_entry.stock_entry.get_pending_work_orders", + filters: { + company: frm.doc.company, + }, }; }); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index e0fbbccff37..0a0f9495677 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1577,6 +1577,28 @@ def make_stock_in_entry(source_name: str, target_doc: str | Document | None = No return doclist +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_pending_work_orders( + doctype: str, txt: str, searchfield: str, start: int, page_length: int, filters: dict +) -> list: + work_order = frappe.qb.DocType("Work Order") + query = frappe.qb.get_query( + "Work Order", + fields=["name", "production_item"], + filters={ + "docstatus": 1, + "company": filters.get("company"), + "name": ("like", f"%{txt}%"), + }, + order_by="name", + limit=cint(page_length), + offset=cint(start), + ignore_permissions=False, + ) + return query.where(work_order.qty > work_order.produced_qty).run() + + @frappe.whitelist() def get_work_order_details(work_order: str, company: str): work_order = frappe.get_doc("Work Order", work_order) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index ae5a9541ac5..536fbdb9263 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -26,7 +26,11 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle make_serial_batch_bundle, ) from erpnext.stock.doctype.serial_no.serial_no import * -from erpnext.stock.doctype.stock_entry.stock_entry import FinishedGoodError, make_stock_in_entry +from erpnext.stock.doctype.stock_entry.stock_entry import ( + FinishedGoodError, + get_pending_work_orders, + make_stock_in_entry, +) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( @@ -3392,6 +3396,24 @@ class TestStockEntryCoverage(ERPNextTestSuite): for bn in list(get_batches_from_bundle(row.serial_and_batch_bundle).keys()): self.assertIn(bn, wo1_batches) + def test_get_pending_work_orders(self): + from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record + + wo = make_wo_order_test_record(qty=2, skip_transfer=True) + + def pending_work_orders(txt=""): + return [ + row[0] + for row in get_pending_work_orders("Work Order", txt, "name", 0, 0, {"company": wo.company}) + ] + + self.assertIn(wo.name, pending_work_orders()) + self.assertIn(wo.name, pending_work_orders(wo.name.lower())) + self.assertNotIn(wo.name, pending_work_orders("no-such-work-order")) + + frappe.db.set_value("Work Order", wo.name, "produced_qty", wo.qty) + self.assertNotIn(wo.name, pending_work_orders()) + def make_serialized_item(self, **args): args = frappe._dict(args)