From 27ec5eabc69bac3812fd16d1e65eaf2516f43c45 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 23 Jun 2026 15:46:26 +0530 Subject: [PATCH] fix(accounts): stop coalescing a DATE with an int in Asset Depreciations report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Asset Depreciations and Balances report tested disposal status with IfNull(asset.disposal_date, 0) != 0 / == 0 — coalescing the DATE column disposal_date with the integer 0. frappe.qb renders this as COALESCE("disposal_date", 0); PostgreSQL requires COALESCE arguments to share a type and raises: psycopg2.errors.DatatypeMismatch: COALESCE types date and integer cannot be matched The predicate is in the WHERE/CASE of every query the report runs (both group_by=Asset Category and group_by=Asset), so the whole report errored on PostgreSQL. MariaDB's IFNULL(date, 0) is permissive and worked. Replace each comparison with the null-test form already used elsewhere in this same file: IfNull(disposal_date, 0) != 0 -> disposal_date.isnotnull(), == 0 -> disposal_date.isnull(). Semantically identical (a stored date is never 0), valid on both engines, MariaDB output unchanged. --- .../asset_depreciations_and_balances.py | 40 +++++++------------ .../test_asset_depreciations_and_balances.py | 26 ++++++++++++ 2 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 erpnext/accounts/report/asset_depreciations_and_balances/test_asset_depreciations_and_balances.py diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 212698765b1..3787896c7aa 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -82,7 +82,7 @@ def get_asset_categories_for_grouped_by_category(filters): asset_capitalization = frappe.qb.DocType("Asset Capitalization") disposal_in_period = ( - (IfNull(asset.disposal_date, 0) != 0) + (asset.disposal_date.isnotnull()) & (asset.disposal_date >= filters.from_date) & (asset.disposal_date <= filters.to_date) ) @@ -92,7 +92,7 @@ def get_asset_categories_for_grouped_by_category(filters): frappe.qb.terms.Case() .when( (asset.purchase_date < filters.from_date) - & ((IfNull(asset.disposal_date, 0) == 0) | (asset.disposal_date >= filters.from_date)), + & ((asset.disposal_date.isnull()) | (asset.disposal_date >= filters.from_date)), asset.net_purchase_amount, ) .else_(0) @@ -209,9 +209,7 @@ def get_assets_for_grouped_by_category(filters): frappe.qb.terms.Case() .when( (gl_entry.posting_date < filters.from_date) - & ( - (IfNull(asset.disposal_date, 0) == 0) | (asset.disposal_date >= filters.from_date) - ), + & ((asset.disposal_date.isnull()) | (asset.disposal_date >= filters.from_date)), gl_entry.debit, ) .else_(0) @@ -222,7 +220,7 @@ def get_assets_for_grouped_by_category(filters): Sum( frappe.qb.terms.Case() .when( - (gl_entry.posting_date <= filters.to_date) & (IfNull(asset.disposal_date, 0) == 0), + (gl_entry.posting_date <= filters.to_date) & (asset.disposal_date.isnull()), gl_entry.credit, ) .else_(0) @@ -233,7 +231,7 @@ def get_assets_for_grouped_by_category(filters): Sum( frappe.qb.terms.Case() .when( - (IfNull(asset.disposal_date, 0) != 0) + (asset.disposal_date.isnotnull()) & (asset.disposal_date >= filters.from_date) & (asset.disposal_date <= filters.to_date) & (gl_entry.posting_date <= asset.disposal_date), @@ -249,10 +247,7 @@ def get_assets_for_grouped_by_category(filters): .when( (gl_entry.posting_date >= filters.from_date) & (gl_entry.posting_date <= filters.to_date) - & ( - (IfNull(asset.disposal_date, 0) == 0) - | (gl_entry.posting_date <= asset.disposal_date) - ), + & ((asset.disposal_date.isnull()) | (gl_entry.posting_date <= asset.disposal_date)), gl_entry.debit, ) .else_(0) @@ -282,7 +277,7 @@ def get_assets_for_grouped_by_category(filters): Sum( frappe.qb.terms.Case() .when( - (IfNull(asset.disposal_date, 0) != 0) & (asset.disposal_date < filters.from_date), + (asset.disposal_date.isnotnull()) & (asset.disposal_date < filters.from_date), 0, ) .else_(asset.opening_accumulated_depreciation) @@ -476,7 +471,7 @@ def get_asset_details_for_grouped_by_category(filters): asset_capitalization = frappe.qb.DocType("Asset Capitalization") disposal_in_period = ( - (IfNull(asset.disposal_date, 0) != 0) + (asset.disposal_date.isnotnull()) & (asset.disposal_date >= filters.from_date) & (asset.disposal_date <= filters.to_date) ) @@ -500,9 +495,7 @@ def get_asset_details_for_grouped_by_category(filters): frappe.qb.terms.Case() .when( (asset.purchase_date < filters.from_date) - & ( - (IfNull(asset.disposal_date, 0) == 0) | (asset.disposal_date >= filters.from_date) - ), + & ((asset.disposal_date.isnull()) | (asset.disposal_date >= filters.from_date)), asset.net_purchase_amount, ) .else_(0) @@ -599,9 +592,7 @@ def get_assets_for_grouped_by_asset(filters): frappe.qb.terms.Case() .when( (gl_entry.posting_date < filters.from_date) - & ( - (IfNull(asset.disposal_date, 0) == 0) | (asset.disposal_date >= filters.from_date) - ), + & ((asset.disposal_date.isnull()) | (asset.disposal_date >= filters.from_date)), gl_entry.debit, ) .else_(0) @@ -612,7 +603,7 @@ def get_assets_for_grouped_by_asset(filters): Sum( frappe.qb.terms.Case() .when( - (gl_entry.posting_date <= filters.to_date) & (IfNull(asset.disposal_date, 0) == 0), + (gl_entry.posting_date <= filters.to_date) & (asset.disposal_date.isnull()), gl_entry.credit, ) .else_(0) @@ -623,7 +614,7 @@ def get_assets_for_grouped_by_asset(filters): Sum( frappe.qb.terms.Case() .when( - (IfNull(asset.disposal_date, 0) != 0) + (asset.disposal_date.isnotnull()) & (asset.disposal_date >= filters.from_date) & (asset.disposal_date <= filters.to_date) & (gl_entry.posting_date <= asset.disposal_date), @@ -639,10 +630,7 @@ def get_assets_for_grouped_by_asset(filters): .when( (gl_entry.posting_date >= filters.from_date) & (gl_entry.posting_date <= filters.to_date) - & ( - (IfNull(asset.disposal_date, 0) == 0) - | (gl_entry.posting_date <= asset.disposal_date) - ), + & ((asset.disposal_date.isnull()) | (gl_entry.posting_date <= asset.disposal_date)), gl_entry.debit, ) .else_(0) @@ -672,7 +660,7 @@ def get_assets_for_grouped_by_asset(filters): Sum( frappe.qb.terms.Case() .when( - (IfNull(asset.disposal_date, 0) != 0) & (asset.disposal_date < filters.from_date), + (asset.disposal_date.isnotnull()) & (asset.disposal_date < filters.from_date), 0, ) .else_(asset.opening_accumulated_depreciation) diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/test_asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/test_asset_depreciations_and_balances.py new file mode 100644 index 00000000000..fdc1f92f22a --- /dev/null +++ b/erpnext/accounts/report/asset_depreciations_and_balances/test_asset_depreciations_and_balances.py @@ -0,0 +1,26 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe + +from erpnext.accounts.report.asset_depreciations_and_balances.asset_depreciations_and_balances import ( + execute, +) +from erpnext.tests.utils import ERPNextTestSuite + + +class TestAssetDepreciationsAndBalancesReport(ERPNextTestSuite): + def test_report_runs_on_both_engines(self): + """The report compared IfNull(asset.disposal_date, 0) against 0 -- coalescing a DATE + column with integer 0. Postgres rejects that (COALESCE types date and integer cannot be + matched) at plan time, so the whole report errored there regardless of data. It must run + on both engines.""" + for group_by in ("Asset Category", "Asset"): + filters = frappe._dict( + company="_Test Company", + from_date="2020-01-01", + to_date="2030-12-31", + group_by=group_by, + ) + result = execute(filters) + self.assertIsInstance(result[1], list)