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.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}
|
||||
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 }
|
||||
},
|
||||
})
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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') {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user