diff --git a/frappe/__init__.py b/frappe/__init__.py index 0d77a099df..1245228fd8 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -378,7 +378,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message as_markdown=False, delayed=True, 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, in_reply_to=None, send_after=None, expose_recipients=None, + cc=[], bcc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False, inline_images=None, template=None, args=None, header=None): """Send email using user's default **Email Account** or global default **Email Account**. @@ -426,7 +426,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message subject=subject, message=message, text_content=text_content, 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, in_reply_to=in_reply_to, + attachments=attachments, reply_to=reply_to, cc=cc, bcc=bcc, message_id=message_id, in_reply_to=in_reply_to, send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification, inline_images=inline_images, header=header) diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index f706f1ea77..c9c1715ae0 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -15,6 +15,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -43,6 +44,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -73,6 +75,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -104,6 +107,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -134,6 +138,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -162,6 +167,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -192,6 +198,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -223,6 +230,39 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "bcc", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "BCC", + "length": 0, + "no_copy": 0, + "options": "Email", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -252,6 +292,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -283,6 +324,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -311,6 +353,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -340,6 +383,7 @@ "width": "400" }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -369,6 +413,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -398,6 +443,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -429,6 +475,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -459,6 +506,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -487,6 +535,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -518,6 +567,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -548,6 +598,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -576,6 +627,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -605,6 +657,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -634,6 +687,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -662,6 +716,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -691,6 +746,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -720,6 +776,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -750,6 +807,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -780,6 +838,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -810,6 +869,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -841,6 +901,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -871,6 +932,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -901,6 +963,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -929,6 +992,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -959,6 +1023,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -989,6 +1054,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1019,6 +1085,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1049,6 +1116,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1079,6 +1147,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1109,6 +1178,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1138,6 +1208,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1166,6 +1237,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -1195,6 +1267,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1224,6 +1297,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -1253,6 +1327,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1283,6 +1358,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1312,6 +1388,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -1342,6 +1419,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1371,6 +1449,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1411,7 +1490,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-03-29 23:06:16.469149", + "modified": "2017-10-25 12:53:49.547620", "modified_by": "Administrator", "module": "Core", "name": "Communication", @@ -1477,26 +1556,6 @@ "submit": 0, "user_permission_doctypes": "[\"Email Account\"]", "write": 0 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Super Email User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 } ], "quick_entry": 0, diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 5a63381d91..c5b6d0c77e 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -189,7 +189,7 @@ class Communication(Document): self.notify(print_html, print_format, attachments, recipients) def notify(self, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, fetched_from_email_account=False): + recipients=None, cc=None, bcc=None,fetched_from_email_account=False): """Calls a delayed task 'sendmail' that enqueus email in Email Queue queue :param print_html: Send given value as HTML attachment @@ -200,13 +200,13 @@ class Communication(Document): :param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient """ - notify(self, print_html, print_format, attachments, recipients, cc, + notify(self, print_html, print_format, attachments, recipients, cc, bcc, fetched_from_email_account) def _notify(self, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None): + recipients=None, cc=None, bcc=None): - _notify(self, print_html, print_format, attachments, recipients, cc) + _notify(self, print_html, print_format, attachments, recipients, cc, bcc) def bot_reply(self): if self.comment_type == 'Bot' and self.communication_type == 'Chat': diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 28c2e5ff0d..ac69cc24e2 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -22,7 +22,7 @@ from frappe.utils.background_jobs import enqueue @frappe.whitelist() def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, - print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, flags=None,read_receipt=None): + print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None, flags=None,read_receipt=None): """Make a new communication. :param doctype: Reference DocType. @@ -58,6 +58,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "sender_full_name":sender_full_name, "recipients": recipients, "cc": cc or None, + "bcc": bcc or None, "communication_medium": communication_medium, "sent_or_received": sent_or_received, "reference_doctype": doctype, @@ -102,10 +103,13 @@ def validate_email(doc): for email in split_emails(doc.cc): validate_email_add(email, throw=True) + for email in split_emails(doc.bcc): + validate_email_add(email, throw=True) + # validate sender def notify(doc, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, fetched_from_email_account=False): + recipients=None, cc=None, bcc=None, fetched_from_email_account=False): """Calls a delayed task 'sendmail' that enqueus email in Email Queue queue :param print_html: Send given value as HTML attachment @@ -113,10 +117,11 @@ def notify(doc, print_html=None, print_format=None, attachments=None, :param attachments: A list of filenames that should be attached when sending this email :param recipients: Email recipients :param cc: Send email as CC to + :param bcc: Send email as BCC to :param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient """ - recipients, cc = get_recipients_and_cc(doc, recipients, cc, + recipients, cc, bcc = get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_account=fetched_from_email_account) if not recipients: @@ -127,16 +132,16 @@ def notify(doc, print_html=None, print_format=None, attachments=None, if frappe.flags.in_test: # for test cases, run synchronously doc._notify(print_html=print_html, print_format=print_format, attachments=attachments, - recipients=recipients, cc=cc) + recipients=recipients, cc=cc, bcc=None) else: check_email_limit(list(set(doc.sent_email_addresses))) enqueue(sendmail, queue="default", timeout=300, event="sendmail", communication_name=doc.name, print_html=print_html, print_format=print_format, attachments=attachments, - recipients=recipients, cc=cc, lang=frappe.local.lang, session=frappe.local.session) + recipients=recipients, cc=cc, bcc=bcc, lang=frappe.local.lang, session=frappe.local.session) def _notify(doc, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None): + recipients=None, cc=None, bcc=None): prepare_to_notify(doc, print_html, print_format, attachments) @@ -148,6 +153,7 @@ def _notify(doc, print_html=None, print_format=None, attachments=None, frappe.sendmail( recipients=(recipients or []), cc=(cc or []), + bcc=(bcc or []), expose_recipients="header", sender=doc.sender, reply_to=doc.incoming_email_account, @@ -190,7 +196,7 @@ def update_parent_mins_to_first_response(doc): parent.run_method('notify_communication', doc) parent.notify_update() -def get_recipients_and_cc(doc, recipients, cc, fetched_from_email_account=False): +def get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_account=False): doc.all_email_addresses = [] doc.sent_email_addresses = [] doc.previous_email_sender = None @@ -201,6 +207,9 @@ def get_recipients_and_cc(doc, recipients, cc, fetched_from_email_account=False) if not cc: cc = get_cc(doc, recipients, fetched_from_email_account=fetched_from_email_account) + if not bcc: + bcc = get_bcc(doc, 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, [] @@ -216,10 +225,13 @@ def get_recipients_and_cc(doc, recipients, cc, fetched_from_email_account=False) # 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)) + original_bcc = split_emails(doc.bcc) + bcc = list(set(bcc) - set(original_bcc) - set(original_recipients)) + if 'Administrator' in recipients: recipients.remove('Administrator') - return recipients, cc + return recipients, cc, bcc def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None): """Prepare to make multipart MIME Email @@ -345,6 +357,34 @@ def get_cc(doc, recipients=None, fetched_from_email_account=False): return cc +def get_bcc(doc, recipients=None, fetched_from_email_account=False): + """Build a list of email addresses for BCC""" + bcc = split_emails(doc.bcc) + + if doc.reference_doctype and doc.reference_name: + if fetched_from_email_account: + bcc.append(get_owner_email(doc)) + bcc += get_assignees(doc) + + if getattr(doc, "send_me_a_copy", False) and doc.sender not in bcc: + bcc.append(doc.sender) + + if bcc: + exclude = [] + exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)] + exclude += [(parse_addr(email)[1] or "").lower() for email in recipients] + + if fetched_from_email_account: + # exclude sender when pulling email + exclude += [parse_addr(doc.sender)[1]] + + if doc.reference_doctype and doc.reference_name: + exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"], + {"reference_doctype": doc.reference_doctype, "reference_name": doc.reference_name}, as_list=True)] + + bcc = filter_email_list(doc, bcc, exclude, is_bcc=True) + + return bcc def add_attachments(name, attachments): '''Add attachments to the given Communiction''' @@ -360,7 +400,7 @@ def add_attachments(name, attachments): save_url(attach.file_url, attach.file_name, "Communication", name, "Home/Attachments", attach.is_private) -def filter_email_list(doc, email_list, exclude, is_cc=False): +def filter_email_list(doc, email_list, exclude, is_cc=False, is_bcc=False): # temp variables filtered = [] email_address_list = [] @@ -382,6 +422,11 @@ def filter_email_list(doc, email_list, exclude, is_cc=False): # don't send to disabled users continue + if is_bcc: + is_user_enabled = frappe.db.get_value("User", email_address, "enabled") + if is_user_enabled==0: + continue + # make sure of case-insensitive uniqueness of email address if email_address not in email_address_list: # append the full email i.e. "Human " @@ -416,7 +461,7 @@ def get_attach_link(doc, print_format): }) def sendmail(communication_name, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, lang=None, session=None): + recipients=None, cc=None, bcc=None, lang=None, session=None): try: if lang: @@ -432,7 +477,7 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments try: communication = frappe.get_doc("Communication", communication_name) communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, - recipients=recipients, cc=cc) + recipients=recipients, cc=cc, bcc=bcc) except MySQLdb.OperationalError as e: # deadlock, try again @@ -453,6 +498,7 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments "attachments": attachments, "recipients": recipients, "cc": cc, + "bcc": bcc, "lang": lang })) frappe.logger(__name__).error(traceback) diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index b3a947312f..799ba2eeec 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -15,7 +15,7 @@ from email.header import Header def get_email(recipients, sender='', msg='', subject='[No Subject]', text_content = None, footer=None, print_html=None, formatted=None, attachments=None, - content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None, + content=None, reply_to=None, cc=[], bcc=[], email_account=None, expose_recipients=None, inline_images=[], header=None): """ Prepare an email with the following format: - multipart/mixed @@ -27,7 +27,7 @@ def get_email(recipients, sender='', msg='', subject='[No Subject]', - attachment """ content = content or msg - emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account, expose_recipients=expose_recipients) + emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, bcc=bcc, email_account=email_account, expose_recipients=expose_recipients) if not content.strip().startswith("<"): content = markdown(content) @@ -51,7 +51,7 @@ class EMail: Also provides a clean way to add binary `FileData` attachments Also sets all messages as multipart/alternative for cleaner reading in text-only clients """ - def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None, expose_recipients=None): + def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), bcc=(), email_account=None, expose_recipients=None): from email import charset as Charset Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') @@ -72,6 +72,7 @@ class EMail: self.msg_alternative = MIMEMultipart('alternative') self.msg_root.attach(self.msg_alternative) self.cc = cc or [] + self.bcc = bcc or [] self.html_set = False self.email_account = email_account or get_outgoing_email_account(sender=sender) @@ -176,8 +177,9 @@ class EMail: self.recipients = [strip(r) for r in self.recipients] self.cc = [strip(r) for r in self.cc] + self.bcc = [strip(r) for r in self.bcc] - for e in self.recipients + (self.cc or []): + for e in self.recipients + (self.cc or []) + (self.bcc or []): validate_email_add(e, True) def replace_sender(self): @@ -207,6 +209,7 @@ class EMail: "To": ', '.join(self.recipients) if self.expose_recipients=="header" else "", "Date": email.utils.formatdate(), "Reply-To": self.reply_to if self.reply_to else None, + "Bcc": ', '.join(self.bcc) if self.bcc else None, "CC": ', '.join(self.cc) if self.cc and self.expose_recipients=="header" else None, 'X-Frappe-Site': get_url(), } diff --git a/frappe/email/queue.py b/frappe/email/queue.py index d34592f055..91ac4cf06a 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -21,7 +21,7 @@ class EmailLimitCrossedError(frappe.ValidationError): pass def send(recipients=None, sender=None, subject=None, message=None, text_content=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, in_reply_to=None, send_after=None, + attachments=None, reply_to=None, cc=[], bcc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None, queue_separately=False, is_notification=False, add_unsubscribe_link=1, inline_images=None, header=None): @@ -61,6 +61,9 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= if isinstance(cc, string_types): cc = split_emails(cc) + if isinstance(bcc, string_types): + bcc = split_emails(bcc) + if isinstance(send_after, int): send_after = add_days(nowdate(), send_after) @@ -112,6 +115,7 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= attachments=attachments, reply_to=reply_to, cc=cc, + bcc=bcc, message_id=message_id, in_reply_to=in_reply_to, send_after=send_after, @@ -174,6 +178,7 @@ def get_email_queue(recipients, sender, subject, **kwargs): attachments=kwargs.get('attachments'), reply_to=kwargs.get('reply_to'), cc=kwargs.get('cc'), + bcc=kwargs.get('bcc'), email_account=kwargs.get('email_account'), expose_recipients=kwargs.get('expose_recipients'), inline_images=kwargs.get('inline_images'), @@ -194,7 +199,7 @@ def get_email_queue(recipients, sender, subject, **kwargs): frappe.log_error('Invalid Email ID Sender: {0}, Recipients: {1}'.format(mail.sender, ', '.join(mail.recipients)), 'Email Not Sent') - e.set_recipients(recipients + kwargs.get('cc', [])) + e.set_recipients(recipients + kwargs.get('cc', []) + kwargs.get('bcc', [])) e.reference_doctype = kwargs.get('reference_doctype') e.reference_name = kwargs.get('reference_name') e.add_unsubscribe_link = kwargs.get("add_unsubscribe_link") @@ -204,6 +209,7 @@ def get_email_queue(recipients, sender, subject, **kwargs): e.communication = kwargs.get('communication') e.send_after = kwargs.get('send_after') e.show_as_cc = ",".join(kwargs.get('cc', [])) + e.show_as_bcc = ",".join(kwargs.get('bcc', [])) e.insert(ignore_permissions=True) return e diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 42924f47b9..ea02974f28 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -48,8 +48,9 @@ frappe.views.CommunicationComposer = Class.extend({ get_fields: function() { var fields= [ {label:__("To"), fieldtype:"Data", reqd: 0, fieldname:"recipients",length:524288}, - {fieldtype: "Section Break", collapsible: 1, label: __("CC & Standard Reply")}, + {fieldtype: "Section Break", collapsible: 1, label: __("CC, BCC & Standard Reply")}, {label:__("CC"), fieldtype:"Data", fieldname:"cc", length:524288}, + {label:__("BCC"), fieldtype:"Data", fieldname:"bcc", length:524288}, {label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply", fieldname:"standard_reply"}, {fieldtype: "Section Break"}, @@ -109,6 +110,7 @@ frappe.views.CommunicationComposer = Class.extend({ this.dialog.fields_dict.recipients.set_value(this.recipients || ''); this.dialog.fields_dict.cc.set_value(this.cc || ''); + this.dialog.fields_dict.bcc.set_value(this.bcc || ''); if(this.dialog.fields_dict.sender) { this.dialog.fields_dict.sender.set_value(this.sender || ''); @@ -123,6 +125,7 @@ frappe.views.CommunicationComposer = Class.extend({ if(!this.forward && !this.recipients && this.last_email) { this.recipients = this.last_email.sender; this.cc = this.last_email.cc; + this.bcc = this.last_email.bcc; } if(!this.forward && !this.recipients) { @@ -446,6 +449,7 @@ frappe.views.CommunicationComposer = Class.extend({ // concat in cc if ( form_values[df.fieldname] ) { form_values.cc = ( form_values.cc ? (form_values.cc + ", ") : "" ) + df.fieldname; + form_values.bcc = ( form_values.bcc ? (form_values.bcc + ", ") : "" ) + df.fieldname; } delete form_values[df.fieldname]; @@ -484,6 +488,7 @@ frappe.views.CommunicationComposer = Class.extend({ args: { recipients: form_values.recipients, cc: form_values.cc, + bcc: form_values.bcc, subject: form_values.subject, content: form_values.content, doctype: me.doc.doctype, @@ -594,7 +599,8 @@ frappe.views.CommunicationComposer = Class.extend({ var me = this; [ this.dialog.fields_dict.recipients.input, - this.dialog.fields_dict.cc.input + this.dialog.fields_dict.cc.input, + this.dialog.fields_dict.bcc.input ].map(function(input) { me.setup_awesomplete_for_input(input); });