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 @@