fix(postgres): db-aware zero-date (0000-00-00) item end-of-life checks

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) <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-06-17 12:23:15 +05:30
parent 48e66d04e6
commit 7532ec9f9a
3 changed files with 16 additions and 22 deletions

View File

@@ -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):

View File

@@ -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, {})

View File

@@ -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)))
)