Merge pull request #1014 from frappe/mergify/bp/main-hotfix/pr-1013

This commit is contained in:
Shariq Ansari 2025-07-05 14:44:24 +05:30 committed by GitHub
commit 3c6bfa3dd0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 118 additions and 274 deletions

View File

@ -14,24 +14,16 @@ def update_deals_email_mobile_no(doc):
) )
for linked_deal in linked_deals: for linked_deal in linked_deals:
deal = frappe.get_cached_doc("CRM Deal", linked_deal.parent) deal = frappe.db.get_values("CRM Deal", linked_deal.parent, ["email", "mobile_no"], as_dict=True)[0]
if deal.email != doc.email_id or deal.mobile_no != doc.mobile_no: if deal.email != doc.email_id or deal.mobile_no != doc.mobile_no:
deal.email = doc.email_id frappe.db.set_value(
deal.mobile_no = doc.mobile_no "CRM Deal",
deal.save(ignore_permissions=True) linked_deal.parent,
{
"email": doc.email_id,
@frappe.whitelist() "mobile_no": doc.mobile_no,
def get_contact(name): },
contact = frappe.get_doc("Contact", name) )
contact.check_permission("read")
contact = contact.as_dict()
if not len(contact):
frappe.throw(_("Contact not found"), frappe.DoesNotExistError)
return contact
@frappe.whitelist() @frappe.whitelist()

View File

@ -1,6 +1,6 @@
import frappe import frappe
from crm.api.doc import get_assigned_users, get_fields_meta from crm.api.doc import get_fields_meta
from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script
@ -32,24 +32,12 @@ def get_deal_contacts(name):
is_primary = contact.is_primary is_primary = contact.is_primary
contact = frappe.get_doc("Contact", contact.contact).as_dict() contact = frappe.get_doc("Contact", contact.contact).as_dict()
def get_primary_email(contact):
for email in contact.email_ids:
if email.is_primary:
return email.email_id
return contact.email_ids[0].email_id if contact.email_ids else ""
def get_primary_mobile_no(contact):
for phone in contact.phone_nos:
if phone.is_primary:
return phone.phone
return contact.phone_nos[0].phone if contact.phone_nos else ""
_contact = { _contact = {
"name": contact.name, "name": contact.name,
"image": contact.image, "image": contact.image,
"full_name": contact.full_name, "full_name": contact.full_name,
"email": get_primary_email(contact), "email": contact.email_id,
"mobile_no": get_primary_mobile_no(contact), "mobile_no": contact.mobile_no,
"is_primary": is_primary, "is_primary": is_primary,
} }
deal_contacts.append(_contact) deal_contacts.append(_contact)

View File

