diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json
index e5c973d8..6338f56d 100644
--- a/crm/fcrm/doctype/crm_deal/crm_deal.json
+++ b/crm/fcrm/doctype/crm_deal/crm_deal.json
@@ -339,7 +339,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-06-20 12:55:41.602364",
+ "modified": "2024-09-16 19:44:19.553715",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Deal",
@@ -371,8 +371,10 @@
"write": 1
}
],
+ "show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
+ "title_field": "organization",
"track_changes": 1
}
\ No newline at end of file
diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.json b/crm/fcrm/doctype/crm_form_script/crm_form_script.json
index 1cc14d9a..9246913a 100644
--- a/crm/fcrm/doctype/crm_form_script/crm_form_script.json
+++ b/crm/fcrm/doctype/crm_form_script/crm_form_script.json
@@ -35,6 +35,7 @@
"default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
+ "hidden": 1,
"label": "Enabled"
},
{
@@ -64,7 +65,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-09-11 12:56:09.288849",
+ "modified": "2024-09-16 19:40:19.340948",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Form Script",
diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.json b/crm/fcrm/doctype/crm_lead/crm_lead.json
index ced8e3cb..e9e77c89 100644
--- a/crm/fcrm/doctype/crm_lead/crm_lead.json
+++ b/crm/fcrm/doctype/crm_lead/crm_lead.json
@@ -291,7 +291,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-02-05 00:58:07.321058",
+ "modified": "2024-09-16 19:46:01.307171",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Lead",
@@ -325,6 +325,7 @@
],
"sender_field": "email",
"sender_name_field": "first_name",
+ "show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
index 99a241c5..fa95678f 100644
--- a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
+++ b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.json
@@ -6,29 +6,33 @@
"engine": "InnoDB",
"field_order": [
"enabled",
- "is_erpnext_in_the_current_site",
+ "is_erpnext_in_different_site",
"column_break_vfru",
"erpnext_company",
"section_break_oubd",
"erpnext_site_url",
"column_break_fllx",
"api_key",
- "api_secret"
+ "api_secret",
+ "section_break_jnbn",
+ "create_customer_on_status_change",
+ "column_break_kbhw",
+ "deal_status"
],
"fields": [
{
- "depends_on": "eval:doc.enabled && !doc.is_erpnext_in_the_current_site",
+ "depends_on": "eval:doc.enabled && doc.is_erpnext_in_different_site",
"fieldname": "api_key",
"fieldtype": "Data",
"label": "API Key",
- "mandatory_depends_on": "eval:!doc.is_erpnext_in_the_current_site"
+ "mandatory_depends_on": "is_erpnext_in_different_site"
},
{
- "depends_on": "eval:doc.enabled && !doc.is_erpnext_in_the_current_site",
+ "depends_on": "eval:doc.enabled && doc.is_erpnext_in_different_site",
"fieldname": "api_secret",
"fieldtype": "Data",
"label": "API Secret",
- "mandatory_depends_on": "eval:!doc.is_erpnext_in_the_current_site"
+ "mandatory_depends_on": "is_erpnext_in_different_site"
},
{
"depends_on": "enabled",
@@ -40,11 +44,11 @@
"fieldtype": "Column Break"
},
{
- "depends_on": "eval:doc.enabled && !doc.is_erpnext_in_the_current_site",
+ "depends_on": "eval:doc.enabled && doc.is_erpnext_in_different_site",
"fieldname": "erpnext_site_url",
"fieldtype": "Data",
"label": "ERPNext Site URL",
- "mandatory_depends_on": "eval:!doc.is_erpnext_in_the_current_site"
+ "mandatory_depends_on": "is_erpnext_in_different_site"
},
{
"depends_on": "enabled",
@@ -57,24 +61,47 @@
"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"
+ },
+ {
+ "default": "0",
+ "depends_on": "enabled",
+ "fieldname": "is_erpnext_in_different_site",
+ "fieldtype": "Check",
+ "label": "Is ERPNext installed on a different site?"
+ },
+ {
+ "fieldname": "section_break_jnbn",
+ "fieldtype": "Section Break"
+ },
+ {
+ "default": "0",
+ "depends_on": "enabled",
+ "fieldname": "create_customer_on_status_change",
+ "fieldtype": "Check",
+ "label": "Create customer on status change"
+ },
+ {
+ "fieldname": "column_break_kbhw",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval:doc.enabled && doc.create_customer_on_status_change",
+ "fieldname": "deal_status",
+ "fieldtype": "Link",
+ "label": "Deal Status",
+ "mandatory_depends_on": "create_customer_on_status_change",
+ "options": "CRM Deal Status"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2024-09-13 15:06:23.317262",
+ "modified": "2024-09-16 21:30:40.097360",
"modified_by": "Administrator",
"module": "FCRM",
"name": "ERPNext CRM Settings",
diff --git a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py
index 565cfdfe..1e80014a 100644
--- a/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py
+++ b/crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py
@@ -18,12 +18,12 @@ class ERPNextCRMSettings(Document):
self.create_crm_form_script()
def validate_if_erpnext_installed(self):
- if self.is_erpnext_in_the_current_site:
+ if not self.is_erpnext_in_different_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 self.is_erpnext_in_different_site:
if not frappe.db.exists("Property Setter", {"name": "Quotation-quotation_to-link_filters"}):
make_property_setter(
doctype="Quotation",
@@ -35,7 +35,7 @@ class ERPNextCRMSettings(Document):
)
def create_custom_fields(self):
- if self.is_erpnext_in_the_current_site:
+ if not self.is_erpnext_in_different_site:
from erpnext.crm.frappe_crm_api import create_custom_fields_for_frappe_crm
create_custom_fields_for_frappe_crm()
else:
@@ -80,7 +80,7 @@ def get_quotation_url(crm_deal, organization):
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:
+ if not erpnext_crm_settings.is_erpnext_in_different_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:
@@ -95,6 +95,7 @@ def create_prospect_in_remote_site(crm_deal, erpnext_crm_settings):
client = get_erpnext_site_client(erpnext_crm_settings)
doc = frappe.get_doc("CRM Deal", crm_deal)
contacts = get_contacts(doc)
+ address = get_organization_address(doc.organization)
return client.post_api("erpnext.crm.frappe_crm_api.create_prospect_against_crm_deal",
{
"organization": doc.organization,
@@ -107,7 +108,8 @@ def create_prospect_in_remote_site(crm_deal, erpnext_crm_settings):
"website": doc.website,
"annual_revenue": doc.annual_revenue,
"contacts": json.dumps(contacts),
- "erpnext_company": erpnext_crm_settings.erpnext_company
+ "erpnext_company": erpnext_crm_settings.erpnext_company,
+ "address": address.as_dict() if address else None
},
)
except Exception:
@@ -130,12 +132,22 @@ def get_contacts(doc):
})
return contacts
+def get_organization_address(organization):
+ address = frappe.get_value("CRM Organization", organization, "address")
+ address = frappe.get_doc("Address", address) if address else None
+ return address
+
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":
+ if (
+ not erpnext_crm_settings.enabled
+ or not erpnext_crm_settings.create_customer_on_status_change
+ or doc.status != erpnext_crm_settings.deal_status
+ ):
return
contacts = get_contacts(doc)
+ address = get_organization_address(doc.organization)
customer = {
"customer_name": doc.organization,
"customer_group": "All Customer Groups",
@@ -146,12 +158,15 @@ def create_customer_in_erpnext(doc, method):
"website": doc.website,
"crm_deal": doc.name,
"contacts": json.dumps(contacts),
+ "address": address.as_dict() if address else None,
}
- if erpnext_crm_settings.is_erpnext_in_the_current_site:
+ if not erpnext_crm_settings.is_erpnext_in_different_site:
from erpnext.crm.frappe_crm_api import create_customer
create_customer(customer)
else:
- create_customer_in_remote_site(customer, erpnext_crm_settings)
+ create_customer_in_remote_site(customer, erpnext_crm_settings)
+
+ frappe.publish_realtime("crm_customer_created")
def create_customer_in_remote_site(customer, erpnext_crm_settings):
client = get_erpnext_site_client(erpnext_crm_settings)
@@ -166,9 +181,10 @@ def create_customer_in_remote_site(customer, erpnext_crm_settings):
def get_crm_form_script():
return """
-function setupForm({ doc, call, $dialog, updateField, createToast }) {
+async function setupForm({ doc, call, $dialog, updateField, createToast }) {
let actions = [];
- if (!["Lost", "Won"].includes(doc?.status)) {
+ let is_erpnext_integration_enabled = await call("frappe.client.get_single_value", {doctype: "ERPNext CRM Settings", field: "enabled"});
+ if (!["Lost", "Won"].includes(doc?.status) && is_erpnext_integration_enabled) {
actions.push({
label: __("Create Quotation"),
onClick: async () => {
diff --git a/frontend/src/components/Fields.vue b/frontend/src/components/Fields.vue
index 13b49157..c2bcd346 100644
--- a/frontend/src/components/Fields.vue
+++ b/frontend/src/components/Fields.vue
@@ -28,7 +28,7 @@
(field.read_only && data[field.name]) ||
!field.read_only ||
!field.hidden) &&
- (!field.depends_on || field.display_depends_on)
+ (!field.depends_on || field.display_via_depends_on)
"
>
{{ __(field.label) }}
- *
+ *
import EditValueModal from '@/components/Modals/EditValueModal.vue'
import AssignmentModal from '@/components/Modals/AssignmentModal.vue'
-import { setupListActions, createToast } from '@/utils'
+import { setupListCustomizations, createToast } from '@/utils'
import { globalStore } from '@/stores/global'
import { capture } from '@/telemetry'
import { call } from 'frappe-ui'
@@ -45,7 +45,7 @@ const list = defineModel()
const router = useRouter()
-const { $dialog } = globalStore()
+const { $dialog, $socket } = globalStore()
const showEditModal = ref(false)
const selectedValues = ref([])
@@ -230,17 +230,20 @@ function reload(unselectAll) {
list.value?.reload()
}
-onMounted(() => {
+onMounted(async () => {
if (!list.value?.data) return
- setupListActions(list.value.data, {
+ let customization = await setupListCustomizations(list.value.data, {
list: list.value,
call,
createToast,
$dialog,
+ $socket,
router,
})
- customBulkActions.value = list.value?.data?.bulkActions || []
- customListActions.value = list.value?.data?.listActions || []
+ customBulkActions.value =
+ customization?.bulkActions || list.value?.data?.bulkActions || []
+ customListActions.value =
+ customization?.actions || list.value?.data?.listActions || []
})
defineExpose({
diff --git a/frontend/src/components/Settings/ERPNextSettings.vue b/frontend/src/components/Settings/ERPNextSettings.vue
index e8a4518c..697a5503 100644
--- a/frontend/src/components/Settings/ERPNextSettings.vue
+++ b/frontend/src/components/Settings/ERPNextSettings.vue
@@ -1,6 +1,11 @@
-
+
\ No newline at end of file
diff --git a/frontend/src/components/Settings/SettingsPage.vue b/frontend/src/components/Settings/SettingsPage.vue
index cf085c99..e9a53bf0 100644
--- a/frontend/src/components/Settings/SettingsPage.vue
+++ b/frontend/src/components/Settings/SettingsPage.vue
@@ -15,6 +15,7 @@
:sections="sections"
:data="data.doc"
/>
+
@@ -36,9 +37,10 @@ import {
createResource,
Spinner,
Badge,
+ ErrorMessage,
} from 'frappe-ui'
import { evaluate_depends_on_value, createToast } from '@/utils'
-import { computed } from 'vue'
+import { ref, computed } from 'vue'
const props = defineProps({
doctype: {
@@ -65,6 +67,8 @@ const fields = createResource({
auto: true,
})
+const error = ref(null)
+
const data = createDocumentResource({
doctype: props.doctype,
name: props.doctype,
@@ -73,6 +77,7 @@ const data = createDocumentResource({
auto: true,
setValue: {
onSuccess: () => {
+ error.value = null
createToast({
title: __('Success'),
text: __(props.successMessage),
@@ -117,10 +122,14 @@ const sections = computed(() => {
} else {
_sections[_sections.length - 1].fields.push({
...field,
- display_depends_on: evaluate_depends_on_value(
+ display_via_depends_on: evaluate_depends_on_value(
field.depends_on,
data.doc,
),
+ mandatory_via_depends_on: evaluate_depends_on_value(
+ field.mandatory_depends_on,
+ data.doc,
+ ),
name: field.value,
})
}
@@ -130,6 +139,24 @@ const sections = computed(() => {
})
function update() {
+ error.value = null
+ if (validateMandatoryFields()) return
data.save.submit()
}
+
+function validateMandatoryFields() {
+ for (let section of sections.value) {
+ for (let field of section.fields) {
+ if (
+ (field.mandatory ||
+ (field.mandatory_depends_on && field.mandatory_via_depends_on)) &&
+ !data.doc[field.name]
+ ) {
+ error.value = __('{0} is mandatory', [__(field.label)])
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/frontend/src/pages/Deal.vue b/frontend/src/pages/Deal.vue
index 27a8f3c5..892b4a41 100644
--- a/frontend/src/pages/Deal.vue
+++ b/frontend/src/pages/Deal.vue
@@ -8,19 +8,14 @@
-
+
-
+
-
+
-
+
@@ -114,7 +107,7 @@
@@ -278,12 +271,7 @@ import Section from '@/components/Section.vue'
import SectionFields from '@/components/SectionFields.vue'
import SLASection from '@/components/SLASection.vue'
import CustomActions from '@/components/CustomActions.vue'
-import {
- createToast,
- setupAssignees,
- setupCustomActions,
- setupCustomStatuses,
-} from '@/utils'
+import { createToast, setupAssignees, setupCustomizations } from '@/utils'
import { getView } from '@/utils/view'
import { globalStore } from '@/stores/global'
import { organizationsStore } from '@/stores/organizations'
@@ -304,7 +292,7 @@ import {
import { ref, computed, h, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
-const { $dialog } = globalStore()
+const { $dialog, $socket } = globalStore()
const { organizations, getOrganization } = organizationsStore()
const { statusOptions, getDealStatus } = statusesStore()
const route = useRoute()
@@ -317,23 +305,33 @@ const props = defineProps({
},
})
+const customActions = ref([])
+const customStatuses = ref([])
+
const deal = createResource({
url: 'crm.fcrm.doctype.crm_deal.api.get_deal',
params: { name: props.dealId },
cache: ['deal', props.dealId],
- onSuccess: (data) => {
+ onSuccess: async (data) => {
let obj = {
doc: data,
$dialog,
+ $socket,
router,
updateField,
createToast,
deleteDoc: deleteDeal,
+ resource: {
+ deal,
+ dealContacts,
+ fieldsLayout,
+ },
call,
}
setupAssignees(data)
- setupCustomStatuses(data, obj)
- setupCustomActions(data, obj)
+ let customization = await setupCustomizations(data, obj)
+ customActions.value = customization.actions || []
+ customStatuses.value = customization.statuses || []
},
})
@@ -533,7 +531,7 @@ async function addContact(contact) {
contact,
})
if (d) {
- deal_contacts.reload()
+ dealContacts.reload()
createToast({
title: __('Contact added'),
icon: 'check',
@@ -548,7 +546,7 @@ async function removeContact(contact) {
contact,
})
if (d) {
- deal_contacts.reload()
+ dealContacts.reload()
createToast({
title: __('Contact removed'),
icon: 'check',
@@ -563,7 +561,7 @@ async function setPrimaryContact(contact) {
contact,
})
if (d) {
- deal_contacts.reload()
+ dealContacts.reload()
createToast({
title: __('Primary contact set'),
icon: 'check',
@@ -572,7 +570,7 @@ async function setPrimaryContact(contact) {
}
}
-const deal_contacts = createResource({
+const dealContacts = createResource({
url: 'crm.fcrm.doctype.crm_deal.api.get_deal_contacts',
params: { name: props.dealId },
cache: ['deal_contacts', props.dealId],
diff --git a/frontend/src/pages/MobileLead.vue b/frontend/src/pages/MobileLead.vue
index 8237344d..a2a480c7 100644
--- a/frontend/src/pages/MobileLead.vue
+++ b/frontend/src/pages/MobileLead.vue
@@ -9,11 +9,7 @@
-
+
-
+