feat(Communication): Move Timeline DocType and Name to Dynamic Links (#7467)
feat(Communication): Move Timeline DocType and Name to Dynamic Links
This commit is contained in:
commit
cf8a53e433
14 changed files with 407 additions and 227 deletions
|
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
|||
from frappe import _
|
||||
from frappe.utils import get_fullname, now
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.utils import get_parent_doc, set_timeline_doc
|
||||
from frappe.core.utils import set_timeline_doc
|
||||
import frappe
|
||||
|
||||
class ActivityLog(Document):
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -41,14 +41,11 @@
|
|||
"user",
|
||||
"column_break_27",
|
||||
"email_template",
|
||||
"link_doctype",
|
||||
"link_name",
|
||||
"timeline_doctype",
|
||||
"timeline_name",
|
||||
"timeline_label",
|
||||
"unread_notification_sent",
|
||||
"seen",
|
||||
"_user_tags",
|
||||
"timeline_links_sections",
|
||||
"timeline_links",
|
||||
"email_inbox",
|
||||
"message_id",
|
||||
"uid",
|
||||
|
|
@ -204,6 +201,7 @@
|
|||
"label": "Date"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "read_receipt",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sent Read Receipt",
|
||||
|
|
@ -220,6 +218,7 @@
|
|||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "read_by_recipient",
|
||||
"fieldtype": "Check",
|
||||
"label": "Read by Recipient",
|
||||
|
|
@ -284,39 +283,6 @@
|
|||
"fieldname": "column_break_27",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "link_doctype",
|
||||
"fieldtype": "Link",
|
||||
"label": "Link DocType",
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "link_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Link Name",
|
||||
"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",
|
||||
|
|
@ -325,6 +291,7 @@
|
|||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "seen",
|
||||
"fieldtype": "Check",
|
||||
"label": "Seen",
|
||||
|
|
@ -368,6 +335,7 @@
|
|||
"options": "Open\nSpam\nTrash"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "has_attachment",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
|
|
@ -398,11 +366,24 @@
|
|||
"label": "Email Template",
|
||||
"options": "Email Template",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "timeline_links_sections",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Timeline Links"
|
||||
},
|
||||
{
|
||||
"fieldname": "timeline_links",
|
||||
"fieldtype": "Table",
|
||||
"label": "Timeline Links",
|
||||
"options": "Dynamic Link",
|
||||
"permlevel": 2
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-comment",
|
||||
"idx": 1,
|
||||
"modified": "2019-05-04 15:36:35.818714",
|
||||
"modified": "2019-05-20 14:14:01.514493",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Communication",
|
||||
|
|
@ -428,6 +409,18 @@
|
|||
"role": "System Manager",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"permlevel": 2,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
|
|
@ -437,6 +430,7 @@
|
|||
}
|
||||
],
|
||||
"search_fields": "subject",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "subject",
|
||||
"track_changes": 1,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ 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
|
||||
|
||||
from email.utils import parseaddr
|
||||
from collections import Counter
|
||||
|
||||
exclude_from_linked_with = True
|
||||
|
|
@ -58,7 +58,10 @@ class Communication(Document):
|
|||
self.set_sender_full_name()
|
||||
|
||||
validate_email(self)
|
||||
set_timeline_doc(self)
|
||||
|
||||
if self.communication_medium == "Email":
|
||||
self.set_timeline_links()
|
||||
self.deduplicate_timeline_links()
|
||||
|
||||
def validate_reference(self):
|
||||
if self.reference_doctype and self.reference_name:
|
||||
|
|
@ -79,6 +82,7 @@ class Communication(Document):
|
|||
circular_linking = True
|
||||
break
|
||||
doc = get_parent_doc(doc)
|
||||
|
||||
if circular_linking:
|
||||
frappe.throw(_("Please make sure the Reference Communication Docs are not circularly linked."), frappe.CircularLinkingError)
|
||||
|
||||
|
|
@ -231,26 +235,66 @@ class Communication(Document):
|
|||
if commit:
|
||||
frappe.db.commit()
|
||||
|
||||
# Timeline Links
|
||||
def set_timeline_links(self):
|
||||
contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc])
|
||||
for contact_name in contacts:
|
||||
self.add_link('Contact', contact_name)
|
||||
|
||||
#link contact's dynamic links to communication
|
||||
add_contact_links_to_communication(self, contact_name)
|
||||
|
||||
def deduplicate_timeline_links(self):
|
||||
if self.timeline_links:
|
||||
links, duplicate = [], False
|
||||
|
||||
for l in self.timeline_links:
|
||||
t = (l.link_doctype, l.link_name)
|
||||
if not t in links:
|
||||
links.append(t)
|
||||
else:
|
||||
duplicate = True
|
||||
|
||||
if duplicate:
|
||||
del self.timeline_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 add_link(self, link_doctype, link_name, autosave=False):
|
||||
self.append("timeline_links",
|
||||
{
|
||||
"link_doctype": link_doctype,
|
||||
"link_name": link_name
|
||||
}
|
||||
)
|
||||
|
||||
if autosave:
|
||||
self.save(ignore_permissions=True)
|
||||
|
||||
def get_links(self):
|
||||
return self.timeline_links
|
||||
|
||||
def remove_link(self, link_doctype, link_name, autosave=False, ignore_permissions=True):
|
||||
for l in self.timeline_links:
|
||||
if l.link_doctype == link_doctype and l.link_name == link_name:
|
||||
self.timeline_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 +314,39 @@ 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_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)
|
||||
|
|
@ -71,12 +71,9 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
"message_id":get_message_id().strip(" <>"),
|
||||
"read_receipt":read_receipt,
|
||||
"has_attachment": 1 if attachments else 0
|
||||
})
|
||||
comm.insert(ignore_permissions=True)
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
if not doctype:
|
||||
# if no reference given, then send it against the communication
|
||||
comm.db_set(dict(reference_doctype='Communication', reference_name=comm.name))
|
||||
comm.save(ignore_permissions=True)
|
||||
|
||||
if isinstance(attachments, string_types):
|
||||
attachments = json.loads(attachments)
|
||||
|
|
@ -557,5 +554,4 @@ def mark_email_as_seen(name=None):
|
|||
|
||||
frappe.response["type"] = 'binary'
|
||||
frappe.response["filename"] = "imaginary_pixel.png"
|
||||
frappe.response["filecontent"] = buffered_obj.getvalue()
|
||||
|
||||
frappe.response["filecontent"] = buffered_obj.getvalue()
|
||||
|
|
@ -44,28 +44,126 @@ class TestCommunication(unittest.TestCase):
|
|||
self.assertFalse(frappe.utils.parse_addr(x)[0])
|
||||
|
||||
def test_circular_linking(self):
|
||||
content = "This was created to test circular linking"
|
||||
a = frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Communication",
|
||||
"content": content,
|
||||
}).insert()
|
||||
"content": "This was created to test circular linking: Communication A",
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
b = frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Communication",
|
||||
"content": content,
|
||||
"content": "This was created to test circular linking: Communication B",
|
||||
"reference_doctype": "Communication",
|
||||
"reference_name": a.name
|
||||
}).insert()
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
c = frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Communication",
|
||||
"content": content,
|
||||
"content": "This was created to test circular linking: Communication C",
|
||||
"reference_doctype": "Communication",
|
||||
"reference_name": b.name
|
||||
}).insert()
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
a = frappe.get_doc("Communication", a.name)
|
||||
a.reference_doctype = "Communication"
|
||||
a.reference_name = c.name
|
||||
|
||||
self.assertRaises(frappe.CircularLinkingError, a.save)
|
||||
|
||||
def test_deduplication_timeline_links(self):
|
||||
note = frappe.get_doc({
|
||||
"doctype": "Note",
|
||||
"title": "deduplication timeline links",
|
||||
"content": "deduplication timeline links"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
comm = frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Communication",
|
||||
"content": "Deduplication of Links",
|
||||
"communication_medium": "Email"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
#adding same link twice
|
||||
comm.add_link(link_doctype="Note", link_name=note.name, autosave=True)
|
||||
comm.add_link(link_doctype="Note", link_name=note.name, autosave=True)
|
||||
|
||||
comm = frappe.get_doc("Communication", comm.name)
|
||||
|
||||
self.assertNotEqual(2, len(comm.timeline_links))
|
||||
|
||||
def test_contacts_attached(self):
|
||||
contact_sender = frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"first_name": frappe.generate_hash(length=10),
|
||||
"email_id": "comm_sender@example.com"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
contact_recipient = frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"first_name": frappe.generate_hash(length=10),
|
||||
"email_id": "comm_recipient@example.com"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
contact_cc = frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"first_name": frappe.generate_hash(length=10),
|
||||
"email_id": "comm_cc@example.com"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
comm = frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"communication_medium": "Email",
|
||||
"subject": "Contacts Attached Test",
|
||||
"sender": "comm_sender@example.com",
|
||||
"recipients": "comm_recipient@example.com",
|
||||
"cc": "comm_cc@example.com"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
comm = frappe.get_doc("Communication", comm.name)
|
||||
|
||||
contact_links = []
|
||||
for timeline_link in comm.timeline_links:
|
||||
contact_links.append(timeline_link.link_name)
|
||||
|
||||
self.assertIn(contact_sender.name, contact_links)
|
||||
self.assertIn(contact_recipient.name, contact_links)
|
||||
self.assertIn(contact_cc.name, contact_links)
|
||||
|
||||
def test_get_communication_data(self):
|
||||
from frappe.desk.form.load import get_communication_data
|
||||
|
||||
note = frappe.get_doc({
|
||||
"doctype": "Note",
|
||||
"title": "get communication data",
|
||||
"content": "get communication data"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
comm_note_1 = frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Communication",
|
||||
"content": "Test Get Communication Data 1",
|
||||
"communication_medium": "Email"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
comm_note_1.add_link(link_doctype="Note", link_name=note.name, autosave=True)
|
||||
|
||||
comm_note_2 = frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Communication",
|
||||
"content": "Test Get Communication Data 2",
|
||||
"communication_medium": "Email"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
comm_note_2.add_link(link_doctype="Note", link_name=note.name, autosave=True)
|
||||
|
||||
comms = get_communication_data("Note", note.name, as_dict=True)
|
||||
|
||||
data = []
|
||||
for comm in comms:
|
||||
data.append(comm.name)
|
||||
|
||||
self.assertIn(comm_note_1.name, data)
|
||||
self.assertIn(comm_note_2.name, data)
|
||||
|
|
@ -1,125 +1,47 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-01-13 04:55:18.835023",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"creation": "2017-01-13 04:55:18.835023",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"link_doctype",
|
||||
"link_name",
|
||||
"link_title"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "link_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Link DocType",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "link_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Link DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "link_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Link Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "link_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "link_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Link Name",
|
||||
"options": "link_doctype",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "link_title",
|
||||
"fieldtype": "Read Only",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Link Title",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "link_title",
|
||||
"fieldtype": "Read Only",
|
||||
"in_list_view": 1,
|
||||
"label": "Link Title",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-01-17 14:25:49.140730",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Dynamic Link",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-05-16 19:54:31.400026",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Dynamic Link",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -43,10 +43,17 @@ class Event(Document):
|
|||
def sync_communication(self):
|
||||
if self.event_participants:
|
||||
for participant in self.event_participants:
|
||||
communication_name = frappe.db.get_value("Communication", dict(reference_doctype=self.doctype, reference_name=self.name, timeline_doctype=participant.reference_doctype, timeline_name=participant.reference_docname), "name")
|
||||
if communication_name:
|
||||
communication = frappe.get_doc("Communication", communication_name)
|
||||
self.update_communication(participant, communication)
|
||||
comms = frappe.get_list("Communication", filters=[
|
||||
["Communication", "reference_doctype", "=", self.doctype],
|
||||
["Communication", "reference_name", "=", self.name],
|
||||
["Dynamic Link", "link_doctype", "=", participant.reference_doctype],
|
||||
["Dynamic Link", "link_name", "=", participant.reference_docname]
|
||||
], fields=["name"])
|
||||
|
||||
if comms:
|
||||
for comm in comms:
|
||||
communication = frappe.get_doc("Communication", comm.name)
|
||||
self.update_communication(participant, communication)
|
||||
else:
|
||||
meta = frappe.get_meta(participant.reference_doctype)
|
||||
if hasattr(meta, "allow_events_in_timeline") and meta.allow_events_in_timeline==1:
|
||||
|
|
@ -62,12 +69,11 @@ class Event(Document):
|
|||
communication.subject = self.subject
|
||||
communication.content = self.description if self.description else self.subject
|
||||
communication.communication_date = self.starts_on
|
||||
communication.timeline_doctype = participant.reference_doctype
|
||||
communication.timeline_name = participant.reference_docname
|
||||
communication.reference_doctype = self.doctype
|
||||
communication.reference_name = self.name
|
||||
communication.communication_medium = communication_mapping[self.event_category] if self.event_category else ""
|
||||
communication.status = "Linked"
|
||||
communication.add_link(participant.reference_doctype, participant.reference_docname)
|
||||
communication.save(ignore_permissions=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
@ -76,9 +82,18 @@ def delete_communication(event, reference_doctype, reference_docname):
|
|||
if isinstance(event, string_types):
|
||||
event = json.loads(event)
|
||||
|
||||
communication_name = frappe.db.get_value("Communication", dict(reference_doctype=event["doctype"], reference_name=event["name"], timeline_doctype=deleted_participant.reference_doctype, timeline_name=deleted_participant.reference_docname), "name")
|
||||
if communication_name:
|
||||
deletion = frappe.get_doc("Communication", communication_name).delete()
|
||||
comms = frappe.get_list("Communication", filters=[
|
||||
["Communication", "reference_doctype", "=", event.get("doctype")],
|
||||
["Communication", "reference_name", "=", event.get("name")],
|
||||
["Dynamic Link", "link_doctype", "=", deleted_participant.reference_doctype],
|
||||
["Dynamic Link", "link_name", "=", deleted_participant.reference_docname]
|
||||
], fields=["name"])
|
||||
|
||||
if comms:
|
||||
deletion = []
|
||||
for comm in comms:
|
||||
delete = frappe.get_doc("Communication", comm.name).delete()
|
||||
deletion.append(delete)
|
||||
|
||||
return deletion
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,11 @@ class ToDo(Document):
|
|||
|
||||
def on_trash(self):
|
||||
# unlink todo from linked comments
|
||||
frappe.db.sql("""update `tabCommunication` set link_doctype=null, link_name=null
|
||||
where link_doctype=%(doctype)s and link_name=%(name)s""", {"doctype": self.doctype, "name": self.name})
|
||||
frappe.db.sql("""
|
||||
delete from `tabDynamic Link`
|
||||
where link_doctype=%(doctype)s and link_name=%(name)s""", {
|
||||
"doctype": self.doctype, "name": self.name
|
||||
})
|
||||
|
||||
self.update_in_reference()
|
||||
|
||||
|
|
|
|||
|
|
@ -160,36 +160,50 @@ 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`.read_by_recipient, `tabCommunication`.rating
|
||||
'''
|
||||
|
||||
conditions = '''communication_type = 'Communication'
|
||||
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 distinct {fields}
|
||||
from `tabCommunication`
|
||||
inner 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)
|
||||
|
||||
return communications
|
||||
|
||||
|
|
@ -229,4 +243,4 @@ def get_view_logs(doctype, docname):
|
|||
|
||||
if view_logs:
|
||||
logs = view_logs
|
||||
return logs
|
||||
return logs
|
||||
|
|
@ -387,7 +387,7 @@ class EmailAccount(Document):
|
|||
communication._seen = json.dumps(users)
|
||||
|
||||
communication.flags.in_receive = True
|
||||
communication.insert(ignore_permissions = 1)
|
||||
communication.insert(ignore_permissions=True)
|
||||
|
||||
# save attachments
|
||||
communication._attachments = email.save_attachments_in_doc(communication)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class TestEmailAccount(unittest.TestCase):
|
|||
email_account.db_set("enable_incoming", 0)
|
||||
|
||||
def test_incoming(self):
|
||||
frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
|
||||
cleanup("test_sender@example.com")
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-1.raw"), "r") as f:
|
||||
test_mails = [f.read()]
|
||||
|
|
@ -52,7 +52,8 @@ class TestEmailAccount(unittest.TestCase):
|
|||
"reference_name": comm.reference_name, "status":"Not Sent"}))
|
||||
|
||||
def test_incoming_with_attach(self):
|
||||
frappe.db.sql("DELETE FROM `tabCommunication` WHERE sender='test_sender@example.com'")
|
||||
cleanup("test_sender@example.com")
|
||||
|
||||
existing_file = frappe.get_doc({'doctype': 'File', 'file_name': 'erpnext-conf-14.png'})
|
||||
frappe.delete_doc("File", existing_file.name)
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ class TestEmailAccount(unittest.TestCase):
|
|||
|
||||
|
||||
def test_incoming_attached_email_from_outlook_plain_text_only(self):
|
||||
frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
|
||||
cleanup("test_sender@example.com")
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-3.raw"), "r") as f:
|
||||
test_mails = [f.read()]
|
||||
|
|
@ -88,7 +89,7 @@ class TestEmailAccount(unittest.TestCase):
|
|||
self.assertTrue("This is an e-mail message sent automatically by Microsoft Outlook while" in comm.content)
|
||||
|
||||
def test_incoming_attached_email_from_outlook_layers(self):
|
||||
frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
|
||||
cleanup("test_sender@example.com")
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-4.raw"), "r") as f:
|
||||
test_mails = [f.read()]
|
||||
|
|
@ -123,8 +124,7 @@ class TestEmailAccount(unittest.TestCase):
|
|||
self.assertTrue("test-mail-002" in sent_mail.get("Subject"))
|
||||
|
||||
def test_threading(self):
|
||||
frappe.db.sql("""delete from tabCommunication
|
||||
where sender in ('test_sender@example.com', 'test@example.com')""")
|
||||
cleanup(["in", ['test_sender@example.com', 'test@example.com']])
|
||||
|
||||
# send
|
||||
sent_name = make(subject = "Test", content="test content",
|
||||
|
|
@ -149,8 +149,7 @@ class TestEmailAccount(unittest.TestCase):
|
|||
self.assertEqual(comm.reference_name, sent.reference_name)
|
||||
|
||||
def test_threading_by_subject(self):
|
||||
frappe.db.sql("""delete from tabCommunication
|
||||
where sender in ('test_sender@example.com', 'test@example.com')""")
|
||||
cleanup(["in", ['test_sender@example.com', 'test@example.com']])
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "test_mails", "reply-2.raw"), "r") as f:
|
||||
test_mails = [f.read()]
|
||||
|
|
@ -170,7 +169,7 @@ class TestEmailAccount(unittest.TestCase):
|
|||
self.assertEqual(comm_list[0].reference_name, comm_list[1].reference_name)
|
||||
|
||||
def test_threading_by_message_id(self):
|
||||
frappe.db.sql("""delete from tabCommunication""")
|
||||
cleanup()
|
||||
frappe.db.sql("""delete from `tabEmail Queue`""")
|
||||
|
||||
# reference document for testing
|
||||
|
|
@ -196,3 +195,13 @@ class TestEmailAccount(unittest.TestCase):
|
|||
# check if threaded correctly
|
||||
self.assertEqual(comm_list[0].reference_doctype, event.doctype)
|
||||
self.assertEqual(comm_list[0].reference_name, event.name)
|
||||
|
||||
def cleanup(sender=None):
|
||||
filters = {}
|
||||
if sender:
|
||||
filters.update({"sender": sender})
|
||||
|
||||
names = frappe.get_list("Communication", filters=filters, fields=["name"])
|
||||
for name in names:
|
||||
frappe.delete_doc_if_exists("Communication", name.name)
|
||||
frappe.delete_doc_if_exists("Dynamic Link", {"parent": name.name})
|
||||
|
|
@ -278,9 +278,8 @@ def delete_dynamic_links(doctype, name):
|
|||
delete_references('Document Follow', doctype, name, 'ref_doctype', 'ref_docname')
|
||||
|
||||
# unlink communications
|
||||
clear_timeline_references(doctype, name)
|
||||
clear_references('Communication', doctype, name)
|
||||
clear_references('Communication', doctype, name, 'link_doctype', 'link_name')
|
||||
clear_references('Communication', doctype, name, 'timeline_doctype', 'timeline_name')
|
||||
|
||||
clear_references('Activity Log', doctype, name)
|
||||
clear_references('Activity Log', doctype, name, 'timeline_doctype', 'timeline_name')
|
||||
|
|
@ -301,6 +300,9 @@ def clear_references(doctype, reference_doctype, reference_name,
|
|||
{1}=%s and {2}=%s'''.format(doctype, reference_doctype_field, reference_name_field), # nosec
|
||||
(reference_doctype, reference_name))
|
||||
|
||||
def clear_timeline_references(link_doctype, link_name):
|
||||
frappe.db.sql("""delete from `tabDynamic Link`
|
||||
where `tabDynamic Link`.link_doctype='{0}' and `tabDynamic Link`.link_name='{1}'""".format(link_doctype, link_name)) # nosec
|
||||
|
||||
def insert_feed(doc):
|
||||
from frappe.utils import get_fullname
|
||||
|
|
|
|||
|
|
@ -241,3 +241,4 @@ frappe.patches.v12_0.reset_home_settings
|
|||
frappe.patches.v12_0.update_print_format_type
|
||||
frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report #2019-05-01
|
||||
frappe.patches.v12_0.remove_feedback_rating
|
||||
frappe.patches.v12_0.move_timeline_links_to_dynamic_links
|
||||
46
frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py
Normal file
46
frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('core', 'doctype', 'communication')
|
||||
|
||||
communications = frappe.db.sql("""
|
||||
SELECT
|
||||
`tabCommunication`.name, `tabCommunication`.creation, `tabCommunication`.modified,
|
||||
`tabCommunication`.modified_by,`tabCommunication`.timeline_doctype, `tabCommunication`.timeline_name,
|
||||
`tabCommunication`.link_doctype, `tabCommunication`.link_name
|
||||
FROM `tabCommunication`
|
||||
WHERE `tabCommunication`.communication_medium='Email'
|
||||
""", as_dict=True)
|
||||
|
||||
for count, communication in enumerate(communications):
|
||||
counter = 1
|
||||
if communication.timeline_doctype and communication.timeline_name:
|
||||
values = [
|
||||
counter, frappe.generate_hash(length=10), 'timeline_links', 'Communication', communication.name,
|
||||
communication.timeline_doctype, communication.timeline_name, communication.creation, communication.modified,
|
||||
communication.modified_by
|
||||
]
|
||||
execute_query(values)
|
||||
counter += 1
|
||||
|
||||
if communication.link_doctype and communication.link_name:
|
||||
values = [
|
||||
counter, frappe.generate_hash(length=10), 'timeline_links', 'Communication', communication.name,
|
||||
communication.link_doctype, communication.link_name, communication.creation, communication.modified,
|
||||
communication.modified_by
|
||||
]
|
||||
execute_query(values)
|
||||
|
||||
def execute_query(values):
|
||||
try:
|
||||
frappe.db.sql("""
|
||||
INSERT INTO `tabDynamic Link`
|
||||
(`idx`, `name`, `parentfield`, `parenttype`, `parent`, `link_doctype`, `link_name`, `creation`,
|
||||
`modified`, `modified_by`)
|
||||
VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9})
|
||||
""".format(values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], values[9]))
|
||||
except Exception as e:
|
||||
values[1] = frappe.generate_hash(length=10)
|
||||
execute_query(values)
|
||||
Loading…
Add table
Reference in a new issue