From 20f3c089aa8fdcdede239d08e5dc35067195a90d Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 28 Aug 2023 21:52:21 +0530 Subject: [PATCH] fix: create/update contact from lead and show on callui also created contacts store which is used now everywhere --- crm/api/session.py | 16 +++++- crm/crm/doctype/crm_lead/crm_lead.py | 77 ++++++++++++++++++++++++++++ frontend/src/components/CallUI.vue | 56 +++++++++++--------- frontend/src/pages/Contacts.vue | 17 ++---- frontend/src/stores/contacts.js | 34 ++++++++++++ 5 files changed, 163 insertions(+), 37 deletions(-) create mode 100644 frontend/src/stores/contacts.js diff --git a/crm/api/session.py b/crm/api/session.py index eadf1e31..e40a769b 100644 --- a/crm/api/session.py +++ b/crm/api/session.py @@ -16,4 +16,18 @@ def get_users(): for user in users: if frappe.session.user == user.name: user.session_user = True - return users \ No newline at end of file + return users + +@frappe.whitelist() +def get_contacts(): + if frappe.session.user == "Guest": + frappe.throw("Authentication failed", exc=frappe.AuthenticationError) + + contacts = frappe.qb.get_query( + "Contact", + fields=['name', 'full_name', 'image', 'email_id', 'mobile_no', 'phone', 'salutation'], + order_by="first_name asc", + distinct=True, + ).run(as_dict=1) + + return contacts \ No newline at end of file diff --git a/crm/crm/doctype/crm_lead/crm_lead.py b/crm/crm/doctype/crm_lead/crm_lead.py index f54ef59b..90f0d0f3 100644 --- a/crm/crm/doctype/crm_lead/crm_lead.py +++ b/crm/crm/doctype/crm_lead/crm_lead.py @@ -14,6 +14,7 @@ class CRMLead(Document): self.set_lead_name() self.set_title() self.validate_email() + self.validate_contact() def set_full_name(self): if self.first_name: @@ -44,6 +45,82 @@ class CRMLead(Document): if self.is_new() or not self.image: self.image = has_gravatar(self.email) + + def validate_contact(self): + link = frappe.db.exists("Dynamic Link", {"link_doctype": "CRM Lead", "link_name": self.name}) + + if link: + for field in ["first_name", "last_name", "email", "mobile_no", "phone", "salutation"]: + if self.has_value_changed(field): + contact = frappe.db.get_value("Dynamic Link", link, "parent") + contact_doc = frappe.get_doc("Contact", contact) + contact_doc.update({ + "first_name": self.first_name or self.lead_name, + "last_name": self.last_name, + "salutation": self.salutation, + }) + if self.has_value_changed("email"): + contact_doc.email_ids = [] + contact_doc.append("email_ids", {"email_id": self.email, "is_primary": 1}) + + if self.has_value_changed("phone"): + contact_doc.append("phone_nos", {"phone": self.phone, "is_primary_phone": 1}) + + if self.has_value_changed("mobile_no"): + contact_doc.phone_nos = [] + contact_doc.append("phone_nos", {"phone": self.mobile_no, "is_primary_mobile_no": 1}) + + contact_doc.save() + break + else: + self.contact_doc = self.create_contact() + self.link_to_contact() + + def before_insert(self): + self.contact_doc = None + self.contact_doc = self.create_contact() + + def after_insert(self): + self.link_to_contact() + + def link_to_contact(self): + # update contact links + if self.contact_doc: + self.contact_doc.append( + "links", {"link_doctype": "CRM Lead", "link_name": self.name, "link_title": self.lead_name} + ) + self.contact_doc.save() + + def create_contact(self): + if not self.lead_name: + self.set_full_name() + self.set_lead_name() + + contact = frappe.new_doc("Contact") + contact.update( + { + "first_name": self.first_name or self.lead_name, + "last_name": self.last_name, + "salutation": self.salutation, + "gender": self.gender, + "designation": self.job_title, + "company_name": self.organization_name, + } + ) + + if self.email: + contact.append("email_ids", {"email_id": self.email, "is_primary": 1}) + + if self.phone: + contact.append("phone_nos", {"phone": self.phone, "is_primary_phone": 1}) + + if self.mobile_no: + contact.append("phone_nos", {"phone": self.mobile_no, "is_primary_mobile_no": 1}) + + contact.insert(ignore_permissions=True) + contact.reload() # load changes by hooks on contact + + return contact @staticmethod def sort_options(): diff --git a/frontend/src/components/CallUI.vue b/frontend/src/components/CallUI.vue index bb1d88cc..8f3265cd 100644 --- a/frontend/src/components/CallUI.vue +++ b/frontend/src/components/CallUI.vue @@ -10,16 +10,16 @@
- -
+
- {{ getUser().full_name }} + {{ contact.full_name }}
-
{{ phoneNumber }}
+
{{ contact.mobile_no }}
@@ -112,12 +112,12 @@ @click="toggleCallWindow" >
-
- {{ getUser().full_name }} + {{ contact.full_name }}
@@ -180,21 +180,23 @@ import NoteIcon from '@/components/Icons/NoteIcon.vue' import MinimizeIcon from '@/components/Icons/MinimizeIcon.vue' import DialpadIcon from '@/components/Icons/DialpadIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue' -import UserAvatar from '@/components/UserAvatar.vue' import CountUpTimer from '@/components/CountUpTimer.vue' import { Device } from '@twilio/voice-sdk' import { useDraggable, useWindowSize } from '@vueuse/core' -import { usersStore } from '@/stores/users' -import { call } from 'frappe-ui' +import { contactsStore } from '@/stores/contacts' +import { Avatar, call } from 'frappe-ui' import { onMounted, provide, ref, watch } from 'vue' import NoteModal from './NoteModal.vue' -const { getUser } = usersStore() +const { getContact } = contactsStore() let device = '' let log = ref('Connecting...') let _call = ref(null) -let phoneNumber = ref('') +const contact = ref({ + full_name: '', + mobile_no: '', +}) let showCallPopup = ref(false) let showSmallCallWindow = ref(false) @@ -202,7 +204,6 @@ let onCall = ref(false) let calling = ref(false) let muted = ref(false) let callPopup = ref(null) -let callPopupHandle = ref(null) let counterUp = ref(null) let callStatus = ref('') const showNoteModal = ref(false) @@ -311,13 +312,20 @@ function toggleMute() { function handleIncomingCall(call) { log.value = `Incoming call from ${call.parameters.From}` - phoneNumber.value = call.parameters.From + // get name of the caller from the phone number + + contact.value = getContact(call.parameters.From) + + if (!contact.value) { + contact.value = { + full_name: 'Unknown', + mobile_no: call.parameters.From, + } + } showCallPopup.value = true _call.value = call - console.log('call', call) - console.log('device: ', device) _call.value.on('accept', (conn) => { console.log('conn', conn) }) @@ -370,15 +378,15 @@ function handleDisconnectedIncomingCall() { counterUp.value.stop() } -async function makeOutgoingCall(number, documentName) { - // remove this hard coded number later - phoneNumber.value = '+917666980887' || number +async function makeOutgoingCall(number) { + contact.value = getContact(number) + if (device) { - log.value = `Attempting to call ${phoneNumber.value} ...` + log.value = `Attempting to call ${contact.value.mobile_no} ...` try { _call.value = await device.connect({ - params: { To: phoneNumber.value }, + params: { To: contact.value.mobile_no }, }) _call.value.on('messageReceived', (message) => { diff --git a/frontend/src/pages/Contacts.vue b/frontend/src/pages/Contacts.vue index 57eae4d6..ce0c485f 100644 --- a/frontend/src/pages/Contacts.vue +++ b/frontend/src/pages/Contacts.vue @@ -47,6 +47,9 @@ import SortIcon from '@/components/Icons/SortIcon.vue' import FilterIcon from '@/components/Icons/FilterIcon.vue' import { FeatherIcon, Button, Dropdown, createListResource } from 'frappe-ui' import { ref, computed } from 'vue' +import { contactsStore } from '@/stores/contacts.js' + +const { contacts } = contactsStore() const list = { title: 'Contacts', @@ -54,16 +57,6 @@ const list = { singular_label: 'Contact', } -const contacts = createListResource({ - type: 'list', - doctype: 'Contact', - fields: ['name', 'full_name', 'image', 'email_id', 'phone'], - orderBy: 'full_name asc', - cache: 'Contacts', - pageLength: 20, - auto: true, -}) - const columns = [ { label: 'Full name', @@ -79,7 +72,7 @@ const columns = [ }, { label: 'Phone', - key: 'phone', + key: 'mobile_no', type: 'phone', size: 'w-44', }, @@ -95,7 +88,7 @@ const rows = computed(() => { image: contact.image, }, email: contact.email_id, - phone: contact.phone, + mobile_no: contact.mobile_no, } }) }) diff --git a/frontend/src/stores/contacts.js b/frontend/src/stores/contacts.js new file mode 100644 index 00000000..999ad0e7 --- /dev/null +++ b/frontend/src/stores/contacts.js @@ -0,0 +1,34 @@ +import { defineStore } from 'pinia' +import { createResource } from 'frappe-ui' +import { reactive } from 'vue' + +export const contactsStore = defineStore('crm-contacts', () => { + let contactsByPhone = reactive({}) + + const contacts = createResource({ + url: 'crm.api.session.get_contacts', + cache: 'contacts', + initialData: [], + transform(contacts) { + for (let contact of contacts) { + contactsByPhone[contact.mobile_no] = contact + } + return contacts + }, + onError(error) { + if (error && error.exc_type === 'AuthenticationError') { + router.push('/login') + } + }, + }) + contacts.fetch() + + function getContact(mobile_no) { + return contactsByPhone[mobile_no] + } + + return { + contacts, + getContact, + } +})