fix(postgres): use portable GroupConcat in Lost Opportunity report

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mihir Kandoi
2026-06-17 15:19:16 +05:30
parent 595a4c8517
commit 0b795a628f
2 changed files with 27 additions and 3 deletions

View File

@@ -5,8 +5,7 @@
import frappe
from frappe import _
from frappe.query_builder import DocType
from frappe.query_builder.custom import GROUP_CONCAT
from frappe.query_builder.functions import Date
from frappe.query_builder.functions import Date, GroupConcat
Opportunity = DocType("Opportunity")
OpportunityLostReasonDetail = DocType("Opportunity Lost Reason Detail")
@@ -72,6 +71,9 @@ def get_columns():
def get_data(filters):
# db-aware GROUP_CONCAT (MariaDB) / STRING_AGG (postgres) with a ", " separator
lost_reasons = GroupConcat(OpportunityLostReasonDetail.lost_reason, ", ", alias="lost_reason")
query = (
frappe.qb.from_(Opportunity)
.left_join(OpportunityLostReasonDetail)
@@ -85,7 +87,7 @@ def get_data(filters):
Opportunity.party_name,
Opportunity.customer_name,
Opportunity.opportunity_type,
GROUP_CONCAT(OpportunityLostReasonDetail.lost_reason, alias="lost_reason").separator(", "),
lost_reasons,
Opportunity.sales_stage,
Opportunity.territory,
)

View File

@@ -0,0 +1,22 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.utils import add_days, today
from erpnext.crm.report.lost_opportunity.lost_opportunity import execute
from erpnext.tests.utils import ERPNextTestSuite
class TestLostOpportunity(ERPNextTestSuite):
def test_report_aggregates_lost_reasons(self):
# Exercises the db-aware GROUP_CONCAT (MariaDB) / STRING_AGG (postgres) aggregation of the
# child "Opportunity Lost Reason Detail" rows. The MySQL-only GROUP_CONCAT term would fail to
# compile on postgres, so simply running the report query guards the portability fix on both
# databases.
company = frappe.db.get_value("Company", {}, "name")
columns, data = execute(
frappe._dict({"company": company, "from_date": add_days(today(), -365), "to_date": today()})
)
self.assertTrue(columns)
self.assertIsInstance(data, list)