From 91d7bc55be47082aedadf53ce109f088d5945079 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 21 Apr 2025 12:26:17 +0530 Subject: [PATCH 1/2] fix: respect field "ignore_user_permissions" property in employee query --- erpnext/controllers/queries.py | 44 +++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index d2563aadf15..f33dd075b09 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -8,9 +8,10 @@ from collections import OrderedDict, defaultdict import frappe from frappe import qb, scrub from frappe.desk.reportview import get_filters_cond, get_match_cond +from frappe.permissions import has_permission from frappe.query_builder import Criterion, CustomFunction from frappe.query_builder.functions import Concat, Locate, Sum -from frappe.utils import nowdate, today, unique +from frappe.utils import cint, nowdate, today, unique from pypika import Order import erpnext @@ -20,10 +21,28 @@ from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_templat # searches for active employees @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -def employee_query(doctype, txt, searchfield, start, page_len, filters): +def employee_query( + doctype, + txt, + searchfield, + start, + page_len, + filters, + reference_doctype: str | None = None, + ignore_user_permissions: bool = False, +): doctype = "Employee" conditions = [] fields = get_fields(doctype, ["name", "employee_name"]) + ignore_permissions = False + + if reference_doctype and ignore_user_permissions: + ignore_permissions = has_ignored_field(reference_doctype, doctype) and has_permission( + doctype, + ptype="select" if frappe.only_has_select_perm(doctype) else "read", + ) + + mcond = "" if ignore_permissions else get_match_cond(doctype) return frappe.db.sql( """select {fields} from `tabEmployee` @@ -42,13 +61,32 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): "fields": ", ".join(fields), "key": searchfield, "fcond": get_filters_cond(doctype, filters, conditions), - "mcond": get_match_cond(doctype), + "mcond": mcond, } ), {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len}, ) +def has_ignored_field(reference_doctype, doctype): + meta = frappe.get_meta(reference_doctype) + for field in meta.fields: + if not field.ignore_user_permissions: + continue + if field.fieldtype == "Link" and field.options == doctype: + return True + elif field.fieldtype == "Dynamic Link": + options = meta.get_link_doctype(field.fieldname) + if not options: + continue + if isinstance(options, str): + options = options.split("\n") + if doctype in options or "Doctype" in options: + return True + + return False + + # searches for leads which are not converted @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs From 4be975f87c1e651cbec97eab846ef08135941547 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 22 Apr 2025 13:00:37 +0530 Subject: [PATCH 2/2] chore: added test case for employee query with user permissions --- erpnext/controllers/queries.py | 2 +- erpnext/controllers/tests/test_queries.py | 54 +++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index f33dd075b09..811a95ec1de 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -81,7 +81,7 @@ def has_ignored_field(reference_doctype, doctype): continue if isinstance(options, str): options = options.split("\n") - if doctype in options or "Doctype" in options: + if doctype in options or "DocType" in options: return True return False diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py index faa97501d6e..124a351212a 100644 --- a/erpnext/controllers/tests/test_queries.py +++ b/erpnext/controllers/tests/test_queries.py @@ -2,6 +2,9 @@ import unittest from functools import partial import frappe +from frappe.core.doctype.user_permission.test_user_permission import create_user +from frappe.core.doctype.user_permission.user_permission import add_user_permissions +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.tests import IntegrationTestCase from erpnext.controllers import queries @@ -85,3 +88,54 @@ class TestQueries(IntegrationTestCase): def test_default_uoms(self): self.assertGreaterEqual(frappe.db.count("UOM", {"enabled": 1}), 10) + + def test_employee_query_with_user_permissions(self): + # party field is a dynamic link field in Payment Entry doctype with ignore_user_permissions=0 + ps = make_property_setter( + doctype="Payment Entry", + fieldname="party", + property="ignore_user_permissions", + value=1, + property_type="Check", + ) + ps.save() + + user = create_user("test_employee_query@example.com", ("Accounts User", "HR User")) + add_user_permissions( + { + "user": user.name, + "doctype": "Employee", + "docname": "_T-Employee-00001", + "is_default": 1, + "apply_to_all_doctypes": 1, + "applicable_doctypes": [], + "hide_descendants": 0, + } + ) + + frappe.reload_doc("accounts", "doctype", "payment entry") + + frappe.set_user(user.name) + params = { + "doctype": "Employee", + "txt": "", + "searchfield": "name", + "start": 0, + "page_len": 20, + "filters": None, + "reference_doctype": "Payment Entry", + "ignore_user_permissions": 1, + } + + result = queries.employee_query(**params) + self.assertGreater(len(result), 1) + + ps.delete(ignore_permissions=1, force=1, delete_permanently=1) + frappe.reload_doc("accounts", "doctype", "payment entry") + frappe.clear_cache() + + # only one employee should be returned even though ignore_user_permissions is passed as 1 + result = queries.employee_query(**params) + self.assertEqual(len(result), 1) + + frappe.set_user("Administrator")