diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py index bd775193c..c978eee64 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.py +++ b/crm/fcrm/doctype/crm_deal/crm_deal.py @@ -5,6 +5,8 @@ from frappe import _ from frappe.model.document import Document +from crm.fcrm.doctype.crm_service_level_agreement.utils import get_sla + class CRMDeal(Document): def before_validate(self): @@ -55,7 +57,7 @@ def set_sla(self): """ Find an SLA to apply to the deal. """ - sla = get_sla("CRM Deal") + sla = get_sla(self) if not sla: return self.sla = sla.name @@ -176,8 +178,3 @@ def set_primary_contact(deal, contact): deal.save() return True -def get_sla(doctype): - sla = frappe.db.exists("CRM Service Level Agreement", {"apply_on": doctype, "enabled": 1}) - if not sla: - return None - return frappe.get_cached_doc("CRM Service Level Agreement", sla) diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.py b/crm/fcrm/doctype/crm_lead/crm_lead.py index bd26b6a53..488a274d3 100644 --- a/crm/fcrm/doctype/crm_lead/crm_lead.py +++ b/crm/fcrm/doctype/crm_lead/crm_lead.py @@ -6,6 +6,7 @@ from frappe.model.document import Document from frappe.utils import has_gravatar, validate_email_address +from crm.fcrm.doctype.crm_service_level_agreement.utils import get_sla class CRMLead(Document): @@ -131,7 +132,7 @@ def set_sla(self): """ Find an SLA to apply to the lead. """ - sla = get_sla("CRM Lead") + sla = get_sla(self) if not sla: return self.sla = sla.name @@ -240,8 +241,3 @@ def convert_to_deal(lead): lead.save() return deal -def get_sla(doctype): - sla = frappe.db.exists("CRM Service Level Agreement", {"apply_on": doctype, "enabled": 1}) - if not sla: - return None - return frappe.get_cached_doc("CRM Service Level Agreement", sla) diff --git a/crm/fcrm/doctype/crm_service_level_agreement/utils.py b/crm/fcrm/doctype/crm_service_level_agreement/utils.py new file mode 100644 index 000000000..e3388195a --- /dev/null +++ b/crm/fcrm/doctype/crm_service_level_agreement/utils.py @@ -0,0 +1,62 @@ +import frappe +from frappe.model.document import Document +from frappe.query_builder import JoinType +from frappe.utils.safe_exec import get_safe_globals + +DOCTYPE = "CRM Service Level Agreement" + +def get_sla(doc: Document) -> Document: + """ + Get Service Level Agreement for `doc` + + :param doc: Lead/Deal to use + :return: Applicable SLA + """ + check_permissions(DOCTYPE, None) + SLA = frappe.qb.DocType(DOCTYPE) + Priority = frappe.qb.DocType("CRM Service Level Priority") + priority = doc.communication_status + q = ( + frappe.qb.from_(SLA) + .select(SLA.name, SLA.condition) + .where(SLA.apply_on == doc.doctype) + .where(SLA.enabled == True) + ) + if priority: + q = ( + q.join(Priority, JoinType.inner) + .on(Priority.parent == SLA.name) + .where(Priority.priority == priority) + ) + sla_list = q.run(as_dict=True) + res = None + for sla in sla_list: + cond = sla.get("condition") + if not cond or frappe.safe_eval(cond, None, get_context(doc)): + res = sla + break + return res + +def check_permissions(doctype, parent): + user = frappe.session.user + permissions = ("select", "read") + has_select_permission, has_read_permission = [ + frappe.has_permission(doctype, perm, user=user, parent_doctype=parent) + for perm in permissions + ] + + if not has_select_permission and not has_read_permission: + frappe.throw(f"Insufficient Permission for {doctype}", frappe.PermissionError) + +def get_context(d: Document) -> dict: + """ + Get safe context for `safe_eval` + + :param doc: `Document` to add in context + :return: Context with `doc` and safe variables + """ + utils = get_safe_globals().get("frappe").get("utils") + return { + "doc": d.as_dict(), + "frappe": frappe._dict(utils=utils), + } \ No newline at end of file