1
0
forked from test/crm

fix: parse call log from backend to get receiver and caller

This commit is contained in:
Shariq Ansari 2025-01-18 18:09:14 +05:30
parent dbadd1bf0b
commit 7dd53d88e5
8 changed files with 144 additions and 114 deletions

View File

@ -6,6 +6,8 @@ from frappe import _
from frappe.desk.form.load import get_docinfo
from frappe.query_builder import JoinType
from crm.fcrm.doctype.crm_call_log.crm_call_log import parse_call_log
@frappe.whitelist()
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}

View File

@ -306,6 +306,7 @@ def get_data(
)
or []
)
data = parse_list_data(data, doctype)
if view_type == "kanban":
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):
if isinstance(filters, dict):
filters_items = filters.items()

View File

@ -4,6 +4,9 @@
import frappe
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):
@staticmethod
@ -77,6 +80,9 @@ class CRMCallLog(Document):
]
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):
for link in self.links:
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})
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()
def create_lead_from_call_log(call_log):
lead = frappe.new_doc("CRM Lead")

View File

@ -1,4 +1,5 @@
import phonenumbers
from frappe.utils import floor
from phonenumbers import NumberParseException
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:
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"

View File

@ -484,7 +484,7 @@ import CommunicationArea from '@/components/CommunicationArea.vue'
import WhatsappTemplateSelectorModal from '@/components/Modals/WhatsappTemplateSelectorModal.vue'
import AllModals from '@/components/Activities/AllModals.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 { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts'
@ -544,40 +544,6 @@ const all_activities = createResource({
cache: ['activity', doc.value.data.name],
auto: true,
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 }
},
})

View File

@ -85,7 +85,8 @@
</template>
<template
v-if="
callLog.doc?.type.label == 'Incoming' && !callLog.doc?.reference_docname
callLog.data?.type.label == 'Incoming' &&
!callLog.data?.reference_docname
"
#actions
>
@ -117,6 +118,7 @@ import {
Tooltip,
createDocumentResource,
call,
createResource,
} from 'frappe-ui'
import { getCallLogDetail } from '@/utils/callLog'
import { ref, computed, h, watch } from 'vue'
@ -136,63 +138,63 @@ const callNoteDoc = ref(null)
const callLog = ref({})
const detailFields = computed(() => {
if (!callLog.value.doc) return []
if (!callLog.value.data) return []
let details = [
{
icon: h(FeatherIcon, {
name: callLog.value.doc.type.icon,
name: callLog.value.data.type.icon,
class: 'h-3.5 w-3.5',
}),
name: 'type',
value: callLog.value.doc.type.label + ' Call',
value: callLog.value.data.type.label + ' Call',
},
{
icon: ContactsIcon,
name: 'receiver',
value: {
receiver: callLog.value.doc.receiver,
caller: callLog.value.doc.caller,
receiver: callLog.value.data.receiver,
caller: callLog.value.data.caller,
},
},
{
icon:
callLog.value.doc.reference_doctype == 'CRM Lead'
callLog.value.data.reference_doctype == 'CRM Lead'
? LeadsIcon
: Dealsicon,
name: 'reference_doctype',
value:
callLog.value.doc.reference_doctype == 'CRM Lead' ? 'Lead' : 'Deal',
callLog.value.data.reference_doctype == 'CRM Lead' ? 'Lead' : 'Deal',
link: () => {
if (callLog.value.doc.reference_doctype == 'CRM Lead') {
if (callLog.value.data.reference_doctype == 'CRM Lead') {
router.push({
name: 'Lead',
params: { leadId: callLog.value.doc.reference_docname },
params: { leadId: callLog.value.data.reference_docname },
})
} else {
router.push({
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,
name: 'creation',
value: callLog.value.doc.creation.label,
tooltip: callLog.value.doc.creation.label,
value: callLog.value.data.creation.label,
tooltip: callLog.value.data.creation.label,
},
{
icon: DurationIcon,
name: 'duration',
value: callLog.value.doc.duration.label,
value: callLog.value.data.duration.label,
},
{
icon: CheckCircleIcon,
name: 'status',
value: callLog.value.doc.status.label,
color: callLog.value.doc.status.color,
value: callLog.value.data.status.label,
color: callLog.value.data.status.color,
},
{
icon: h(FeatherIcon, {
@ -200,7 +202,7 @@ const detailFields = computed(() => {
class: 'h-4 w-4 mt-2',
}),
name: 'recording_url',
value: callLog.value.doc.recording_url,
value: callLog.value.data.recording_url,
},
{
icon: NoteIcon,
@ -216,7 +218,7 @@ const detailFields = computed(() => {
function createLead() {
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) => {
if (d) {
router.push({ name: 'Lead', params: { leadId: d } })
@ -226,24 +228,9 @@ function createLead() {
watch(show, (val) => {
if (val) {
callLog.value = createDocumentResource({
doctype: 'CRM Call Log',
name: props.name,
fields: [
'name',
'caller',
'receiver',
'duration',
'type',
'status',
'from',
'to',
'note',
'recording_url',
'reference_doctype',
'reference_docname',
'creation',
],
callLog.value = createResource({
url: 'crm.fcrm.doctype.crm_call_log.crm_call_log.get_call_log',
params: { name: props.name },
cache: ['call_log', props.name],
auto: true,
transform: (doc) => {

View File

@ -1,41 +1,15 @@
import { secondsToDuration, formatDate, timeAgo } from '@/utils'
import { formatDate, timeAgo } from '@/utils'
import { getMeta } from '@/stores/meta'
import { usersStore } from '@/stores/users'
import { contactsStore } from '@/stores/contacts'
const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
getMeta('CRM Call Log')
const { getUser } = usersStore()
const { getContact, getLeadContact } = contactsStore()
export function getCallLogDetail(row, log, columns = []) {
let incoming = log.type === 'Incoming'
if (row === 'caller') {
if (row === 'duration') {
return {
label: incoming
? 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),
label: log.duration,
icon: 'clock',
}
} else if (row === 'type') {

View File

@ -113,19 +113,6 @@ export function htmlToText(html) {
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) {
return str.charAt(0).toUpperCase() + str.slice(1)
}