diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.json b/crm/fcrm/doctype/crm_deal/crm_deal.json index bbef186d..6b7ce0fe 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.json +++ b/crm/fcrm/doctype/crm_deal/crm_deal.json @@ -14,6 +14,8 @@ "column_break_ijan", "status", "deal_owner", + "lost_reason", + "lost_notes", "section_break_jgpm", "probability", "deal_value", @@ -391,12 +393,25 @@ { "fieldname": "column_break_kpxa", "fieldtype": "Column Break" + }, + { + "fieldname": "lost_reason", + "fieldtype": "Link", + "label": "Lost Reason", + "mandatory_depends_on": "eval: doc.status == \"Lost\"", + "options": "CRM Lost Reason" + }, + { + "fieldname": "lost_notes", + "fieldtype": "Text", + "label": "Lost Notes", + "mandatory_depends_on": "eval: doc.lost_reason == \"Other\"" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-06-16 11:42:49.413483", + "modified": "2025-07-02 11:07:50.192089", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Deal", diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py index b7e4ec83..6e056ffe 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.py +++ b/crm/fcrm/doctype/crm_deal/crm_deal.py @@ -24,7 +24,8 @@ class CRMDeal(Document): self.assign_agent(self.deal_owner) if self.has_value_changed("status"): add_status_change_log(self) - self.update_close_date() + self.validate_forcasting_fields() + self.validate_lost_reason() def after_insert(self): if self.deal_owner: @@ -141,6 +142,32 @@ class CRMDeal(Document): if self.status == "Won" and not self.close_date: self.close_date = frappe.utils.nowdate() + def update_default_probability(self): + """ + Update the default probability based on the status. + """ + if not self.probability or self.probability == 0: + self.probability = frappe.db.get_value("CRM Deal Status", self.status, "probability") or 0 + + def validate_forcasting_fields(self): + self.update_close_date() + self.update_default_probability() + if frappe.db.get_single_value("FCRM Settings", "enable_forecasting"): + if not self.deal_value or self.deal_value == 0: + frappe.throw(_("Deal Value is required."), frappe.MandatoryError) + if not self.close_date: + frappe.throw(_("Close Date is required."), frappe.MandatoryError) + + def validate_lost_reason(self): + """ + Validate the lost reason if the status is set to "Lost". + """ + if self.status == "Lost": + if not self.lost_reason: + frappe.throw(_("Please specify a reason for losing the deal."), frappe.ValidationError) + elif self.lost_reason == "Other" and not self.lost_notes: + frappe.throw(_("Please specify the reason for losing the deal."), frappe.ValidationError) + @staticmethod def default_list_data(): columns = [ diff --git a/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json b/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json index ae026c74..d9b5f203 100644 --- a/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json +++ b/crm/fcrm/doctype/crm_deal_status/crm_deal_status.json @@ -37,13 +37,14 @@ { "fieldname": "probability", "fieldtype": "Percent", + "in_list_view": 1, "label": "Probability" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-06-11 13:00:34.518808", + "modified": "2025-07-01 12:06:42.937440", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Deal Status", diff --git a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py index 1fd85824..34a361e9 100644 --- a/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py +++ b/crm/fcrm/doctype/crm_fields_layout/crm_fields_layout.py @@ -47,6 +47,13 @@ def get_fields_layout(doctype: str, type: str, parent_doctype: str | None = None fields = frappe.get_meta(doctype).fields fields = [field for field in fields if field.fieldname in allowed_fields] + required_fields = [] + + if type == "Required Fields": + required_fields = [ + field for field in frappe.get_meta(doctype, False).fields if field.reqd and not field.default + ] + for tab in tabs: for section in tab.get("sections"): if section.get("columns"): @@ -60,6 +67,32 @@ def get_fields_layout(doctype: str, type: str, parent_doctype: str | None = None handle_perm_level_restrictions(field, doctype, parent_doctype) column["fields"][column.get("fields").index(field["fieldname"])] = field + # remove field from required_fields if it is already present + if ( + type == "Required Fields" + and field.reqd + and any(f.get("fieldname") == field.get("fieldname") for f in required_fields) + ): + required_fields = [ + f for f in required_fields if f.get("fieldname") != field.get("fieldname") + ] + + if type == "Required Fields" and required_fields and tabs: + tabs[-1].get("sections").append( + { + "label": "Required Fields", + "name": "required_fields_section_" + str(random_string(4)), + "opened": True, + "hideLabel": True, + "columns": [ + { + "name": "required_fields_column_" + str(random_string(4)), + "fields": [field.as_dict() for field in required_fields], + } + ], + } + ) + return tabs or [] @@ -83,6 +116,8 @@ def get_sidepanel_sections(doctype): fields = frappe.get_meta(doctype).fields fields = [field for field in fields if field.fieldtype not in not_allowed_fieldtypes] + add_forecasting_section(layout, doctype) + for section in layout: section["name"] = section.get("name") or section.get("label") for column in section.get("columns") if section.get("columns") else []: @@ -100,6 +135,38 @@ def get_sidepanel_sections(doctype): return layout +def add_forecasting_section(layout, doctype): + if ( + doctype == "CRM Deal" + and frappe.db.get_single_value("FCRM Settings", "enable_forecasting") + and not any(section.get("name") == "forecasted_sales_section" for section in layout) + ): + contacts_section_index = next( + ( + i + for i, section in enumerate(layout) + if section.get("name") == "contacts_section" or section.get("label") == "Contacts" + ), + None, + ) + + if contacts_section_index is not None: + layout.insert( + contacts_section_index + 1, + { + "name": "forecasted_sales_section", + "label": "Forecasted Sales", + "opened": True, + "columns": [ + { + "name": "column_" + str(random_string(4)), + "fields": ["close_date", "probability", "deal_value"], + } + ], + }, + ) + + def handle_perm_level_restrictions(field, doctype, parent_doctype=None): if field.permlevel == 0: return diff --git a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json b/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json index 416ccd7e..87fb72f3 100644 --- a/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json +++ b/crm/fcrm/doctype/crm_lead_source/crm_lead_source.json @@ -27,9 +27,10 @@ "label": "Details" } ], + "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-01-02 22:13:30.498404", + "modified": "2025-06-30 16:53:51.721752", "modified_by": "Administrator", "module": "FCRM", "name": "CRM Lead Source", @@ -44,7 +45,7 @@ "print": 1, "read": 1, "report": 1, - "role": "Sales User", + "role": "System Manager", "share": 1, "write": 1 }, @@ -60,6 +61,15 @@ "share": 1, "write": 1 }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1 + }, { "email": 1, "export": 1, @@ -71,7 +81,8 @@ } ], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/crm/fcrm/doctype/crm_lost_reason/__init__.py b/crm/fcrm/doctype/crm_lost_reason/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/crm/fcrm/doctype/crm_lost_reason/crm_lost_reason.js b/crm/fcrm/doctype/crm_lost_reason/crm_lost_reason.js new file mode 100644 index 00000000..effd824a --- /dev/null +++ b/crm/fcrm/doctype/crm_lost_reason/crm_lost_reason.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("CRM Lost Reason", { +// refresh(frm) { + +// }, +// }); diff --git a/crm/fcrm/doctype/crm_lost_reason/crm_lost_reason.json b/crm/fcrm/doctype/crm_lost_reason/crm_lost_reason.json new file mode 100644 index 00000000..ce774060 --- /dev/null +++ b/crm/fcrm/doctype/crm_lost_reason/crm_lost_reason.json @@ -0,0 +1,79 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:lost_reason", + "creation": "2025-06-30 16:51:31.082360", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "lost_reason", + "description" + ], + "fields": [ + { + "fieldname": "lost_reason", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Lost Reason", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-06-30 16:59:15.094049", + "modified_by": "Administrator", + "module": "FCRM", + "name": "CRM Lost Reason", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "row_format": "Dynamic", + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/crm/fcrm/doctype/crm_lost_reason/crm_lost_reason.py b/crm/fcrm/doctype/crm_lost_reason/crm_lost_reason.py new file mode 100644 index 00000000..b43a399c --- /dev/null +++ b/crm/fcrm/doctype/crm_lost_reason/crm_lost_reason.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CRMLostReason(Document): + pass diff --git a/crm/fcrm/doctype/crm_lost_reason/test_crm_lost_reason.py b/crm/fcrm/doctype/crm_lost_reason/test_crm_lost_reason.py new file mode 100644 index 00000000..2f353477 --- /dev/null +++ b/crm/fcrm/doctype/crm_lost_reason/test_crm_lost_reason.py @@ -0,0 +1,30 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase, UnitTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + +class UnitTestCRMLostReason(UnitTestCase): + """ + Unit tests for CRMLostReason. + Use this class for testing individual functions and methods. + """ + + pass + + +class IntegrationTestCRMLostReason(IntegrationTestCase): + """ + Integration tests for CRMLostReason. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json index de27e245..e679b023 100644 --- a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json +++ b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.json @@ -60,7 +60,7 @@ }, { "default": "0", - "description": "It will make deal's \"Expected Closure Date\" mandatory to get accurate forecasting insights", + "description": "It will make deal's \"Close Date\" & \"Deal Value\" mandatory to get accurate forecasting insights", "fieldname": "enable_forecasting", "fieldtype": "Check", "label": "Enable Forecasting" @@ -69,7 +69,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-06-11 19:12:16.762499", + "modified": "2025-07-01 13:20:48.757603", "modified_by": "Administrator", "module": "FCRM", "name": "FCRM Settings", diff --git a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py index f22114f1..1460c265 100644 --- a/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py +++ b/crm/fcrm/doctype/fcrm_settings/fcrm_settings.py @@ -39,6 +39,11 @@ class FCRMSettings(Document): "reqd", "close_date", ) + delete_property_setter( + "CRM Deal", + "reqd", + "deal_value", + ) else: make_property_setter( "CRM Deal", @@ -47,6 +52,13 @@ class FCRMSettings(Document): 1 if self.enable_forecasting else 0, "Check", ) + make_property_setter( + "CRM Deal", + "deal_value", + "reqd", + 1 if self.enable_forecasting else 0, + "Check", + ) def get_standard_dropdown_items(): diff --git a/crm/install.py b/crm/install.py index 5ffa5a67..ae0a437c 100644 --- a/crm/install.py +++ b/crm/install.py @@ -20,6 +20,7 @@ def after_install(force=False): add_email_template_custom_fields() add_default_industries() add_default_lead_sources() + add_default_lost_reasons() add_standard_dropdown_items() add_default_scripts() frappe.db.commit() @@ -68,30 +69,37 @@ def add_default_deal_statuses(): statuses = { "Qualification": { "color": "gray", + "probability": 10, "position": 1, }, "Demo/Making": { "color": "orange", + "probability": 25, "position": 2, }, "Proposal/Quotation": { "color": "blue", + "probability": 50, "position": 3, }, "Negotiation": { "color": "yellow", + "probability": 70, "position": 4, }, "Ready to Close": { "color": "purple", + "probability": 90, "position": 5, }, "Won": { "color": "green", + "probability": 100, "position": 6, }, "Lost": { "color": "red", + "probability": 0, "position": 7, }, } @@ -103,6 +111,7 @@ def add_default_deal_statuses(): doc = frappe.new_doc("CRM Deal Status") doc.deal_status = status doc.color = statuses[status]["color"] + doc.probability = statuses[status]["probability"] doc.position = statuses[status]["position"] doc.insert() @@ -343,6 +352,44 @@ def add_default_lead_sources(): doc.insert() +def add_default_lost_reasons(): + lost_reasons = [ + { + "reason": "Pricing", + "description": "The prospect found the pricing to be too high or not competitive.", + }, + {"reason": "Competition", "description": "The prospect chose a competitor's product or service."}, + { + "reason": "Budget Constraints", + "description": "The prospect did not have the budget to proceed with the purchase.", + }, + { + "reason": "Missing Features", + "description": "The prospect felt that the product or service was missing key features they needed.", + }, + { + "reason": "Long Sales Cycle", + "description": "The sales process took too long, leading to loss of interest.", + }, + { + "reason": "No Decision-Maker", + "description": "The prospect was not the decision-maker and could not proceed.", + }, + {"reason": "Unresponsive Prospect", "description": "The prospect did not respond to follow-ups."}, + {"reason": "Poor Fit", "description": "The prospect was not a good fit for the product or service."}, + {"reason": "Other", "description": ""}, + ] + + for reason in lost_reasons: + if frappe.db.exists("CRM Lost Reason", reason["reason"]): + continue + + doc = frappe.new_doc("CRM Lost Reason") + doc.lost_reason = reason["reason"] + doc.description = reason["description"] + doc.insert() + + def add_standard_dropdown_items(): crm_settings = frappe.get_single("FCRM Settings") diff --git a/crm/patches.txt b/crm/patches.txt index ee56be84..484c73dc 100644 --- a/crm/patches.txt +++ b/crm/patches.txt @@ -12,4 +12,5 @@ crm.patches.v1_0.create_default_sidebar_fields_layout crm.patches.v1_0.update_deal_quick_entry_layout crm.patches.v1_0.update_layouts_to_new_format crm.patches.v1_0.move_twilio_agent_to_telephony_agent -crm.patches.v1_0.create_default_scripts # 13-06-2025 \ No newline at end of file +crm.patches.v1_0.create_default_scripts # 13-06-2025 +crm.patches.v1_0.update_deal_status_probabilities \ No newline at end of file diff --git a/crm/patches/v1_0/update_deal_status_probabilities.py b/crm/patches/v1_0/update_deal_status_probabilities.py new file mode 100644 index 00000000..3460784e --- /dev/null +++ b/crm/patches/v1_0/update_deal_status_probabilities.py @@ -0,0 +1,24 @@ +import frappe + + +def execute(): + deal_statuses = frappe.get_all("CRM Deal Status", fields=["name", "probability", "deal_status"]) + + for status in deal_statuses: + if status.probability is None or status.probability == 0: + if status.deal_status == "Qualification": + probability = 10 + elif status.deal_status == "Demo/Making": + probability = 25 + elif status.deal_status == "Proposal/Quotation": + probability = 50 + elif status.deal_status == "Negotiation": + probability = 70 + elif status.deal_status == "Ready to Close": + probability = 90 + elif status.deal_status == "Won": + probability = 100 + else: + probability = 0 + + frappe.db.set_value("CRM Deal Status", status.name, "probability", probability) diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 3404f805..63df320c 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -56,6 +56,7 @@ declare module 'vue' { ContactsIcon: typeof import('./src/components/Icons/ContactsIcon.vue')['default'] ContactsListView: typeof import('./src/components/ListViews/ContactsListView.vue')['default'] ConvertIcon: typeof import('./src/components/Icons/ConvertIcon.vue')['default'] + ConvertToDealModal: typeof import('./src/components/Modals/ConvertToDealModal.vue')['default'] CountUpTimer: typeof import('./src/components/CountUpTimer.vue')['default'] CreateDocumentModal: typeof import('./src/components/Modals/CreateDocumentModal.vue')['default'] CRMLogo: typeof import('./src/components/Icons/CRMLogo.vue')['default'] @@ -161,6 +162,7 @@ declare module 'vue' { ListIcon: typeof import('./src/components/Icons/ListIcon.vue')['default'] ListRows: typeof import('./src/components/ListViews/ListRows.vue')['default'] LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default'] + LostReasonModal: typeof import('./src/components/Modals/LostReasonModal.vue')['default'] LucideInfo: typeof import('~icons/lucide/info')['default'] LucideMoreHorizontal: typeof import('~icons/lucide/more-horizontal')['default'] LucidePlus: typeof import('~icons/lucide/plus')['default'] diff --git a/frontend/package.json b/frontend/package.json index 1f359a5c..7672e8df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,7 @@ "@tiptap/extension-paragraph": "^2.12.0", "@twilio/voice-sdk": "^2.10.2", "@vueuse/integrations": "^10.3.0", - "frappe-ui": "^0.1.162", + "frappe-ui": "^0.1.166", "gemoji": "^8.1.0", "lodash": "^4.17.21", "mime": "^4.0.1", diff --git a/frontend/src/components/Activities/Activities.vue b/frontend/src/components/Activities/Activities.vue index 1c420e04..e13095aa 100644 --- a/frontend/src/components/Activities/Activities.vue +++ b/frontend/src/components/Activities/Activities.vue @@ -368,6 +368,7 @@ @@ -518,7 +519,7 @@ const props = defineProps({ }, }) -const emit = defineEmits(['afterSave']) +const emit = defineEmits(['beforeSave', 'afterSave']) const route = useRoute() diff --git a/frontend/src/components/Activities/AllModals.vue b/frontend/src/components/Activities/AllModals.vue index e4f7498b..ec58b6ee 100644 --- a/frontend/src/components/Activities/AllModals.vue +++ b/frontend/src/components/Activities/AllModals.vue @@ -19,6 +19,7 @@ v-if="showCallLogModal" v-model="showCallLogModal" :data="callLog" + :referenceDoc="referenceDoc" :options="{ afterInsert: () => activities.reload() }" /> @@ -87,10 +88,12 @@ function showNote(n) { // Call Logs const showCallLogModal = ref(false) const callLog = ref({}) +const referenceDoc = ref({}) function createCallLog() { let doctype = props.doctype let docname = props.doc.data?.name + referenceDoc.value = { ...props.doc.data } callLog.value = { reference_doctype: doctype, reference_docname: docname, diff --git a/frontend/src/components/Activities/DataFields.vue b/frontend/src/components/Activities/DataFields.vue index 88119f80..5b864ef1 100644 --- a/frontend/src/components/Activities/DataFields.vue +++ b/frontend/src/components/Activities/DataFields.vue @@ -66,7 +66,7 @@ import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue' import { usersStore } from '@/stores/users' import { useDocument } from '@/data/document' import { isMobileView } from '@/composables/settings' -import { ref, watch } from 'vue' +import { ref, watch, getCurrentInstance } from 'vue' const props = defineProps({ doctype: { @@ -79,10 +79,13 @@ const props = defineProps({ }, }) -const emit = defineEmits(['afterSave']) +const emit = defineEmits(['beforeSave', 'afterSave']) const { isManager } = usersStore() +const instance = getCurrentInstance() +const attrs = instance?.vnode?.props ?? {} + const showDataFieldsModal = ref(false) const { document } = useDocument(props.doctype, props.docname) @@ -107,9 +110,15 @@ function saveChanges() { return acc }, {}) - document.save.submit(null, { - onSuccess: () => emit('afterSave', changes), - }) + const hasListener = attrs['onBeforeSave'] !== undefined + + if (hasListener) { + emit('beforeSave', changes) + } else { + document.save.submit(null, { + onSuccess: () => emit('afterSave', changes), + }) + } } watch( diff --git a/frontend/src/components/Modals/AddressModal.vue b/frontend/src/components/Modals/AddressModal.vue index e7c4543f..c1e9ba20 100644 --- a/frontend/src/components/Modals/AddressModal.vue +++ b/frontend/src/components/Modals/AddressModal.vue @@ -84,7 +84,10 @@ const error = ref(null) const title = ref(null) const editMode = ref(false) -const { document: _address } = useDocument('Address', props.address || '') +const { document: _address, triggerOnBeforeCreate } = useDocument( + 'Address', + props.address || '', +) const dialogOptions = computed(() => { let title = !editMode.value @@ -95,8 +98,7 @@ const dialogOptions = computed(() => { { label: editMode.value ? __('Save') : __('Create'), variant: 'solid', - onClick: () => - editMode.value ? updateAddress() : createAddress.submit(), + onClick: () => (editMode.value ? updateAddress() : createAddress()), }, ] @@ -119,7 +121,10 @@ const callBacks = { loading.value = false if (err.exc_type == 'MandatoryError') { const errorMessage = err.messages - .map((msg) => msg.split(': ')[2].trim()) + .map((msg) => { + let arr = msg.split(': ') + return arr[arr.length - 1].trim() + }) .join(', ') error.value = __('These fields are required: {0}', [errorMessage]) return @@ -133,16 +138,22 @@ async function updateAddress() { await _address.save.submit(null, callBacks) } -const createAddress = createResource({ +async function createAddress() { + loading.value = true + error.value = null + + await triggerOnBeforeCreate?.() + + await _createAddress.submit({ + doc: { + doctype: 'Address', + ..._address.doc, + }, + }) +} + +const _createAddress = createResource({ url: 'frappe.client.insert', - makeParams() { - return { - doc: { - doctype: 'Address', - ..._address.doc, - }, - } - }, onSuccess(doc) { loading.value = false if (doc.name) { diff --git a/frontend/src/components/Modals/CallLogDetailModal.vue b/frontend/src/components/Modals/CallLogDetailModal.vue index 739f78ef..c7d336e1 100644 --- a/frontend/src/components/Modals/CallLogDetailModal.vue +++ b/frontend/src/components/Modals/CallLogDetailModal.vue @@ -34,7 +34,7 @@