mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-14 01:20:41 +00:00
fix(stock): apply company restriction to Item Price (#58948)
This commit is contained in:
@@ -330,12 +330,14 @@ permission_query_conditions = {
|
||||
"Item": "erpnext.stock.doctype.company_restriction.company_restriction.get_permission_query_conditions",
|
||||
"Customer": "erpnext.stock.doctype.company_restriction.company_restriction.get_permission_query_conditions",
|
||||
"Supplier": "erpnext.stock.doctype.company_restriction.company_restriction.get_permission_query_conditions",
|
||||
"Item Price": "erpnext.stock.doctype.company_restriction.company_restriction.get_inherited_permission_query_conditions",
|
||||
}
|
||||
|
||||
has_permission = {
|
||||
"Item": "erpnext.stock.doctype.company_restriction.company_restriction.has_permission",
|
||||
"Customer": "erpnext.stock.doctype.company_restriction.company_restriction.has_permission",
|
||||
"Supplier": "erpnext.stock.doctype.company_restriction.company_restriction.has_permission",
|
||||
"Item Price": "erpnext.stock.doctype.company_restriction.company_restriction.has_inherited_permission",
|
||||
}
|
||||
|
||||
has_website_permission = {
|
||||
|
||||
@@ -11,6 +11,8 @@ from pypika.terms import Bracket, ExistsCriterion
|
||||
|
||||
RESTRICTABLE_MASTER_DOCTYPES = ("Item", "Customer", "Supplier")
|
||||
|
||||
RESTRICTION_INHERITED_FROM = {"Item Price": ("Item", "item_code")}
|
||||
|
||||
COMPANY_RESTRICTION_EXEMPT_DOCTYPES = frozenset(
|
||||
{
|
||||
"Asset",
|
||||
@@ -71,6 +73,25 @@ def get_permission_query_conditions(user, doctype=None):
|
||||
return get_restriction_criterion(doctype, allowed_companies)
|
||||
|
||||
|
||||
def get_inherited_permission_query_conditions(user, doctype=None):
|
||||
if not (inherited := RESTRICTION_INHERITED_FROM.get(doctype)):
|
||||
return None
|
||||
|
||||
master_doctype, fieldname = inherited
|
||||
allowed_companies = get_allowed_companies(user, master_doctype)
|
||||
if not allowed_companies:
|
||||
return None
|
||||
|
||||
child = frappe.qb.DocType(doctype)
|
||||
master = frappe.qb.DocType(master_doctype)
|
||||
allowed_masters = (
|
||||
frappe.qb.from_(master)
|
||||
.select(master.name)
|
||||
.where(get_restriction_criterion(master_doctype, allowed_companies))
|
||||
)
|
||||
return child[fieldname].isin(allowed_masters)
|
||||
|
||||
|
||||
def get_restriction_criterion(doctype, companies):
|
||||
parent = frappe.qb.DocType(doctype)
|
||||
restriction = frappe.qb.DocType("Company Restriction")
|
||||
@@ -98,6 +119,17 @@ def has_permission(doc, ptype=None, user=None):
|
||||
return any(row.company in allowed_companies for row in doc.get("allowed_companies") or [])
|
||||
|
||||
|
||||
def has_inherited_permission(doc, ptype=None, user=None):
|
||||
if not (inherited := RESTRICTION_INHERITED_FROM.get(doc.doctype)):
|
||||
return True
|
||||
|
||||
master_doctype, fieldname = inherited
|
||||
if not (master_name := doc.get(fieldname)):
|
||||
return True
|
||||
|
||||
return has_permission(frappe.get_cached_doc(master_doctype, master_name), ptype, user)
|
||||
|
||||
|
||||
def validate_allowed_companies(doc, method=None):
|
||||
if not doc.get("restrict_to_companies"):
|
||||
doc.set("allowed_companies", [])
|
||||
|
||||
@@ -98,15 +98,7 @@ class TestCompanyRestriction(ERPNextTestSuite):
|
||||
def test_unrestricted_party_ignores_company_permission(self):
|
||||
customer = make_customer("_Test Party Details Company Permission Customer")
|
||||
user = self.make_user_with_roles("test_party_details_company@example.com", ["Sales User"])
|
||||
permission = {
|
||||
"user": user,
|
||||
"allow": "Company",
|
||||
"for_value": "_Test Company 1",
|
||||
"apply_to_all_doctypes": 1,
|
||||
}
|
||||
if not frappe.db.exists("User Permission", permission):
|
||||
frappe.get_doc({"doctype": "User Permission", **permission}).insert(ignore_permissions=True)
|
||||
frappe.clear_cache(user=user)
|
||||
self.allow_company(user, "_Test Company 1")
|
||||
|
||||
with self.set_user(user):
|
||||
results = party_query(
|
||||
@@ -155,6 +147,61 @@ class TestCompanyRestriction(ERPNextTestSuite):
|
||||
stock_entry.reload()
|
||||
stock_entry.cancel()
|
||||
|
||||
def allow_company(self, user, company):
|
||||
permission = {
|
||||
"user": user,
|
||||
"allow": "Company",
|
||||
"for_value": company,
|
||||
"apply_to_all_doctypes": 1,
|
||||
}
|
||||
if not frappe.db.exists("User Permission", permission):
|
||||
frappe.get_doc({"doctype": "User Permission", **permission}).insert(ignore_permissions=True)
|
||||
frappe.clear_cache(user=user)
|
||||
|
||||
def make_item_price(self, item_code):
|
||||
return (
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"price_list": "_Test Price List",
|
||||
"item_code": item_code,
|
||||
"price_list_rate": 100,
|
||||
}
|
||||
)
|
||||
.insert()
|
||||
.name
|
||||
)
|
||||
|
||||
def test_item_price_inherits_item_company_restriction(self):
|
||||
restricted = make_item()
|
||||
allowed = make_item()
|
||||
self.restrict_to_companies("Item", restricted.name, ["_Test Company 1"])
|
||||
prices = {item.name: self.make_item_price(item.name) for item in (restricted, allowed)}
|
||||
|
||||
user = self.make_user_with_roles("test_item_price_restriction@example.com", ["Sales Master Manager"])
|
||||
self.allow_company(user, "_Test Company")
|
||||
|
||||
with self.set_user(user):
|
||||
visible = frappe.get_list(
|
||||
"Item Price",
|
||||
filters={"item_code": ("in", [restricted.name, allowed.name])},
|
||||
pluck="item_code",
|
||||
)
|
||||
self.assertEqual(visible, [allowed.name])
|
||||
|
||||
self.assertFalse(frappe.has_permission("Item Price", doc=prices[restricted.name]))
|
||||
self.assertTrue(frappe.has_permission("Item Price", doc=prices[allowed.name]))
|
||||
|
||||
def test_item_price_is_visible_without_company_permission(self):
|
||||
restricted = make_item()
|
||||
self.restrict_to_companies("Item", restricted.name, ["_Test Company 1"])
|
||||
price = self.make_item_price(restricted.name)
|
||||
|
||||
user = self.make_user_with_roles("test_item_price_unrestricted@example.com", ["Sales Master Manager"])
|
||||
|
||||
with self.set_user(user):
|
||||
self.assertTrue(frappe.has_permission("Item Price", doc=price))
|
||||
|
||||
def make_user_with_roles(self, email, roles):
|
||||
if not frappe.db.exists("User", email):
|
||||
frappe.get_doc(
|
||||
|
||||
Reference in New Issue
Block a user