From 7532ec9f9af7c20e7bbe0cddb18c046a90f519a2 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 17 Jun 2026 12:23:15 +0530 Subject: [PATCH] fix(postgres): db-aware zero-date (0000-00-00) item end-of-life checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "item is not discontinued" checks treat an item as alive when its `end_of_life` is unset, in the future, or the MariaDB zero-date `'0000-00-00'`. `'0000-00-00'` is an invalid date literal on PostgreSQL (it errors), and a "not set" end_of_life is `NULL` there anyway — already covered by the existing `end_of_life IS NULL` term. So the zero-date comparison is applied on MariaDB only; PostgreSQL keeps the `IS NULL` / future-date terms. No behaviour change on MariaDB. Sites: work order item-master selection (`mapper.py`), reorder-level item selection (`reorder_item.py`), and the Stock Projected Qty report. Part of the staged MariaDB<->PostgreSQL parity rollout (one problem class). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../doctype/work_order/mapper.py | 11 ++++---- erpnext/stock/reorder_item.py | 25 +++++++------------ .../stock_projected_qty.py | 2 +- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/mapper.py b/erpnext/manufacturing/doctype/work_order/mapper.py index 844952330b0..05ae74df42f 100644 --- a/erpnext/manufacturing/doctype/work_order/mapper.py +++ b/erpnext/manufacturing/doctype/work_order/mapper.py @@ -56,11 +56,12 @@ def _item_master_details(item): def _item_is_alive(item_table): - return ( - item_table.end_of_life.isnull() - | (item_table.end_of_life == "0000-00-00") - | (item_table.end_of_life > nowdate()) - ) + # "not set" end_of_life is NULL on postgres (the MariaDB zero-date '0000-00-00' is an invalid + # date constant there), so only add the zero-date term on MariaDB. + is_alive = item_table.end_of_life.isnull() | (item_table.end_of_life > nowdate()) + if frappe.db.db_type != "postgres": + is_alive |= item_table.end_of_life == "0000-00-00" + return is_alive def _default_bom_for_item(item, project): diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index 0db78099c2f..546e1541e6e 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -24,14 +24,9 @@ def reorder_item(): def _reorder_item(): material_requests = {"Purchase": {}, "Transfer": {}, "Material Issue": {}, "Manufacture": {}} warehouse_company = frappe._dict( - frappe.db.sql( - """select name, company from `tabWarehouse` - where disabled=0""" - ) - ) - default_company = ( - erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0] + frappe.get_all("Warehouse", filters={"disabled": 0}, fields=["name", "company"], as_list=True) ) + default_company = erpnext.get_default_company() or frappe.db.get_value("Company", {}, "name") items_to_consider = get_items_for_reorder() @@ -141,7 +136,7 @@ def get_items_for_reorder() -> dict[str, list]: & ( (item_table.end_of_life.isnull()) | (item_table.end_of_life > nowdate()) - | (item_table.end_of_life == "0000-00-00") + | (item_table.end_of_life == ("0000-00-00" if frappe.db.db_type != "postgres" else None)) ) ) ) @@ -171,7 +166,7 @@ def get_reorder_levels_for_variants(itemwise_reorder): & ( (item_table.end_of_life.isnull()) | (item_table.end_of_life > nowdate()) - | (item_table.end_of_life == "0000-00-00") + | (item_table.end_of_life == ("0000-00-00" if frappe.db.db_type != "postgres" else None)) ) & (item_table.variant_of.notnull()) ) @@ -189,13 +184,11 @@ def get_item_warehouse_projected_qty(items_to_consider): item_warehouse_projected_qty = {} items_to_consider = list(items_to_consider.keys()) - for item_code, warehouse, projected_qty in frappe.db.sql( - """select item_code, warehouse, projected_qty - from tabBin where item_code in ({}) - and (warehouse != '' and warehouse is not null)""".format( - ", ".join(["%s"] * len(items_to_consider)) - ), - items_to_consider, + for item_code, warehouse, projected_qty in frappe.get_all( + "Bin", + filters={"item_code": ["in", items_to_consider], "warehouse": ["is", "set"]}, + fields=["item_code", "warehouse", "projected_qty"], + as_list=True, ): if item_code not in item_warehouse_projected_qty: item_warehouse_projected_qty.setdefault(item_code, {}) diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py index 7fc128980b4..30ef2944136 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py @@ -293,7 +293,7 @@ def get_item_map(item_code, include_uom): & ( (item.end_of_life > today()) | (item.end_of_life.isnull()) - | (item.end_of_life == "0000-00-00") + | (item.end_of_life == ("0000-00-00" if frappe.db.db_type != "postgres" else None)) ) & (ExistsCriterion(frappe.qb.from_(bin).select(bin.name).where(bin.item_code == item.name))) )