From b05abbc53b3655b02db17ba2e8165519f195c1c2 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Sun, 7 Jun 2026 21:53:33 +0530 Subject: [PATCH 1/2] fix: apply user permissions to receivable/payable reports --- .../accounts_receivable.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 0cd34e030d6..56eec27c6d0 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -928,8 +928,28 @@ class ReceivablePayableReport: if self.filters.project: self.qb_selection_filter.append(self.ple.project.isin(self.filters.project)) + self.add_user_permission_filters() + self.add_accounting_dimensions_filters() + def add_user_permission_filters(self): + # Party is a dynamic link, so match conditions cannot auto-apply Customer/Supplier user permissions + from frappe.core.doctype.user_permission.user_permission import get_user_permissions + from frappe.permissions import get_allowed_docs_for_doctype + + user_permissions = get_user_permissions() + if not user_permissions: + return + + for party_type in self.party_type: + if party_type not in user_permissions: + continue + + allowed_parties = get_allowed_docs_for_doctype(user_permissions[party_type], party_type) + self.qb_selection_filter.append( + (self.ple.party_type != party_type) | self.ple.party.isin(allowed_parties or [""]) + ) + def get_cost_center_conditions(self): cost_center_list = get_cost_centers_with_children(self.filters.cost_center) self.qb_selection_filter.append(self.ple.cost_center.isin(cost_center_list)) From 4200d17c9b208c72749e426a66a09cb48aeadbd9 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Sun, 7 Jun 2026 21:58:21 +0530 Subject: [PATCH 2/2] test: cover user permission scoping in receivable report --- .../test_accounts_receivable.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 9b8b8b709db..1b7476c4907 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -1245,3 +1245,44 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin): self.assertEqual(len(report[1]), 1) row = report[1][0] self.assertEqual([si.name, project.name, 60], [row.voucher_no, row.project, row.outstanding]) + + def test_accounts_receivable_respects_user_permissions(self): + # Party is a dynamic link on Payment Ledger Entry, so user permissions on Customer + # must be applied explicitly. The report should only show permitted customers. + original_customer = self.customer + second_customer = "_Test AR Perm Customer" + + # create_customer overrides self.customer, so build the restricted invoice first + self.create_customer(customer_name=second_customer) + self.create_sales_invoice(no_payment_schedule=True) + + self.customer = original_customer + allowed_invoice = self.create_sales_invoice(no_payment_schedule=True) + + test_user = "test_ar_user_permission@example.com" + if not frappe.db.exists("User", test_user): + user = frappe.new_doc("User") + user.email = test_user + user.first_name = "AR Perm" + user.append("roles", {"role": "Accounts User"}) + user.save() + + frappe.permissions.add_user_permission("Customer", original_customer, test_user) + + filters = { + "company": self.company, + "party_type": "Customer", + "report_date": today(), + "range": "30, 60, 90, 120", + } + + frappe.set_user(test_user) + try: + report = execute(filters) + finally: + frappe.set_user("Administrator") + + parties = {row.party for row in report[1]} + self.assertIn(original_customer, parties) + self.assertNotIn(second_customer, parties) + self.assertEqual(allowed_invoice.customer, original_customer)