From bca6ef575ad99585270b42adadeac9497a975495 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 30 Jun 2025 18:42:20 +0530 Subject: [PATCH 1/3] feat: added on before create hook in document.js (cherry picked from commit 6b7bdf5afbbd40e6799abdf5de7fc3aa2301abb7) --- frontend/src/data/document.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/frontend/src/data/document.js b/frontend/src/data/document.js index 41bdea34..a77174cd 100644 --- a/frontend/src/data/document.js +++ b/frontend/src/data/document.js @@ -110,6 +110,14 @@ export function useDocument(doctype, docname) { await trigger(handler) } + async function triggerOnBeforeCreate() { + const args = Array.from(arguments) + const handler = async function () { + await (this.onBeforeCreate?.(...args) || this.on_before_create?.(...args)) + } + await trigger(handler) + } + async function triggerOnSave() { const handler = async function () { await (this.onSave?.() || this.on_save?.()) @@ -202,26 +210,12 @@ export function useDocument(doctype, docname) { await runSequentially(tasks) } - function getOldValue(fieldname, row) { - if (!documentsCache[doctype][docname || '']) return '' - - const document = documentsCache[doctype][docname || ''] - const oldDoc = document.originalDoc - - if (row?.name) { - return oldDoc?.[row.parentfield]?.find((r) => r.name === row.name)?.[ - fieldname - ] - } - - return oldDoc?.[fieldname] || document.doc[fieldname] - } - return { document: documentsCache[doctype][docname || ''], assignees, getControllers, triggerOnLoad, + triggerOnBeforeCreate, triggerOnSave, triggerOnRefresh, triggerOnChange, From d8f990ac8ead7f33a5160b901ebdd0af7c43da41 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 30 Jun 2025 18:43:19 +0530 Subject: [PATCH 2/3] fix: added on before create hook in call log modal (cherry picked from commit ac13b7a3bd58021a91793817f07b4703bd8f42b4) --- .../src/components/Modals/CallLogModal.vue | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/Modals/CallLogModal.vue b/frontend/src/components/Modals/CallLogModal.vue index 8ab881cd..9b1cbcb0 100644 --- a/frontend/src/components/Modals/CallLogModal.vue +++ b/frontend/src/components/Modals/CallLogModal.vue @@ -85,7 +85,7 @@ const loading = ref(false) const error = ref(null) const editMode = ref(false) -const { document: callLog } = useDocument( +const { document: callLog, triggerOnBeforeCreate } = useDocument( 'CRM Call Log', props.data?.name || '', ) @@ -97,8 +97,7 @@ const dialogOptions = computed(() => { { label: editMode.value ? __('Save') : __('Create'), variant: 'solid', - onClick: () => - editMode.value ? updateCallLog() : createCallLog.submit(), + onClick: () => (editMode.value ? updateCallLog() : createCallLog()), }, ] @@ -135,18 +134,21 @@ async function updateCallLog() { await callLog.save.submit(null, callBacks) } -const createCallLog = createResource({ +async function createCallLog() { + Object.assign(callLog.doc, { + doctype: 'CRM Call Log', + id: getRandom(6), + telephony_medium: 'Manual', + }) + + await triggerOnBeforeCreate?.() + await _createCallLog.submit({ + doc: callLog.doc, + }) +} + +const _createCallLog = createResource({ url: 'frappe.client.insert', - makeParams() { - return { - doc: { - doctype: 'CRM Call Log', - id: getRandom(6), - telephony_medium: 'Manual', - ...callLog.doc, - }, - } - }, onSuccess(doc) { loading.value = false if (doc.name) { From f6af8d562e313b1faf77827cc15fbdd7b18dfa73 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 30 Jun 2025 19:16:24 +0530 Subject: [PATCH 3/3] fix: added on before create hook in all modals (cherry picked from commit 2e5c1bc3b564a9c7e1dd49f521cd4ec36b202102) --- .../src/components/Modals/AddressModal.vue | 32 +++-- .../src/components/Modals/ContactModal.vue | 4 +- .../components/Modals/CreateDocumentModal.vue | 11 +- frontend/src/components/Modals/DealModal.vue | 10 +- frontend/src/components/Modals/LeadModal.vue | 120 +++++++++--------- .../components/Modals/OrganizationModal.vue | 8 +- 6 files changed, 108 insertions(+), 77 deletions(-) diff --git a/frontend/src/components/Modals/AddressModal.vue b/frontend/src/components/Modals/AddressModal.vue index e7c4543f..444490c4 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()), }, ] @@ -133,16 +135,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/ContactModal.vue b/frontend/src/components/Modals/ContactModal.vue index 25a0c022..4578ca35 100644 --- a/frontend/src/components/Modals/ContactModal.vue +++ b/frontend/src/components/Modals/ContactModal.vue @@ -86,7 +86,7 @@ const show = defineModel() const loading = ref(false) -const { document: _contact } = useDocument('Contact') +const { document: _contact, triggerOnBeforeCreate } = useDocument('Contact') async function createContact() { if (_contact.doc.email_id) { @@ -99,6 +99,8 @@ async function createContact() { delete _contact.doc.mobile_no } + await triggerOnBeforeCreate?.() + const doc = await call('frappe.client.insert', { doc: { doctype: 'Contact', diff --git a/frontend/src/components/Modals/CreateDocumentModal.vue b/frontend/src/components/Modals/CreateDocumentModal.vue index be5bbf01..0a2933a7 100644 --- a/frontend/src/components/Modals/CreateDocumentModal.vue +++ b/frontend/src/components/Modals/CreateDocumentModal.vue @@ -27,7 +27,7 @@
- +
@@ -51,6 +51,7 @@ import FieldLayout from '@/components/FieldLayout/FieldLayout.vue' import EditIcon from '@/components/Icons/EditIcon.vue' import { usersStore } from '@/stores/users' +import { useDocument } from '@/data/document' import { isMobileView } from '@/composables/settings' import { showQuickEntryModal, quickEntryProps } from '@/composables/modals' import { FeatherIcon, createResource, ErrorMessage, call } from 'frappe-ui' @@ -76,7 +77,7 @@ const show = defineModel() const loading = ref(false) const error = ref(null) -let _data = ref({}) +const { document: _data, triggerOnBeforeCreate } = useDocument(props.doctype) const dialogOptions = computed(() => { let doctype = props.doctype @@ -109,12 +110,14 @@ async function create() { loading.value = true error.value = null + await triggerOnBeforeCreate?.() + let doc = await call( 'frappe.client.insert', { doc: { doctype: props.doctype, - ..._data.value, + ..._data.doc, }, }, { @@ -138,7 +141,7 @@ watch( if (!value) return nextTick(() => { - _data.value = { ...props.data } + _data.doc = { ...props.data } }) }, ) diff --git a/frontend/src/components/Modals/DealModal.vue b/frontend/src/components/Modals/DealModal.vue index 8bf9498b..7ccb128f 100644 --- a/frontend/src/components/Modals/DealModal.vue +++ b/frontend/src/components/Modals/DealModal.vue @@ -98,7 +98,11 @@ const show = defineModel() const router = useRouter() const error = ref(null) -const { document: deal, triggerOnChange } = useDocument('CRM Deal') +const { + document: deal, + triggerOnChange, + triggerOnBeforeCreate, +} = useDocument('CRM Deal') const hasOrganizationSections = ref(true) const hasContactSections = ref(true) @@ -175,7 +179,7 @@ const dealStatuses = computed(() => { return statuses }) -function createDeal() { +async function createDeal() { if (deal.doc.website && !deal.doc.website.startsWith('http')) { deal.doc.website = 'https://' + deal.doc.website } @@ -186,6 +190,8 @@ function createDeal() { deal.doc['mobile_no'] = null } else deal.doc['contact'] = null + await triggerOnBeforeCreate?.() + createResource({ url: 'crm.fcrm.doctype.crm_deal.crm_deal.create_deal', params: { args: deal.doc }, diff --git a/frontend/src/components/Modals/LeadModal.vue b/frontend/src/components/Modals/LeadModal.vue index d82dbe52..7c2dc728 100644 --- a/frontend/src/components/Modals/LeadModal.vue +++ b/frontend/src/components/Modals/LeadModal.vue @@ -74,7 +74,11 @@ const router = useRouter() const error = ref(null) const isLeadCreating = ref(false) -const { document: lead, triggerOnChange } = useDocument('CRM Lead') +const { + document: lead, + triggerOnChange, + triggerOnBeforeCreate, +} = useDocument('CRM Lead') const leadStatuses = computed(() => { let statuses = statusOptions('lead', null, [], triggerOnChange) @@ -112,71 +116,73 @@ const tabs = createResource({ const createLead = createResource({ url: 'frappe.client.insert', - makeParams(values) { - return { - doc: { - doctype: 'CRM Lead', - ...values, - }, - } - }, }) -function createNewLead() { +async function createNewLead() { if (lead.doc.website && !lead.doc.website.startsWith('http')) { lead.doc.website = 'https://' + lead.doc.website } - createLead.submit(lead.doc, { - validate() { - error.value = null - if (!lead.doc.first_name) { - error.value = __('First Name is mandatory') - return error.value - } - if (lead.doc.annual_revenue) { - if (typeof lead.doc.annual_revenue === 'string') { - lead.doc.annual_revenue = lead.doc.annual_revenue.replace(/,/g, '') - } else if (isNaN(lead.doc.annual_revenue)) { - error.value = __('Annual Revenue should be a number') + await triggerOnBeforeCreate?.() + + createLead.submit( + { + doc: { + doctype: 'CRM Lead', + ...lead.doc, + }, + }, + { + validate() { + error.value = null + if (!lead.doc.first_name) { + error.value = __('First Name is mandatory') return error.value } - } - if ( - lead.doc.mobile_no && - isNaN(lead.doc.mobile_no.replace(/[-+() ]/g, '')) - ) { - error.value = __('Mobile No should be a number') - return error.value - } - if (lead.doc.email && !lead.doc.email.includes('@')) { - error.value = __('Invalid Email') - return error.value - } - if (!lead.doc.status) { - error.value = __('Status is required') - return error.value - } - isLeadCreating.value = true + if (lead.doc.annual_revenue) { + if (typeof lead.doc.annual_revenue === 'string') { + lead.doc.annual_revenue = lead.doc.annual_revenue.replace(/,/g, '') + } else if (isNaN(lead.doc.annual_revenue)) { + error.value = __('Annual Revenue should be a number') + return error.value + } + } + if ( + lead.doc.mobile_no && + isNaN(lead.doc.mobile_no.replace(/[-+() ]/g, '')) + ) { + error.value = __('Mobile No should be a number') + return error.value + } + if (lead.doc.email && !lead.doc.email.includes('@')) { + error.value = __('Invalid Email') + return error.value + } + if (!lead.doc.status) { + error.value = __('Status is required') + return error.value + } + isLeadCreating.value = true + }, + onSuccess(data) { + capture('lead_created') + isLeadCreating.value = false + show.value = false + router.push({ name: 'Lead', params: { leadId: data.name } }) + updateOnboardingStep('create_first_lead', true, false, () => { + localStorage.setItem('firstLead' + user, data.name) + }) + }, + onError(err) { + isLeadCreating.value = false + if (!err.messages) { + error.value = err.message + return + } + error.value = err.messages.join('\n') + }, }, - onSuccess(data) { - capture('lead_created') - isLeadCreating.value = false - show.value = false - router.push({ name: 'Lead', params: { leadId: data.name } }) - updateOnboardingStep('create_first_lead', true, false, () => { - localStorage.setItem('firstLead' + user, data.name) - }) - }, - onError(err) { - isLeadCreating.value = false - if (!err.messages) { - error.value = err.message - return - } - error.value = err.messages.join('\n') - }, - }) + ) } function openQuickEntryModal() { diff --git a/frontend/src/components/Modals/OrganizationModal.vue b/frontend/src/components/Modals/OrganizationModal.vue index a0d12887..c5ac5f5f 100644 --- a/frontend/src/components/Modals/OrganizationModal.vue +++ b/frontend/src/components/Modals/OrganizationModal.vue @@ -88,9 +88,15 @@ const show = defineModel() const loading = ref(false) const error = ref(null) -const { document: organization } = useDocument('CRM Organization') +const { document: organization, triggerOnBeforeCreate } = + useDocument('CRM Organization') async function createOrganization() { + loading.value = true + error.value = null + + await triggerOnBeforeCreate?.() + const doc = await call( 'frappe.client.insert', {