From f5dd494716ec36cc677b646fe580ef98a8eca271 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 16 Jul 2019 11:07:25 +0530 Subject: [PATCH 01/10] fix(call popup): Multiple changes - Remove summary from call log use comments instead - Add contact and lead if matched to the call log - Add received by field with Employee Link - DocPerm to allow Employees to have read access on call log - Remove unwanted code - Add a method to get lead just by providing phone number - Show popup only to Employee to which the system is trying to connect. - Remove custom code from call popup dialog and replace it with standard fields - Increase timeout to hide popup after the call has been disconnected. --- .../doctype/call_log/call_log.json | 76 +++++++++-- .../doctype/call_log/call_log.py | 43 +++++- erpnext/crm/doctype/lead/lead.py | 23 ++++ erpnext/crm/doctype/utils.py | 74 ++++------- .../exotel_integration.py | 9 +- erpnext/public/js/call_popup/call_popup.js | 122 ++++++++---------- 6 files changed, 211 insertions(+), 136 deletions(-) diff --git a/erpnext/communication/doctype/call_log/call_log.json b/erpnext/communication/doctype/call_log/call_log.json index 110030d3de..43da3b3f85 100644 --- a/erpnext/communication/doctype/call_log/call_log.json +++ b/erpnext/communication/doctype/call_log/call_log.json @@ -8,12 +8,18 @@ "from", "to", "column_break_3", + "received_by", "medium", + "caller_information", + "contact", + "contact_name", + "column_break_10", + "lead", + "lead_name", "section_break_5", "status", "duration", - "recording_url", - "summary" + "recording_url" ], "fields": [ { @@ -60,12 +66,6 @@ "label": "Duration", "read_only": 1 }, - { - "fieldname": "summary", - "fieldtype": "Data", - "label": "Summary", - "read_only": 1 - }, { "fieldname": "recording_url", "fieldtype": "Data", @@ -77,10 +77,56 @@ "fieldtype": "Data", "label": "Medium", "read_only": 1 + }, + { + "fieldname": "received_by", + "fieldtype": "Link", + "label": "Received By", + "options": "Employee", + "read_only": 1 + }, + { + "fieldname": "caller_information", + "fieldtype": "Section Break", + "label": "Caller Information" + }, + { + "fieldname": "contact", + "fieldtype": "Link", + "label": "Contact", + "options": "Contact", + "read_only": 1 + }, + { + "fieldname": "lead", + "fieldtype": "Link", + "label": "Lead ", + "options": "Lead", + "read_only": 1 + }, + { + "fetch_from": "contact.name", + "fieldname": "contact_name", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Name", + "read_only": 1 + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "fetch_from": "lead.lead_name", + "fieldname": "lead_name", + "fieldtype": "Data", + "hidden": 1, + "label": "Lead Name", + "read_only": 1 } ], "in_create": 1, - "modified": "2019-07-01 09:09:48.516722", + "modified": "2019-07-15 19:18:31.267379", "modified_by": "Administrator", "module": "Communication", "name": "Call Log", @@ -97,10 +143,20 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1 } ], "sort_field": "modified", "sort_order": "ASC", "title_field": "from", - "track_changes": 1 + "track_changes": 1, + "track_views": 1 } \ No newline at end of file diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/communication/doctype/call_log/call_log.py index 66f1064e58..8d9e39b4c7 100644 --- a/erpnext/communication/doctype/call_log/call_log.py +++ b/erpnext/communication/doctype/call_log/call_log.py @@ -4,16 +4,51 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document from erpnext.crm.doctype.utils import get_employee_emails_for_popup +from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number +from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number class CallLog(Document): + def before_insert(self): + number = self.get('from').lstrip('0') + self.contact = get_contact_with_phone_number(number) + self.lead = get_lead_with_phone_number(number) + def after_insert(self): - employee_emails = get_employee_emails_for_popup(self.medium) - for email in employee_emails: - frappe.publish_realtime('show_call_popup', self, user=email) + self.trigger_call_popup() def on_update(self): doc_before_save = self.get_doc_before_save() - if doc_before_save and doc_before_save.status in ['Ringing'] and self.status in ['Missed', 'Completed']: + if not doc_before_save: return + if doc_before_save.status in ['Ringing'] and self.status in ['Missed', 'Completed']: frappe.publish_realtime('call_{id}_disconnected'.format(id=self.id), self) + elif doc_before_save.to != self.to: + self.trigger_call_popup() + + def trigger_call_popup(self): + employee_email = get_employee_email(self.to) + if employee_email: + frappe.publish_realtime('show_call_popup', self, user=employee_email) + +@frappe.whitelist() +def add_call_summary(call_log, summary): + doc = frappe.get_doc('Call Log', call_log) + doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '

' + summary) + +def get_employee_email(number): + if not number: return + number = number.lstrip('0') + + employee = frappe.cache().hget('employee_with_number', number) + if employee: return employee + + employees = frappe.get_all('Employee', or_filters={ + 'phone': ['like', '%{}'.format(number)], + }, limit=1) + + employee = employees[0].name if employees else None + frappe.cache().hset('employee_with_number', number, employee) + + return employee \ No newline at end of file diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 4343db04b4..15174ef7a2 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -23,6 +23,13 @@ class Lead(SellingController): self.get("__onload").is_customer = customer load_address_and_contact(self) + def on_update(self): + doc = self.get_doc_before_save() + for field in ['mobile_no', 'phone']: + old_number = doc.get(field) + if old_number and old_number != self.get(field): + frappe.cache().hdel('lead_with_number', old_number) + def validate(self): self.set_lead_name() self._prev = frappe._dict({ @@ -230,3 +237,19 @@ def make_lead_from_communication(communication, ignore_communication_links=False link_communication_to_document(doc, "Lead", lead_name, ignore_communication_links) return lead_name + +def get_lead_with_phone_number(number): + if not number: return + + lead = frappe.cache().hget('lead_with_number', number) + if lead: return lead + + leads = frappe.get_all('Lead', or_filters={ + 'phone': ['like', '%{}'.format(number)], + 'mobile_no': ['like', '%{}'.format(number)] + }, limit=1) + + lead = leads[0].name if leads else None + frappe.cache().hset('lead_with_number', number, lead) + + return lead \ No newline at end of file diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py index 9cfab15995..0cd2557cc8 100644 --- a/erpnext/crm/doctype/utils.py +++ b/erpnext/crm/doctype/utils.py @@ -3,80 +3,54 @@ from frappe import _ import json @frappe.whitelist() -def get_document_with_phone_number(number): - # finds contacts and leads - if not number: return - number = number.lstrip('0') - number_filter = { - 'phone': ['like', '%{}'.format(number)], - 'mobile_no': ['like', '%{}'.format(number)] - } - contacts = frappe.get_all('Contact', or_filters=number_filter, limit=1) +def get_last_interaction(contact=None, lead=None): - if contacts: - return frappe.get_doc('Contact', contacts[0].name) - - leads = frappe.get_all('Lead', or_filters=number_filter, limit=1) - - if leads: - return frappe.get_doc('Lead', leads[0].name) - -@frappe.whitelist() -def get_last_interaction(number, reference_doc): - reference_doc = json.loads(reference_doc) if reference_doc else get_document_with_phone_number(number) - - if not reference_doc: return - - reference_doc = frappe._dict(reference_doc) + if not contact and not lead: return last_communication = {} last_issue = {} - if reference_doc.doctype == 'Contact': - customer_name = '' + if contact: query_condition = '' - for link in reference_doc.links: - link = frappe._dict(link) + contact = frappe.get_doc('Contact', contact) + for link in contact.links: if link.link_doctype == 'Customer': - customer_name = link.link_name + last_issue = get_last_issue_from_customer(link.link_name) query_condition += "(`reference_doctype`='{}' AND `reference_name`='{}') OR".format(link.link_doctype, link.link_name) if query_condition: + # remove extra appended 'OR' query_condition = query_condition[:-2] - last_communication = frappe.db.sql(""" + communication = frappe.db.sql(""" SELECT `name`, `content` FROM `tabCommunication` - WHERE {} + WHERE + `sent_or_received`='Received' AND + ({}) ORDER BY `modified` LIMIT 1 - """.format(query_condition)) # nosec + """.format(query_condition)) # nosec - if customer_name: - last_issue = frappe.get_all('Issue', { - 'customer': customer_name - }, ['name', 'subject', 'customer'], limit=1) - - elif reference_doc.doctype == 'Lead': + if lead: last_communication = frappe.get_all('Communication', filters={ 'reference_doctype': reference_doc.doctype, 'reference_name': reference_doc.name, 'sent_or_received': 'Received' }, fields=['name', 'content'], limit=1) + last_communication = last_communication[0] if last_communication else None + return { - 'last_communication': last_communication[0] if last_communication else None, - 'last_issue': last_issue[0] if last_issue else None + 'last_communication': last_communication, + 'last_issue': last_issue } -@frappe.whitelist() -def add_call_summary(docname, summary): - call_log = frappe.get_doc('Call Log', docname) - summary = _('Call Summary by {0}: {1}').format( - frappe.utils.get_fullname(frappe.session.user), summary) - if not call_log.summary: - call_log.summary = summary - else: - call_log.summary += '
' + summary - call_log.save(ignore_permissions=True) +def get_last_issue_from_customer(customer_name): + issues = frappe.get_all('Issue', { + 'customer': customer_name + }, ['name', 'subject', 'customer'], limit=1) + + return issues[0] if issues else None + def get_employee_emails_for_popup(communication_medium): now_time = frappe.utils.nowtime() diff --git a/erpnext/erpnext_integrations/exotel_integration.py b/erpnext/erpnext_integrations/exotel_integration.py index c04cedce31..09c399e6aa 100644 --- a/erpnext/erpnext_integrations/exotel_integration.py +++ b/erpnext/erpnext_integrations/exotel_integration.py @@ -18,6 +18,8 @@ def handle_incoming_call(**kwargs): call_log = get_call_log(call_payload) if not call_log: create_call_log(call_payload) + else: + update_call_log(call_payload, call_log=call_log) @frappe.whitelist(allow_guest=True) def handle_end_call(**kwargs): @@ -27,10 +29,11 @@ def handle_end_call(**kwargs): def handle_missed_call(**kwargs): update_call_log(kwargs, 'Missed') -def update_call_log(call_payload, status): - call_log = get_call_log(call_payload) +def update_call_log(call_payload, status='Ringing', call_log=None): + call_log = call_log or get_call_log(call_payload) if call_log: call_log.status = status + call_log.to = call_payload.get('DialWhomNumber') call_log.duration = call_payload.get('DialCallDuration') or 0 call_log.recording_url = call_payload.get('RecordingUrl') call_log.save(ignore_permissions=True) @@ -48,7 +51,7 @@ def get_call_log(call_payload): def create_call_log(call_payload): call_log = frappe.new_doc('Call Log') call_log.id = call_payload.get('CallSid') - call_log.to = call_payload.get('CallTo') + call_log.to = call_payload.get('DialWhomNumber') call_log.medium = call_payload.get('To') call_log.status = 'Ringing' setattr(call_log, 'from', call_payload.get('CallFrom')) diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js index 89657a1837..847b501ea3 100644 --- a/erpnext/public/js/call_popup/call_popup.js +++ b/erpnext/public/js/call_popup/call_popup.js @@ -11,12 +11,44 @@ class CallPopup { 'static': true, 'minimizable': true, 'fields': [{ - 'fieldname': 'caller_info', - 'fieldtype': 'HTML' + 'fieldname': 'name', + 'label': 'Name', + 'default': this.get_caller_name() || __('Unknown Caller'), + 'fieldtype': 'Data', + 'read_only': 1 + }, { + 'fieldtype': 'Button', + 'label': __('Open Contact'), + 'click': () => frappe.set_route('Form', 'Contact', this.call_log.contact), + 'depends_on': () => this.call_log.contact + }, { + 'fieldtype': 'Button', + 'label': __('Open Lead'), + 'click': () => frappe.set_route('Form', 'Lead', this.call_log.lead), + 'depends_on': () => this.call_log.lead + }, { + 'fieldtype': 'Button', + 'label': __('Make New Contact'), + 'click': () => frappe.new_doc('Contact', { 'mobile_no': this.caller_number }), + 'depends_on': () => !this.get_caller_name() + }, { + 'fieldtype': 'Button', + 'label': __('Make New Lead'), + 'click': () => frappe.new_doc('Lead', { 'mobile_no': this.caller_number }), + 'depends_on': () => !this.get_caller_name() + }, { + 'fieldtype': 'Column Break', + }, { + 'fieldname': 'number', + 'label': 'Phone Number', + 'fieldtype': 'Data', + 'default': this.caller_number, + 'read_only': 1 }, { 'fielname': 'last_interaction', 'fieldtype': 'Section Break', 'label': __('Activity'), + 'depends_on': () => this.get_caller_name() }, { 'fieldtype': 'Small Text', 'label': __('Last Communication'), @@ -30,7 +62,7 @@ class CallPopup { 'read_only': true, 'default': `${__('No issue raised by the customer.')}` }, { - 'fieldtype': 'Column Break', + 'fieldtype': 'Section Break', }, { 'fieldtype': 'Small Text', 'label': __('Call Summary'), @@ -41,13 +73,19 @@ class CallPopup { 'click': () => { const call_summary = this.dialog.get_value('call_summary'); if (!call_summary) return; - frappe.xcall('erpnext.crm.doctype.utils.add_call_summary', { - 'docname': this.call_log.id, + frappe.xcall('erpnext.communication.doctype.call_log.call_log.add_call_summary', { + 'call_log': this.call_log.name, 'summary': call_summary, }).then(() => { this.close_modal(); frappe.show_alert({ - message: `${__('Call Summary Saved')}
${__('View call log')}`, + message: `${__('Call Summary Saved')} +
+ + ${__('View call log')} + `, indicator: 'green' }); }); @@ -55,71 +93,14 @@ class CallPopup { }], }); this.set_call_status(); - this.make_caller_info_section(); this.dialog.get_close_btn().show(); + this.make_last_interaction_section(); this.dialog.$body.addClass('call-popup'); this.dialog.set_secondary_action(this.close_modal.bind(this)); frappe.utils.play_sound('incoming-call'); this.dialog.show(); } - make_caller_info_section() { - const wrapper = this.dialog.get_field('caller_info').$wrapper; - wrapper.append(`
${__("Loading...")}
`); - frappe.xcall('erpnext.crm.doctype.utils.get_document_with_phone_number', { - 'number': this.caller_number - }).then(contact_doc => { - wrapper.empty(); - const contact = this.contact = contact_doc; - if (!contact) { - this.setup_unknown_caller(wrapper); - } else { - this.setup_known_caller(wrapper); - this.set_call_status(); - this.make_last_interaction_section(); - } - }); - } - - setup_unknown_caller(wrapper) { - wrapper.append(` -
- ${__('Unknown Number')}: ${this.caller_number} - -
- `).find('button').click( - () => frappe.set_route(`Form/Contact/New Contact?phone=${this.caller_number}`) - ); - } - - setup_known_caller(wrapper) { - const contact = this.contact; - const contact_name = frappe.utils.get_form_link(contact.doctype, contact.name, true, this.get_caller_name()); - const links = contact.links ? contact.links : []; - - let contact_links = ''; - - links.forEach(link => { - contact_links += `
${link.link_doctype}: ${frappe.utils.get_form_link(link.link_doctype, link.link_name, true)}
`; - }); - wrapper.append(` -
- ${frappe.avatar(null, 'avatar-xl', contact.name, contact.image || '')} -
-
${contact_name}
-
${contact.mobile_no || ''}
-
${contact.phone_no || ''}
- ${contact_links} -
-
- `); - } - set_indicator(color, blink=false) { let classes = `indicator ${color} ${blink ? 'blink': ''}`; this.dialog.header.find('.indicator').attr('class', classes); @@ -129,7 +110,7 @@ class CallPopup { let title = ''; call_status = call_status || this.call_log.status; if (['Ringing'].includes(call_status) || !call_status) { - title = __('Incoming call from {0}', [this.get_caller_name()]); + title = __('Incoming call from {0}', [this.get_caller_name() || this.caller_number]); this.set_indicator('blue', true); } else if (call_status === 'In Progress') { title = __('Call Connected'); @@ -164,13 +145,13 @@ class CallPopup { if (!this.dialog.get_value('call_summary')) { this.close_modal(); } - }, 10000); + }, 30000); } make_last_interaction_section() { frappe.xcall('erpnext.crm.doctype.utils.get_last_interaction', { - 'number': this.caller_number, - 'reference_doc': this.contact + 'contact': this.call_log.contact, + 'lead': this.call_log.lead }).then(data => { const comm_field = this.dialog.get_field('last_communication'); if (data.last_communication) { @@ -188,9 +169,12 @@ class CallPopup { } }); } + get_caller_name() { - return this.contact ? this.contact.lead_name || this.contact.name || '' : this.caller_number; + let log = this.call_log; + return log.contact_name || log.lead_name; } + setup_listener() { frappe.realtime.on(`call_${this.call_log.id}_disconnected`, call_log => { this.call_disconnected(call_log); From e7d277d51e0d7485ae66b1c8980391fa151ec8a8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 16 Jul 2019 13:53:41 +0530 Subject: [PATCH 02/10] fix: Last communication query --- erpnext/communication/doctype/call_log/call_log.json | 7 +------ erpnext/crm/doctype/utils.py | 8 ++++---- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/erpnext/communication/doctype/call_log/call_log.json b/erpnext/communication/doctype/call_log/call_log.json index 43da3b3f85..508ade9764 100644 --- a/erpnext/communication/doctype/call_log/call_log.json +++ b/erpnext/communication/doctype/call_log/call_log.json @@ -145,13 +145,8 @@ "write": 1 }, { - "email": 1, - "export": 1, - "print": 1, "read": 1, - "report": 1, - "role": "Employee", - "share": 1 + "role": "Employee" } ], "sort_field": "modified", diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py index 0cd2557cc8..7e9a8f4688 100644 --- a/erpnext/crm/doctype/utils.py +++ b/erpnext/crm/doctype/utils.py @@ -20,7 +20,7 @@ def get_last_interaction(contact=None, lead=None): if query_condition: # remove extra appended 'OR' query_condition = query_condition[:-2] - communication = frappe.db.sql(""" + last_communication = frappe.db.sql(""" SELECT `name`, `content` FROM `tabCommunication` WHERE @@ -28,12 +28,12 @@ def get_last_interaction(contact=None, lead=None): ({}) ORDER BY `modified` LIMIT 1 - """.format(query_condition)) # nosec + """.format(query_condition), as_dict=1) # nosec if lead: last_communication = frappe.get_all('Communication', filters={ - 'reference_doctype': reference_doc.doctype, - 'reference_name': reference_doc.name, + 'reference_doctype': 'Contact', + 'reference_name': contact, 'sent_or_received': 'Received' }, fields=['name', 'content'], limit=1) From 82cfccab2f4fe20b4c9ad730b8530bbec510b24b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 17 Jul 2019 11:52:35 +0530 Subject: [PATCH 03/10] fix: Get user_id of employee instead of name - Remove caching from lead --- erpnext/communication/doctype/call_log/call_log.py | 5 +++-- erpnext/crm/doctype/lead/lead.py | 11 ----------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/communication/doctype/call_log/call_log.py index 8d9e39b4c7..fae649cd50 100644 --- a/erpnext/communication/doctype/call_log/call_log.py +++ b/erpnext/communication/doctype/call_log/call_log.py @@ -46,9 +46,10 @@ def get_employee_email(number): employees = frappe.get_all('Employee', or_filters={ 'phone': ['like', '%{}'.format(number)], - }, limit=1) + 'user_id': ['!=', ''] + }, fields=['user_id'], limit=1) - employee = employees[0].name if employees else None + employee = employees[0].user_id if employees else None frappe.cache().hset('employee_with_number', number, employee) return employee \ No newline at end of file diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 15174ef7a2..3ee68447b6 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -23,13 +23,6 @@ class Lead(SellingController): self.get("__onload").is_customer = customer load_address_and_contact(self) - def on_update(self): - doc = self.get_doc_before_save() - for field in ['mobile_no', 'phone']: - old_number = doc.get(field) - if old_number and old_number != self.get(field): - frappe.cache().hdel('lead_with_number', old_number) - def validate(self): self.set_lead_name() self._prev = frappe._dict({ @@ -241,15 +234,11 @@ def make_lead_from_communication(communication, ignore_communication_links=False def get_lead_with_phone_number(number): if not number: return - lead = frappe.cache().hget('lead_with_number', number) - if lead: return lead - leads = frappe.get_all('Lead', or_filters={ 'phone': ['like', '%{}'.format(number)], 'mobile_no': ['like', '%{}'.format(number)] }, limit=1) lead = leads[0].name if leads else None - frappe.cache().hset('lead_with_number', number, lead) return lead \ No newline at end of file From 095748030582eb2846ba2131aa10b9a4ea6e6139 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 5 Aug 2019 11:32:51 +0530 Subject: [PATCH 04/10] fix: Show popup to employees with same phone number --- .../doctype/call_log/call_log.py | 24 ++++++++--------- erpnext/crm/doctype/utils.py | 8 +++--- erpnext/hr/doctype/employee/employee.py | 8 ++++++ erpnext/public/js/call_popup/call_popup.js | 27 +++++++++++-------- 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/communication/doctype/call_log/call_log.py index fae649cd50..cf5c12e0dc 100644 --- a/erpnext/communication/doctype/call_log/call_log.py +++ b/erpnext/communication/doctype/call_log/call_log.py @@ -28,28 +28,28 @@ class CallLog(Document): self.trigger_call_popup() def trigger_call_popup(self): - employee_email = get_employee_email(self.to) - if employee_email: - frappe.publish_realtime('show_call_popup', self, user=employee_email) + employee_emails = get_employee_emails(self.to) + for email in employee_emails: + frappe.publish_realtime('show_call_popup', self, user=email) @frappe.whitelist() def add_call_summary(call_log, summary): doc = frappe.get_doc('Call Log', call_log) doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '

' + summary) -def get_employee_email(number): +def get_employee_emails(number): + '''Returns employee's emails of employees that have passed phone number''' if not number: return - number = number.lstrip('0') - employee = frappe.cache().hget('employee_with_number', number) - if employee: return employee + employee_emails = frappe.cache().hget('employees_with_number', number) + if employee_emails: return employee_emails - employees = frappe.get_all('Employee', or_filters={ - 'phone': ['like', '%{}'.format(number)], + employees = frappe.get_all('Employee', filters={ + 'cell_number': ['like', '%{}'.format(number.lstrip('0'))], 'user_id': ['!=', ''] - }, fields=['user_id'], limit=1) + }, fields=['user_id']) - employee = employees[0].user_id if employees else None - frappe.cache().hset('employee_with_number', number, employee) + employee_emails = [employee.user_id for employee in employees] + frappe.cache().hset('employees_with_number', number, employee_emails) return employee \ No newline at end of file diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py index 7e9a8f4688..c809469efe 100644 --- a/erpnext/crm/doctype/utils.py +++ b/erpnext/crm/doctype/utils.py @@ -7,8 +7,8 @@ def get_last_interaction(contact=None, lead=None): if not contact and not lead: return - last_communication = {} - last_issue = {} + last_communication = None + last_issue = None if contact: query_condition = '' contact = frappe.get_doc('Contact', contact) @@ -32,8 +32,8 @@ def get_last_interaction(contact=None, lead=None): if lead: last_communication = frappe.get_all('Communication', filters={ - 'reference_doctype': 'Contact', - 'reference_name': contact, + 'reference_doctype': 'Lead', + 'reference_name': lead, 'sent_or_received': 'Received' }, fields=['name', 'content'], limit=1) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index cf418b0e8f..67f7443efd 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -76,6 +76,7 @@ class Employee(NestedSet): if self.user_id: self.update_user() self.update_user_permissions() + self.reset_employee_emails_cache() def update_user_permissions(self): if not self.create_user_permission: return @@ -214,6 +215,13 @@ class Employee(NestedSet): doc.validate_employee_creation() doc.db_set("employee", self.name) + def reset_employee_emails_cache(self): + prev_doc = self.get_doc_before_save() + if (self.cell_number != prev_doc.cell_number or + self.user_id != prev_doc.user_id): + frappe.cache().hdel('employees_with_number', prev_doc.cell_number) + frappe.cache().hdel('employees_with_number', self.cell_number) + def get_timeline_data(doctype, name): '''Return timeline for attendance''' return dict(frappe.db.sql('''select unix_timestamp(attendance_date), count(*) diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js index 847b501ea3..5278b322a4 100644 --- a/erpnext/public/js/call_popup/call_popup.js +++ b/erpnext/public/js/call_popup/call_popup.js @@ -49,18 +49,19 @@ class CallPopup { 'fieldtype': 'Section Break', 'label': __('Activity'), 'depends_on': () => this.get_caller_name() + }, { + 'fieldtype': 'Small Text', + 'label': __('Last Issue'), + 'fieldname': 'last_issue', + 'read_only': true, + 'depends_on': () => this.call_log.contact, + 'default': `${__('No issue has been raised by the caller.')}` }, { 'fieldtype': 'Small Text', 'label': __('Last Communication'), 'fieldname': 'last_communication', 'read_only': true, 'default': `${__('No communication found.')}` - }, { - 'fieldtype': 'Small Text', - 'label': __('Last Issue'), - 'fieldname': 'last_issue', - 'read_only': true, - 'default': `${__('No issue raised by the customer.')}` }, { 'fieldtype': 'Section Break', }, { @@ -79,13 +80,15 @@ class CallPopup { }).then(() => { this.close_modal(); frappe.show_alert({ - message: `${__('Call Summary Saved')} + message: ` + ${__('Call Summary Saved')}
${__('View call log')} - `, + + `, indicator: 'green' }); }); @@ -163,9 +166,11 @@ class CallPopup { const issue = data.last_issue; const issue_field = this.dialog.get_field("last_issue"); issue_field.set_value(issue.subject); - issue_field.$wrapper.append(` - ${__('View all issues from {0}', [issue.customer])} - `); + issue_field.$wrapper.append(` + + ${__('View all issues from {0}', [issue.customer])} + + `); } }); } From 7d3f1fef1c8ca1f9165826ca521d792c5ec4957e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 5 Aug 2019 21:21:45 +0530 Subject: [PATCH 05/10] fix: Employee selection for call popup - Check if employee with matched number is also scheduled to receive popup --- .../doctype/call_log/call_log.py | 19 +++++++++++++------ erpnext/crm/doctype/utils.py | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/communication/doctype/call_log/call_log.py index cf5c12e0dc..d29794ebc8 100644 --- a/erpnext/communication/doctype/call_log/call_log.py +++ b/erpnext/communication/doctype/call_log/call_log.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from erpnext.crm.doctype.utils import get_employee_emails_for_popup +from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number @@ -28,8 +28,16 @@ class CallLog(Document): self.trigger_call_popup() def trigger_call_popup(self): - employee_emails = get_employee_emails(self.to) - for email in employee_emails: + scheduled_employees = get_scheduled_employees_for_popup(self.to) + employee_emails = get_employees_with_number(self.to) + + # check if employees with matched number are scheduled to receive popup + emails = set(scheduled_employees).intersection(employee_emails) + + # # if no employee found with matching phone number then show popup to scheduled employees + # emails = emails or scheduled_employees if employee_emails + + for email in emails: frappe.publish_realtime('show_call_popup', self, user=email) @frappe.whitelist() @@ -37,9 +45,8 @@ def add_call_summary(call_log, summary): doc = frappe.get_doc('Call Log', call_log) doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '

' + summary) -def get_employee_emails(number): - '''Returns employee's emails of employees that have passed phone number''' - if not number: return +def get_employees_with_number(number): + if not number: return [] employee_emails = frappe.cache().hget('employees_with_number', number) if employee_emails: return employee_emails diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py index c809469efe..1cfd89c4ea 100644 --- a/erpnext/crm/doctype/utils.py +++ b/erpnext/crm/doctype/utils.py @@ -52,7 +52,7 @@ def get_last_issue_from_customer(customer_name): return issues[0] if issues else None -def get_employee_emails_for_popup(communication_medium): +def get_scheduled_employees_for_popup(communication_medium): now_time = frappe.utils.nowtime() weekday = frappe.utils.get_weekday() From 89d8a0b6fd09dae416dfe8225b684ec3d09a6d65 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 6 Aug 2019 05:44:13 +0530 Subject: [PATCH 06/10] fix: reset_employee_emails_cache method --- erpnext/hr/doctype/employee/employee.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 67f7443efd..d407945da7 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -217,10 +217,12 @@ class Employee(NestedSet): def reset_employee_emails_cache(self): prev_doc = self.get_doc_before_save() - if (self.cell_number != prev_doc.cell_number or - self.user_id != prev_doc.user_id): - frappe.cache().hdel('employees_with_number', prev_doc.cell_number) - frappe.cache().hdel('employees_with_number', self.cell_number) + cell_number = self.get('cell_number') + prev_number = prev_doc.get('cell_number') + if (cell_number != prev_number or + self.get('user_id') != prev_doc.get('user_id')): + frappe.cache().hdel('employees_with_number', cell_number) + frappe.cache().hdel('employees_with_number', prev_number) def get_timeline_data(doctype, name): '''Return timeline for attendance''' From 4ca82f9308de391fbe28727a45eecfc27f68f3ac Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 6 Aug 2019 05:49:01 +0530 Subject: [PATCH 07/10] fix: Show contact and lead name in list view --- erpnext/communication/doctype/call_log/call_log.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/communication/doctype/call_log/call_log.json b/erpnext/communication/doctype/call_log/call_log.json index 508ade9764..cfc08eb084 100644 --- a/erpnext/communication/doctype/call_log/call_log.json +++ b/erpnext/communication/doctype/call_log/call_log.json @@ -109,6 +109,7 @@ "fieldname": "contact_name", "fieldtype": "Data", "hidden": 1, + "in_list_view": 1, "label": "Contact Name", "read_only": 1 }, @@ -121,12 +122,13 @@ "fieldname": "lead_name", "fieldtype": "Data", "hidden": 1, + "in_list_view": 1, "label": "Lead Name", "read_only": 1 } ], "in_create": 1, - "modified": "2019-07-15 19:18:31.267379", + "modified": "2019-08-06 05:46:53.144683", "modified_by": "Administrator", "module": "Communication", "name": "Call Log", From 9f4b27011690f2bac4cf22552d94dc057eef26d7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 6 Aug 2019 09:03:23 +0530 Subject: [PATCH 08/10] fix: prev_doc --- erpnext/hr/doctype/employee/employee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index d407945da7..3fc330e2d2 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -216,7 +216,7 @@ class Employee(NestedSet): doc.db_set("employee", self.name) def reset_employee_emails_cache(self): - prev_doc = self.get_doc_before_save() + prev_doc = self.get_doc_before_save() or {} cell_number = self.get('cell_number') prev_number = prev_doc.get('cell_number') if (cell_number != prev_number or From 429bfcfd83d3b46bb2cf725307889f96a6660407 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 8 Aug 2019 19:16:32 +0530 Subject: [PATCH 09/10] fix: Make requested changes --- .../communication/doctype/call_log/call_log.py | 2 ++ erpnext/crm/doctype/utils.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/communication/doctype/call_log/call_log.py index d29794ebc8..fdbdb634a0 100644 --- a/erpnext/communication/doctype/call_log/call_log.py +++ b/erpnext/communication/doctype/call_log/call_log.py @@ -12,6 +12,8 @@ from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number class CallLog(Document): def before_insert(self): + # strip 0 from the start of the number for proper number comparisions + # eg. 07888383332 should match with 7888383332 number = self.get('from').lstrip('0') self.contact = get_contact_with_phone_number(number) self.lead = get_lead_with_phone_number(number) diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py index 1cfd89c4ea..55532761c2 100644 --- a/erpnext/crm/doctype/utils.py +++ b/erpnext/crm/doctype/utils.py @@ -11,11 +11,13 @@ def get_last_interaction(contact=None, lead=None): last_issue = None if contact: query_condition = '' + values = [] contact = frappe.get_doc('Contact', contact) for link in contact.links: if link.link_doctype == 'Customer': last_issue = get_last_issue_from_customer(link.link_name) - query_condition += "(`reference_doctype`='{}' AND `reference_name`='{}') OR".format(link.link_doctype, link.link_name) + query_condition += "(`reference_doctype`=%s AND `reference_name`=%s) OR" + values += [link_link_doctype, link_link_name] if query_condition: # remove extra appended 'OR' @@ -23,19 +25,18 @@ def get_last_interaction(contact=None, lead=None): last_communication = frappe.db.sql(""" SELECT `name`, `content` FROM `tabCommunication` - WHERE - `sent_or_received`='Received' AND - ({}) + WHERE `sent_or_received`='Received' + AND ({}) ORDER BY `modified` LIMIT 1 - """.format(query_condition), as_dict=1) # nosec + """.format(query_condition), values, as_dict=1) # nosec if lead: last_communication = frappe.get_all('Communication', filters={ 'reference_doctype': 'Lead', 'reference_name': lead, 'sent_or_received': 'Received' - }, fields=['name', 'content'], limit=1) + }, fields=['name', 'content'], order_by='`creation` DESC', limit=1) last_communication = last_communication[0] if last_communication else None @@ -47,7 +48,7 @@ def get_last_interaction(contact=None, lead=None): def get_last_issue_from_customer(customer_name): issues = frappe.get_all('Issue', { 'customer': customer_name - }, ['name', 'subject', 'customer'], limit=1) + }, ['name', 'subject', 'customer'], order_by='`creation` DESC', limit=1) return issues[0] if issues else None From d4edd284e699c1d6626c46e52ba10c8edaf84c73 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 9 Aug 2019 19:23:04 +0530 Subject: [PATCH 10/10] fix: Set Contact or Lead for call log - Set contact or lead to call log on new Contact or Lead creation --- .../doctype/call_log/call_log.py | 24 ++++++++++++++++++- erpnext/hooks.py | 8 +++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/communication/doctype/call_log/call_log.py index fdbdb634a0..c9fdfbe447 100644 --- a/erpnext/communication/doctype/call_log/call_log.py +++ b/erpnext/communication/doctype/call_log/call_log.py @@ -61,4 +61,26 @@ def get_employees_with_number(number): employee_emails = [employee.user_id for employee in employees] frappe.cache().hset('employees_with_number', number, employee_emails) - return employee \ No newline at end of file + return employee + +def set_caller_information(doc, state): + '''Called from hoooks on creation of Lead or Contact''' + if doc.doctype not in ['Lead', 'Contact']: return + + numbers = [doc.get('phone'), doc.get('mobile_no')] + for_doc = doc.doctype.lower() + + for number in numbers: + if not number: continue + print(number) + filters = frappe._dict({ + 'from': ['like', '%{}'.format(number.lstrip('0'))], + for_doc: '' + }) + + logs = frappe.get_all('Call Log', filters=filters) + + for log in logs: + call_log = frappe.get_doc('Call Log', log.name) + call_log.set(for_doc, doc.name) + call_log.save(ignore_permissions=True) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 47d1a68efc..be9a4fb264 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -231,8 +231,12 @@ doc_events = { ('Sales Invoice', 'Purchase Invoice', 'Delivery Note'): { 'validate': 'erpnext.regional.india.utils.set_place_of_supply' }, - "Contact":{ - "on_trash": "erpnext.support.doctype.issue.issue.update_issue" + "Contact": { + "on_trash": "erpnext.support.doctype.issue.issue.update_issue", + "after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information" + }, + "Lead": { + "after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information" }, "Email Unsubscribe": { "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"