diff --git a/crm/fcrm/doctype/erpnext_crm_settings/__init__.py b/crm/fcrm/doctype/erpnext_crm_settings/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.js b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.js
new file mode 100644
index 00000000..e69de29b
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
new file mode 100644
index 00000000..6d71314e
--- /dev/null
+++ b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
@@ -0,0 +1,97 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2024-07-02 15:23:17.022214",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "enabled",
+ "is_erpnext_in_the_current_site",
+ "column_break_vfru",
+ "erpnext_company",
+ "section_break_oubd",
+ "erpnext_site_url",
+ "column_break_fllx",
+ "api_key",
+ "api_secret"
+ ],
+ "fields": [
+ {
+ "depends_on": "eval:!doc.is_erpnext_in_the_current_site",
+ "fieldname": "api_key",
+ "fieldtype": "Data",
+ "label": "API Key",
+ "mandatory_depends_on": "eval:!doc.is_erpnext_in_the_current_site"
+ },
+ {
+ "depends_on": "eval:!doc.is_erpnext_in_the_current_site",
+ "fieldname": "api_secret",
+ "fieldtype": "Data",
+ "label": "API Secret",
+ "mandatory_depends_on": "eval:!doc.is_erpnext_in_the_current_site"
+ },
+ {
+ "depends_on": "enabled",
+ "fieldname": "section_break_oubd",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_fllx",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval:!doc.is_erpnext_in_the_current_site",
+ "fieldname": "erpnext_site_url",
+ "fieldtype": "Data",
+ "label": "ERPNext Site URL",
+ "mandatory_depends_on": "eval:!doc.is_erpnext_in_the_current_site"
+ },
+ {
+ "depends_on": "enabled",
+ "fieldname": "erpnext_company",
+ "fieldtype": "Data",
+ "label": "Company in ERPNext Site",
+ "mandatory_depends_on": "enabled"
+ },
+ {
+ "fieldname": "column_break_vfru",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "depends_on": "enabled",
+ "fieldname": "is_erpnext_in_the_current_site",
+ "fieldtype": "Check",
+ "label": "Is ERPNext in the current site?"
+ },
+ {
+ "default": "0",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "label": "Enabled"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2024-09-12 18:14:44.323068",
+ "modified_by": "Administrator",
+ "module": "FCRM",
+ "name": "ERPNext CRM Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "creation",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py
new file mode 100644
index 00000000..20f994bc
--- /dev/null
+++ b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py
@@ -0,0 +1,194 @@
+# Copyright (c) 2024, Frappe and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
+from frappe.model.document import Document
+from frappe.frappeclient import FrappeClient
+from frappe.utils import get_url_to_form
+from erpnext.crm.frappe_crm_api import create_custom_fields_for_frappe_crm, create_customer
+import json
+
+class ERPNextCRMSettings(Document):
+ def validate(self):
+ if self.enabled:
+ self.validate_if_erpnext_installed()
+ self.add_quotation_to_option()
+ self.create_custom_fields()
+ self.create_crm_form_script()
+
+ def validate_if_erpnext_installed(self):
+ if self.is_erpnext_in_the_current_site:
+ if "erpnext" not in frappe.get_installed_apps():
+ frappe.throw(_("ERPNext is not installed in the current site"))
+
+ def add_quotation_to_option(self):
+ if self.is_erpnext_in_the_current_site:
+ if not frappe.db.exists("Property Setter", {"name": "Quotation-quotation_to-link_filters"}):
+ make_property_setter(
+ doctype="Quotation",
+ fieldname="quotation_to",
+ property="link_filters",
+ value='[["DocType","name","in", ["Customer", "Lead", "Prospect", "Frappe CRM Deal"]]]',
+ property_type="JSON",
+ validate_fields_for_doctype=False,
+ )
+
+ def create_custom_fields(self):
+ if self.is_erpnext_in_the_current_site:
+ create_custom_fields_for_frappe_crm()
+ else:
+ self.create_custom_fields_in_remote_site()
+
+ def create_custom_fields_in_remote_site(self):
+ client = get_erpnext_site_client(self)
+ try:
+ client.post_api("erpnext.crm.frappe_crm_api.create_custom_fields_for_frappe_crm")
+ except Exception as e:
+ frappe.log_error(
+ frappe.get_traceback(),
+ f"Error while creating custom field in the remote erpnext site: {self.erpnext_site_url}"
+ )
+ frappe.throw("Error while creating custom field in the remote erpnext site, check error log for more details")
+
+ def create_crm_form_script(self):
+ if not frappe.db.exists("CRM Form Script", "Create Quotation from CRM Deal"):
+ script = get_crm_form_script()
+ frappe.get_doc({
+ "doctype": "CRM Form Script",
+ "name": "Create Quotation from CRM Deal",
+ "dt": "CRM Deal",
+ "view": "Form",
+ "script": script,
+ "enabled": 1,
+ "is_standard": 1
+ }).insert()
+
+def get_erpnext_site_client(erpnext_crm_settings):
+ site_url = erpnext_crm_settings.erpnext_site_url
+ api_key = erpnext_crm_settings.api_key
+ api_secret = erpnext_crm_settings.api_secret
+
+ return FrappeClient(
+ site_url, api_key=api_key, api_secret=api_secret
+ )
+
+@frappe.whitelist()
+def get_quotation_url(crm_deal, organization):
+ erpnext_crm_settings = frappe.get_single("ERPNext CRM Settings")
+ if not erpnext_crm_settings.enabled:
+ frappe.throw(_("ERPNext is not integrated with the CRM"))
+
+ if erpnext_crm_settings.is_erpnext_in_the_current_site:
+ quotation_url = get_url_to_form("Quotation")
+ return f"{quotation_url}/new?quotation_to=CRM Deal&crm_deal={crm_deal}&party_name={crm_deal}"
+ else:
+ site_url = erpnext_crm_settings.get("erpnext_site_url")
+ quotation_url = f"{site_url}/app/quotation"
+
+ prospect = create_prospect_in_remote_site(crm_deal, erpnext_crm_settings)
+ return f"{quotation_url}/new?quotation_to=Prospect&crm_deal={crm_deal}&party_name={prospect}"
+
+def create_prospect_in_remote_site(crm_deal, erpnext_crm_settings):
+ try:
+ client = get_erpnext_site_client(erpnext_crm_settings)
+ doc = frappe.get_doc("CRM Deal", crm_deal)
+ contacts = get_contacts(doc)
+ return client.post_api("erpnext.crm.frappe_crm_api.create_prospect_against_crm_deal",
+ {
+ "organization": doc.organization,
+ "lead_name": doc.lead_name,
+ "no_of_employees": doc.no_of_employees,
+ "deal_owner": doc.deal_owner,
+ "crm_deal": doc.name,
+ "territory": doc.territory,
+ "industry": doc.industry,
+ "website": doc.website,
+ "annual_revenue": doc.annual_revenue,
+ "contacts": json.dumps(contacts),
+ "erpnext_company": erpnext_crm_settings.erpnext_company
+ },
+ )
+ except Exception as e:
+ frappe.log_error(
+ frappe.get_traceback(),
+ f"Error while creating prospect in remote site: {erpnext_crm_settings.erpnext_site_url}"
+ )
+ raise
+ pass
+
+def get_contacts(doc):
+ contacts = []
+ for c in doc.contacts:
+ contacts.append({
+ "contact": c.contact,
+ "full_name": c.full_name,
+ "email": c.email,
+ "mobile_no": c.mobile_no,
+ "gender": c.gender,
+ "is_primary": c.is_primary,
+ })
+ return contacts
+
+def create_customer_in_erpnext(doc, method):
+ erpnext_crm_settings = frappe.get_single("ERPNext CRM Settings")
+ if not erpnext_crm_settings.enabled or doc.status != "Won":
+ return
+
+ contacts = get_contacts(doc)
+ customer = {
+ "customer_name": doc.organization,
+ "customer_group": "All Customer Groups",
+ "customer_type": "Company",
+ "territory": doc.territory,
+ "default_currency": doc.currency,
+ "industry": doc.industry,
+ "website": doc.website,
+ "crm_deal": doc.name,
+ "contacts": json.dumps(contacts),
+ }
+ if erpnext_crm_settings.is_erpnext_in_the_current_site:
+ create_customer(customer)
+ else:
+ create_customer_in_remote_site(customer, erpnext_crm_settings)
+
+def create_customer_in_remote_site(customer, erpnext_crm_settings):
+ client = get_erpnext_site_client(erpnext_crm_settings)
+ try:
+ client.post_api("erpnext.crm.frappe_crm_api.create_customer", customer)
+ except Exception as e:
+ frappe.log_error(
+ frappe.get_traceback(),
+ "Error while creating customer in remote site"
+ )
+ pass
+
+def get_crm_form_script():
+ return """
+function setupForm({ doc, call, $dialog, updateField, createToast }) {
+ let actions = [];
+ if (!["Lost", "Won"].includes(doc?.status)) {
+ actions.push({
+ label: __("Create Quotation"),
+ onClick: async () => {
+ let quotation_url = await call(
+ "crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.get_quotation_url",
+ {
+ crm_deal: doc.name,
+ organization: doc.organization
+ }
+ );
+
+ if (quotation_url) {
+ window.open(quotation_url, '_blank');
+ }
+ }
+ })
+ }
+
+ return {
+ actions: actions,
+ };
+}
+"""
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/test_erpnext_crm_settings.py b/crm/fcrm/doctype/erpnext_crm_settings/test_erpnext_crm_settings.py
new file mode 100644
index 00000000..17ae0284
--- /dev/null
+++ b/crm/fcrm/doctype/erpnext_crm_settings/test_erpnext_crm_settings.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestERPNextCRMSettings(FrappeTestCase):
+ pass
diff --git a/crm/hooks.py b/crm/hooks.py
index e53fc8aa..fa7e606a 100644
--- a/crm/hooks.py
+++ b/crm/hooks.py
@@ -152,6 +152,9 @@ doc_events = {
"validate": ["crm.api.whatsapp.validate"],
"on_update": ["crm.api.whatsapp.on_update"],
},
+ "CRM Deal": {
+ "on_update": ["crm.fcrm.doctype.erpnext_crm_settings.erpnext_crm_settings.create_customer_in_erpnext"],
+ },
}
# Scheduled Tasks
diff --git a/frontend/src/components/Icons/ERPNextIcon.vue b/frontend/src/components/Icons/ERPNextIcon.vue
new file mode 100644
index 00000000..e512b92d
--- /dev/null
+++ b/frontend/src/components/Icons/ERPNextIcon.vue
@@ -0,0 +1,20 @@
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/Settings/ERPNextSettings.vue b/frontend/src/components/Settings/ERPNextSettings.vue
new file mode 100644
index 00000000..e8a4518c
--- /dev/null
+++ b/frontend/src/components/Settings/ERPNextSettings.vue
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/Settings/SettingsModal.vue b/frontend/src/components/Settings/SettingsModal.vue
index 16487387..2bbe9922 100644
--- a/frontend/src/components/Settings/SettingsModal.vue
+++ b/frontend/src/components/Settings/SettingsModal.vue
@@ -39,10 +39,12 @@