Merge pull request #867 from frappe/main-hotfix

This commit is contained in:
Shariq Ansari 2025-05-26 18:02:36 +05:30 committed by GitHub
commit fc50f669fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 136 additions and 55 deletions

View File

@ -14,7 +14,7 @@ export function useDocument(doctype, docname) {
documentsCache[doctype][docname] = createDocumentResource({ documentsCache[doctype][docname] = createDocumentResource({
doctype: doctype, doctype: doctype,
name: docname, name: docname,
onSuccess: () => setupFormScript(), onSuccess: async () => await setupFormScript(),
setValue: { setValue: {
onSuccess: () => { onSuccess: () => {
toast.success(__('Document updated successfully')) toast.success(__('Document updated successfully'))
@ -27,23 +27,49 @@ export function useDocument(doctype, docname) {
}) })
} }
function setupFormScript() { async function setupFormScript() {
if (controllersCache[doctype]?.[docname]) return if (
controllersCache[doctype] &&
typeof controllersCache[doctype][docname] === 'object'
) {
return
}
if (!controllersCache[doctype]) { if (!controllersCache[doctype]) {
controllersCache[doctype] = {} controllersCache[doctype] = {}
} }
controllersCache[doctype][docname] = setupScript( controllersCache[doctype][docname] = {}
documentsCache[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) { function getControllers(row = null) {
const _doctype = row?.doctype || doctype const _doctype = row?.doctype || doctype
return (controllersCache[doctype]?.[docname] || []).filter( const controllerKey = _doctype.replace(/\s+/g, '')
(c) => c.constructor.name === _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() { async function triggerOnRefresh() {
@ -100,7 +126,15 @@ export function useDocument(doctype, docname) {
async function triggerOnCreateLead() { async function triggerOnCreateLead() {
const args = Array.from(arguments) const args = Array.from(arguments)
const handler = async function () { 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) await trigger(handler)
} }
@ -139,5 +173,6 @@ export function useDocument(doctype, docname) {
triggerOnRefresh, triggerOnRefresh,
setupFormScript, setupFormScript,
triggerOnCreateLead, triggerOnCreateLead,
triggerConvertToDeal,
} }
} }

View File

@ -20,15 +20,23 @@ export function getScript(doctype, view = 'Form') {
doctypeScripts[doctype][script.name] = script || {} doctypeScripts[doctype][script.name] = script || {}
} }
}, },
onError: (err) => {
console.error(
`Error loading CRM Form Scripts for ${doctype} (view: ${view}):`,
err,
)
},
}) })
if (!doctypeScripts[doctype] && !scripts.loading) { if (!doctypeScripts[doctype] && !scripts.loading) {
scripts.fetch() scripts.fetch()
} }
function setupScript(document, helpers = {}) { async function setupScript(document, helpers = {}) {
let scripts = doctypeScripts[doctype] await scripts.promise
if (!scripts) return null
let scriptDefs = doctypeScripts[doctype]
if (!scriptDefs || Object.keys(scriptDefs).length === 0) return null
const { $dialog, $socket, makeCall } = globalStore() const { $dialog, $socket, makeCall } = globalStore()
@ -42,7 +50,7 @@ export function getScript(doctype, view = 'Form') {
makePhoneCall: makeCall, makePhoneCall: makeCall,
} }
return setupMultipleFormControllers(scripts, document, helpers) return setupMultipleFormControllers(scriptDefs, document, helpers)
} }
function setupMultipleFormControllers(scriptStrings, document, helpers) { function setupMultipleFormControllers(scriptStrings, document, helpers) {
@ -126,10 +134,10 @@ export function getScript(doctype, view = 'Form') {
return meta[doctype] return meta[doctype]
} }
setupHelperMethods(FormClass, document) const getDoc = () => document.doc
if (isChildDoctype) { if (isChildDoctype) {
instance.doc = createDocProxy(document.doc, parentInstance, instance) instance.doc = createDocProxy(getDoc, parentInstance, instance)
if (!parentInstance._childInstances) { if (!parentInstance._childInstances) {
parentInstance._childInstances = [] parentInstance._childInstances = []
@ -137,22 +145,21 @@ export function getScript(doctype, view = 'Form') {
parentInstance._childInstances.push(instance) parentInstance._childInstances.push(instance)
} else { } else {
instance.doc = createDocProxy(document.doc, instance) instance.doc = createDocProxy(getDoc, instance)
} }
return instance return instance
} }
function setupHelperMethods(FormClass, document) { function setupHelperMethods(FormClass) {
if (typeof FormClass.prototype.getRow !== 'function') { if (typeof FormClass.prototype.getRow !== 'function') {
FormClass.prototype.getRow = function (parentField, idx) { FormClass.prototype.getRow = function (parentField, idx) {
let data = document.doc
idx = idx || this.currentRowIdx idx = idx || this.currentRowIdx
let dt = null let dt = null
if (this instanceof Array) { if (this instanceof Array) {
const { getFields } = getMeta(data.doctype) const { getFields } = getMeta(this.doc.doctype)
let fields = getFields() let fields = getFields()
let field = fields.find((f) => f.fieldname === parentField) let field = fields.find((f) => f.fieldname === parentField)
dt = field?.options?.replace(/\s+/g, '') 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( console.warn(
__('⚠️ No data found for parent field: {0}', [parentField]), __('⚠️ No data found for parent field: {0}', [parentField]),
) )
return null return null
} }
const row = data[parentField].find((r) => r.idx === idx) const row = this.doc[parentField].find((r) => r.idx === idx)
if (!row) { if (!row) {
console.warn( console.warn(
@ -180,7 +187,7 @@ export function getScript(doctype, view = 'Form') {
return null return null
} }
row.parent = row.parent || data.name row.parent = row.parent || this.doc.name
if (this instanceof Array && dt) { if (this instanceof Array && dt) {
return createDocProxy( return createDocProxy(
@ -220,46 +227,76 @@ export function getScript(doctype, view = 'Form') {
const FormClass = new Function(...helperKeys, wrappedScript)( const FormClass = new Function(...helperKeys, wrappedScript)(
...helperValues, ...helperValues,
) )
setupHelperMethods(FormClass)
return FormClass return FormClass
} }
function createDocProxy(data, instance, childInstance = null) { function createDocProxy(source, instance, childInstance = null) {
return new Proxy(data, { const isFunction = typeof source === 'function'
get(target, prop) { const getCurrentData = () => (isFunction ? source() : source)
if (prop === 'trigger') {
if ('trigger' in data) { return new Proxy(
console.warn( {},
__( {
'⚠️ Avoid using "trigger" as a field name — it conflicts with the built-in trigger() method.', 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) => { return currentDocData[prop]
const method = instance[methodName] },
if (typeof method === 'function') { set(target, prop, value) {
return method.apply(instance, args) const currentDocData = getCurrentData()
} else { if (!currentDocData) return false
console.warn(
__('⚠️ Method "{0}" not found in class.', [methodName]),
)
}
}
}
if (prop === 'getRow') { currentDocData[prop] = value
return instance.getRow.bind( return true
childInstance || instance._childInstances || instance, },
) has(target, prop) {
} const currentDocData = getCurrentData()
if (!currentDocData) return false
return target[prop] 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 { return {

View File

@ -355,6 +355,7 @@ import { usersStore } from '@/stores/users'
import { globalStore } from '@/stores/global' import { globalStore } from '@/stores/global'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { getMeta } from '@/stores/meta' import { getMeta } from '@/stores/meta'
import { useDocument } from '@/data/document'
import { import {
whatsappEnabled, whatsappEnabled,
callEnabled, callEnabled,
@ -614,6 +615,8 @@ const existingOrganizationChecked = ref(false)
const existingContact = ref('') const existingContact = ref('')
const existingOrganization = ref('') const existingOrganization = ref('')
const { triggerConvertToDeal } = useDocument('CRM Lead', props.leadId)
async function convertToDeal() { async function convertToDeal() {
if (existingContactChecked.value && !existingContact.value) { if (existingContactChecked.value && !existingContact.value) {
toast.error(__('Please select an existing contact')) toast.error(__('Please select an existing contact'))
@ -633,6 +636,12 @@ async function convertToDeal() {
existingOrganization.value = '' existingOrganization.value = ''
} }
await triggerConvertToDeal?.(
lead.data,
deal,
() => (showConvertToDealModal.value = false),
)
let _deal = await call('crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', { let _deal = await call('crm.fcrm.doctype.crm_lead.crm_lead.convert_to_deal', {
lead: lead.data.name, lead: lead.data.name,
deal, deal,