fix: get contact detail and if is lead or deal and show on call popup window
This commit is contained in:
parent
7301e8d62c
commit
f9c97ce51b
@ -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}
|
||||||
|
|||||||
@ -198,6 +198,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
|
'showCallLog',
|
||||||
'loadMore',
|
'loadMore',
|
||||||
'updatePageCount',
|
'updatePageCount',
|
||||||
'columnWidthUpdated',
|
'columnWidthUpdated',
|
||||||
|
|||||||
@ -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'
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user