From 9b452f12b7dc1d3cb1f16030641fb544895fa462 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:41:15 +0530 Subject: [PATCH] fix(accounts receivable): made territory field multi select (backport #57322) (#57346) fix(accounts receivable): made territory field multi select (#57322) (cherry picked from commit 1029cd988adef74a1830cdaaa816c163645ed259) Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com> --- .../accounts_payable/accounts_payable.js | 5 +- .../accounts_payable/test_accounts_payable.py | 30 +++++++++++ .../accounts_payable_summary.js | 5 +- .../accounts_receivable.js | 5 +- .../accounts_receivable.py | 53 +++++++++---------- .../test_accounts_receivable.py | 32 +++++++++++ .../accounts_receivable_summary.js | 5 +- 7 files changed, 104 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 4edd29a1494..ece851c9a69 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -117,8 +117,11 @@ frappe.query_reports["Accounts Payable"] = { { fieldname: "supplier_group", label: __("Supplier Group"), - fieldtype: "Link", + fieldtype: "MultiSelectList", options: "Supplier Group", + get_data: function (txt) { + return frappe.db.get_link_options("Supplier Group", txt); + }, hidden: 1, }, { diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index cced8318dec..fb6bcb59b8a 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -117,6 +117,36 @@ class TestAccountsPayable(ERPNextTestSuite, AccountsTestMixin): self.assertEqual(len(report[1]), 2) self.assertEqual([pi.name, payment_term1.payment_term_name], [row.voucher_no, row.payment_term]) + def test_supplier_group_filter(self): + pi = self.create_purchase_invoice() + supplier_group = frappe.db.get_value("Supplier", self.supplier, "supplier_group") + other_group = frappe.get_doc( + doctype="Supplier Group", + supplier_group_name="_Test Supplier Group AP", + parent_supplier_group="All Supplier Groups", + ).insert() + + filters = { + "company": self.company, + "party_type": "Supplier", + "report_date": today(), + "range": "30, 60, 90, 120", + "supplier_group": supplier_group, + } + self.assertIn(pi.name, [row.voucher_no for row in execute(filters)[1]]) + + filters.update({"supplier_group": [other_group.name]}) + self.assertEqual(len(execute(filters)[1]), 0) + + filters.update({"supplier_group": [supplier_group, other_group.name]}) + self.assertIn(pi.name, [row.voucher_no for row in execute(filters)[1]]) + + filters.update({"supplier_group": ["All Supplier Groups"]}) + self.assertIn(pi.name, [row.voucher_no for row in execute(filters)[1]]) + + filters.update({"supplier_group": ["_Test Supplier Group Mars"]}) + self.assertRaises(frappe.ValidationError, execute, filters) + def test_project_filter(self): project = frappe.get_doc( {"doctype": "Project", "project_name": "_Test AP Project", "company": self.company} diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 0b3bc077698..a5a42cb963b 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -100,8 +100,11 @@ frappe.query_reports["Accounts Payable Summary"] = { { fieldname: "supplier_group", label: __("Supplier Group"), - fieldtype: "Link", + fieldtype: "MultiSelectList", options: "Supplier Group", + get_data: function (txt) { + return frappe.db.get_link_options("Supplier Group", txt); + }, }, { fieldname: "based_on_payment_terms", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index e2b3cbb3e0c..97af9e6c1f6 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -140,8 +140,11 @@ frappe.query_reports["Accounts Receivable"] = { { fieldname: "territory", label: __("Territory"), - fieldtype: "Link", + fieldtype: "MultiSelectList", options: "Territory", + get_data: function (txt) { + return frappe.db.get_link_options("Territory", txt); + }, }, { fieldname: "group_by_party", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index cd0b5f9d246..47fdf9516f7 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -1019,7 +1019,13 @@ class ReceivablePayableReport: self.qb_selection_filter.append(self.ple.party.isin(customers)) if self.filters.get("territory"): - self.get_hierarchical_filters("Territory", "territory") + territories = get_nested_set_children("Territory", self.filters.territory) + customers = ( + qb.from_(self.customer) + .select(self.customer.name) + .where(self.customer["territory"].isin(territories)) + ) + self.qb_selection_filter.append(self.ple.party.isin(customers)) if self.filters.get("payment_terms_template"): customer_ptt = self.ple.party.isin( @@ -1040,11 +1046,10 @@ class ReceivablePayableReport: def add_supplier_filters(self): supplier = qb.DocType("Supplier") if self.filters.get("supplier_group"): + groups = get_party_group_with_children("Supplier", self.filters.supplier_group) self.qb_selection_filter.append( self.ple.party.isin( - qb.from_(supplier) - .select(supplier.name) - .where(supplier.supplier_group == self.filters.get("supplier_group")) + qb.from_(supplier).select(supplier.name).where(supplier.supplier_group.isin(groups)) ) ) @@ -1096,16 +1101,6 @@ class ReceivablePayableReport: return ptt - def get_hierarchical_filters(self, doctype, key): - lft, rgt = frappe.db.get_value(doctype, self.filters.get(key), ["lft", "rgt"]) - - doc = qb.DocType(doctype) - ple = self.ple - customer = self.customer - groups = qb.from_(doc).select(doc.name).where((doc.lft >= lft) & (doc.rgt <= rgt)) - customers = qb.from_(customer).select(customer.name).where(customer[key].isin(groups)) - self.qb_selection_filter.append(ple.party.isin(customers)) - def add_accounting_dimensions_filters(self): accounting_dimensions = get_accounting_dimensions(as_list=False) @@ -1349,19 +1344,23 @@ def get_party_group_with_children(party, party_groups): if party not in ("Customer", "Supplier"): return [] - group_dtype = f"{party} Group" - if not isinstance(party_groups, list): - party_groups = [d.strip() for d in party_groups.strip().split(",") if d] + return get_nested_set_children(f"{party} Group", party_groups) - all_party_groups = [] - for d in party_groups: - if frappe.db.exists(group_dtype, d): - lft, rgt = frappe.db.get_value(group_dtype, d, ["lft", "rgt"]) - children = frappe.get_all( - group_dtype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, pluck="name" - ) - all_party_groups += children + +def get_nested_set_children(doctype, values): + if not isinstance(values, list): + values = [d.strip() for d in values.split(",") if d.strip()] + + if not values: + frappe.throw(_("Please select a valid {0}").format(_(doctype))) + + all_values = [] + for d in values: + if frappe.db.exists(doctype, d): + lft, rgt = frappe.db.get_value(doctype, d, ["lft", "rgt"]) + children = frappe.get_all(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, pluck="name") + all_values += children else: - frappe.throw(_("{0}: {1} does not exist").format(group_dtype, d)) + frappe.throw(_("{0}: {1} does not exist").format(doctype, d)) - return list(set(all_party_groups)) + return list(set(all_values)) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 0ddb8cbfc0e..07c6edb4a04 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -779,6 +779,38 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): # Assert that the customer group of each row is in the list of customer groups self.assertIn(row.customer_group, cus_groups_list) + def test_territory_filter(self): + self.create_sales_invoice() + territory = frappe.db.get_value("Customer", self.customer, "territory") + + filters = { + "company": self.company, + "report_date": today(), + "range": "30, 60, 90, 120", + "territory": territory, + } + report = execute(filters)[1] + self.assertEqual(len(report), 1) + self.assertEqual( + [100.0, 100.0, territory], [report[0].invoiced, report[0].outstanding, report[0].territory] + ) + + filters.update({"territory": ["_Test Territory United States"]}) + self.assertEqual(len(execute(filters)[1]), 0) + + filters.update({"territory": [territory, "_Test Territory United States"]}) + self.assertEqual(len(execute(filters)[1]), 1) + + frappe.db.set_value("Customer", self.customer, "territory", "_Test Territory Maharashtra") + filters.update({"territory": ["_Test Territory India"]}) + self.assertEqual(len(execute(filters)[1]), 1) + + filters.update({"territory": ["_Test Territory Mars"]}) + self.assertRaises(frappe.ValidationError, execute, filters) + + filters.update({"territory": " "}) + self.assertRaises(frappe.ValidationError, execute, filters) + def test_party_account_filter(self): si1 = self.create_sales_invoice() jane = frappe.get_doc( diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index c15ec8b0124..3d7121f3836 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -106,8 +106,11 @@ frappe.query_reports["Accounts Receivable Summary"] = { { fieldname: "territory", label: __("Territory"), - fieldtype: "Link", + fieldtype: "MultiSelectList", options: "Territory", + get_data: function (txt) { + return frappe.db.get_link_options("Territory", txt); + }, }, { fieldname: "sales_partner",