fix: use document.doc instead of lead.data/deal.data

This commit is contained in:
Shariq Ansari 2025-07-30 12:56:27 +05:30
parent ca5d82f5be
commit 0144bc109a
3 changed files with 162 additions and 152 deletions

View File

@ -1,5 +1,5 @@
<template> <template>
<LayoutHeader v-if="deal.data"> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs"> <Breadcrumbs :items="breadcrumbs">
<template #prefix="{ item }"> <template #prefix="{ item }">
@ -7,38 +7,30 @@
</template> </template>
</Breadcrumbs> </Breadcrumbs>
</template> </template>
<template #right-header> <template v-if="!errorTitle" #right-header>
<CustomActions <CustomActions
v-if="deal.data._customActions?.length" v-if="document._actions?.length"
:actions="deal.data._customActions" :actions="document._actions"
/> />
<CustomActions <CustomActions
v-if="document.actions?.length" v-if="document.actions?.length"
:actions="document.actions" :actions="document.actions"
/> />
<AssignTo <AssignTo v-model="assignees.data" :data="doc" doctype="CRM Deal" />
v-model="assignees.data"
:data="document.doc"
doctype="CRM Deal"
/>
<Dropdown <Dropdown
v-if="document.doc" v-if="doc"
:options=" :options="
statusOptions( statusOptions(
'deal', 'deal',
document.statuses?.length document.statuses?.length ? document.statuses : document._statuses,
? document.statuses
: deal.data._customStatuses,
triggerStatusChange, triggerStatusChange,
) )
" "
> >
<template #default="{ open }"> <template #default="{ open }">
<Button :label="document.doc.status"> <Button v-if="doc.status" :label="doc.status">
<template #prefix> <template #prefix>
<IndicatorIcon <IndicatorIcon :class="getDealStatus(doc.status).color" />
:class="getDealStatus(document.doc.status).color"
/>
</template> </template>
<template #suffix> <template #suffix>
<FeatherIcon <FeatherIcon
@ -69,9 +61,9 @@
<Resizer side="right" class="flex flex-col justify-between border-l"> <Resizer side="right" class="flex flex-col justify-between border-l">
<div <div
class="flex h-10.5 cursor-copy items-center border-b px-5 py-2.5 text-lg font-medium text-ink-gray-9" class="flex h-10.5 cursor-copy items-center border-b px-5 py-2.5 text-lg font-medium text-ink-gray-9"
@click="copyToClipboard(deal.data.name)" @click="copyToClipboard(doc.name)"
> >
{{ __(deal.data.name) }} {{ __(doc.name) }}
</div> </div>
<div class="flex items-center justify-start gap-5 border-b p-5"> <div class="flex items-center justify-start gap-5 border-b p-5">
<Tooltip :text="__('Organization logo')"> <Tooltip :text="__('Organization logo')">
@ -102,9 +94,7 @@
<div> <div>
<Button <Button
@click=" @click="
deal.data.email doc.email ? openEmailBox() : toast.error(__('No email set'))
? openEmailBox()
: toast.error(__('No email set'))
" "
> >
<template #icon><Email2Icon /></template> <template #icon><Email2Icon /></template>
@ -115,8 +105,8 @@
<div> <div>
<Button <Button
@click=" @click="
deal.data.website doc.website
? openWebsite(deal.data.website) ? openWebsite(doc.website)
: toast.error(__('No website set')) : toast.error(__('No website set'))
" "
> >
@ -134,7 +124,7 @@
<Tooltip :text="__('Delete')"> <Tooltip :text="__('Delete')">
<div> <div>
<Button <Button
@click="deleteDealWithModal(deal.data.name)" @click="deleteDealWithModal(doc.name)"
variant="subtle" variant="subtle"
icon="trash-2" icon="trash-2"
theme="red" theme="red"
@ -145,8 +135,8 @@
</div> </div>
</div> </div>
<SLASection <SLASection
v-if="deal.data.sla_status" v-if="doc.sla_status"
v-model="deal.data" v-model="doc"
@updateField="updateField" @updateField="updateField"
/> />
<div <div
@ -157,7 +147,7 @@
:sections="sections.data" :sections="sections.data"
:addContact="addContact" :addContact="addContact"
doctype="CRM Deal" doctype="CRM Deal"
:docname="deal.data.name" :docname="doc.name"
@reload="sections.reload" @reload="sections.reload"
@beforeFieldChange="beforeStatusChange" @beforeFieldChange="beforeStatusChange"
@afterFieldChange="reloadAssignees" @afterFieldChange="reloadAssignees"
@ -172,7 +162,7 @@
(value, close) => { (value, close) => {
_contact = { _contact = {
first_name: value, first_name: value,
company_name: deal.data.organization, company_name: doc.organization,
} }
showContactModal = true showContactModal = true
close() close()
@ -308,7 +298,7 @@
:data="_organization" :data="_organization"
:options="{ :options="{
redirect: false, redirect: false,
afterInsert: (doc) => updateField('organization', doc.name), afterInsert: (_doc) => updateField('organization', _doc.name),
}" }"
/> />
<ContactModal <ContactModal
@ -317,14 +307,14 @@
:contact="_contact" :contact="_contact"
:options="{ :options="{
redirect: false, redirect: false,
afterInsert: (doc) => addContact(doc.name), afterInsert: (_doc) => addContact(_doc.name),
}" }"
/> />
<FilesUploader <FilesUploader
v-if="deal.data?.name" v-if="doc?.name"
v-model="showFilesUploader" v-model="showFilesUploader"
doctype="CRM Deal" doctype="CRM Deal"
:docname="deal.data.name" :docname="doc.name"
@after=" @after="
() => { () => {
activities?.all_activities?.reload() activities?.all_activities?.reload()
@ -336,7 +326,7 @@
v-if="showDeleteLinkedDocModal" v-if="showDeleteLinkedDocModal"
v-model="showDeleteLinkedDocModal" v-model="showDeleteLinkedDocModal"
:doctype="'CRM Deal'" :doctype="'CRM Deal'"
:docname="props.dealId" :docname="dealId"
name="Deals" name="Deals"
/> />
<LostReasonModal <LostReasonModal
@ -396,7 +386,15 @@ import {
toast, toast,
} from 'frappe-ui' } from 'frappe-ui'
import { useOnboarding } from 'frappe-ui/frappe' import { useOnboarding } from 'frappe-ui/frappe'
import { ref, computed, h, onMounted, onBeforeUnmount, nextTick } from 'vue' import {
ref,
computed,
h,
onMounted,
onBeforeUnmount,
nextTick,
watch,
} from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useActiveTabManager } from '@/composables/useActiveTabManager' import { useActiveTabManager } from '@/composables/useActiveTabManager'
@ -421,45 +419,60 @@ const props = defineProps({
const errorTitle = ref('') const errorTitle = ref('')
const errorMessage = ref('') const errorMessage = ref('')
const { triggerOnChange, assignees, document, scripts, error } = useDocument(
'CRM Deal',
props.dealId,
)
const doc = computed(() => document.doc || {})
watch(error, (err) => {
if (err) {
errorTitle.value = __(
err.exc_type == 'DoesNotExistError'
? 'Document not found'
: 'Error occurred',
)
errorMessage.value = __(err.messages?.[0] || 'An error occurred')
} else {
errorTitle.value = ''
errorMessage.value = ''
}
})
watch(
() => document.doc,
async (_doc) => {
if (scripts.data?.length) {
let s = await setupCustomizations(scripts.data, {
doc: _doc,
$dialog,
$socket,
router,
toast,
updateField,
createToast: toast.create,
deleteDoc: deleteDeal,
call,
})
document._actions = s.actions || []
document._statuses = s.statuses || []
}
},
{ once: true },
)
const deal = createResource({ const deal = createResource({
url: 'crm.fcrm.doctype.crm_deal.api.get_deal', url: 'crm.fcrm.doctype.crm_deal.api.get_deal',
params: { name: props.dealId }, params: { name: props.dealId },
cache: ['deal', props.dealId], cache: ['deal', props.dealId],
onSuccess: (data) => { onSuccess: (data) => {
errorTitle.value = ''
errorMessage.value = ''
if (data.organization) { if (data.organization) {
organization.update({ organization.update({
params: { doctype: 'CRM Organization', name: data.organization }, params: { doctype: 'CRM Organization', name: data.organization },
}) })
organization.fetch() organization.fetch()
} }
setupCustomizations(deal, {
doc: data,
$dialog,
$socket,
router,
toast,
updateField,
createToast: toast.create,
deleteDoc: deleteDeal,
resource: {
deal,
dealContacts,
sections,
},
call,
})
},
onError: (err) => {
if (err.messages?.[0]) {
errorTitle.value = __('Not permitted')
errorMessage.value = __(err.messages?.[0])
} else {
router.push({ name: 'Deals' })
}
}, },
}) })
@ -520,7 +533,7 @@ function updateDeal(fieldname, value, callback) {
} }
function validateRequired(fieldname, value) { function validateRequired(fieldname, value) {
let meta = deal.data.fields_meta || {} let meta = deal.data?.fields_meta || {}
if (meta[fieldname]?.reqd && !value) { if (meta[fieldname]?.reqd && !value) {
toast.error(__('{0} is a required field', [meta[fieldname].label])) toast.error(__('{0} is a required field', [meta[fieldname].label]))
return true return true
@ -548,14 +561,14 @@ const breadcrumbs = computed(() => {
items.push({ items.push({
label: title.value, label: title.value,
route: { name: 'Deal', params: { dealId: deal.data.name } }, route: { name: 'Deal', params: { dealId: props.dealId } },
}) })
return items return items
}) })
const title = computed(() => { const title = computed(() => {
let t = doctypeMeta['CRM Deal']?.title_field || 'name' let t = doctypeMeta['CRM Deal']?.title_field || 'name'
return deal.data?.[t] || props.dealId return doc.value?.[t] || props.dealId
}) })
usePageMeta(() => { usePageMeta(() => {
@ -746,7 +759,7 @@ function updateField(name, value, callback) {
} }
updateDeal(name, value, () => { updateDeal(name, value, () => {
deal.data[name] = value doc.value[name] = value
callback?.() callback?.()
}) })
} }
@ -769,11 +782,6 @@ function openEmailBox() {
nextTick(() => (activities.value.emailBox.show = true)) nextTick(() => (activities.value.emailBox.show = true))
} }
const { assignees, document, triggerOnChange } = useDocument(
'CRM Deal',
props.dealId,
)
async function triggerStatusChange(value) { async function triggerStatusChange(value) {
await triggerOnChange('status', value) await triggerOnChange('status', value)
setLostReason() setLostReason()
@ -795,7 +803,10 @@ function setLostReason() {
} }
function beforeStatusChange(data) { function beforeStatusChange(data) {
if (data?.hasOwnProperty('status') && getDealStatus(data.status).type == 'Lost') { if (
data?.hasOwnProperty('status') &&
getDealStatus(data.status).type == 'Lost'
) {
setLostReason() setLostReason()
} else { } else {
document.save.submit(null, { document.save.submit(null, {

View File

@ -1,5 +1,5 @@
<template> <template>
<LayoutHeader v-if="lead.data"> <LayoutHeader>
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs"> <Breadcrumbs :items="breadcrumbs">
<template #prefix="{ item }"> <template #prefix="{ item }">
@ -7,38 +7,30 @@
</template> </template>
</Breadcrumbs> </Breadcrumbs>
</template> </template>
<template #right-header> <template v-if="!errorTitle" #right-header>
<CustomActions <CustomActions
v-if="lead.data._customActions?.length" v-if="document._actions?.length"
:actions="lead.data._customActions" :actions="document._actions"
/> />
<CustomActions <CustomActions
v-if="document.actions?.length" v-if="document.actions?.length"
:actions="document.actions" :actions="document.actions"
/> />
<AssignTo <AssignTo v-model="assignees.data" :data="doc" doctype="CRM Lead" />
v-model="assignees.data"
:data="document.doc"
doctype="CRM Lead"
/>
<Dropdown <Dropdown
v-if="document.doc" v-if="doc"
:options=" :options="
statusOptions( statusOptions(
'lead', 'lead',
document.statuses?.length document.statuses?.length ? document.statuses : document._statuses,
? document.statuses
: lead.data._customStatuses,
triggerStatusChange, triggerStatusChange,
) )
" "
> >
<template #default="{ open }"> <template #default="{ open }">
<Button :label="document.doc.status"> <Button v-if="doc.status" :label="doc.status">
<template #prefix> <template #prefix>
<IndicatorIcon <IndicatorIcon :class="getLeadStatus(doc.status).color" />
:class="getLeadStatus(document.doc.status).color"
/>
</template> </template>
<template #suffix> <template #suffix>
<FeatherIcon <FeatherIcon
@ -56,7 +48,7 @@
/> />
</template> </template>
</LayoutHeader> </LayoutHeader>
<div v-if="lead?.data" class="flex h-full overflow-hidden"> <div v-if="lead.data" class="flex h-full overflow-hidden">
<Tabs as="div" v-model="tabIndex" :tabs="tabs"> <Tabs as="div" v-model="tabIndex" :tabs="tabs">
<template #tab-panel> <template #tab-panel>
<Activities <Activities
@ -74,9 +66,9 @@
<Resizer class="flex flex-col justify-between border-l" side="right"> <Resizer class="flex flex-col justify-between border-l" side="right">
<div <div
class="flex h-10.5 cursor-copy items-center border-b px-5 py-2.5 text-lg font-medium text-ink-gray-9" class="flex h-10.5 cursor-copy items-center border-b px-5 py-2.5 text-lg font-medium text-ink-gray-9"
@click="copyToClipboard(lead.data.name)" @click="copyToClipboard(doc.name)"
> >
{{ __(lead.data.name) }} {{ __(doc.name) }}
</div> </div>
<FileUploader <FileUploader
@success="(file) => updateField('image', file.file_url)" @success="(file) => updateField('image', file.file_url)"
@ -89,17 +81,17 @@
size="3xl" size="3xl"
class="size-12" class="size-12"
:label="title" :label="title"
:image="lead.data.image" :image="doc.image"
/> />
<component <component
:is="lead.data.image ? Dropdown : 'div'" :is="doc.image ? Dropdown : 'div'"
v-bind=" v-bind="
lead.data.image doc.image
? { ? {
options: [ options: [
{ {
icon: 'upload', icon: 'upload',
label: lead.data.image label: doc.image
? __('Change image') ? __('Change image')
: __('Upload image'), : __('Upload image'),
onClick: openFileSelector, onClick: openFileSelector,
@ -127,7 +119,7 @@
</component> </component>
</div> </div>
<div class="flex flex-col gap-2.5 truncate"> <div class="flex flex-col gap-2.5 truncate">
<Tooltip :text="lead.data.lead_name || __('Set first name')"> <Tooltip :text="doc.lead_name || __('Set first name')">
<div class="truncate text-2xl font-medium text-ink-gray-9"> <div class="truncate text-2xl font-medium text-ink-gray-9">
{{ title }} {{ title }}
</div> </div>
@ -138,8 +130,8 @@
<Button <Button
@click=" @click="
() => () =>
lead.data.mobile_no doc.mobile_no
? makeCall(lead.data.mobile_no) ? makeCall(doc.mobile_no)
: toast.error(__('No phone number set')) : toast.error(__('No phone number set'))
" "
> >
@ -153,7 +145,7 @@
<div> <div>
<Button <Button
@click=" @click="
lead.data.email doc.email
? openEmailBox() ? openEmailBox()
: toast.error(__('No email set')) : toast.error(__('No email set'))
" "
@ -168,8 +160,8 @@
<div> <div>
<Button <Button
@click=" @click="
lead.data.website doc.website
? openWebsite(lead.data.website) ? openWebsite(doc.website)
: toast.error(__('No website set')) : toast.error(__('No website set'))
" "
> >
@ -191,7 +183,7 @@
<Tooltip :text="__('Delete')"> <Tooltip :text="__('Delete')">
<div> <div>
<Button <Button
@click="deleteLeadWithModal(lead.data.name)" @click="deleteLeadWithModal(doc.name)"
variant="subtle" variant="subtle"
theme="red" theme="red"
icon="trash-2" icon="trash-2"
@ -205,8 +197,8 @@
</template> </template>
</FileUploader> </FileUploader>
<SLASection <SLASection
v-if="lead.data.sla_status" v-if="doc.sla_status"
v-model="lead.data" v-model="doc"
@updateField="updateField" @updateField="updateField"
/> />
<div <div
@ -216,7 +208,7 @@
<SidePanelLayout <SidePanelLayout
:sections="sections.data" :sections="sections.data"
doctype="CRM Lead" doctype="CRM Lead"
:docname="lead.data.name" :docname="doc.name"
@reload="sections.reload" @reload="sections.reload"
@afterFieldChange="reloadAssignees" @afterFieldChange="reloadAssignees"
/> />
@ -231,13 +223,13 @@
<ConvertToDealModal <ConvertToDealModal
v-if="showConvertToDealModal" v-if="showConvertToDealModal"
v-model="showConvertToDealModal" v-model="showConvertToDealModal"
:lead="lead.data" :lead="doc"
/> />
<FilesUploader <FilesUploader
v-if="lead.data?.name" v-if="doc?.name"
v-model="showFilesUploader" v-model="showFilesUploader"
doctype="CRM Lead" doctype="CRM Lead"
:docname="lead.data.name" :docname="doc.name"
@after=" @after="
() => { () => {
activities?.all_activities?.reload() activities?.all_activities?.reload()
@ -249,7 +241,7 @@
v-if="showDeleteLinkedDocModal" v-if="showDeleteLinkedDocModal"
v-model="showDeleteLinkedDocModal" v-model="showDeleteLinkedDocModal"
:doctype="'CRM Lead'" :doctype="'CRM Lead'"
:docname="props.leadId" :docname="leadId"
name="Leads" name="Leads"
/> />
</template> </template>
@ -327,44 +319,58 @@ const errorMessage = ref('')
const showDeleteLinkedDocModal = ref(false) const showDeleteLinkedDocModal = ref(false)
const showConvertToDealModal = ref(false) const showConvertToDealModal = ref(false)
const { triggerOnChange, assignees, document } = useDocument( const { triggerOnChange, assignees, document, scripts, error } = useDocument(
'CRM Lead', 'CRM Lead',
props.leadId, props.leadId,
) )
const doc = computed(() => document.doc || {})
async function triggerStatusChange(value) { async function triggerStatusChange(value) {
await triggerOnChange('status', value) await triggerOnChange('status', value)
document.save.submit() document.save.submit()
} }
watch(error, (err) => {
if (err) {
errorTitle.value = __(
err.exc_type == 'DoesNotExistError'
? 'Document not found'
: 'Error occurred',
)
errorMessage.value = __(err.messages?.[0] || 'An error occurred')
} else {
errorTitle.value = ''
errorMessage.value = ''
}
})
watch(
() => document.doc,
async (_doc) => {
if (scripts.data?.length) {
let s = await setupCustomizations(scripts.data, {
doc: _doc,
$dialog,
$socket,
router,
toast,
updateField,
createToast: toast.create,
deleteDoc: deleteLead,
call,
})
document._actions = s.actions || []
document._statuses = s.statuses || []
}
},
{ once: true },
)
const lead = createResource({ const lead = createResource({
url: 'crm.fcrm.doctype.crm_lead.api.get_lead', url: 'crm.fcrm.doctype.crm_lead.api.get_lead',
params: { name: props.leadId }, params: { name: props.leadId },
cache: ['lead', props.leadId], cache: ['lead', props.leadId],
onSuccess: (data) => {
errorTitle.value = ''
errorMessage.value = ''
setupCustomizations(lead, {
doc: data,
$dialog,
$socket,
router,
toast,
updateField,
createToast: toast.create,
deleteDoc: deleteLead,
resource: { lead, sections },
call,
})
},
onError: (err) => {
if (err.messages?.[0]) {
errorTitle.value = __('Not permitted')
errorMessage.value = __(err.messages?.[0])
} else {
router.push({ name: 'Leads' })
}
},
}) })
onMounted(() => { onMounted(() => {
@ -402,7 +408,7 @@ function updateLead(fieldname, value, callback) {
} }
function validateRequired(fieldname, value) { function validateRequired(fieldname, value) {
let meta = lead.data.fields_meta || {} let meta = lead.data?.fields_meta || {}
if (meta[fieldname]?.reqd && !value) { if (meta[fieldname]?.reqd && !value) {
toast.error(__('{0} is a required field', [meta[fieldname].label])) toast.error(__('{0} is a required field', [meta[fieldname].label]))
return true return true
@ -430,14 +436,14 @@ const breadcrumbs = computed(() => {
items.push({ items.push({
label: title.value, label: title.value,
route: { name: 'Lead', params: { leadId: lead.data.name } }, route: { name: 'Lead', params: { leadId: props.leadId } },
}) })
return items return items
}) })
const title = computed(() => { const title = computed(() => {
let t = doctypeMeta['CRM Lead']?.title_field || 'name' let t = doctypeMeta['CRM Lead']?.title_field || 'name'
return lead.data?.[t] || props.leadId return doc?.[t] || props.leadId
}) })
usePageMeta(() => { usePageMeta(() => {
@ -521,7 +527,7 @@ const sections = createResource({
function updateField(name, value, callback) { function updateField(name, value, callback) {
updateLead(name, value, () => { updateLead(name, value, () => {
lead.data[name] = value doc[name] = value
callback?.() callback?.()
}) })
} }

View File

@ -278,25 +278,18 @@ async function getFormScript(script, obj) {
return formScript || {} return formScript || {}
} }
export async function setupCustomizations(doc, obj) { export async function setupCustomizations(scripts, obj) {
if (!doc.data?._form_script) return [] if (!scripts) return []
let statuses = [] let statuses = []
let actions = [] let actions = []
if (Array.isArray(doc.data._form_script)) { if (Array.isArray(scripts)) {
for (let script of doc.data._form_script) { for (let s of scripts) {
let _script = await getFormScript(script, obj) let _script = await getFormScript(s.script, obj)
actions = actions.concat(_script?.actions || []) actions = actions.concat(_script?.actions || [])
statuses = statuses.concat(_script?.statuses || []) statuses = statuses.concat(_script?.statuses || [])
} }
} else {
let _script = await getFormScript(doc.data._form_script, obj)
actions = _script?.actions || []
statuses = _script?.statuses || []
} }
doc.data._customStatuses = statuses
doc.data._customActions = actions
return { statuses, actions } return { statuses, actions }
} }