snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 1s
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 1s
This commit is contained in:
119
crm/permissions/org_hierarchy.py
Normal file
119
crm/permissions/org_hierarchy.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.utils.caching import request_cache
|
||||
|
||||
_OWNER_FIELD = {
|
||||
"CRM Lead": "lead_owner",
|
||||
"CRM Deal": "deal_owner",
|
||||
}
|
||||
|
||||
|
||||
def hierarchy_enabled() -> bool:
|
||||
return bool(frappe.db.get_single_value("FCRM Settings", "enable_sales_hierarchy"))
|
||||
|
||||
|
||||
def _permission_query_conditions(user: str | None, doctype: str):
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
|
||||
if user == "Administrator":
|
||||
return ""
|
||||
|
||||
roles = frappe.get_roles(user)
|
||||
if "System Manager" in roles:
|
||||
return ""
|
||||
|
||||
in_tree = hierarchy_enabled() and _in_hierarchy(user)
|
||||
|
||||
# Sales Manager outside the tree retains the default ie sees everything
|
||||
if "Sales Manager" in roles and not in_tree:
|
||||
return ""
|
||||
|
||||
owner_field = _OWNER_FIELD[doctype]
|
||||
DT = frappe.qb.DocType(doctype)
|
||||
Todo = frappe.qb.DocType("ToDo").as_("_todo")
|
||||
|
||||
if in_tree:
|
||||
# Owner is the user themselves or any member of their subtree
|
||||
q1 = (DT[owner_field] == user) | DT[owner_field].isin(_team_mem_query(user))
|
||||
# Assigned to the user or any member of their subtree by ToDo
|
||||
q2 = DT.name.isin(
|
||||
frappe.qb.from_(Todo)
|
||||
.select(Todo.reference_name)
|
||||
.where(
|
||||
(Todo.reference_type == doctype)
|
||||
& (Todo.status != "Cancelled")
|
||||
& ((Todo.allocated_to == user) | (Todo.allocated_to.isin(_team_mem_query(user))))
|
||||
)
|
||||
)
|
||||
return q1 | q2
|
||||
|
||||
# Sales User default: own records and records directly assigned to them
|
||||
q1 = DT[owner_field] == user
|
||||
q2 = DT.name.isin(
|
||||
frappe.qb.from_(Todo)
|
||||
.select(Todo.reference_name)
|
||||
.where((Todo.reference_type == doctype) & (Todo.status != "Cancelled") & (Todo.allocated_to == user))
|
||||
)
|
||||
return q1 | q2
|
||||
|
||||
|
||||
def get_lead_permission_query_conditions(user=None):
|
||||
cond = _permission_query_conditions(user, "CRM Lead")
|
||||
return cond.get_sql(with_namespace=True, quote_char="`", secondary_quote_char="'") if cond else ""
|
||||
|
||||
|
||||
def get_deal_permission_query_conditions(user=None):
|
||||
cond = _permission_query_conditions(user, "CRM Deal")
|
||||
return cond.get_sql(with_namespace=True, quote_char="`", secondary_quote_char="'") if cond else ""
|
||||
|
||||
|
||||
def _has_permission(doc, ptype, user, doctype: str) -> bool | None:
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
|
||||
if user == "Administrator":
|
||||
return True
|
||||
|
||||
roles = frappe.get_roles(user)
|
||||
if "System Manager" in roles:
|
||||
return True
|
||||
|
||||
if ptype == "create" or not doc.name:
|
||||
return True
|
||||
|
||||
in_tree = hierarchy_enabled() and _in_hierarchy(user)
|
||||
if "Sales Manager" in roles and not in_tree:
|
||||
return True
|
||||
|
||||
conditions = _permission_query_conditions(user, doctype)
|
||||
DT = frappe.qb.DocType(doctype)
|
||||
return bool(
|
||||
frappe.qb.from_(DT).select(DT.name).where(DT.name == doc.name).where(conditions).limit(1).run()
|
||||
)
|
||||
|
||||
|
||||
def has_lead_permission(doc, ptype, user):
|
||||
return _has_permission(doc, ptype, user, "CRM Lead")
|
||||
|
||||
|
||||
def has_deal_permission(doc, ptype, user):
|
||||
return _has_permission(doc, ptype, user, "CRM Deal")
|
||||
|
||||
|
||||
def _in_hierarchy(user: str) -> bool:
|
||||
return bool(frappe.db.exists("CRM Sales Hierarchy", {"user": user}))
|
||||
|
||||
|
||||
def _team_mem_query(user: str):
|
||||
Mgr = frappe.qb.DocType("CRM Sales Hierarchy").as_("_sqmgr")
|
||||
Member = frappe.qb.DocType("CRM Sales Hierarchy").as_("_sqmem")
|
||||
return (
|
||||
frappe.qb.from_(Mgr)
|
||||
.join(Member)
|
||||
.on((Member.lft >= Mgr.lft) & (Member.lft <= Mgr.rgt))
|
||||
.select(Member.user)
|
||||
.where(Mgr.user == user)
|
||||
)
|
||||
Reference in New Issue
Block a user