feat: Communication refactor initial bringup

This commit is contained in:
Himanshu Warekar 2019-05-13 20:39:49 +05:30
parent fc9803553c
commit 8381e0048a
6 changed files with 260 additions and 57 deletions

View file

@ -54,7 +54,15 @@ frappe.ui.form.on("Communication", {
frm.trigger('show_relink_dialog');
});
if(frm.doc.communication_type=="Communication"
frm.add_custom_button(__("Add link"), function() {
frm.trigger('show_add_link_dialog');
});
frm.add_custom_button(__("Remove link"), function() {
frm.trigger('show_remove_link_dialog');
});
if(frm.doc.communication_type=="Communication"
&& frm.doc.communication_medium == "Email"
&& frm.doc.sent_or_received == "Received") {
@ -90,7 +98,7 @@ frappe.ui.form.on("Communication", {
}
}
if(frm.doc.communication_type=="Communication"
if(frm.doc.communication_type=="Communication"
&& frm.doc.communication_medium == "Phone"
&& frm.doc.sent_or_received == "Received"){
@ -148,6 +156,74 @@ frappe.ui.form.on("Communication", {
d.show();
},
show_add_link_dialog: function(frm){
var d = new frappe.ui.Dialog ({
title: __("Add new link to Communication"),
fields: [{
"fieldtype": "Link",
"options": "DocType",
"label": __("Document Type"),
"fieldname": "link_doctype",
"reqd": 1
},
{
"fieldtype": "Dynamic Link",
"options": "link_doctype",
"label": __("Document Name"),
"fieldname": "link_name",
"reqd": 1
}],
primary_action: ({ link_doctype, link_name }) => {
d.hide();
frm.call('add_link', {
link_doctype,
link_name,
autosave: true
}).then(() => frm.refresh());
},
primary_action_label: __('Add Link')
});
d.fields_dict.link_doctype.get_query = function() {
return {
"filters": {
"name": ["!=", "Communication"],
}
};
};
d.show();
},
show_remove_link_dialog: function(frm){
let options = '';
for(var link in frm.doc.dynamic_links){
let dynamic_link = frm.doc.dynamic_links[link];
options += '\n' + dynamic_link.link_doctype + ': ' + dynamic_link.link_name;
}
var d = new frappe.ui.Dialog ({
title: __("Remove link from Communication"),
fields: [{
"fieldtype": "Select",
"options": options,
"label": __("Link"),
"fieldname": "link",
"reqd": 1
}],
primary_action: ({ link }) => {
d.hide();
frm.call('remove_link', {
link_doctype: link.split(":")[0].trim(),
link_name: link.split(":")[1].trim(),
autosave: true,
ignore_permissions: false
}).then(() => frm.refresh());
},
primary_action_label: __('Remove Link')
});
d.show();
},
mark_as_read_unread: function(frm) {
var action = frm.doc.seen? "Unread": "Read";
var flag = "(\\SEEN)";
@ -185,7 +261,7 @@ frappe.ui.form.on("Communication", {
forward_mail: function(frm) {
var args = frm.events.get_mail_args(frm)
$.extend(args, {
$.extend(args, {
forward: true,
subject: __("Fw: {0}", [frm.doc.subject]),
})

View file

@ -1,7 +1,7 @@
{
"allow_import": 1,
"creation": "2013-01-29 10:47:14",
"description": "Keep a track of all communications",
"description": "Keeps track of all communications",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
@ -43,12 +43,11 @@
"email_template",
"link_doctype",
"link_name",
"timeline_doctype",
"timeline_name",
"timeline_label",
"unread_notification_sent",
"seen",
"_user_tags",
"timeline_links_sections",
"dynamic_links",
"email_inbox",
"message_id",
"uid",
@ -298,25 +297,6 @@
"options": "link_doctype",
"read_only": 1
},
{
"fieldname": "timeline_doctype",
"fieldtype": "Link",
"label": "Timeline DocType",
"options": "DocType",
"read_only": 1
},
{
"fieldname": "timeline_name",
"fieldtype": "Dynamic Link",
"label": "Timeline Name",
"options": "timeline_doctype",
"read_only": 1
},
{
"fieldname": "timeline_label",
"fieldtype": "Data",
"label": "Timeline field Name"
},
{
"default": "0",
"fieldname": "unread_notification_sent",
@ -398,11 +378,22 @@
"label": "Email Template",
"options": "Email Template",
"read_only": 1
},
{
"fieldname": "timeline_links_sections",
"fieldtype": "Section Break",
"label": "Timeline Links"
},
{
"fieldname": "dynamic_links",
"fieldtype": "Table",
"label": "Dynamic Links",
"options": "Dynamic Link"
}
],
"icon": "fa fa-comment",
"idx": 1,
"modified": "2019-05-04 15:36:35.818714",
"modified": "2019-05-13 19:55:35.757242",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",

View file

@ -8,7 +8,7 @@ from frappe.model.document import Document
from frappe.utils import validate_email_address, get_fullname, strip_html, cstr
from frappe.core.doctype.communication.email import (validate_email,
notify, _notify, update_parent_mins_to_first_response)
from frappe.core.utils import get_parent_doc, set_timeline_doc
from frappe.core.utils import get_parent_doc
from frappe.utils.bot import BotReply
from frappe.utils import parse_addr
from frappe.core.doctype.comment.comment import update_comment_in_doc
@ -58,7 +58,6 @@ class Communication(Document):
self.set_sender_full_name()
validate_email(self)
set_timeline_doc(self)
def validate_reference(self):
if self.reference_doctype and self.reference_name:
@ -231,26 +230,86 @@ class Communication(Document):
if commit:
frappe.db.commit()
# Timeline Links
def deduplicate_dynamic_links(self):
if self.dynamic_links:
links, duplicate = [], False
for l in self.dynamic_links:
t = (l.link_doctype, l.link_name)
if not t in links:
links.append(t)
else:
duplicate = True
if duplicate:
del self.dynamic_links[:] # make it python 2 compatible as list.clear() is python 3 only
for l in links:
self.add_link(link_doctype=l[0], link_name=l[1])
def validate_circular_links(self):
for dynamic_link in self.dynamic_links:
# Prevent circular linking of Communication DocTypes
if dynamic_link.link_doctype == "Communication":
circular_linking = False
circular_level_1 = get_timeline_parent_doc(dynamic_link.link_doctype, dynamic_link.link_name)
# Level 1
if circular_level_1:
for link in circular_level_1.dynamic_links:
if link.link_doctype == "Communication":
circular_level_2 = get_timeline_parent_doc(link.link_doctype, link.link_name)
# Level 2
if circular_level_2:
for ref_link in circular_level_2.dynamic_links:
if ref_link.link_doctype == "Communication":
circular_level_3 = get_timeline_parent_doc(ref_link.link_doctype, ref_link.link_name)
# Level 3
if circular_level_3:
if circular_level_3.name == self.name:
circular_linking = True
break
if circular_linking:
frappe.throw(_("Please make sure the Timeline Communication Docs are not circularly linked."), frappe.CircularLinkingError)
def add_link(self, link_doctype, link_name, autosave=False):
self.append("dynamic_links",
{
"link_doctype": link_doctype,
"link_name": link_name
}
)
if autosave:
self.save(ignore_permissions=True)
def get_links(self):
return self.dynamic_links
def remove_link(self, link_doctype, link_name, autosave=False, ignore_permissions=True):
for l in self.dynamic_links:
if l.link_doctype == link_doctype and l.link_name == link_name:
self.dynamic_links.remove(l)
if autosave:
self.save(ignore_permissions=ignore_permissions)
def on_doctype_update():
"""Add indexes in `tabCommunication`"""
frappe.db.add_index("Communication", ["reference_doctype", "reference_name"])
frappe.db.add_index("Communication", ["timeline_doctype", "timeline_name"])
frappe.db.add_index("Communication", ["link_doctype", "link_name"])
frappe.db.add_index("Communication", ["status", "communication_type"])
def has_permission(doc, ptype, user):
if ptype=="read":
if (doc.reference_doctype == "Communication" and doc.reference_name == doc.name) \
or (doc.timeline_doctype == "Communication" and doc.timeline_name == doc.name):
return
if doc.reference_doctype == "Communication" and doc.reference_name == doc.name:
return
if doc.reference_doctype and doc.reference_name:
if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name):
return True
if doc.timeline_doctype and doc.timeline_name:
if frappe.has_permission(doc.timeline_doctype, ptype="read", doc=doc.timeline_name):
return True
def get_permission_query_conditions_for_communication(user):
if not user: user = frappe.session.user
@ -270,3 +329,9 @@ def get_permission_query_conditions_for_communication(user):
email_accounts = [ '"%s"'%account.get("email_account") for account in accounts ]
return """tabCommunication.email_account in ({email_accounts})"""\
.format(email_accounts=','.join(email_accounts))
def get_timeline_parent_doc(link_doctype, link_name):
"""Returns document of `link_doctype`, `link_name`"""
if link_doctype and link_name:
parent_doc = frappe.get_doc(link_doctype, link_name)
return parent_doc if parent_doc else None

View file

@ -17,6 +17,7 @@ import frappe.email.smtp
import time
from frappe import _
from frappe.utils.background_jobs import enqueue
from email.utils import parseaddr
@frappe.whitelist()
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
@ -78,6 +79,15 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
# if no reference given, then send it against the communication
comm.db_set(dict(reference_doctype='Communication', reference_name=comm.name))
# contacts = get_contacts([sender, recipients, cc, bcc])
# for contact_name in contacts:
# comm.add_link('Contact', contact_name)
# #link contact's dynamic links to communication
# add_contact_links_to_communication(comm, contact_name)
# comm.save(ignore_permissions=True)
if isinstance(attachments, string_types):
attachments = json.loads(attachments)
@ -559,3 +569,38 @@ def mark_email_as_seen(name=None):
frappe.response["filename"] = "imaginary_pixel.png"
frappe.response["filecontent"] = buffered_obj.getvalue()
def get_contacts(email_strings):
email_addrs = []
for email_string in email_strings:
if email_string:
for email in email_string.split(","):
parsed_email = parseaddr(email)[1]
if parsed_email:
email_addrs.append(parsed_email)
contacts = []
for email in email_addrs:
contact_name = frappe.db.get_value('Contact', {'email_id': email})
if not contact_name:
contact = frappe.get_doc({
"doctype": "Contact",
"first_name": frappe.unscrub(email.split("@")[0]),
"email_id": email
}).insert(ignore_permissions=True)
contact_name = contact.name
contacts.append(contact_name)
return contacts
def add_contact_links_to_communication(communication, contact_name):
contact_links = frappe.get_list("Dynamic Link", filters={
"parenttype": "Contact",
"parent": contact_name
}, fields=["link_doctype", "link_name"])
if contact_links:
for contact_link in contact_links:
communication.add_link(contact_link.link_doctype, contact_link.link_name)

View file

@ -161,36 +161,55 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
group_by=None, as_dict=True):
'''Returns list of communications for a given document'''
if not fields:
fields = '''`name`, `communication_type`,`communication_medium`, `comment_type`,
`communication_date`, `content`, `sender`, `sender_full_name`, `cc`, `bcc`,
`creation`, `subject`, `delivery_status`, `_liked_by`,
`timeline_doctype`, `timeline_name`, `reference_doctype`, `reference_name`,
`link_doctype`, `link_name`, `read_by_recipient`, `rating`, 'Communication' AS `doctype`'''
fields = '''
`tabCommunication`.name, `tabCommunication`.communication_type, `tabCommunication`.communication_medium,
`tabCommunication`.comment_type, `tabCommunication`.communication_date, `tabCommunication`.content,
`tabCommunication`.sender, `tabCommunication`.sender_full_name, `tabCommunication`.cc, `tabCommunication`.bcc,
`tabCommunication`.creation, `tabCommunication`.subject, `tabCommunication`.delivery_status,
`tabCommunication`._liked_by, `tabCommunication`.reference_doctype, `tabCommunication`.reference_name,
`tabCommunication`.link_doctype, `tabCommunication`.link_name, `tabCommunication`.read_by_recipient,
`tabCommunication`.rating, `tabDynamic Link`.link_doctype, `tabDynamic Link`.link_name
'''
conditions = '''communication_type in ('Communication', 'Feedback')
and (
(reference_doctype=%(doctype)s and reference_name=%(name)s)
conditions = '''
`tabCommunication`.communication_type in ('Communication', 'Feedback')
and (
(`tabCommunication`.reference_doctype=%(doctype)s and `tabCommunication`.reference_name=%(name)s)
or (
(timeline_doctype=%(doctype)s and timeline_name=%(name)s)
and (communication_type='Communication')
(`tabDynamic Link`.link_doctype=%(doctype)s and `tabDynamic Link`.link_name=%(name)s)
and (`tabCommunication`.communication_type='Communication')
)
)'''
)
'''
if after:
# find after a particular date
conditions+= ' and creation > {0}'.format(after)
conditions += '''
and `tabCommunication`.creation > {0}
'''.format(after)
if doctype=='User':
conditions+= " and not (reference_doctype='User' and communication_type='Communication')"
conditions += '''
and not (`tabCommunication`.reference_doctype='User' and `tabCommunication`.communication_type='Communication')
'''
communications = frappe.db.sql("""select {fields}
communications = frappe.db.sql('''
select {fields}
from `tabCommunication`
left join `tabDynamic Link`
on `tabCommunication`.name=`tabDynamic Link`.parent
where {conditions} {group_by}
order by creation desc LIMIT %(limit)s OFFSET %(start)s""".format(
fields = fields, conditions=conditions, group_by=group_by or ""),
{ "doctype": doctype, "name": name, "start": frappe.utils.cint(start), "limit": limit },
as_dict=as_dict)
order by `tabCommunication`.creation desc
limit %(limit)s offset %(start)s'''.format(
fields = fields,
conditions=conditions,
group_by=group_by or ""
),{
"doctype": doctype,
"name": name,
"start": frappe.utils.cint(start),
"limit": limit
}, as_dict=as_dict, debug=True)
return communications
@ -245,4 +264,4 @@ def get_view_logs(doctype, docname):
if view_logs:
logs = view_logs
return logs
return logs

View file

@ -20,7 +20,7 @@ from datetime import datetime, timedelta
from frappe.desk.form import assign_to
from frappe.utils.user import get_system_managers
from frappe.utils.background_jobs import enqueue, get_jobs
from frappe.core.doctype.communication.email import set_incoming_outgoing_accounts
from frappe.core.doctype.communication.email import set_incoming_outgoing_accounts, get_contacts, add_contact_links_to_communication
from frappe.utils.scheduler import log
from frappe.utils.html_utils import clean_email_html
@ -386,6 +386,13 @@ class EmailAccount(Document):
users = list(set([ user.get("parent") for user in users ]))
communication._seen = json.dumps(users)
# contacts = get_contacts([sender, recipients, cc, bcc])
# for contact_name in contacts:
# comm.add_link('Contact', contact_name)
# #link contact's dynamic links to communication
# add_contact_links_to_communication(comm, contact_name)
communication.flags.in_receive = True
communication.insert(ignore_permissions = 1)