1
0
forked from test/crm

fix: get contact detail and if is lead or deal and show on call popup window

This commit is contained in:
Shariq Ansari 2025-01-17 20:57:58 +05:30
parent 7301e8d62c
commit f9c97ce51b
3 changed files with 169 additions and 47 deletions

View File

@ -1,4 +1,8 @@
import frappe import frappe
from frappe.query_builder import Order
from pypika.functions import Replace
from crm.utils import are_same_phone_number, parse_phone_number
@frappe.whitelist() @frappe.whitelist()
@ -46,3 +50,76 @@ def create_and_add_task_to_call_log(call_sid, task):
call_log = frappe.get_doc("CRM Call Log", call_sid) call_log = frappe.get_doc("CRM Call Log", call_sid)
call_log.link_with_reference_doc("CRM Task", _task.name) call_log.link_with_reference_doc("CRM Task", _task.name)
@frappe.whitelist()
def get_contact_by_phone_number(phone_number):
"""Get contact by phone number."""
number = parse_phone_number(phone_number)
if number.get("is_valid"):
return get_contact(number.get("national_number"))
else:
return get_contact(phone_number, exact_match=True)
def get_contact(phone_number, exact_match=False):
cleaned_number = (
phone_number.strip()
.replace(" ", "")
.replace("-", "")
.replace("(", "")
.replace(")", "")
.replace("+", "")
)
# Check if the number is associated with a contact
Contact = frappe.qb.DocType("Contact")
normalized_phone = Replace(
Replace(Replace(Replace(Replace(Contact.mobile_no, " ", ""), "-", ""), "(", ""), ")", ""), "+", ""
)
query = (
frappe.qb.from_(Contact)
.select(Contact.name, Contact.full_name, Contact.image, Contact.mobile_no)
.where(normalized_phone.like(f"%{cleaned_number}%"))
.orderby("modified", order=Order.desc)
)
contacts = query.run(as_dict=True)
if len(contacts):
# Check if the contact is associated with a deal
for contact in contacts:
if frappe.db.exists("CRM Contacts", {"contact": contact.name, "is_primary": 1}):
deal = frappe.db.get_value(
"CRM Contacts", {"contact": contact.name, "is_primary": 1}, "parent"
)
if are_same_phone_number(contact.mobile_no, phone_number, validate=not exact_match):
contact["deal"] = deal
return contact
# Else, return the first contact
if are_same_phone_number(contacts[0].mobile_no, phone_number, validate=not exact_match):
return contacts[0]
# Else, Check if the number is associated with a lead
Lead = frappe.qb.DocType("CRM Lead")
normalized_phone = Replace(
Replace(Replace(Replace(Replace(Lead.mobile_no, " ", ""), "-", ""), "(", ""), ")", ""), "+", ""
)
query = (
frappe.qb.from_(Lead)
.select(Lead.name, Lead.lead_name, Lead.image, Lead.mobile_no)
.where(Lead.converted == 0)
.where(normalized_phone.like(f"%{cleaned_number}%"))
.orderby("modified", order=Order.desc)
)
leads = query.run(as_dict=True)
if len(leads):
for lead in leads:
if are_same_phone_number(lead.mobile_no, phone_number, validate=not exact_match):
lead["lead"] = lead.name
return lead
return {"mobile_no": phone_number}

View File

@ -198,6 +198,7 @@ const props = defineProps({
}) })
const emit = defineEmits([ const emit = defineEmits([
'showCallLog',
'loadMore', 'loadMore',
'updatePageCount', 'updatePageCount',
'columnWidthUpdated', 'columnWidthUpdated',

View File

@ -48,21 +48,26 @@
class="header flex items-center justify-between gap-1 text-base cursor-move select-none" class="header flex items-center justify-between gap-1 text-base cursor-move select-none"
> >
<div class="flex gap-2 items-center truncate"> <div class="flex gap-2 items-center truncate">
<div v-if="showNote || showTask" class="flex items-center gap-3"> <div
v-if="showNote || showTask"
class="flex items-center gap-3 truncate"
>
<Avatar <Avatar
v-if="contact?.image" v-if="contact?.image"
:image="contact.image" :image="contact.image"
:label="contact.full_name" :label="contact.full_name"
class="!size-7" class="!size-7 shrink-0"
/> />
<div <div
v-else v-else
class="flex justify-center items-center size-7 rounded-full bg-surface-gray-6" class="flex justify-center items-center size-7 rounded-full bg-surface-gray-6 shrink-0"
> >
<AvatarIcon class="size-3" /> <AvatarIcon class="size-3" />
</div> </div>
<div class="flex flex-col gap-1 text-base leading-4"> <div
<div class="font-medium"> class="flex flex-col gap-1 text-base leading-4 overflow-hidden"
>
<div class="font-medium truncate">
{{ contact?.full_name ?? phoneNumber }} {{ contact?.full_name ?? phoneNumber }}
</div> </div>
<div class="text-ink-gray-6"> <div class="text-ink-gray-6">
@ -115,15 +120,24 @@
</div> </div>
</div> </div>
<Button <div class="flex">
@click="toggleCallPopup" <Button
class="bg-surface-gray-7 text-ink-white hover:bg-surface-gray-6 shrink-0" @click="toggleCallPopup"
size="md" class="bg-surface-gray-7 text-ink-white hover:bg-surface-gray-6 shrink-0"
> size="md"
<template #icon> >
<MinimizeIcon class="h-4 w-4 cursor-pointer" /> <template #icon>
</template> <MinimizeIcon class="h-4 w-4 cursor-pointer" />
</Button> </template>
</Button>
<Button
v-if="callStatus == 'Call ended' || callStatus == 'No answer'"
@click="closeCallPopup"
class="bg-surface-gray-7 text-ink-white hover:bg-surface-gray-6 shrink-0"
icon="x"
size="md"
/>
</div>
</div> </div>
<div class="body flex-1"> <div class="body flex-1">
<div v-if="showNote"> <div v-if="showNote">
@ -184,43 +198,45 @@
<TaskIcon class="w-4 h-4" /> <TaskIcon class="w-4 h-4" />
</template> </template>
</Button> </Button>
</div>
<div class="flex gap-2">
<Button <Button
v-if="callStatus == 'Call ended' || callStatus == 'No answer'" v-if="contact.deal || contact.lead"
@click="closeCallPopup"
class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5" class="bg-surface-gray-6 text-ink-white hover:bg-surface-gray-5"
:label="__('Close')"
size="md" size="md"
/> :label="contact.deal ? __('Deal') : __('Lead')"
<Button @click="openDealOrLead"
v-if="(note && note != '<p></p>') || task.title" >
@click="save" <template #suffix>
class="bg-surface-white !text-ink-gray-9 hover:!bg-surface-gray-3" <ArrowUpRightIcon class="w-4 h-4" />
variant="solid" </template>
:label="__('Save')" </Button>
size="md"
/>
</div> </div>
<Button
v-if="(note && note != '<p></p>') || task.title"
@click="save"
class="bg-surface-white !text-ink-gray-9 hover:!bg-surface-gray-3"
variant="solid"
:label="__('Save')"
size="md"
/>
</div> </div>
</div> </div>
<CountUpTimer ref="counterUp" /> <CountUpTimer ref="counterUp" />
</div> </div>
</template> </template>
<script setup> <script setup>
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
import AvatarIcon from '@/components/Icons/AvatarIcon.vue' import AvatarIcon from '@/components/Icons/AvatarIcon.vue'
import MinimizeIcon from '@/components/Icons/MinimizeIcon.vue' import MinimizeIcon from '@/components/Icons/MinimizeIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue' import NoteIcon from '@/components/Icons/NoteIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue' import TaskIcon from '@/components/Icons/TaskIcon.vue'
import TaskPanel from './TaskPanel.vue' import TaskPanel from '@/components/Telephony/TaskPanel.vue'
import CountUpTimer from '@/components/CountUpTimer.vue' import CountUpTimer from '@/components/CountUpTimer.vue'
import { TextEditor, Avatar, Button, call } from 'frappe-ui' import { TextEditor, Avatar, Button, call, createResource } from 'frappe-ui'
import { globalStore } from '@/stores/global' import { globalStore } from '@/stores/global'
import { contactsStore } from '@/stores/contacts'
import { useDraggable, useWindowSize } from '@vueuse/core' import { useDraggable, useWindowSize } from '@vueuse/core'
import { ref, computed, onBeforeUnmount } from 'vue' import { ref, onBeforeUnmount, watch } from 'vue'
import { useRouter } from 'vue-router'
const { getContact, getLeadContact } = contactsStore()
const { $socket } = globalStore() const { $socket } = globalStore()
const callPopupHeader = ref(null) const callPopupHeader = ref(null)
@ -248,18 +264,28 @@ const phoneNumber = ref('')
const callData = ref(null) const callData = ref(null)
const counterUp = ref(null) const counterUp = ref(null)
const contact = computed(() => { const contact = ref({
if (!phoneNumber.value) { full_name: '',
image: '',
mobile_no: '',
})
watch(phoneNumber, (value) => {
if (!value) return
getContact.fetch()
})
const getContact = createResource({
url: 'crm.integrations.api.get_contact_by_phone_number',
makeParams() {
return { return {
full_name: '', phone_number: phoneNumber.value,
image: '',
} }
} },
let _contact = getContact(phoneNumber.value) cache: ['contact', phoneNumber.value],
if (!_contact) { onSuccess(data) {
_contact = getLeadContact(phoneNumber.value) contact.value = data
} },
return _contact
}) })
const note = ref('') const note = ref('')
@ -362,6 +388,22 @@ onBeforeUnmount(() => {
$socket.off('exotel_call') $socket.off('exotel_call')
}) })
const router = useRouter()
function openDealOrLead() {
if (contact.value.deal) {
router.push({
name: 'Deal',
params: { dealId: contact.value.deal },
})
} else if (contact.value.lead) {
router.push({
name: 'Lead',
params: { leadId: contact.value.lead },
})
}
}
function closeCallPopup() { function closeCallPopup() {
showCallPopup.value = false showCallPopup.value = false
showSmallCallPopup.value = false showSmallCallPopup.value = false
@ -416,7 +458,8 @@ function updateStatus(data) {
) { ) {
counterUp.value.stop() counterUp.value.stop()
callDuration.value = getTime( callDuration.value = getTime(
data['Legs[0][OnCallDuration]'] || data.DialCallDuration, parseInt(data['Legs[0][OnCallDuration]']) ||
parseInt(data.DialCallDuration),
) )
return 'Call ended' return 'Call ended'
} }
@ -431,11 +474,12 @@ function updateStatus(data) {
return 'Incoming call' return 'Incoming call'
} else if ( } else if (
data.Direction == 'incoming' && data.Direction == 'incoming' &&
(data.EventType == 'Terminal' || data.CallType == 'completed') && (data.CallType == 'completed' || data.CallType == 'client-hangup') &&
(data.Status == 'free' || data.DialCallStatus == 'completed') (data.DialCallStatus == 'completed' || data.DialCallStatus == 'canceled')
) { ) {
callDuration.value = counterUp.value.getTime( callDuration.value = counterUp.value.getTime(
data['Legs[0][OnCallDuration]'] || data.DialCallDuration, parseInt(data['Legs[0][OnCallDuration]']) ||
parseInt(data.DialCallDuration),
) )
return 'Call ended' return 'Call ended'
} }