fix: parse call log from backend to get receiver and caller
This commit is contained in:
parent
dbadd1bf0b
commit
7dd53d88e5
@ -6,6 +6,8 @@ from frappe import _
|
|||||||
from frappe.desk.form.load import get_docinfo
|
from frappe.desk.form.load import get_docinfo
|
||||||
from frappe.query_builder import JoinType
|
from frappe.query_builder import JoinType
|
||||||
|
|
||||||
|
from crm.fcrm.doctype.crm_call_log.crm_call_log import parse_call_log
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_activities(name):
|
def get_activities(name):
|
||||||
@ -439,6 +441,8 @@ def get_linked_calls(name):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
calls = [parse_call_log(call) for call in calls] if calls else []
|
||||||
|
|
||||||
return {"calls": calls, "notes": notes, "tasks": tasks}
|
return {"calls": calls, "notes": notes, "tasks": tasks}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -306,6 +306,7 @@ def get_data(
|
|||||||
)
|
)
|
||||||
or []
|
or []
|
||||||
)
|
)
|
||||||
|
data = parse_list_data(data, doctype)
|
||||||
|
|
||||||
if view_type == "kanban":
|
if view_type == "kanban":
|
||||||
if not rows:
|
if not rows:
|
||||||
@ -479,6 +480,13 @@ def get_data(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_list_data(data, doctype):
|
||||||
|
_list = get_controller(doctype)
|
||||||
|
if hasattr(_list, "parse_list_data"):
|
||||||
|
data = _list.parse_list_data(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def convert_filter_to_tuple(doctype, filters):
|
def convert_filter_to_tuple(doctype, filters):
|
||||||
if isinstance(filters, dict):
|
if isinstance(filters, dict):
|
||||||
filters_items = filters.items()
|
filters_items = filters.items()
|
||||||
|
|||||||
@ -4,6 +4,9 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
from crm.integrations.api import get_contact_by_phone_number
|
||||||
|
from crm.utils import seconds_to_duration
|
||||||
|
|
||||||
|
|
||||||
class CRMCallLog(Document):
|
class CRMCallLog(Document):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -77,6 +80,9 @@ class CRMCallLog(Document):
|
|||||||
]
|
]
|
||||||
return {"columns": columns, "rows": rows}
|
return {"columns": columns, "rows": rows}
|
||||||
|
|
||||||
|
def parse_list_data(calls):
|
||||||
|
return [parse_call_log(call) for call in calls] if calls else []
|
||||||
|
|
||||||
def has_link(self, doctype, name):
|
def has_link(self, doctype, name):
|
||||||
for link in self.links:
|
for link in self.links:
|
||||||
if link.link_doctype == doctype and link.link_name == name:
|
if link.link_doctype == doctype and link.link_name == name:
|
||||||
@ -89,6 +95,69 @@ class CRMCallLog(Document):
|
|||||||
self.append("links", {"link_doctype": reference_doctype, "link_name": reference_name})
|
self.append("links", {"link_doctype": reference_doctype, "link_name": reference_name})
|
||||||
|
|
||||||
|
|
||||||
|
def parse_call_log(call):
|
||||||
|
call["show_recording"] = False
|
||||||
|
call["duration"] = seconds_to_duration(call.get("duration"))
|
||||||
|
if call.get("type") == "Incoming":
|
||||||
|
call["activity_type"] = "incoming_call"
|
||||||
|
contact = get_contact_by_phone_number(call.get("from"))
|
||||||
|
receiver = (
|
||||||
|
frappe.db.get_values("User", call.get("receiver"), ["full_name", "user_image"])[0]
|
||||||
|
if call.get("receiver")
|
||||||
|
else [None, None]
|
||||||
|
)
|
||||||
|
call["caller"] = {
|
||||||
|
"label": contact.get("full_name", "Unknown"),
|
||||||
|
"image": contact.get("image"),
|
||||||
|
}
|
||||||
|
call["receiver"] = {
|
||||||
|
"label": receiver[0],
|
||||||
|
"image": receiver[1],
|
||||||
|
}
|
||||||
|
elif call.get("type") == "Outgoing":
|
||||||
|
call["activity_type"] = "outgoing_call"
|
||||||
|
contact = get_contact_by_phone_number(call.get("to"))
|
||||||
|
caller = (
|
||||||
|
frappe.db.get_values("User", call.get("caller"), ["full_name", "user_image"])[0]
|
||||||
|
if call.get("caller")
|
||||||
|
else [None, None]
|
||||||
|
)
|
||||||
|
call["caller"] = {
|
||||||
|
"label": caller[0],
|
||||||
|
"image": caller[1],
|
||||||
|
}
|
||||||
|
call["receiver"] = {
|
||||||
|
"label": contact.get("full_name", "Unknown"),
|
||||||
|
"image": contact.get("image"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return call
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_call_log(name):
|
||||||
|
call = frappe.get_cached_doc(
|
||||||
|
"CRM Call Log",
|
||||||
|
name,
|
||||||
|
fields=[
|
||||||
|
"name",
|
||||||
|
"caller",
|
||||||
|
"receiver",
|
||||||
|
"duration",
|
||||||
|
"type",
|
||||||
|
"status",
|
||||||
|
"from",
|
||||||
|
"to",
|
||||||
|
"note",
|
||||||
|
"recording_url",
|
||||||
|
"reference_doctype",
|
||||||
|
"reference_docname",
|
||||||
|
"creation",
|
||||||
|
],
|
||||||
|
).as_dict()
|
||||||
|
return parse_call_log(call)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_lead_from_call_log(call_log):
|
def create_lead_from_call_log(call_log):
|
||||||
lead = frappe.new_doc("CRM Lead")
|
lead = frappe.new_doc("CRM Lead")
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import phonenumbers
|
import phonenumbers
|
||||||
|
from frappe.utils import floor
|
||||||
from phonenumbers import NumberParseException
|
from phonenumbers import NumberParseException
|
||||||
from phonenumbers import PhoneNumberFormat as PNF
|
from phonenumbers import PhoneNumberFormat as PNF
|
||||||
|
|
||||||
@ -58,3 +59,37 @@ def are_same_phone_number(number1, number2, default_region="IN", validate=True):
|
|||||||
|
|
||||||
except phonenumbers.NumberParseException:
|
except phonenumbers.NumberParseException:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def seconds_to_duration(seconds):
|
||||||
|
if not seconds:
|
||||||
|
return "0s"
|
||||||
|
|
||||||
|
hours = floor(seconds // 3600)
|
||||||
|
minutes = floor((seconds % 3600) // 60)
|
||||||
|
seconds = floor((seconds % 3600) % 60)
|
||||||
|
|
||||||
|
# 1h 0m 0s -> 1h
|
||||||
|
# 0h 1m 0s -> 1m
|
||||||
|
# 0h 0m 1s -> 1s
|
||||||
|
# 1h 1m 0s -> 1h 1m
|
||||||
|
# 1h 0m 1s -> 1h 1s
|
||||||
|
# 0h 1m 1s -> 1m 1s
|
||||||
|
# 1h 1m 1s -> 1h 1m 1s
|
||||||
|
|
||||||
|
if hours and minutes and seconds:
|
||||||
|
return f"{hours}h {minutes}m {seconds}s"
|
||||||
|
elif hours and minutes:
|
||||||
|
return f"{hours}h {minutes}m"
|
||||||
|
elif hours and seconds:
|
||||||
|
return f"{hours}h {seconds}s"
|
||||||
|
elif minutes and seconds:
|
||||||
|
return f"{minutes}m {seconds}s"
|
||||||
|
elif hours:
|
||||||
|
return f"{hours}h"
|
||||||
|
elif minutes:
|
||||||
|
return f"{minutes}m"
|
||||||
|
elif seconds:
|
||||||
|
return f"{seconds}s"
|
||||||
|
else:
|
||||||
|
return "0s"
|
||||||
|
|||||||
@ -484,7 +484,7 @@ import CommunicationArea from '@/components/CommunicationArea.vue'
|
|||||||
import WhatsappTemplateSelectorModal from '@/components/Modals/WhatsappTemplateSelectorModal.vue'
|
import WhatsappTemplateSelectorModal from '@/components/Modals/WhatsappTemplateSelectorModal.vue'
|
||||||
import AllModals from '@/components/Activities/AllModals.vue'
|
import AllModals from '@/components/Activities/AllModals.vue'
|
||||||
import FilesUploader from '@/components/FilesUploader/FilesUploader.vue'
|
import FilesUploader from '@/components/FilesUploader/FilesUploader.vue'
|
||||||
import { timeAgo, formatDate, secondsToDuration, startCase } from '@/utils'
|
import { timeAgo, formatDate, startCase } from '@/utils'
|
||||||
import { globalStore } from '@/stores/global'
|
import { globalStore } from '@/stores/global'
|
||||||
import { usersStore } from '@/stores/users'
|
import { usersStore } from '@/stores/users'
|
||||||
import { contactsStore } from '@/stores/contacts'
|
import { contactsStore } from '@/stores/contacts'
|
||||||
@ -544,40 +544,6 @@ const all_activities = createResource({
|
|||||||
cache: ['activity', doc.value.data.name],
|
cache: ['activity', doc.value.data.name],
|
||||||
auto: true,
|
auto: true,
|
||||||
transform: ([versions, calls, notes, tasks, attachments]) => {
|
transform: ([versions, calls, notes, tasks, attachments]) => {
|
||||||
if (calls?.length) {
|
|
||||||
calls.forEach((doc) => {
|
|
||||||
doc.show_recording = false
|
|
||||||
doc.activity_type =
|
|
||||||
doc.type === 'Incoming' ? 'incoming_call' : 'outgoing_call'
|
|
||||||
doc.duration = secondsToDuration(doc.duration)
|
|
||||||
if (doc.type === 'Incoming') {
|
|
||||||
doc.caller = {
|
|
||||||
label:
|
|
||||||
getContact(doc.from)?.full_name ||
|
|
||||||
getLeadContact(doc.from)?.full_name ||
|
|
||||||
'Unknown',
|
|
||||||
image:
|
|
||||||
getContact(doc.from)?.image || getLeadContact(doc.from)?.image,
|
|
||||||
}
|
|
||||||
doc.receiver = {
|
|
||||||
label: getUser(doc.receiver).full_name,
|
|
||||||
image: getUser(doc.receiver).user_image,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
doc.caller = {
|
|
||||||
label: getUser(doc.caller).full_name,
|
|
||||||
image: getUser(doc.caller).user_image,
|
|
||||||
}
|
|
||||||
doc.receiver = {
|
|
||||||
label:
|
|
||||||
getContact(doc.to)?.full_name ||
|
|
||||||
getLeadContact(doc.to)?.full_name ||
|
|
||||||
'Unknown',
|
|
||||||
image: getContact(doc.to)?.image || getLeadContact(doc.to)?.image,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return { versions, calls, notes, tasks, attachments }
|
return { versions, calls, notes, tasks, attachments }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -85,7 +85,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
v-if="
|
v-if="
|
||||||
callLog.doc?.type.label == 'Incoming' && !callLog.doc?.reference_docname
|
callLog.data?.type.label == 'Incoming' &&
|
||||||
|
!callLog.data?.reference_docname
|
||||||
"
|
"
|
||||||
#actions
|
#actions
|
||||||
>
|
>
|
||||||
@ -117,6 +118,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
createDocumentResource,
|
createDocumentResource,
|
||||||
call,
|
call,
|
||||||
|
createResource,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { getCallLogDetail } from '@/utils/callLog'
|
import { getCallLogDetail } from '@/utils/callLog'
|
||||||
import { ref, computed, h, watch } from 'vue'
|
import { ref, computed, h, watch } from 'vue'
|
||||||
@ -136,63 +138,63 @@ const callNoteDoc = ref(null)
|
|||||||
const callLog = ref({})
|
const callLog = ref({})
|
||||||
|
|
||||||
const detailFields = computed(() => {
|
const detailFields = computed(() => {
|
||||||
if (!callLog.value.doc) return []
|
if (!callLog.value.data) return []
|
||||||
let details = [
|
let details = [
|
||||||
{
|
{
|
||||||
icon: h(FeatherIcon, {
|
icon: h(FeatherIcon, {
|
||||||
name: callLog.value.doc.type.icon,
|
name: callLog.value.data.type.icon,
|
||||||
class: 'h-3.5 w-3.5',
|
class: 'h-3.5 w-3.5',
|
||||||
}),
|
}),
|
||||||
name: 'type',
|
name: 'type',
|
||||||
value: callLog.value.doc.type.label + ' Call',
|
value: callLog.value.data.type.label + ' Call',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: ContactsIcon,
|
icon: ContactsIcon,
|
||||||
name: 'receiver',
|
name: 'receiver',
|
||||||
value: {
|
value: {
|
||||||
receiver: callLog.value.doc.receiver,
|
receiver: callLog.value.data.receiver,
|
||||||
caller: callLog.value.doc.caller,
|
caller: callLog.value.data.caller,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon:
|
icon:
|
||||||
callLog.value.doc.reference_doctype == 'CRM Lead'
|
callLog.value.data.reference_doctype == 'CRM Lead'
|
||||||
? LeadsIcon
|
? LeadsIcon
|
||||||
: Dealsicon,
|
: Dealsicon,
|
||||||
name: 'reference_doctype',
|
name: 'reference_doctype',
|
||||||
value:
|
value:
|
||||||
callLog.value.doc.reference_doctype == 'CRM Lead' ? 'Lead' : 'Deal',
|
callLog.value.data.reference_doctype == 'CRM Lead' ? 'Lead' : 'Deal',
|
||||||
link: () => {
|
link: () => {
|
||||||
if (callLog.value.doc.reference_doctype == 'CRM Lead') {
|
if (callLog.value.data.reference_doctype == 'CRM Lead') {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Lead',
|
name: 'Lead',
|
||||||
params: { leadId: callLog.value.doc.reference_docname },
|
params: { leadId: callLog.value.data.reference_docname },
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Deal',
|
name: 'Deal',
|
||||||
params: { dealId: callLog.value.doc.reference_docname },
|
params: { dealId: callLog.value.data.reference_docname },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
condition: () => callLog.value.doc.reference_docname,
|
condition: () => callLog.value.data.reference_docname,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: CalendarIcon,
|
icon: CalendarIcon,
|
||||||
name: 'creation',
|
name: 'creation',
|
||||||
value: callLog.value.doc.creation.label,
|
value: callLog.value.data.creation.label,
|
||||||
tooltip: callLog.value.doc.creation.label,
|
tooltip: callLog.value.data.creation.label,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: DurationIcon,
|
icon: DurationIcon,
|
||||||
name: 'duration',
|
name: 'duration',
|
||||||
value: callLog.value.doc.duration.label,
|
value: callLog.value.data.duration.label,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: CheckCircleIcon,
|
icon: CheckCircleIcon,
|
||||||
name: 'status',
|
name: 'status',
|
||||||
value: callLog.value.doc.status.label,
|
value: callLog.value.data.status.label,
|
||||||
color: callLog.value.doc.status.color,
|
color: callLog.value.data.status.color,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: h(FeatherIcon, {
|
icon: h(FeatherIcon, {
|
||||||
@ -200,7 +202,7 @@ const detailFields = computed(() => {
|
|||||||
class: 'h-4 w-4 mt-2',
|
class: 'h-4 w-4 mt-2',
|
||||||
}),
|
}),
|
||||||
name: 'recording_url',
|
name: 'recording_url',
|
||||||
value: callLog.value.doc.recording_url,
|
value: callLog.value.data.recording_url,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: NoteIcon,
|
icon: NoteIcon,
|
||||||
@ -216,7 +218,7 @@ const detailFields = computed(() => {
|
|||||||
|
|
||||||
function createLead() {
|
function createLead() {
|
||||||
call('crm.fcrm.doctype.crm_call_log.crm_call_log.create_lead_from_call_log', {
|
call('crm.fcrm.doctype.crm_call_log.crm_call_log.create_lead_from_call_log', {
|
||||||
call_log: callLog.value.doc,
|
call_log: callLog.value.data,
|
||||||
}).then((d) => {
|
}).then((d) => {
|
||||||
if (d) {
|
if (d) {
|
||||||
router.push({ name: 'Lead', params: { leadId: d } })
|
router.push({ name: 'Lead', params: { leadId: d } })
|
||||||
@ -226,24 +228,9 @@ function createLead() {
|
|||||||
|
|
||||||
watch(show, (val) => {
|
watch(show, (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
callLog.value = createDocumentResource({
|
callLog.value = createResource({
|
||||||
doctype: 'CRM Call Log',
|
url: 'crm.fcrm.doctype.crm_call_log.crm_call_log.get_call_log',
|
||||||
name: props.name,
|
params: { name: props.name },
|
||||||
fields: [
|
|
||||||
'name',
|
|
||||||
'caller',
|
|
||||||
'receiver',
|
|
||||||
'duration',
|
|
||||||
'type',
|
|
||||||
'status',
|
|
||||||
'from',
|
|
||||||
'to',
|
|
||||||
'note',
|
|
||||||
'recording_url',
|
|
||||||
'reference_doctype',
|
|
||||||
'reference_docname',
|
|
||||||
'creation',
|
|
||||||
],
|
|
||||||
cache: ['call_log', props.name],
|
cache: ['call_log', props.name],
|
||||||
auto: true,
|
auto: true,
|
||||||
transform: (doc) => {
|
transform: (doc) => {
|
||||||
|
|||||||
@ -1,41 +1,15 @@
|
|||||||
import { secondsToDuration, formatDate, timeAgo } from '@/utils'
|
import { formatDate, timeAgo } from '@/utils'
|
||||||
import { getMeta } from '@/stores/meta'
|
import { getMeta } from '@/stores/meta'
|
||||||
import { usersStore } from '@/stores/users'
|
|
||||||
import { contactsStore } from '@/stores/contacts'
|
|
||||||
|
|
||||||
const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
|
||||||
getMeta('CRM Call Log')
|
getMeta('CRM Call Log')
|
||||||
const { getUser } = usersStore()
|
|
||||||
const { getContact, getLeadContact } = contactsStore()
|
|
||||||
|
|
||||||
export function getCallLogDetail(row, log, columns = []) {
|
export function getCallLogDetail(row, log, columns = []) {
|
||||||
let incoming = log.type === 'Incoming'
|
let incoming = log.type === 'Incoming'
|
||||||
|
|
||||||
if (row === 'caller') {
|
if (row === 'duration') {
|
||||||
return {
|
return {
|
||||||
label: incoming
|
label: log.duration,
|
||||||
? getContact(log.from)?.full_name ||
|
|
||||||
getLeadContact(log.from)?.full_name ||
|
|
||||||
'Unknown'
|
|
||||||
: log.caller && getUser(log.caller).full_name,
|
|
||||||
image: incoming
|
|
||||||
? getContact(log.from)?.image || getLeadContact(log.from)?.image
|
|
||||||
: log.caller && getUser(log.caller).user_image,
|
|
||||||
}
|
|
||||||
} else if (row === 'receiver') {
|
|
||||||
return {
|
|
||||||
label: incoming
|
|
||||||
? log.receiver && getUser(log.receiver).full_name
|
|
||||||
: getContact(log.to)?.full_name ||
|
|
||||||
getLeadContact(log.to)?.full_name ||
|
|
||||||
'Unknown',
|
|
||||||
image: incoming
|
|
||||||
? log.receiver && getUser(log.receiver).user_image
|
|
||||||
: getContact(log.to)?.image || getLeadContact(log.to)?.image,
|
|
||||||
}
|
|
||||||
} else if (row === 'duration') {
|
|
||||||
return {
|
|
||||||
label: secondsToDuration(log.duration),
|
|
||||||
icon: 'clock',
|
icon: 'clock',
|
||||||
}
|
}
|
||||||
} else if (row === 'type') {
|
} else if (row === 'type') {
|
||||||
|
|||||||
@ -113,19 +113,6 @@ export function htmlToText(html) {
|
|||||||
return div.textContent || div.innerText || ''
|
return div.textContent || div.innerText || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export function secondsToDuration(seconds) {
|
|
||||||
const hours = Math.floor(seconds / 3600)
|
|
||||||
const minutes = Math.floor((seconds % 3600) / 60)
|
|
||||||
const _seconds = Math.floor((seconds % 3600) % 60)
|
|
||||||
|
|
||||||
if (hours == 0 && minutes == 0) {
|
|
||||||
return `${_seconds}s`
|
|
||||||
} else if (hours == 0) {
|
|
||||||
return `${minutes}m ${_seconds}s`
|
|
||||||
}
|
|
||||||
return `${hours}h ${minutes}m ${_seconds}s`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function startCase(str) {
|
export function startCase(str) {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user