From 956be58c9c3b7df31eafc0687955060f30f2f3bb Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Fri, 7 Aug 2026 17:20:00 +0530 Subject: [PATCH] fix: apply Sales Person user permissions in Accounts Receivable The report only narrowed by sales person when the filter was set, so a user restricted to a Sales Person saw every row once the filter was cleared. Resolve the permitted Sales Persons from user permissions and apply them on top of the filter. Each Sales Team parent type is matched against its own applicable_for scope, so a permission scoped to one doctype cannot authorise rows through the other. Descendants are already expanded by get_user_permissions, so Hide Descendants is respected. Gated to Receivable, since the class is shared with Accounts Payable. (cherry picked from commit 8b09ba429e2c4779890fda1fe6543943fb124277) # Conflicts: # erpnext/accounts/report/accounts_receivable/accounts_receivable.py --- .../accounts_receivable.py | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 47fdf9516f7..5b4e35b14fb 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -6,7 +6,11 @@ from collections import OrderedDict import frappe from frappe import _, qb, query_builder, scrub +<<<<<<< HEAD from frappe.database.schema import get_definition +======= +from frappe.permissions import get_allowed_docs_for_doctype +>>>>>>> 8b09ba429e (fix: apply Sales Person user permissions in Accounts Receivable) from frappe.query_builder import Criterion from frappe.query_builder.functions import Date, Substring, Sum from frappe.utils import cint, cstr, flt, getdate, nowdate @@ -52,6 +56,7 @@ class ReceivablePayableReport: self.filters = frappe._dict(filters or {}) self.qb_selection_filter = [] self.ple = qb.DocType("Payment Ledger Entry") + self.sales_person_records = None self.filters.report_date = getdate(self.filters.report_date or nowdate()) self.age_as_on = ( getdate(nowdate()) @@ -92,6 +97,7 @@ class ReceivablePayableReport: self.party_type = get_party_types_from_account_type(self.account_type) self.party_details = {} self.invoices = set() + self.sales_person_records = None self.skip_total_row = 0 self.advance_payment_doctypes = get_advance_payment_doctypes() @@ -206,7 +212,7 @@ class ReceivablePayableReport: def get_invoices(self, ple): if ple.voucher_type in ("Sales Invoice", "Purchase Invoice"): - if self.filters.get("sales_person"): + if self.sales_person_records is not None: if ple.voucher_no in self.sales_person_records.get( "Sales Invoice", [] ) or ple.party in self.sales_person_records.get("Customer", []): @@ -237,7 +243,7 @@ class ReceivablePayableReport: ] def get_voucher_balance(self, ple): - if self.filters.get("sales_person"): + if self.sales_person_records is not None: if not ( ple.party in self.sales_person_records.get("Customer", []) or ple.against_voucher_no in self.sales_person_records.get("Sales Invoice", []) @@ -896,9 +902,37 @@ class ReceivablePayableReport: self.ple_query = query + def get_permitted_sales_persons(self, parenttype): + if self.account_type != "Receivable": + return None + + permissions = frappe.permissions.get_user_permissions(frappe.session.user).get("Sales Person", []) + if not permissions: + return None + + return get_allowed_docs_for_doctype(permissions, parenttype) + def get_sales_invoices_or_customers_based_on_sales_person(self): + parenttypes = ["Customer", "Sales Invoice"] + permitted = {p: self.get_permitted_sales_persons(p) for p in parenttypes} + + if not (self.filters.get("sales_person") or any(p is not None for p in permitted.values())): + return + + steam = frappe.qb.DocType("Sales Team") + + scope = [] + for parenttype in parenttypes: + criterion = steam.parenttype == parenttype + if (allowed := permitted[parenttype]) is not None: + criterion &= steam.sales_person.isin(allowed or [""]) + scope.append(criterion) + + conditions = [Criterion.any(scope)] + if self.filters.get("sales_person"): lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"]) +<<<<<<< HEAD # nosemgrep records = frappe.db.sql( @@ -910,11 +944,26 @@ class ReceivablePayableReport: """, (lft, rgt), as_dict=1, +======= + sp = frappe.qb.DocType("Sales Person") + conditions.append( + steam.sales_person.isin( + frappe.qb.from_(sp).select(sp.name).where((sp.lft >= lft) & (sp.rgt <= rgt)) + ) +>>>>>>> 8b09ba429e (fix: apply Sales Person user permissions in Accounts Receivable) ) - self.sales_person_records = frappe._dict() - for d in records: - self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent) + records = ( + frappe.qb.from_(steam) + .select(steam.parent, steam.parenttype) + .distinct() + .where(Criterion.all(conditions)) + .run(as_dict=1) + ) + + self.sales_person_records = frappe._dict() + for d in records: + self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent) def get_invoices_based_on_sales_partner(self): if not self.filters.get("sales_partner"):