Added CC in Communication to manually specify whom to notify. frappe/erpnext#3697
This commit is contained in:
parent
b28bda4beb
commit
2b9cb67e1f
12 changed files with 669 additions and 432 deletions
|
|
@ -309,7 +309,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
|
|||
as_markdown=False, bulk=False, reference_doctype=None, reference_name=None,
|
||||
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
|
||||
attachments=None, content=None, doctype=None, name=None, reply_to=None,
|
||||
cc=(), message_id=None, as_bulk=False, send_after=None):
|
||||
cc=(), message_id=None, as_bulk=False, send_after=None, expose_recipients=False):
|
||||
"""Send email using user's default **Email Account** or global default **Email Account**.
|
||||
|
||||
|
||||
|
|
@ -327,6 +327,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
|
|||
:param reply_to: Reply-To email id.
|
||||
:param message_id: Used for threading. If a reply is received to this email, Message-Id is sent back as In-Reply-To in received email.
|
||||
:param send_after: Send after the given datetime.
|
||||
:param expose_recipients: Display all recipients in the footer message - "This email was sent to"
|
||||
"""
|
||||
|
||||
if bulk or as_bulk:
|
||||
|
|
@ -335,7 +336,8 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
|
|||
subject=subject, message=content or message,
|
||||
reference_doctype = doctype or reference_doctype, reference_name = name or reference_name,
|
||||
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message,
|
||||
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, send_after=send_after)
|
||||
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, send_after=send_after,
|
||||
expose_recipients=expose_recipients)
|
||||
else:
|
||||
import frappe.email
|
||||
if as_markdown:
|
||||
|
|
|
|||
|
|
@ -37,20 +37,108 @@
|
|||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "sent_or_received",
|
||||
"depends_on": "",
|
||||
"fieldname": "communication_medium",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Sent or Received",
|
||||
"label": "Communication Medium",
|
||||
"no_copy": 0,
|
||||
"options": "Sent\nReceived",
|
||||
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "recipients",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Recipients",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:doc.communication_medium===\"Email\"",
|
||||
"fieldname": "cc",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "CC",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:doc.communication_medium!==\"Email\"",
|
||||
"fieldname": "phone_no",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Phone No.",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
|
|
@ -78,6 +166,28 @@
|
|||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "sent_or_received",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Sent or Received",
|
||||
"no_copy": 0,
|
||||
"options": "Sent\nReceived",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
|
|
@ -102,6 +212,27 @@
|
|||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
|
|
@ -127,7 +258,136 @@
|
|||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_5",
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "content",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Content",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "400"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"fieldname": "additional_info",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "More Information",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "sender",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Sender",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "sender_full_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Sender Full Name",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "Today",
|
||||
"fieldname": "communication_date",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Date",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
|
|
@ -194,230 +454,19 @@
|
|||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldname": "in_reply_to",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "In Reply To",
|
||||
"no_copy": 0,
|
||||
"options": "Communication",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "content",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Content",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "400"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "additional_info",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Additional Info",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "recipients",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Recipients",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "phone_no",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Phone No.",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "communication_medium",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Communication Medium",
|
||||
"no_copy": 0,
|
||||
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "sender",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Sender",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "sender_full_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Sender Full Name",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "section_break2",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"no_copy": 0,
|
||||
"options": "simple",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break4",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "By",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
|
|
@ -474,39 +523,19 @@
|
|||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break5",
|
||||
"fieldtype": "Column Break",
|
||||
"default": "0",
|
||||
"fieldname": "unread_notification_sent",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "On",
|
||||
"label": "Unread Notification Sent",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "Today",
|
||||
"fieldname": "communication_date",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Date",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
|
|
@ -533,29 +562,6 @@
|
|||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "0",
|
||||
"fieldname": "unread_notification_sent",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Unread Notification Sent",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
|
|
@ -567,7 +573,7 @@
|
|||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-08-14 17:46:20.902296",
|
||||
"modified": "2015-09-15 05:51:16.112080",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Communication",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals, absolute_import
|
|||
import frappe
|
||||
import json
|
||||
from email.utils import formataddr, parseaddr
|
||||
from frappe.utils import get_url, get_formatted_email, cstr, cint
|
||||
from frappe.utils import get_url, get_formatted_email, cstr, cint, validate_email_add, split_emails
|
||||
from frappe.utils.file_manager import get_file
|
||||
import frappe.email.smtp
|
||||
from frappe import _
|
||||
|
|
@ -33,6 +33,14 @@ class Communication(Document):
|
|||
else:
|
||||
self.status = "Open"
|
||||
|
||||
# validate recipients
|
||||
for email in split_emails(self.recipients):
|
||||
validate_email_add(email, throw=True)
|
||||
|
||||
# validate CC
|
||||
for email in split_emails(self.cc):
|
||||
validate_email_add(email, throw=True)
|
||||
|
||||
def after_insert(self):
|
||||
# send new comment to listening clients
|
||||
comment = self.as_dict()
|
||||
|
|
@ -73,51 +81,41 @@ class Communication(Document):
|
|||
self.send_me_a_copy = send_me_a_copy
|
||||
self.notify(print_html, print_format, attachments, recipients)
|
||||
|
||||
def set_incoming_outgoing_accounts(self):
|
||||
self.incoming_email_account = self.outgoing_email_account = None
|
||||
|
||||
if self.reference_doctype:
|
||||
self.incoming_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": self.reference_doctype, "enable_incoming": 1}, "email_id")
|
||||
|
||||
self.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": self.reference_doctype, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True)
|
||||
|
||||
if not self.incoming_email_account:
|
||||
self.incoming_email_account = frappe.db.get_value("Email Account", {"default_incoming": 1}, "email_id")
|
||||
|
||||
if not self.outgoing_email_account:
|
||||
self.outgoing_email_account = frappe.db.get_value("Email Account", {"default_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict()
|
||||
|
||||
def notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False):
|
||||
def notify(self, print_html=None, print_format=None, attachments=None,
|
||||
recipients=None, cc=None, fetched_from_email_account=False):
|
||||
"""Calls a delayed celery task 'sendmail' that enqueus email in Bulk Email queue
|
||||
|
||||
:param print_html: Send given value as HTML attachment
|
||||
:param print_format: Attach print format of parent document
|
||||
:param attachments: A list of filenames that should be attached when sending this email
|
||||
:param recipients: Email recipients
|
||||
:param except_recipient: True when pulling email, the notification shouldn't go to the main recipient
|
||||
:param cc: Send email as CC to
|
||||
:param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient
|
||||
|
||||
"""
|
||||
recipients, cc = self.get_recipients_and_cc(recipients, cc,
|
||||
fetched_from_email_account=fetched_from_email_account)
|
||||
|
||||
self.emails_not_sent_to = set(self.all_email_addresses) - set(recipients) - set(cc)
|
||||
|
||||
if frappe.flags.in_test:
|
||||
# for test cases, run synchronously
|
||||
self._notify(print_html=print_html, print_format=print_format, attachments=attachments,
|
||||
recipients=recipients, except_recipient=except_recipient)
|
||||
recipients=recipients, cc=cc)
|
||||
else:
|
||||
from frappe.tasks import sendmail
|
||||
sendmail.delay(frappe.local.site, self.name,
|
||||
print_html=print_html, print_format=print_format, attachments=attachments,
|
||||
recipients=recipients, except_recipient=except_recipient)
|
||||
recipients=recipients, cc=cc)
|
||||
|
||||
def _notify(self, print_html=None, print_format=None, attachments=None,
|
||||
recipients=None, cc=None):
|
||||
|
||||
def _notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False):
|
||||
self.prepare_to_notify(print_html, print_format, attachments)
|
||||
if not recipients:
|
||||
recipients = self.get_recipients(except_recipient=except_recipient)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=recipients,
|
||||
recipients=(recipients or []) + (cc or []),
|
||||
expose_recipients=True,
|
||||
sender=self.sender,
|
||||
reply_to=self.incoming_email_account,
|
||||
subject=self.subject,
|
||||
|
|
@ -130,6 +128,27 @@ class Communication(Document):
|
|||
bulk=True
|
||||
)
|
||||
|
||||
def get_recipients_and_cc(self, recipients, cc, fetched_from_email_account=False):
|
||||
self.all_email_addresses = []
|
||||
|
||||
if not recipients:
|
||||
recipients = self.get_recipients()
|
||||
|
||||
if not cc:
|
||||
cc = self.get_cc(recipients, fetched_from_email_account=fetched_from_email_account)
|
||||
|
||||
if fetched_from_email_account:
|
||||
# email was already sent to the original recipient by the sender's email service
|
||||
original_recipients, recipients = recipients, []
|
||||
|
||||
# cc that was received in the email
|
||||
original_cc = split_emails(self.cc)
|
||||
|
||||
# don't cc to people who already received the mail from sender's email service
|
||||
cc = list(set(cc) - set(original_cc) - set(original_recipients))
|
||||
|
||||
return recipients, cc
|
||||
|
||||
def prepare_to_notify(self, print_html=None, print_format=None, attachments=None):
|
||||
"""Prepare to make multipart MIME Email
|
||||
|
||||
|
|
@ -165,78 +184,129 @@ class Communication(Document):
|
|||
else:
|
||||
self.attachments.append(a)
|
||||
|
||||
def get_recipients(self, except_recipient=False):
|
||||
"""Build a list of users to which this email should go to"""
|
||||
def set_incoming_outgoing_accounts(self):
|
||||
self.incoming_email_account = self.outgoing_email_account = None
|
||||
|
||||
if self.reference_doctype:
|
||||
self.incoming_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": self.reference_doctype, "enable_incoming": 1}, "email_id")
|
||||
|
||||
self.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": self.reference_doctype, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True)
|
||||
|
||||
if not self.incoming_email_account:
|
||||
self.incoming_email_account = frappe.db.get_value("Email Account", {"default_incoming": 1}, "email_id")
|
||||
|
||||
if not self.outgoing_email_account:
|
||||
self.outgoing_email_account = frappe.db.get_value("Email Account", {"default_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict()
|
||||
|
||||
def get_recipients(self):
|
||||
"""Build a list of email addresses for To"""
|
||||
# [EDGE CASE] self.recipients can be None when an email is sent as BCC
|
||||
original_recipients = [s.strip() for s in cstr(self.recipients).split(",")]
|
||||
recipients = original_recipients[:]
|
||||
recipients = split_emails(self.recipients)
|
||||
|
||||
if recipients:
|
||||
# this will be used to eventually find email addresses that aren't sent to
|
||||
self.all_email_addresses.extend(recipients)
|
||||
|
||||
# exclude email accounts
|
||||
exclude = [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
|
||||
exclude += [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True)
|
||||
if d[0]]
|
||||
|
||||
recipients = self.filter_email_list(recipients, exclude)
|
||||
|
||||
return recipients
|
||||
|
||||
def get_cc(self, recipients=None, fetched_from_email_account=False):
|
||||
"""Build a list of email addresses for CC"""
|
||||
# get a copy of CC list
|
||||
cc = split_emails(self.cc)
|
||||
|
||||
if self.reference_doctype and self.reference_name:
|
||||
recipients += self.get_earlier_participants()
|
||||
recipients += self.get_commentors()
|
||||
recipients += self.get_assignees()
|
||||
recipients += self.get_starrers()
|
||||
if not cc or fetched_from_email_account:
|
||||
# if CC is not mentioned from the UI or is a fetched email, add follows to CC
|
||||
cc.append(self.get_owner_email())
|
||||
cc += self.get_assignees()
|
||||
cc += self.get_starrers()
|
||||
|
||||
# remove unsubscribed recipients
|
||||
unsubscribed = [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)]
|
||||
email_accounts = [d[0] for d in frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
|
||||
sender = parseaddr(self.sender)[1]
|
||||
if fetched_from_email_account and self.in_reply_to:
|
||||
# add sender of previous reply
|
||||
cc.append(frappe.db.get_value("Communication", self.in_reply_to, "sender"))
|
||||
|
||||
if cc:
|
||||
# this will be used to eventually find email addresses that aren't sent to
|
||||
self.all_email_addresses.extend(cc)
|
||||
|
||||
# exclude email accounts, unfollows, recipients and unsubscribes
|
||||
exclude = [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
|
||||
exclude += [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True)
|
||||
if d[0]]
|
||||
exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)]
|
||||
exclude += [parseaddr(email)[1] for email in recipients]
|
||||
|
||||
if fetched_from_email_account:
|
||||
# exclude sender when pulling email
|
||||
exclude += [parseaddr(self.sender)[1]]
|
||||
|
||||
if self.reference_doctype and self.reference_name:
|
||||
exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"],
|
||||
{"reference_doctype": self.reference_doctype, "reference_name": self.reference_name}, as_list=True)]
|
||||
|
||||
cc = self.filter_email_list(cc, exclude)
|
||||
|
||||
if getattr(self, "send_me_a_copy", False) and self.sender not in cc:
|
||||
self.all_email_addresses.append(self.sender)
|
||||
cc.append(self.sender)
|
||||
|
||||
return cc
|
||||
|
||||
def filter_email_list(self, email_list, exclude):
|
||||
# temp variables
|
||||
filtered = []
|
||||
email_addresses = []
|
||||
for e in list(set(recipients)):
|
||||
if (e=="Administrator") or ((e==self.sender) and (e not in original_recipients)) or \
|
||||
(e in unsubscribed) or (e in email_accounts):
|
||||
email_address_list = []
|
||||
|
||||
for email in list(set(email_list)):
|
||||
if email in exclude:
|
||||
continue
|
||||
|
||||
email_id = parseaddr(e)[1]
|
||||
|
||||
if not email_id:
|
||||
email_address = (parseaddr(email)[1] or "").lower()
|
||||
if not email_address:
|
||||
continue
|
||||
|
||||
if email_id==sender or email_id in unsubscribed or email_id in email_accounts:
|
||||
continue
|
||||
|
||||
if except_recipient and (e==self.recipients or email_id==self.recipients):
|
||||
# while pulling email, don't send email to current recipient
|
||||
if email_address in exclude:
|
||||
continue
|
||||
|
||||
# make sure of case-insensitive uniqueness of email address
|
||||
if email_id.lower() not in email_addresses:
|
||||
if email_address not in email_address_list:
|
||||
# append the full email i.e. "Human <human@example.com>"
|
||||
filtered.append(e)
|
||||
email_addresses.append(email_id.lower())
|
||||
|
||||
if getattr(self, "send_me_a_copy", False):
|
||||
filtered.append(self.sender)
|
||||
filtered.append(email)
|
||||
email_address_list.append(email_address)
|
||||
|
||||
return filtered
|
||||
|
||||
def get_starrers(self):
|
||||
"""Return list of users who have starred this document."""
|
||||
if self.reference_doctype and self.reference_name:
|
||||
return self.get_parent_doc().get_starred_by()
|
||||
else:
|
||||
return []
|
||||
return [( get_formatted_email(user) or user ) for user in self.get_parent_doc().get_starred_by()]
|
||||
|
||||
def get_earlier_participants(self):
|
||||
return frappe.db.sql_list("""
|
||||
select distinct sender
|
||||
from tabCommunication where
|
||||
reference_doctype=%s and reference_name=%s""",
|
||||
(self.reference_doctype, self.reference_name))
|
||||
|
||||
def get_commentors(self):
|
||||
return frappe.db.sql_list("""
|
||||
select distinct comment_by
|
||||
from tabComment where
|
||||
comment_doctype=%s and comment_docname=%s and
|
||||
ifnull(unsubscribed, 0)=0 and comment_by!='Administrator'""",
|
||||
(self.reference_doctype, self.reference_name))
|
||||
def get_owner_email(self):
|
||||
owner = self.get_parent_doc().owner
|
||||
return get_formatted_email(owner) or owner
|
||||
|
||||
def get_assignees(self):
|
||||
return [d.owner for d in frappe.db.get_all("ToDo", filters={"reference_type": self.reference_doctype,
|
||||
"reference_name": self.reference_name, "status": "Open"}, fields=["owner"])]
|
||||
return [( get_formatted_email(d.owner) or d.owner ) for d in
|
||||
frappe.db.get_all("ToDo", filters={
|
||||
"reference_type": self.reference_doctype,
|
||||
"reference_name": self.reference_name,
|
||||
"status": "Open"
|
||||
}, fields=["owner"])
|
||||
]
|
||||
|
||||
def get_attach_link(self, print_format):
|
||||
"""Returns public link for the attachment via `templates/emails/print_link.html`."""
|
||||
|
|
@ -256,7 +326,7 @@ def on_doctype_update():
|
|||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
|
||||
sender=None, recipients=None, communication_medium="Email", send_email=False,
|
||||
print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False,
|
||||
send_me_a_copy=False):
|
||||
send_me_a_copy=False, cc=None):
|
||||
"""Make a new communication.
|
||||
|
||||
:param doctype: Reference DocType.
|
||||
|
|
@ -289,6 +359,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
"content": content,
|
||||
"sender": sender,
|
||||
"recipients": recipients,
|
||||
"cc": cc or None,
|
||||
"communication_medium": "Email",
|
||||
"sent_or_received": sent_or_received,
|
||||
"reference_doctype": doctype,
|
||||
|
|
@ -300,15 +371,13 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
# if not committed, delayed task doesn't find the communication
|
||||
frappe.db.commit()
|
||||
|
||||
recipients = None
|
||||
if send_email:
|
||||
comm.send_me_a_copy = send_me_a_copy
|
||||
recipients = comm.get_recipients()
|
||||
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy, recipients=recipients)
|
||||
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy)
|
||||
|
||||
return {
|
||||
"name": comm.name,
|
||||
"recipients": ", ".join(recipients) if recipients else None
|
||||
"emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ from frappe.email.smtp import SMTPServer, get_outgoing_email_account
|
|||
from frappe.email.email_body import get_email, get_formatted_html
|
||||
from frappe.utils.verified_command import get_signed_params, verify_request
|
||||
from html2text import html2text
|
||||
from frappe.utils import get_url, nowdate, encode, now_datetime, add_days
|
||||
from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split_emails
|
||||
|
||||
class BulkLimitCrossedError(frappe.ValidationError): pass
|
||||
|
||||
def send(recipients=None, sender=None, subject=None, message=None, reference_doctype=None,
|
||||
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
|
||||
attachments=None, reply_to=None, cc=(), message_id=None, send_after=None):
|
||||
attachments=None, reply_to=None, cc=(), message_id=None, send_after=None,
|
||||
expose_recipients=False):
|
||||
"""Add email to sending queue (Bulk Email)
|
||||
|
||||
:param recipients: List of recipients.
|
||||
|
|
@ -39,7 +40,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
|
|||
return
|
||||
|
||||
if isinstance(recipients, basestring):
|
||||
recipients = recipients.split(",")
|
||||
recipients = split_emails(recipients)
|
||||
|
||||
if isinstance(send_after, int):
|
||||
send_after = add_days(nowdate(), send_after)
|
||||
|
|
@ -66,23 +67,30 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
|
|||
else:
|
||||
unsubscribed = []
|
||||
|
||||
for email in filter(None, list(set(recipients))):
|
||||
if email not in unsubscribed:
|
||||
email_content = formatted
|
||||
email_text_context = text_content
|
||||
recipients = [r for r in list(set(recipients)) if r and r not in unsubscribed]
|
||||
|
||||
if reference_doctype:
|
||||
unsubscribe_url = get_unsubcribed_url(reference_doctype, reference_name, email,
|
||||
unsubscribe_method, unsubscribe_params)
|
||||
for email in recipients:
|
||||
email_content = formatted
|
||||
email_text_context = text_content
|
||||
|
||||
# add to queue
|
||||
email_content = add_unsubscribe_link(email_content, email, reference_doctype,
|
||||
reference_name, unsubscribe_url, unsubscribe_message)
|
||||
if reference_doctype:
|
||||
unsubscribe_link = get_unsubscribe_link(
|
||||
reference_doctype=reference_doctype,
|
||||
reference_name=reference_name,
|
||||
email=email,
|
||||
recipients=recipients,
|
||||
expose_recipients=expose_recipients,
|
||||
unsubscribe_method=unsubscribe_method,
|
||||
unsubscribe_params=unsubscribe_params,
|
||||
unsubscribe_message=unsubscribe_message
|
||||
)
|
||||
|
||||
email_text_context += "\n" + _("This email was sent to {0}. To unsubscribe click on this link: {1}").format(email, unsubscribe_url)
|
||||
email_content = email_content.replace("<!--unsubscribe link here-->", unsubscribe_link.html)
|
||||
email_text_context += unsubscribe_link.text
|
||||
|
||||
add(email, sender, subject, email_content, email_text_context, reference_doctype,
|
||||
reference_name, attachments, reply_to, cc, message_id, send_after)
|
||||
# add to queue
|
||||
add(email, sender, subject, email_content, email_text_context, reference_doctype,
|
||||
reference_name, attachments, reply_to, cc, message_id, send_after)
|
||||
|
||||
def add(email, sender, subject, formatted, text_content=None,
|
||||
reference_doctype=None, reference_name=None, attachments=None, reply_to=None,
|
||||
|
|
@ -129,18 +137,41 @@ def check_bulk_limit(recipients):
|
|||
throw(_("Email limit {0} crossed").format(monthly_bulk_mail_limit),
|
||||
BulkLimitCrossedError)
|
||||
|
||||
def add_unsubscribe_link(message, email, reference_doctype, reference_name, unsubscribe_url, unsubscribe_message):
|
||||
unsubscribe_link = """<div style="padding: 7px; text-align: center; color: #8D99A6;">
|
||||
{email}. <a href="{unsubscribe_url}" style="color: #8D99A6; text-decoration: underline;
|
||||
target="_blank">{unsubscribe_message}.
|
||||
</a>
|
||||
</div>""".format(unsubscribe_url = unsubscribe_url,
|
||||
email= _("This email was sent to {0}").format(email),
|
||||
unsubscribe_message = unsubscribe_message or _("Unsubscribe from this list"))
|
||||
def get_unsubscribe_link(reference_doctype, reference_name,
|
||||
email, recipients, expose_recipients, unsubscribe_method, unsubscribe_params, unsubscribe_message):
|
||||
|
||||
message = message.replace("<!--unsubscribe link here-->", unsubscribe_link)
|
||||
unsubscribe_email = recipients if expose_recipients else [email]
|
||||
unsubscribe_email = _("This email was sent to {0}").format(", ".join(unsubscribe_email))
|
||||
|
||||
return message
|
||||
if not unsubscribe_message:
|
||||
unsubscribe_message = _("Unsubscribe from this list")
|
||||
|
||||
unsubscribe_url = get_unsubcribed_url(reference_doctype, reference_name, email,
|
||||
unsubscribe_method, unsubscribe_params)
|
||||
|
||||
html = """<div style="margin: 15px auto; padding: 0px 7px; text-align: center; color: #8d99a6;">
|
||||
{email}
|
||||
<p style="margin: 15px auto;">
|
||||
<a href="{unsubscribe_url}" style="color: #8d99a6; text-decoration: underline;
|
||||
target="_blank">{unsubscribe_message}
|
||||
</a>
|
||||
</p>
|
||||
</div>""".format(
|
||||
unsubscribe_url = unsubscribe_url,
|
||||
email=unsubscribe_email,
|
||||
unsubscribe_message=unsubscribe_message
|
||||
)
|
||||
|
||||
text = "\n{email}\n\n{unsubscribe_message}: {unsubscribe_url}".format(
|
||||
email=unsubscribe_email,
|
||||
unsubscribe_message=unsubscribe_message,
|
||||
unsubscribe_url=unsubscribe_url
|
||||
)
|
||||
|
||||
return frappe._dict({
|
||||
"html": html,
|
||||
"text": text
|
||||
})
|
||||
|
||||
def get_unsubcribed_url(reference_doctype, reference_name, email, unsubscribe_method, unsubscribe_params):
|
||||
params = {"email": email.encode("utf-8"),
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class EmailAccount(Document):
|
|||
|
||||
else:
|
||||
frappe.db.commit()
|
||||
communication.notify(attachments=communication._attachments, except_recipient=True)
|
||||
communication.notify(attachments=communication._attachments, fetched_from_email_account=True)
|
||||
|
||||
if exceptions:
|
||||
raise Exception, frappe.as_json(exceptions)
|
||||
|
|
@ -158,6 +158,7 @@ class EmailAccount(Document):
|
|||
"sender_full_name": email.from_real_name,
|
||||
"sender": email.from_email,
|
||||
"recipients": email.mail.get("To"),
|
||||
"cc": email.mail.get("CC"),
|
||||
"email_account": self.name,
|
||||
"communication_medium": "Email"
|
||||
})
|
||||
|
|
@ -208,6 +209,9 @@ class EmailAccount(Document):
|
|||
if frappe.db.exists("Communication", in_reply_to):
|
||||
parent = frappe.get_doc("Communication", in_reply_to)
|
||||
|
||||
# set in_reply_to of current communication
|
||||
communication.in_reply_to = in_reply_to
|
||||
|
||||
if parent.reference_name:
|
||||
parent = frappe.get_doc(parent.reference_doctype,
|
||||
parent.reference_name)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe.utils.pdf import get_pdf
|
||||
from frappe.email.smtp import get_outgoing_email_account
|
||||
from frappe.utils import get_url, scrub_urls, strip, expand_relative_urls, cint
|
||||
from frappe.utils import get_url, scrub_urls, strip, expand_relative_urls, cint, split_emails
|
||||
import email.utils
|
||||
from markdown2 import markdown
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ class EMail:
|
|||
|
||||
if isinstance(recipients, basestring):
|
||||
recipients = recipients.replace(';', ',').replace('\n', '')
|
||||
recipients = recipients.split(',')
|
||||
recipients = split_emails(recipients)
|
||||
|
||||
# remove null
|
||||
recipients = filter(None, (strip(r) for r in recipients))
|
||||
|
|
@ -238,18 +238,18 @@ def get_footer(email_account, footer=None):
|
|||
footer = footer or ""
|
||||
|
||||
if email_account and email_account.footer:
|
||||
footer += email_account.footer
|
||||
footer += '<div style="margin: 15px auto;">{0}</div>'.format(email_account.footer)
|
||||
|
||||
footer += "<!--unsubscribe link here-->"
|
||||
|
||||
company_address = frappe.db.get_default("email_footer_address")
|
||||
|
||||
if company_address:
|
||||
footer += '<div style="text-align: center; color: #8d99a6">{0}</div>'\
|
||||
footer += '<div style="margin: 15px auto; text-align: center; color: #8d99a6">{0}</div>'\
|
||||
.format(company_address.replace("\n", "<br>"))
|
||||
|
||||
if not cint(frappe.db.get_default("disable_standard_email_footer")):
|
||||
for default_mail_footer in frappe.get_hooks("default_mail_footer"):
|
||||
footer += default_mail_footer
|
||||
footer += '<div style="margin: 15px auto;">{0}</div>'.format(default_mail_footer)
|
||||
|
||||
return footer
|
||||
|
|
|
|||
|
|
@ -193,6 +193,29 @@ $.extend(frappe.user, {
|
|||
is_report_manager: function() {
|
||||
return frappe.user.has_role(['Administrator', 'System Manager', 'Report Manager']);
|
||||
},
|
||||
|
||||
get_formatted_email: function(email) {
|
||||
var fullname = frappe.user.full_name(email);
|
||||
|
||||
if (!fullname) {
|
||||
return email;
|
||||
} else {
|
||||
// to quote or to not
|
||||
var quote = '';
|
||||
|
||||
// only if these special characters are found
|
||||
// why? To make the output same as that in python!
|
||||
if (fullname.search(/[\[\]\\()<>@,:;".]/) !== -1) {
|
||||
quote = '"';
|
||||
}
|
||||
|
||||
return repl('%(quote)s%(fullname)s%(quote)s <%(email)s>', {
|
||||
fullname: fullname,
|
||||
email: email,
|
||||
quote: quote
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.session_alive = true;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ frappe.utils = {
|
|||
});
|
||||
return out.join(newline);
|
||||
},
|
||||
escape_html: function(txt) {
|
||||
return $("<div></div>").text(txt || "").html();
|
||||
},
|
||||
is_url: function(txt) {
|
||||
return txt.toLowerCase().substr(0,7)=='http://'
|
||||
|| txt.toLowerCase().substr(0,8)=='https://'
|
||||
|
|
|
|||
|
|
@ -2,12 +2,17 @@
|
|||
// MIT License. See license.txt
|
||||
|
||||
frappe.ui.is_starred = function(doc) {
|
||||
var starred = frappe.ui.get_starred_by(doc);
|
||||
return starred.indexOf(user)===-1 ? false : true;
|
||||
}
|
||||
|
||||
frappe.ui.get_starred_by = function(doc) {
|
||||
var starred = doc._starred_by;
|
||||
if(starred) {
|
||||
starred = JSON.parse(starred);
|
||||
return starred.indexOf(user)===-1 ? false : true;
|
||||
}
|
||||
return false;
|
||||
|
||||
return starred || [];
|
||||
}
|
||||
|
||||
frappe.ui.toggle_star = function($btn, doctype, name) {
|
||||
|
|
|
|||
|
|
@ -14,41 +14,7 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
this.dialog = new frappe.ui.Dialog({
|
||||
title: __("Add Reply") + ": " + (this.subject || ""),
|
||||
no_submit_on_enter: true,
|
||||
fields: [
|
||||
{label:__("To"), fieldtype:"Data", reqd: 1, fieldname:"recipients"},
|
||||
|
||||
{fieldtype: "Section Break"},
|
||||
{fieldtype: "Column Break"},
|
||||
{label:__("Subject"), fieldtype:"Data", reqd: 1,
|
||||
fieldname:"subject"},
|
||||
{fieldtype: "Column Break"},
|
||||
{label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply",
|
||||
fieldname:"standard_reply"},
|
||||
|
||||
{fieldtype: "Section Break"},
|
||||
{label:__("Message"), fieldtype:"Text Editor", reqd: 1,
|
||||
fieldname:"content"},
|
||||
|
||||
{fieldtype: "Section Break"},
|
||||
{fieldtype: "Column Break"},
|
||||
{label:__("Send As Email"), fieldtype:"Check",
|
||||
fieldname:"send_email"},
|
||||
{label:__("Send me a copy"), fieldtype:"Check",
|
||||
fieldname:"send_me_a_copy"},
|
||||
{label:__("Communication Medium"), fieldtype:"Select",
|
||||
options: ["Phone", "Chat", "Email", "SMS", "Visit", "Other"],
|
||||
fieldname:"communication_medium"},
|
||||
{label:__("Sent or Received"), fieldtype:"Select",
|
||||
options: ["Received", "Sent"],
|
||||
fieldname:"sent_or_received"},
|
||||
{label:__("Attach Document Print"), fieldtype:"Check",
|
||||
fieldname:"attach_document_print"},
|
||||
{label:__("Select Print Format"), fieldtype:"Select",
|
||||
fieldname:"select_print_format"},
|
||||
{fieldtype: "Column Break"},
|
||||
{label:__("Select Attachments"), fieldtype:"HTML",
|
||||
fieldname:"select_attachments"}
|
||||
],
|
||||
fields: this.get_fields(),
|
||||
primary_action_label: "Send",
|
||||
primary_action: function() {
|
||||
me.send_action();
|
||||
|
|
@ -79,6 +45,100 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
this.dialog.show();
|
||||
|
||||
},
|
||||
|
||||
get_fields: function() {
|
||||
var cc_fields = this.get_cc_fields();
|
||||
|
||||
var fields_before_cc = [
|
||||
{fieldtype: "Section Break"},
|
||||
{label:__("To"), fieldtype:"Data", reqd: 1, fieldname:"recipients"},
|
||||
{fieldtype: "Section Break", collapsible: 1, label: "CC & Standard Reply"},
|
||||
{label:__("CC"), fieldtype:"Data", fieldname:"cc"},
|
||||
];
|
||||
|
||||
var fields_after_cc = [
|
||||
{label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply",
|
||||
fieldname:"standard_reply"},
|
||||
{fieldtype: "Section Break"},
|
||||
{label:__("Subject"), fieldtype:"Data", reqd: 1,
|
||||
fieldname:"subject"},
|
||||
{fieldtype: "Section Break"},
|
||||
{label:__("Message"), fieldtype:"Text Editor", reqd: 1,
|
||||
fieldname:"content"},
|
||||
{fieldtype: "Section Break"},
|
||||
{fieldtype: "Column Break"},
|
||||
{label:__("Send As Email"), fieldtype:"Check",
|
||||
fieldname:"send_email"},
|
||||
{label:__("Send me a copy"), fieldtype:"Check",
|
||||
fieldname:"send_me_a_copy"},
|
||||
{label:__("Communication Medium"), fieldtype:"Select",
|
||||
options: ["Phone", "Chat", "Email", "SMS", "Visit", "Other"],
|
||||
fieldname:"communication_medium"},
|
||||
{label:__("Sent or Received"), fieldtype:"Select",
|
||||
options: ["Received", "Sent"],
|
||||
fieldname:"sent_or_received"},
|
||||
{label:__("Attach Document Print"), fieldtype:"Check",
|
||||
fieldname:"attach_document_print"},
|
||||
{label:__("Select Print Format"), fieldtype:"Select",
|
||||
fieldname:"select_print_format"},
|
||||
{fieldtype: "Column Break"},
|
||||
{label:__("Select Attachments"), fieldtype:"HTML",
|
||||
fieldname:"select_attachments"}
|
||||
];
|
||||
|
||||
return fields_before_cc.concat(cc_fields).concat(fields_after_cc);
|
||||
},
|
||||
|
||||
get_cc_fields: function() {
|
||||
var cc = [ [this.frm.doc.owner, 1] ];
|
||||
|
||||
var starred_by = frappe.ui.get_starred_by(this.frm.doc);
|
||||
if (starred_by) {
|
||||
for ( var i=0, l=starred_by.length; i<l; i++ ) {
|
||||
cc.push( [starred_by[i], 1] );
|
||||
}
|
||||
}
|
||||
|
||||
var assignments = this.frm.get_docinfo().assignments;
|
||||
if (assignments) {
|
||||
for ( var i=0, l=assignments.length; i<l; i++ ) {
|
||||
cc.push( [assignments[i].owner, 1] );
|
||||
}
|
||||
}
|
||||
|
||||
var comments = this.frm.get_docinfo().comments;
|
||||
if (comments) {
|
||||
for ( var i=0, l=comments.length; i<l; i++ ) {
|
||||
cc.push( [comments[i].comment_by, 0] );
|
||||
}
|
||||
}
|
||||
|
||||
var added = [];
|
||||
var cc_fields = [];
|
||||
for ( var i=0, l=cc.length; i<l; i++ ) {
|
||||
var email = cc[i][0];
|
||||
var default_value = cc[i][1];
|
||||
|
||||
if ( !email || added.indexOf(email)!==-1 || email.indexOf("@")===-1 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// for deduplication
|
||||
added.push(email);
|
||||
|
||||
email = frappe.user.get_formatted_email(email);
|
||||
cc_fields.push({
|
||||
"label": frappe.utils.escape_html(email),
|
||||
"fieldtype": "Check",
|
||||
"fieldname": email,
|
||||
"is_cc_checkbox": 1,
|
||||
"default": default_value
|
||||
});
|
||||
}
|
||||
|
||||
return cc_fields;
|
||||
},
|
||||
|
||||
prepare: function() {
|
||||
this.setup_subject_and_recipients();
|
||||
this.setup_print();
|
||||
|
|
@ -284,10 +344,10 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
},
|
||||
|
||||
send_action: function() {
|
||||
var me = this,
|
||||
form_values = me.dialog.get_values(),
|
||||
btn = me.dialog.get_primary_btn();
|
||||
var me = this;
|
||||
var btn = me.dialog.get_primary_btn();
|
||||
|
||||
var form_values = this.get_values();
|
||||
if(!form_values) return;
|
||||
|
||||
var selected_attachments = $.map($(me.dialog.wrapper)
|
||||
|
|
@ -312,6 +372,26 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
}
|
||||
},
|
||||
|
||||
get_values: function() {
|
||||
var form_values = this.dialog.get_values();
|
||||
|
||||
// cc
|
||||
for ( var i=0, l=this.dialog.fields.length; i < l; i++ ) {
|
||||
var df = this.dialog.fields[i];
|
||||
|
||||
if ( df.is_cc_checkbox ) {
|
||||
// concat in cc
|
||||
if ( form_values[df.fieldname] ) {
|
||||
form_values.cc = ( form_values.cc ? (form_values.cc + ", ") : "" ) + df.fieldname;
|
||||
}
|
||||
|
||||
delete form_values[df.fieldname];
|
||||
}
|
||||
}
|
||||
|
||||
return form_values;
|
||||
},
|
||||
|
||||
send_email: function(btn, form_values, selected_attachments, print_html, print_format) {
|
||||
var me = this;
|
||||
|
||||
|
|
@ -334,6 +414,7 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
method:"frappe.core.doctype.communication.communication.make",
|
||||
args: {
|
||||
recipients: form_values.recipients,
|
||||
cc: form_values.cc,
|
||||
subject: form_values.subject,
|
||||
content: form_values.content,
|
||||
doctype: me.doc.doctype,
|
||||
|
|
@ -349,8 +430,11 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
btn: btn,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
if(form_values.send_email && r.message["recipients"])
|
||||
msgprint(__("Email sent to {0}", [r.message["recipients"]]));
|
||||
if(form_values.send_email && r.message["emails_not_sent_to"]) {
|
||||
msgprint( __("Email not sent to {0}",
|
||||
[ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) );
|
||||
}
|
||||
|
||||
me.dialog.hide();
|
||||
|
||||
if (cur_frm) {
|
||||
|
|
|
|||
|
|
@ -185,7 +185,8 @@ def run_async_task(self, site=None, user=None, cmd=None, form_dict=None, hijack_
|
|||
|
||||
|
||||
@celery_task()
|
||||
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False):
|
||||
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None,
|
||||
recipients=None, cc=None):
|
||||
try:
|
||||
frappe.connect(site=site)
|
||||
|
||||
|
|
@ -193,7 +194,8 @@ def sendmail(site, communication_name, print_html=None, print_format=None, attac
|
|||
for i in xrange(3):
|
||||
try:
|
||||
communication = frappe.get_doc("Communication", communication_name)
|
||||
communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, recipients=recipients, except_recipient=except_recipient)
|
||||
communication._notify(print_html=print_html, print_format=print_format, attachments=attachments,
|
||||
recipients=recipients, cc=cc)
|
||||
except MySQLdb.OperationalError, e:
|
||||
# deadlock, try again
|
||||
if e.args[0]==1213:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import os, sys, re, urllib
|
|||
import frappe
|
||||
import requests
|
||||
|
||||
|
||||
# utility functions like cint, int, flt, etc.
|
||||
from frappe.utils.data import *
|
||||
|
||||
|
|
@ -89,6 +88,15 @@ def validate_email_add(email_str, throw=False):
|
|||
|
||||
return matched
|
||||
|
||||
def split_emails(txt):
|
||||
email_list = []
|
||||
for email in re.split(''',(?=(?:[^"]|"[^"]*")*$)''', cstr(txt)):
|
||||
email = strip(cstr(email))
|
||||
if email:
|
||||
email_list.append(email)
|
||||
|
||||
return email_list
|
||||
|
||||
def random_string(length):
|
||||
"""generate a random string"""
|
||||
import string
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue