feat(crm_settings)!: enable frappe crm data synchronization (backport #56268) (#56384)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
This commit is contained in:
mergify[bot]
2026-06-23 22:02:23 +05:30
committed by GitHub
parent 3dc128881c
commit 00ba64baae
6 changed files with 167 additions and 27 deletions

View File

@@ -20,7 +20,11 @@
"section_break_13",
"carry_forward_communication_and_comments",
"column_break_junk",
"update_timestamp_on_new_communication"
"update_timestamp_on_new_communication",
"frappe_crm_section",
"enable_frappe_crm_data_synchronization",
"column_break_jbzj",
"allowed_users"
],
"fields": [
{
@@ -105,6 +109,30 @@
"fieldname": "enable_opportunity_creation_from_contact_us",
"fieldtype": "Check",
"label": "Enable Opportunity Creation from Contact Us"
},
{
"fieldname": "frappe_crm_section",
"fieldtype": "Section Break",
"label": "Frappe CRM"
},
{
"fieldname": "column_break_jbzj",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.enable_frappe_crm_data_synchronization === 1;",
"fieldname": "allowed_users",
"fieldtype": "Table MultiSelect",
"label": "Allowed Users",
"options": "Frappe CRM Allowed User",
"permlevel": 1
},
{
"default": "0",
"fieldname": "enable_frappe_crm_data_synchronization",
"fieldtype": "Check",
"label": "Enable Frappe CRM Data Synchronization",
"permlevel": 1
}
],
"grid_page_length": 50,
@@ -112,7 +140,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-06-11 23:09:49.750381",
"modified": "2026-06-22 01:26:13.474915",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM Settings",
@@ -146,6 +174,16 @@
"role": "Sales Master Manager",
"share": 1,
"write": 1
},
{
"delete": 1,
"email": 1,
"permlevel": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"row_format": "Dynamic",

View File

@@ -3,6 +3,7 @@
import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields, delete_custom_fields
from frappe.model.document import Document
@@ -15,12 +16,16 @@ class CRMSettings(Document):
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.crm.doctype.frappe_crm_allowed_user.frappe_crm_allowed_user import FrappeCRMAllowedUser
allow_lead_duplication_based_on_emails: DF.Check
allowed_users: DF.TableMultiSelect[FrappeCRMAllowedUser]
auto_creation_of_contact: DF.Check
campaign_naming_by: DF.Literal["Campaign Name", "Naming Series"]
carry_forward_communication_and_comments: DF.Check
close_opportunity_after_days: DF.Int
default_valid_till: DF.Data | None
enable_frappe_crm_data_synchronization: DF.Check
enable_opportunity_creation_from_contact_us: DF.Check
update_timestamp_on_new_communication: DF.Check
# end: auto-generated types
@@ -28,6 +33,7 @@ class CRMSettings(Document):
def validate(self):
frappe.db.set_default("campaign_naming_by", self.get("campaign_naming_by", ""))
self.validate_enable_opportunity_creation_from_contact_us()
self.validate_allowed_users()
def validate_enable_opportunity_creation_from_contact_us(self):
contact_disabled = frappe.get_single_value("Contact Us Settings", "is_disabled")
@@ -38,3 +44,43 @@ class CRMSettings(Document):
"Cannot enable Opportunity creation from Contact Us because the Contact Us form is disabled."
)
)
def validate_allowed_users(self):
if self.enable_frappe_crm_data_synchronization and not self.allowed_users:
frappe.throw(
_(
"Please add atleast one user on Allowed Users to allow Data Synchronization from Frappe CRM site."
)
)
def before_save(self):
self.clear_allowed_users()
def on_update(self):
self.custom_fields_for_frappe_crm_data_sync()
def clear_allowed_users(self):
if not self.enable_frappe_crm_data_synchronization:
self.allowed_users = []
def custom_fields_for_frappe_crm_data_sync(self):
custom_fields = {
"Quotation": [
{
"fieldname": "crm_deal",
"fieldtype": "Data",
"label": "Frappe CRM Deal",
"insert_after": "party_name",
}
],
"Customer": [
{
"fieldname": "crm_deal",
"fieldtype": "Data",
"label": "Frappe CRM Deal",
"insert_after": "prospect_name",
}
],
}
create_custom_fields(custom_fields, ignore_validate=True)

View File

@@ -0,0 +1,36 @@
{
"actions": [],
"allow_bulk_edit": 1,
"allow_rename": 1,
"creation": "2026-06-22 00:47:12.265968",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"user"
],
"fields": [
{
"fieldname": "user",
"fieldtype": "Link",
"in_list_view": 1,
"label": "User",
"options": "User",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-06-22 01:49:54.586410",
"modified_by": "Administrator",
"module": "CRM",
"name": "Frappe CRM Allowed User",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,23 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class FrappeCRMAllowedUser(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
user: DF.Link
# end: auto-generated types
_DOCTYPE_NAME = "Frappe CRM Allowed User"

View File

@@ -2,35 +2,12 @@ import json
import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
@frappe.whitelist()
def create_custom_fields_for_frappe_crm():
frappe.only_for("System Manager")
custom_fields = {
"Quotation": [
{
"fieldname": "crm_deal",
"fieldtype": "Data",
"label": "Frappe CRM Deal",
"insert_after": "party_name",
}
],
"Customer": [
{
"fieldname": "crm_deal",
"fieldtype": "Data",
"label": "Frappe CRM Deal",
"insert_after": "prospect_name",
}
],
}
create_custom_fields(custom_fields, ignore_validate=True)
@frappe.whitelist()
def create_prospect_against_crm_deal():
validate_frappe_crm_sync()
doc = frappe.form_dict
prospect = frappe.new_doc("Prospect")
prospect.company_name = doc.organization or doc.lead_name
@@ -161,6 +138,8 @@ CUSTOMER_ALLOWED_FIELDS = {
@frappe.whitelist()
def create_customer(customer_data=None):
validate_frappe_crm_sync()
if not customer_data:
customer_data = frappe.form_dict
@@ -181,3 +160,21 @@ def create_customer(customer_data=None):
except Exception:
frappe.log_error(frappe.get_traceback(), "Error while creating customer against Frappe CRM Deal")
pass
def validate_frappe_crm_sync():
CRMSettings = frappe.get_single("CRM Settings")
if not CRMSettings.enable_frappe_crm_data_synchronization:
frappe.throw(
_("Frappe CRM data synchronization is not enabled on ERPNext. Contact System Manager of ERPNext.")
)
allowed_users = [d.user for d in CRMSettings.allowed_users]
if frappe.session.user not in allowed_users:
frappe.throw(
_(
"User not allowed to synchronize data from Frappe CRM on ERPNext. Contact System Manager of ERPNext."
),
exc=frappe.PermissionError,
)