diff --git a/frontend/src/data/document.js b/frontend/src/data/document.js index 514eb277..bca9f713 100644 --- a/frontend/src/data/document.js +++ b/frontend/src/data/document.js @@ -14,7 +14,7 @@ export function useDocument(doctype, docname) { documentsCache[doctype][docname] = createDocumentResource({ doctype: doctype, name: docname, - onSuccess: () => setupFormScript(), + onSuccess: async () => await setupFormScript(), setValue: { onSuccess: () => { toast.success(__('Document updated successfully')) @@ -27,23 +27,49 @@ export function useDocument(doctype, docname) { }) } - function setupFormScript() { - if (controllersCache[doctype]?.[docname]) return + async function setupFormScript() { + if ( + controllersCache[doctype] && + typeof controllersCache[doctype][docname] === 'object' + ) { + return + } if (!controllersCache[doctype]) { controllersCache[doctype] = {} } - controllersCache[doctype][docname] = setupScript( - documentsCache[doctype][docname], - ) + controllersCache[doctype][docname] = {} + + const controllersArray = await setupScript(documentsCache[doctype][docname]) + + if (!controllersArray || controllersArray.length === 0) return + + const organizedControllers = {} + for (const controller of controllersArray) { + const controllerKey = controller.constructor.name // e.g., "CRMLead", "CRMProducts" + if (!organizedControllers[controllerKey]) { + organizedControllers[controllerKey] = [] + } + organizedControllers[controllerKey].push(controller) + } + controllersCache[doctype][docname] = organizedControllers } function getControllers(row = null) { const _doctype = row?.doctype || doctype - return (controllersCache[doctype]?.[docname] || []).filter( - (c) => c.constructor.name === _doctype.replace(/\s+/g, ''), - ) + const controllerKey = _doctype.replace(/\s+/g, '') + + const docControllers = controllersCache[doctype]?.[docname] + + if ( + typeof docControllers === 'object' && + docControllers !== null && + !Array.isArray(docControllers) + ) { + return docControllers[controllerKey] || [] + } + return [] } async function triggerOnRefresh() { @@ -100,7 +126,15 @@ export function useDocument(doctype, docname) { async function triggerOnCreateLead() { const args = Array.from(arguments) const handler = async function () { - await this.on_create_lead(...args) + await this.on_create_lead?.(...args) + } + await trigger(handler) + } + + async function triggerConvertToDeal() { + const args = Array.from(arguments) + const handler = async function () { + await this.convert_to_deal?.(...args) } await trigger(handler) } @@ -139,5 +173,6 @@ export function useDocument(doctype, docname) { triggerOnRefresh, setupFormScript, triggerOnCreateLead, + triggerConvertToDeal, } } diff --git a/frontend/src/data/script.js b/frontend/src/data/script.js index fe2b9f02..265f0bf2 100644 --- a/frontend/src/data/script.js +++ b/frontend/src/data/script.js @@ -20,15 +20,23 @@ export function getScript(doctype, view = 'Form') { doctypeScripts[doctype][script.name] = script || {} } }, + onError: (err) => { + console.error( + `Error loading CRM Form Scripts for ${doctype} (view: ${view}):`, + err, + ) + }, }) if (!doctypeScripts[doctype] && !scripts.loading) { scripts.fetch() } - function setupScript(document, helpers = {}) { - let scripts = doctypeScripts[doctype] - if (!scripts) return null + async function setupScript(document, helpers = {}) { + await scripts.promise + + let scriptDefs = doctypeScripts[doctype] + if (!scriptDefs || Object.keys(scriptDefs).length === 0) return null const { $dialog, $socket, makeCall } = globalStore() @@ -42,7 +50,7 @@ export function getScript(doctype, view = 'Form') { makePhoneCall: makeCall, } - return setupMultipleFormControllers(scripts, document, helpers) + return setupMultipleFormControllers(scriptDefs, document, helpers) } function setupMultipleFormControllers(scriptStrings, document, helpers) { @@ -126,10 +134,10 @@ export function getScript(doctype, view = 'Form') { return meta[doctype] } - setupHelperMethods(FormClass, document) + const getDoc = () => document.doc if (isChildDoctype) { - instance.doc = createDocProxy(document.doc, parentInstance, instance) + instance.doc = createDocProxy(getDoc, parentInstance, instance) if (!parentInstance._childInstances) { parentInstance._childInstances = [] @@ -137,22 +145,21 @@ export function getScript(doctype, view = 'Form') { parentInstance._childInstances.push(instance) } else { - instance.doc = createDocProxy(document.doc, instance) + instance.doc = createDocProxy(getDoc, instance) } return instance } - function setupHelperMethods(FormClass, document) { + function setupHelperMethods(FormClass) { if (typeof FormClass.prototype.getRow !== 'function') { FormClass.prototype.getRow = function (parentField, idx) { - let data = document.doc idx = idx || this.currentRowIdx let dt = null if (this instanceof Array) { - const { getFields } = getMeta(data.doctype) + const { getFields } = getMeta(this.doc.doctype) let fields = getFields() let field = fields.find((f) => f.fieldname === parentField) dt = field?.options?.replace(/\s+/g, '') @@ -162,13 +169,13 @@ export function getScript(doctype, view = 'Form') { } } - if (!data[parentField]) { + if (!this.doc[parentField]) { console.warn( __('⚠️ No data found for parent field: {0}', [parentField]), ) return null } - const row = data[parentField].find((r) => r.idx === idx) + const row = this.doc[parentField].find((r) => r.idx === idx) if (!row) { console.warn( @@ -180,7 +187,7 @@ export function getScript(doctype, view = 'Form') { return null } - row.parent = row.parent || data.name + row.parent = row.parent || this.doc.name if (this instanceof Array && dt) { return createDocProxy( @@ -220,46 +227,76 @@ export function getScript(doctype, view = 'Form') { const FormClass = new Function(...helperKeys, wrappedScript)( ...helperValues, ) + + setupHelperMethods(FormClass) + return FormClass } - function createDocProxy(data, instance, childInstance = null) { - return new Proxy(data, { - get(target, prop) { - if (prop === 'trigger') { - if ('trigger' in data) { - console.warn( - __( - '⚠️ Avoid using "trigger" as a field name — it conflicts with the built-in trigger() method.', - ), + function createDocProxy(source, instance, childInstance = null) { + const isFunction = typeof source === 'function' + const getCurrentData = () => (isFunction ? source() : source) + + return new Proxy( + {}, + { + get(target, prop) { + const currentDocData = getCurrentData() + if (!currentDocData) return undefined + + if (prop === 'trigger') { + if (currentDocData && 'trigger' in currentDocData) { + console.warn( + __( + '⚠️ Avoid using "trigger" as a field name — it conflicts with the built-in trigger() method.', + ), + ) + } + + return (methodName, ...args) => { + const method = instance[methodName] + if (typeof method === 'function') { + return method.apply(instance, args) + } else { + console.warn( + __('⚠️ Method "{0}" not found in class.', [methodName]), + ) + } + } + } + + if (prop === 'getRow') { + return instance.getRow.bind( + childInstance || instance._childInstances || instance, ) } - return (methodName, ...args) => { - const method = instance[methodName] - if (typeof method === 'function') { - return method.apply(instance, args) - } else { - console.warn( - __('⚠️ Method "{0}" not found in class.', [methodName]), - ) - } - } - } + return currentDocData[prop] + }, + set(target, prop, value) { + const currentDocData = getCurrentData() + if (!currentDocData) return false - if (prop === 'getRow') { - return instance.getRow.bind( - childInstance || instance._childInstances || instance, - ) - } - - return target[prop] + currentDocData[prop] = value + return true + }, + has(target, prop) { + const currentDocData = getCurrentData() + if (!currentDocData) return false + return prop in currentDocData + }, + ownKeys(target) { + const currentDocData = getCurrentData() + if (!currentDocData) return [] + return Reflect.ownKeys(currentDocData) + }, + getOwnPropertyDescriptor(target, prop) { + const currentDocData = getCurrentData() + if (!currentDocData) return undefined + return Reflect.getOwnPropertyDescriptor(currentDocData, prop) + }, }, - set(target, prop, value) { - target[prop] = value - return true - }, - }) + ) } return { diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index 8499616e..65cf3f49 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -355,6 +355,7 @@ import { usersStore } from '@/stores/users' import { globalStore } from '@/stores/global' import { statusesStore } from '@/stores/statuses' import { getMeta } from '@/stores/meta' +import { useDocument } from '@/data/document' import { whatsappEnabled, callEnabled, @@ -614,6 +615,8 @@ const existingOrganizationChecked = ref(false) const existingContact = ref('') const existingOrganization = ref('') +const { triggerConvertToDeal } = useDocument('CRM Lead', props.leadId) + async function convertToDeal() { if (existingContactChecked.value && !existingContact.value) { toast.error(__('Please select an existing contact')) @@ -633,6 +636,12 @@ async function convertToDeal() { existingOrganization.value = '' } + await triggerConvertToDeal?.( + lead.data, + deal, + () => (showConvertToDealModal.value = false), + ) + let _deal = await call('crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', { lead: lead.data.name, deal,