@ -130,14 +130,16 @@
{ {
"fieldname": "email", "fieldname": "email",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Email", "label": "Primary Email",
"options": "Email" "options": "Email",
"read_only": 1
}, },
{ {
"fieldname": "mobile_no", "fieldname": "mobile_no",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Mobile No", "label": "Primary Mobile No",
"options": "Phone" "options": "Phone",
"read_only": 1
}, },
{ {
"default": "Qualification", "default": "Qualification",
@ -250,8 +252,9 @@
{ {
"fieldname": "phone", "fieldname": "phone",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Phone", "label": "Primary Phone",
"options": "Phone" "options": "Phone",
"read_only": 1
}, },
{ {
"fieldname": "log_tab", "fieldname": "log_tab",
@ -411,7 +414,7 @@
"grid_page_length": 50, "grid_page_length": 50,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2025-07-02 11:07:50.192089", "modified": "2025-07-05 12:25:05.927806",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "FCRM", "module": "FCRM",
"name": "CRM Deal", "name": "CRM Deal",

View File

@ -1,6 +1,6 @@
import frappe import frappe
from crm.api.doc import get_assigned_users, get_fields_meta from crm.api.doc import get_fields_meta
from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script

View File

@ -125,7 +125,7 @@
/> />
<div v-else> <div v-else>
<div <div
class="p-1.5 px-7 text-base text-ink-gray-4" class="p-1.5 pl-3 pr-4 text-base text-ink-gray-4"
> >
{{ {{
__('No {0} Available', [field.label]) __('No {0} Available', [field.label])

View File

@ -1,5 +1,5 @@
<template> <template>
<LayoutHeader v-if="contact.data"> <LayoutHeader v-if="contact.doc">
<template #left-header> <template #left-header>
<Breadcrumbs :items="breadcrumbs"> <Breadcrumbs :items="breadcrumbs">
<template #prefix="{ item }"> <template #prefix="{ item }">
@ -8,9 +8,9 @@
</Breadcrumbs> </Breadcrumbs>
</template> </template>
</LayoutHeader> </LayoutHeader>
<div v-if="contact.data" ref="parentRef" class="flex h-full"> <div v-if="contact.doc" ref="parentRef" class="flex h-full">
<Resizer <Resizer
v-if="contact.data" v-if="contact.doc"
:parent="$refs.parentRef" :parent="$refs.parentRef"
class="flex h-full flex-col overflow-hidden border-r" class="flex h-full flex-col overflow-hidden border-r"
> >
@ -26,18 +26,18 @@
<Avatar <Avatar
size="3xl" size="3xl"
class="h-15.5 w-15.5" class="h-15.5 w-15.5"
:label="contact.data.full_name" :label="contact.doc.full_name"
:image="contact.data.image" :image="contact.doc.image"
/> />
<component <component
:is="contact.data.image ? Dropdown : 'div'" :is="contact.doc.image ? Dropdown : 'div'"
v-bind=" v-bind="
contact.data.image contact.doc.image
? { ? {
options: [ options: [
{ {
icon: 'upload', icon: 'upload',
label: contact.data.image label: contact.doc.image
? __('Change image') ? __('Change image')
: __('Upload image'), : __('Upload image'),
onClick: openFileSelector, onClick: openFileSelector,
@ -66,36 +66,34 @@
</div> </div>
<div class="flex flex-col gap-2 truncate text-ink-gray-9"> <div class="flex flex-col gap-2 truncate text-ink-gray-9">
<div class="truncate text-2xl font-medium"> <div class="truncate text-2xl font-medium">
<span v-if="contact.data.salutation"> <span v-if="contact.doc.salutation">
{{ contact.data.salutation + '. ' }} {{ contact.doc.salutation + '. ' }}
</span> </span>
<span>{{ contact.data.full_name }}</span> <span>{{ contact.doc.full_name }}</span>
</div> </div>
<div <div
v-if="contact.data.company_name" v-if="contact.doc.company_name"
class="flex items-center gap-1.5 text-base text-ink-gray-8" class="flex items-center gap-1.5 text-base text-ink-gray-8"
> >
<Avatar <Avatar
size="xs" size="xs"
:label="contact.data.company_name" :label="contact.doc.company_name"
:image=" :image="
getOrganization(contact.data.company_name) getOrganization(contact.doc.company_name)
?.organization_logo ?.organization_logo
" "
/> />
<span class="">{{ contact.data.company_name }}</span> <span class="">{{ contact.doc.company_name }}</span>
</div> </div>
<ErrorMessage :message="__(error)" /> <ErrorMessage :message="__(error)" />
</div> </div>
</div> </div>
<div class="flex gap-1.5"> <div class="flex gap-1.5">
<Button <Button
v-if="contact.data.actual_mobile_no" v-if="callEnabled && contact.doc.mobile_no"
:label="__('Make Call')" :label="__('Make Call')"
size="sm" size="sm"
@click=" @click="callEnabled && makeCall(contact.doc.mobile_no)"
callEnabled && makeCall(contact.data.actual_mobile_no)
"
> >
<template #prefix> <template #prefix>
<PhoneIcon class="h-4 w-4" /> <PhoneIcon class="h-4 w-4" />
@ -105,12 +103,9 @@
:label="__('Delete')" :label="__('Delete')"
theme="red" theme="red"
size="sm" size="sm"
icon-left="trash-2"
@click="deleteContact()" @click="deleteContact()"
> />
<template #prefix>
<FeatherIcon name="trash-2" class="h-4 w-4" />
</template>
</Button>
</div> </div>
</div> </div>
</template> </template>
@ -123,7 +118,7 @@
<SidePanelLayout <SidePanelLayout
:sections="sections.data" :sections="sections.data"
doctype="Contact" doctype="Contact"
:docname="contact.data.name" :docname="contact.doc.name"
@reload="sections.reload" @reload="sections.reload"
/> />
</div> </div>
@ -176,7 +171,7 @@
v-if="showDeleteLinkedDocModal" v-if="showDeleteLinkedDocModal"
v-model="showDeleteLinkedDocModal" v-model="showDeleteLinkedDocModal"
:doctype="'Contact'" :doctype="'Contact'"
:docname="contact.data.name" :docname="contact.doc.name"
name="Contacts" name="Contacts"
/> />
</template> </template>
@ -192,14 +187,15 @@ import CameraIcon from '@/components/Icons/CameraIcon.vue'
import DealsIcon from '@/components/Icons/DealsIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue'
import DealsListView from '@/components/ListViews/DealsListView.vue' import DealsListView from '@/components/ListViews/DealsListView.vue'
import { formatDate, timeAgo, validateIsImageFile } from '@/utils' import { formatDate, timeAgo, validateIsImageFile } from '@/utils'
import { showAddressModal, addressProps } from '@/composables/modals'
import { getView } from '@/utils/view' import { getView } from '@/utils/view'
import { useDocument } from '@/data/document'
import { getSettings } from '@/stores/settings' import { getSettings } from '@/stores/settings'
import { getMeta } from '@/stores/meta' import { getMeta } from '@/stores/meta'
import { globalStore } from '@/stores/global.js' import { globalStore } from '@/stores/global.js'
import { usersStore } from '@/stores/users.js' import { usersStore } from '@/stores/users.js'
import { organizationsStore } from '@/stores/organizations.js' import { organizationsStore } from '@/stores/organizations.js'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { showAddressModal, addressProps } from '@/composables/modals'
import { callEnabled } from '@/composables/settings' import { callEnabled } from '@/composables/settings'
import { import {
Breadcrumbs, Breadcrumbs,
@ -213,10 +209,10 @@ import {
toast, toast,
} from 'frappe-ui' } from 'frappe-ui'
import { ref, computed, h } from 'vue' import { ref, computed, h } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute } from 'vue-router'
const { brand } = getSettings() const { brand } = getSettings()
const { $dialog, makeCall } = globalStore() const { makeCall } = globalStore()
const { getUser } = usersStore() const { getUser } = usersStore()
const { getOrganization } = organizationsStore() const { getOrganization } = organizationsStore()
@ -231,38 +227,11 @@ const props = defineProps({
}) })
const route = useRoute() const route = useRoute()
const router = useRouter()
const _contact = ref({})
const errorTitle = ref('') const errorTitle = ref('')
const errorMessage = ref('') const errorMessage = ref('')
const contact = createResource({ const { document: contact } = useDocument('Contact', props.contactId)
url: 'crm.api.contact.get_contact',
cache: ['contact', props.contactId],
params: { name: props.contactId },
auto: true,
transform: (data) => {
return {
...data,
actual_mobile_no: data.mobile_no,
mobile_no: data.mobile_no,
}
},
onSuccess: () => {
errorTitle.value = ''
errorMessage.value = ''
},
onError: (err) => {
if (err.messages?.[0]) {
errorTitle.value = __('Not permitted')
errorMessage.value = __(err.messages?.[0])
} else {
router.push({ name: 'Contacts' })
}
},
})
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }] let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }]
@ -291,7 +260,7 @@ const breadcrumbs = computed(() => {
const title = computed(() => { const title = computed(() => {
let t = doctypeMeta['Contact']?.title_field || 'name' let t = doctypeMeta['Contact']?.title_field || 'name'
return contact.data?.[t] || props.contactId return contact.doc?.[t] || props.contactId
}) })
usePageMeta(() => { usePageMeta(() => {
@ -306,14 +275,13 @@ async function deleteContact() {
showDeleteLinkedDocModal.value = true showDeleteLinkedDocModal.value = true
} }
async function changeContactImage(file) { function changeContactImage(file) {
await call('frappe.client.set_value', { contact.doc.image = file?.file_url || ''
doctype: 'Contact', contact.save.submit(null, {
name: props.contactId, onSuccess: () => {
fieldname: 'image', toast.success(__('Contact image updated'))
value: file?.file_url || '', },
}) })
contact.reload()
} }
const tabIndex = ref(0) const tabIndex = ref(0)
@ -358,22 +326,18 @@ function getParsedSections(_sections) {
read_only: false, read_only: false,
fieldtype: 'Dropdown', fieldtype: 'Dropdown',
options: options:
contact.data?.email_ids?.map((email) => { contact.doc?.email_ids?.map((email) => {
return { return {
name: email.name, name: email.name,
value: email.email_id, value: email.email_id,
selected: email.email_id === contact.data.email_id, selected: email.email_id === contact.doc.email_id,
placeholder: 'john@doe.com', placeholder: 'john@doe.com',
onClick: () => { onClick: () => {
_contact.value.email_id = email.email_id
setAsPrimary('email', email.email_id) setAsPrimary('email', email.email_id)
}, },
onSave: (option, isNew) => { onSave: (option, isNew) => {
if (isNew) { if (isNew) {
createNew('email', option.value) createNew('email', option.value)
if (contact.data.email_ids.length === 1) {
_contact.value.email_id = option.value
}
} else { } else {
editOption( editOption(
'Contact Email', 'Contact Email',
@ -384,24 +348,15 @@ function getParsedSections(_sections) {
} }
}, },
onDelete: async (option, isNew) => { onDelete: async (option, isNew) => {
contact.data.email_ids = contact.data.email_ids.filter( contact.doc.email_ids = contact.doc.email_ids.filter(
(email) => email.name !== option.name (email) => email.name !== option.name,
) )
!isNew && (await deleteOption('Contact Email', option.name)) !isNew && (await deleteOption('Contact Email', option.name))
if (_contact.value.email_id === option.value) {
if (contact.data.email_ids.length === 0) {
_contact.value.email_id = ''
} else {
_contact.value.email_id = contact.data.email_ids.find(
(email) => email.is_primary
)?.email_id
}
}
}, },
} }
}) || [], }) || [],
create: () => { create: () => {
contact.data?.email_ids?.push({ contact.doc?.email_ids?.push({
name: 'new-1', name: 'new-1',
value: '', value: '',
selected: false, selected: false,
@ -415,22 +370,17 @@ function getParsedSections(_sections) {
read_only: false, read_only: false,
fieldtype: 'Dropdown', fieldtype: 'Dropdown',
options: options:
contact.data?.phone_nos?.map((phone) => { contact.doc?.phone_nos?.map((phone) => {
return { return {
name: phone.name, name: phone.name,
value: phone.phone, value: phone.phone,
selected: phone.phone === contact.data.actual_mobile_no, selected: phone.phone === contact.doc.mobile_no,
onClick: () => { onClick: () => {
_contact.value.actual_mobile_no = phone.phone
_contact.value.mobile_no = phone.phone
setAsPrimary('mobile_no', phone.phone) setAsPrimary('mobile_no', phone.phone)
}, },
onSave: (option, isNew) => { onSave: (option, isNew) => {
if (isNew) { if (isNew) {
createNew('phone', option.value) createNew('phone', option.value)
if (contact.data.phone_nos.length === 1) {
_contact.value.actual_mobile_no = option.value
}
} else { } else {
editOption( editOption(
'Contact Phone', 'Contact Phone',
@ -441,25 +391,15 @@ function getParsedSections(_sections) {
} }
}, },
onDelete: async (option, isNew) => { onDelete: async (option, isNew) => {
contact.data.phone_nos = contact.data.phone_nos.filter( contact.doc.phone_nos = contact.doc.phone_nos.filter(
(phone) => phone.name !== option.name (phone) => phone.name !== option.name,
) )
!isNew && (await deleteOption('Contact Phone', option.name)) !isNew && (await deleteOption('Contact Phone', option.name))
if (_contact.value.actual_mobile_no === option.value) {
if (contact.data.phone_nos.length === 0) {
_contact.value.actual_mobile_no = ''
} else {
_contact.value.actual_mobile_no =
contact.data.phone_nos.find(
(phone) => phone.is_primary_mobile_no
)?.phone
}
}
}, },
} }
}) || [], }) || [],
create: () => { create: () => {
contact.data?.phone_nos?.push({ contact.doc?.phone_nos?.push({
name: 'new-1', name: 'new-1',
value: '', value: '',
selected: false, selected: false,
@ -471,7 +411,6 @@ function getParsedSections(_sections) {
return { return {
...field, ...field,
create: (value, close) => { create: (value, close) => {
_contact.value.address = value
openAddressModal() openAddressModal()
close() close()
}, },
@ -489,7 +428,7 @@ function getParsedSections(_sections) {
async function setAsPrimary(field, value) { async function setAsPrimary(field, value) {
let d = await call('crm.api.contact.set_as_primary', { let d = await call('crm.api.contact.set_as_primary', {
contact: contact.data.name, contact: contact.doc.name,
field, field,
value, value,
}) })
@ -502,7 +441,7 @@ async function setAsPrimary(field, value) {
async function createNew(field, value) { async function createNew(field, value) {
if (!value) return if (!value) return
let d = await call('crm.api.contact.create_new', { let d = await call('crm.api.contact.create_new', {
contact: contact.data.name, contact: contact.doc.name,
field, field,
value, value,
}) })

View File

@ -1,5 +1,5 @@
<template> <template>
<LayoutHeader v-if="contact.data"> <LayoutHeader v-if="contact.doc">
<header <header
class="relative flex h-10.5 items-center justify-between gap-2 py-2.5 pl-2" class="relative flex h-10.5 items-center justify-between gap-2 py-2.5 pl-2"
> >
@ -10,7 +10,7 @@
</Breadcrumbs> </Breadcrumbs>
</header> </header>
</LayoutHeader> </LayoutHeader>
<div v-if="contact.data" class="flex flex-col h-full overflow-hidden"> <div v-if="contact.doc" class="flex flex-col h-full overflow-hidden">
<FileUploader <FileUploader
@success="changeContactImage" @success="changeContactImage"
:validateFile="validateIsImageFile" :validateFile="validateIsImageFile"
@ -22,18 +22,18 @@
<Avatar <Avatar
size="3xl" size="3xl"
class="h-14.5 w-14.5" class="h-14.5 w-14.5"
:label="contact.data.full_name" :label="contact.doc.full_name"
:image="contact.data.image" :image="contact.doc.image"
/> />
<component <component
:is="contact.data.image ? Dropdown : 'div'" :is="contact.doc.image ? Dropdown : 'div'"
v-bind=" v-bind="
contact.data.image contact.doc.image
? { ? {
options: [ options: [
{ {
icon: 'upload', icon: 'upload',
label: contact.data.image label: contact.doc.image
? __('Change image') ? __('Change image')
: __('Upload image'), : __('Upload image'),
onClick: openFileSelector, onClick: openFileSelector,
@ -62,19 +62,17 @@
</div> </div>
<div class="flex flex-col gap-2 truncate"> <div class="flex flex-col gap-2 truncate">
<div class="truncate text-lg font-medium text-ink-gray-9"> <div class="truncate text-lg font-medium text-ink-gray-9">
<span v-if="contact.data.salutation"> <span v-if="contact.doc.salutation">
{{ contact.data.salutation + '. ' }} {{ contact.doc.salutation + '. ' }}
</span> </span>
<span>{{ contact.data.full_name }}</span> <span>{{ contact.doc.full_name }}</span>
</div> </div>
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
<Button <Button
v-if="contact.data.actual_mobile_no" v-if="callEnabled && contact.doc.mobile_no"
:label="__('Make Call')" :label="__('Make Call')"
size="sm" size="sm"
@click=" @click="callEnabled && makeCall(contact.doc.mobile_no)"
callEnabled && makeCall(contact.data.actual_mobile_no)
"
> >
<template #prefix> <template #prefix>
<PhoneIcon class="h-4 w-4" /> <PhoneIcon class="h-4 w-4" />
@ -84,19 +82,15 @@
:label="__('Delete')" :label="__('Delete')"
theme="red" theme="red"
size="sm" size="sm"
icon-left="trash-2"
@click="deleteContact" @click="deleteContact"
> />
<template #prefix>
<FeatherIcon name="trash-2" class="h-4 w-4" />
</template>
</Button>
<Avatar <Avatar
v-if="contact.data.company_name" v-if="contact.doc.company_name"
size="md" size="md"
:label="contact.data.company_name" :label="contact.doc.company_name"
:image=" :image="
getOrganization(contact.data.company_name) getOrganization(contact.doc.company_name)?.organization_logo
?.organization_logo
" "
/> />
</div> </div>
@ -135,7 +129,7 @@
<SidePanelLayout <SidePanelLayout
:sections="sections.data" :sections="sections.data"
doctype="Contact" doctype="Contact"
:docname="contact.data.name" :docname="contact.doc.name"
@reload="sections.reload" @reload="sections.reload"
/> />
</div> </div>
@ -172,13 +166,14 @@ import DealsIcon from '@/components/Icons/DealsIcon.vue'
import DealsListView from '@/components/ListViews/DealsListView.vue' import DealsListView from '@/components/ListViews/DealsListView.vue'
import { formatDate, timeAgo, validateIsImageFile } from '@/utils' import { formatDate, timeAgo, validateIsImageFile } from '@/utils'
import { getView } from '@/utils/view' import { getView } from '@/utils/view'
import { showAddressModal, addressProps } from '@/composables/modals' import { useDocument } from '@/data/document'
import { getSettings } from '@/stores/settings' import { getSettings } from '@/stores/settings'
import { getMeta } from '@/stores/meta' import { getMeta } from '@/stores/meta'
import { globalStore } from '@/stores/global.js' import { globalStore } from '@/stores/global.js'
import { usersStore } from '@/stores/users.js' import { usersStore } from '@/stores/users.js'
import { organizationsStore } from '@/stores/organizations.js' import { organizationsStore } from '@/stores/organizations.js'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { showAddressModal, addressProps } from '@/composables/modals'
import { callEnabled } from '@/composables/settings' import { callEnabled } from '@/composables/settings'
import { import {
Breadcrumbs, Breadcrumbs,
@ -214,23 +209,7 @@ const props = defineProps({
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const _contact = ref({}) const { document: contact } = useDocument('Contact', props.contactId)
const contact = createResource({
url: 'crm.api.contact.get_contact',
cache: ['contact', props.contactId],
params: {
name: props.contactId,
},
auto: true,
transform: (data) => {
return {
...data,
actual_mobile_no: data.mobile_no,
mobile_no: data.mobile_no,
}
},
})
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }] let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }]
@ -259,7 +238,7 @@ const breadcrumbs = computed(() => {
const title = computed(() => { const title = computed(() => {
let t = doctypeMeta['Contact']?.title_field || 'name' let t = doctypeMeta['Contact']?.title_field || 'name'
return contact.data?.[t] || props.contactId return contact.doc?.[t] || props.contactId
}) })
usePageMeta(() => { usePageMeta(() => {
@ -269,14 +248,13 @@ usePageMeta(() => {
} }
}) })
async function changeContactImage(file) { function changeContactImage(file) {
await call('frappe.client.set_value', { contact.doc.image = file?.file_url || ''
doctype: 'Contact', contact.save.submit(null, {
name: props.contactId, onSuccess: () => {
fieldname: 'image', toast.success(__('Contact image updated'))
value: file?.file_url || '', },
}) })
contact.reload()
} }
async function deleteContact() { async function deleteContact() {
@ -346,22 +324,18 @@ function getParsedSections(_sections) {
...field, ...field,
type: 'dropdown', type: 'dropdown',
options: options:
contact.data?.email_ids?.map((email) => { contact.doc?.email_ids?.map((email) => {
return { return {
name: email.name, name: email.name,
value: email.email_id, value: email.email_id,
selected: email.email_id === contact.data.email_id, selected: email.email_id === contact.doc.email_id,
placeholder: 'john@doe.com', placeholder: 'john@doe.com',
onClick: () => { onClick: () => {
_contact.value.email_id = email.email_id
setAsPrimary('email', email.email_id) setAsPrimary('email', email.email_id)
}, },
onSave: (option, isNew) => { onSave: (option, isNew) => {
if (isNew) { if (isNew) {
createNew('email', option.value) createNew('email', option.value)
if (contact.data.email_ids.length === 1) {
_contact.value.email_id = option.value
}
} else { } else {
editOption( editOption(
'Contact Email', 'Contact Email',
@ -372,24 +346,15 @@ function getParsedSections(_sections) {
} }
}, },
onDelete: async (option, isNew) => { onDelete: async (option, isNew) => {
contact.data.email_ids = contact.data.email_ids.filter( contact.doc.email_ids = contact.doc.email_ids.filter(
(email) => email.name !== option.name, (email) => email.name !== option.name,
) )
!isNew && (await deleteOption('Contact Email', option.name)) !isNew && (await deleteOption('Contact Email', option.name))
if (_contact.value.email_id === option.value) {
if (contact.data.email_ids.length === 0) {
_contact.value.email_id = ''
} else {
_contact.value.email_id = contact.data.email_ids.find(
(email) => email.is_primary,
)?.email_id
}
}
}, },
} }
}) || [], }) || [],
create: () => { create: () => {
contact.data?.email_ids?.push({ contact.doc?.email_ids?.push({
name: 'new-1', name: 'new-1',
value: '', value: '',
selected: false, selected: false,
@ -403,22 +368,17 @@ function getParsedSections(_sections) {
read_only: false, read_only: false,
fieldtype: 'dropdown', fieldtype: 'dropdown',
options: options:
contact.data?.phone_nos?.map((phone) => { contact.doc?.phone_nos?.map((phone) => {
return { return {
name: phone.name, name: phone.name,
value: phone.phone, value: phone.phone,
selected: phone.phone === contact.data.actual_mobile_no, selected: phone.phone === contact.doc.mobile_no,
onClick: () => { onClick: () => {
_contact.value.actual_mobile_no = phone.phone
_contact.value.mobile_no = phone.phone
setAsPrimary('mobile_no', phone.phone) setAsPrimary('mobile_no', phone.phone)
}, },
onSave: (option, isNew) => { onSave: (option, isNew) => {
if (isNew) { if (isNew) {
createNew('phone', option.value) createNew('phone', option.value)
if (contact.data.phone_nos.length === 1) {
_contact.value.actual_mobile_no = option.value
}
} else { } else {
editOption( editOption(
'Contact Phone', 'Contact Phone',
@ -429,25 +389,15 @@ function getParsedSections(_sections) {
} }
}, },
onDelete: async (option, isNew) => { onDelete: async (option, isNew) => {
contact.data.phone_nos = contact.data.phone_nos.filter( contact.doc.phone_nos = contact.doc.phone_nos.filter(
(phone) => phone.name !== option.name, (phone) => phone.name !== option.name,
) )
!isNew && (await deleteOption('Contact Phone', option.name)) !isNew && (await deleteOption('Contact Phone', option.name))
if (_contact.value.actual_mobile_no === option.value) {
if (contact.data.phone_nos.length === 0) {
_contact.value.actual_mobile_no = ''
} else {
_contact.value.actual_mobile_no =
contact.data.phone_nos.find(
(phone) => phone.is_primary_mobile_no,
)?.phone
}
}
}, },
} }
}) || [], }) || [],
create: () => { create: () => {
contact.data?.phone_nos?.push({ contact.doc?.phone_nos?.push({
name: 'new-1', name: 'new-1',
value: '', value: '',
selected: false, selected: false,
@ -459,7 +409,6 @@ function getParsedSections(_sections) {
return { return {
...field, ...field,
create: (value, close) => { create: (value, close) => {
_contact.value.address = value
openAddressModal() openAddressModal()
close() close()
}, },
@ -477,7 +426,7 @@ function getParsedSections(_sections) {
async function setAsPrimary(field, value) { async function setAsPrimary(field, value) {
let d = await call('crm.api.contact.set_as_primary', { let d = await call('crm.api.contact.set_as_primary', {
contact: contact.data.name, contact: contact.doc.name,
field, field,
value, value,
}) })
@ -490,7 +439,7 @@ async function setAsPrimary(field, value) {
async function createNew(field, value) { async function createNew(field, value) {
if (!value) return if (!value) return
let d = await call('crm.api.contact.create_new', { let d = await call('crm.api.contact.create_new', {
contact: contact.data.name, contact: contact.doc.name,
field, field,
value, value,
}) })

View File

@ -158,6 +158,7 @@ import CameraIcon from '@/components/Icons/CameraIcon.vue'
import DealsIcon from '@/components/Icons/DealsIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue'
import ContactsIcon from '@/components/Icons/ContactsIcon.vue' import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
import { showAddressModal, addressProps } from '@/composables/modals' import { showAddressModal, addressProps } from '@/composables/modals'
import { useDocument } from '@/data/document'
import { getSettings } from '@/stores/settings' import { getSettings } from '@/stores/settings'
import { getMeta } from '@/stores/meta' import { getMeta } from '@/stores/meta'
import { globalStore } from '@/stores/global' import { globalStore } from '@/stores/global'
@ -175,7 +176,6 @@ import {
TabPanel, TabPanel,
call, call,
createListResource, createListResource,
createDocumentResource,
usePageMeta, usePageMeta,
createResource, createResource,
toast, toast,
@ -199,13 +199,10 @@ const { doctypeMeta } = getMeta('CRM Organization')
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const organization = createDocumentResource({ const { document: organization } = useDocument(
doctype: 'CRM Organization', 'CRM Organization',
name: props.organizationId, props.organizationId,
cache: ['organization', props.organizationId], )
fields: ['*'],
auto: true,
})
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }] let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }]
@ -288,8 +285,6 @@ function openWebsite() {
else window.open(organization.doc.website, '_blank') else window.open(organization.doc.website, '_blank')
} }
const _organization = ref({})
const sections = createResource({ const sections = createResource({
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections', url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'CRM Organization'], cache: ['sidePanelSections', 'CRM Organization'],
@ -306,7 +301,6 @@ function getParsedSections(_sections) {
return { return {
...field, ...field,
create: (value, close) => { create: (value, close) => {
_organization.value.address = value
openAddressModal() openAddressModal()
close() close()
}, },

View File

@ -186,9 +186,9 @@ import CameraIcon from '@/components/Icons/CameraIcon.vue'
import DealsIcon from '@/components/Icons/DealsIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue'
import ContactsIcon from '@/components/Icons/ContactsIcon.vue' import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
import { showAddressModal, addressProps } from '@/composables/modals' import { showAddressModal, addressProps } from '@/composables/modals'
import { useDocument } from '@/data/document'
import { getSettings } from '@/stores/settings' import { getSettings } from '@/stores/settings'
import { getMeta } from '@/stores/meta' import { getMeta } from '@/stores/meta'
import { globalStore } from '@/stores/global'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { statusesStore } from '@/stores/statuses' import { statusesStore } from '@/stores/statuses'
import { getView } from '@/utils/view' import { getView } from '@/utils/view'
@ -202,13 +202,12 @@ import {
Tabs, Tabs,
call, call,
createListResource, createListResource,
createDocumentResource,
usePageMeta, usePageMeta,
createResource, createResource,
toast, toast,
} from 'frappe-ui' } from 'frappe-ui'
import { h, computed, ref } from 'vue' import { h, computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute } from 'vue-router'
import DeleteLinkedDocModal from '@/components/DeleteLinkedDocModal.vue' import DeleteLinkedDocModal from '@/components/DeleteLinkedDocModal.vue'
const props = defineProps({ const props = defineProps({
@ -220,37 +219,20 @@ const props = defineProps({
const { brand } = getSettings() const { brand } = getSettings()
const { getUser } = usersStore() const { getUser } = usersStore()
const { $dialog } = globalStore()
const { getDealStatus } = statusesStore() const { getDealStatus } = statusesStore()
const { doctypeMeta } = getMeta('CRM Organization') const { doctypeMeta } = getMeta('CRM Organization')
const route = useRoute() const route = useRoute()
const router = useRouter()
const errorTitle = ref('') const errorTitle = ref('')
const errorMessage = ref('') const errorMessage = ref('')
const showDeleteLinkedDocModal = ref(false) const showDeleteLinkedDocModal = ref(false)
const organization = createDocumentResource({ const { document: organization } = useDocument(
doctype: 'CRM Organization', 'CRM Organization',
name: props.organizationId, props.organizationId,
cache: ['organization', props.organizationId], )
fields: ['*'],
auto: true,
onSuccess: () => {
errorTitle.value = ''
errorMessage.value = ''
},
onError: (err) => {
if (err.messages?.[0]) {
errorTitle.value = __('Not permitted')
errorMessage.value = __(err.messages?.[0])
} else {
router.push({ name: 'Organizations' })
}
},
})
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }] let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }]
@ -319,8 +301,6 @@ function openWebsite() {
else window.open(organization.doc.website, '_blank') else window.open(organization.doc.website, '_blank')
} }
const _organization = ref({})
const sections = createResource({ const sections = createResource({
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections', url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_sidepanel_sections',
cache: ['sidePanelSections', 'CRM Organization'], cache: ['sidePanelSections', 'CRM Organization'],
@ -337,7 +317,6 @@ function getParsedSections(_sections) {
return { return {
...field, ...field,
create: (value, close) => { create: (value, close) => {
_organization.value.address = value
openAddressModal() openAddressModal()
close() close()
}, },