20characters
+[mail_force_email_notification](mail_force_email_notification/) | 18.0.1.0.0 | | Context key to define notifications to be sent by emaildefined by force_notification_by_email context key
[mail_inline_css](mail_inline_css/) | 18.0.1.0.0 | | Convert style tags in inline style in your mails
[mail_layout_preview](mail_layout_preview/) | 18.0.1.0.0 | | Preview email templates in the browser
[mail_notification_clean_status_error](mail_notification_clean_status_error/) | 18.0.1.0.0 | [](https://github.com/sebalix) | Extend Odoo scheduled action to also delete notifications in error.
diff --git a/mail_force_email_notification/README.rst b/mail_force_email_notification/README.rst
index ed7486826..686068cde 100644
--- a/mail_force_email_notification/README.rst
+++ b/mail_force_email_notification/README.rst
@@ -7,7 +7,7 @@ Mail Force Email Notification
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:abe986a2c488a0007237bf74d451abb23f15ac28889b9a30d3b40de0eab937aa
+ !! source digest: sha256:bc735d617f6e55d39427e192b0b118b534714ab642d16ffc1e71aa6e19394f8d
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/mail_force_email_notification/static/description/index.html b/mail_force_email_notification/static/description/index.html
index 314a348ad..68c89d7a1 100644
--- a/mail_force_email_notification/static/description/index.html
+++ b/mail_force_email_notification/static/description/index.html
@@ -367,7 +367,7 @@ Mail Force Email Notification
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:abe986a2c488a0007237bf74d451abb23f15ac28889b9a30d3b40de0eab937aa
+!! source digest: sha256:bc735d617f6e55d39427e192b0b118b534714ab642d16ffc1e71aa6e19394f8d
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

This module extends the functionality of Odoo’s mail notification system
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml
index a34b4e860..066a59d5f 100644
--- a/setup/_metapackage/pyproject.toml
+++ b/setup/_metapackage/pyproject.toml
@@ -1,9 +1,10 @@
[project]
name = "odoo-addons-oca-mail"
-version = "18.0.20250320.0"
+version = "18.0.20250402.0"
dependencies = [
"odoo-addon-mail_autosubscribe==18.0.*",
"odoo-addon-mail_debrand==18.0.*",
+ "odoo-addon-mail_force_email_notification==18.0.*",
"odoo-addon-mail_inline_css==18.0.*",
"odoo-addon-mail_layout_preview==18.0.*",
"odoo-addon-mail_notification_clean_status_error==18.0.*",
From e0099176523bbc8ec2a4f033ec4cd7f9e6890c8f Mon Sep 17 00:00:00 2001
From: Hai Lang
Date: Tue, 23 May 2023 10:58:24 +0700
Subject: [PATCH 145/228] [ADD] mail_composer_cc_bcc: send email to cc, bcc
addresses
---
mail_composer_cc_bcc/README.rst | 137 +++++
mail_composer_cc_bcc/__init__.py | 2 +
mail_composer_cc_bcc/__manifest__.py | 30 ++
mail_composer_cc_bcc/models/__init__.py | 7 +
mail_composer_cc_bcc/models/mail_mail.py | 269 ++++++++++
mail_composer_cc_bcc/models/mail_message.py | 25 +
mail_composer_cc_bcc/models/mail_template.py | 52 ++
mail_composer_cc_bcc/models/mail_thread.py | 117 +++++
mail_composer_cc_bcc/models/res_company.py | 23 +
mail_composer_cc_bcc/readme/CONFIGURE.rst | 8 +
mail_composer_cc_bcc/readme/CONTRIBUTORS.rst | 3 +
mail_composer_cc_bcc/readme/CREDITS.rst | 1 +
mail_composer_cc_bcc/readme/DESCRIPTION.rst | 16 +
mail_composer_cc_bcc/readme/USAGE.rst | 10 +
.../static/description/index.html | 477 ++++++++++++++++++
.../static/img/email_template_form_cc_bcc.png | Bin 0 -> 57023 bytes
.../mail_compose_message_default_cc_bcc.png | Bin 0 -> 46731 bytes
.../mail_compose_message_template_cc_bcc.png | Bin 0 -> 51843 bytes
.../img/res_company_form_default_cc_bcc.png | Bin 0 -> 77464 bytes
mail_composer_cc_bcc/tests/__init__.py | 1 +
.../tests/test_mail_cc_bcc.py | 180 +++++++
.../views/mail_mail_views.xml | 16 +
.../views/mail_message_views.xml | 22 +
.../views/mail_template_views.xml | 17 +
.../views/res_company_views.xml | 22 +
mail_composer_cc_bcc/wizards/__init__.py | 1 +
.../wizards/account_invoice_send_views.xml | 26 +
.../wizards/mail_compose_message.py | 81 +++
.../wizards/mail_compose_message_view.xml | 24 +
29 files changed, 1567 insertions(+)
create mode 100644 mail_composer_cc_bcc/README.rst
create mode 100644 mail_composer_cc_bcc/__init__.py
create mode 100644 mail_composer_cc_bcc/__manifest__.py
create mode 100644 mail_composer_cc_bcc/models/__init__.py
create mode 100644 mail_composer_cc_bcc/models/mail_mail.py
create mode 100644 mail_composer_cc_bcc/models/mail_message.py
create mode 100644 mail_composer_cc_bcc/models/mail_template.py
create mode 100644 mail_composer_cc_bcc/models/mail_thread.py
create mode 100644 mail_composer_cc_bcc/models/res_company.py
create mode 100644 mail_composer_cc_bcc/readme/CONFIGURE.rst
create mode 100644 mail_composer_cc_bcc/readme/CONTRIBUTORS.rst
create mode 100644 mail_composer_cc_bcc/readme/CREDITS.rst
create mode 100644 mail_composer_cc_bcc/readme/DESCRIPTION.rst
create mode 100644 mail_composer_cc_bcc/readme/USAGE.rst
create mode 100644 mail_composer_cc_bcc/static/description/index.html
create mode 100644 mail_composer_cc_bcc/static/img/email_template_form_cc_bcc.png
create mode 100644 mail_composer_cc_bcc/static/img/mail_compose_message_default_cc_bcc.png
create mode 100644 mail_composer_cc_bcc/static/img/mail_compose_message_template_cc_bcc.png
create mode 100644 mail_composer_cc_bcc/static/img/res_company_form_default_cc_bcc.png
create mode 100644 mail_composer_cc_bcc/tests/__init__.py
create mode 100644 mail_composer_cc_bcc/tests/test_mail_cc_bcc.py
create mode 100644 mail_composer_cc_bcc/views/mail_mail_views.xml
create mode 100644 mail_composer_cc_bcc/views/mail_message_views.xml
create mode 100644 mail_composer_cc_bcc/views/mail_template_views.xml
create mode 100644 mail_composer_cc_bcc/views/res_company_views.xml
create mode 100644 mail_composer_cc_bcc/wizards/__init__.py
create mode 100644 mail_composer_cc_bcc/wizards/account_invoice_send_views.xml
create mode 100644 mail_composer_cc_bcc/wizards/mail_compose_message.py
create mode 100644 mail_composer_cc_bcc/wizards/mail_compose_message_view.xml
diff --git a/mail_composer_cc_bcc/README.rst b/mail_composer_cc_bcc/README.rst
new file mode 100644
index 000000000..fa2e61621
--- /dev/null
+++ b/mail_composer_cc_bcc/README.rst
@@ -0,0 +1,137 @@
+================
+Email CC and BCC
+================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:0c7083411c956f0bb0a56d0339e1ea49c39986e9d1b0fd3f73164cdd77515d50
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Alpha
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github
+ :target: https://github.com/OCA/social/tree/15.0/mail_composer_cc_bcc
+ :alt: OCA/social
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/social-15-0/social-15-0-mail_composer_cc_bcc
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=15.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+Odoo native does not support defining a Cc field in the Mail Composer
+by default; instead, it only has a unique Recipients fields, which is
+confusing for a lot of end users.
+
+This module allows to properly separate To:, Cc:, and Bcc: fields
+in the Mail Composer.
+
+Features
+~~~~~~~~
+
+* Add Cc and Bcc fields to mail composer form. Send only once to multiple email
+ addresses.
+* Add Cc and Bcc fields to company form to use them as default in mail composer
+ form.
+* Add Bcc field to mail template form. Use Cc and Bcc fields to lookup partners
+ by email then add them to corresponding fields in mail composer form.
+
+.. IMPORTANT::
+ This is an alpha version, the data model and design can change at any time without warning.
+ Only for development or testing purpose, do not use in production.
+ `More details on development status `_
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Configuration
+=============
+
+In company form there are two fields to set default cc and bcc
+partners.
+
+ .. image:: https://raw.githubusercontent.com/OCA/social/15.0/mail_composer_cc_bcc/static/img/res_company_form_default_cc_bcc.png
+
+In template form there are two fields to set cc and bcc emails.
+
+ .. image:: https://raw.githubusercontent.com/OCA/social/15.0/mail_composer_cc_bcc/static/img/email_template_form_cc_bcc.png
+
+Usage
+=====
+
+The partners cc and bcc from company form will be used to fill in mail composer
+form.
+
+ .. image:: https://raw.githubusercontent.com/OCA/social/15.0/mail_composer_cc_bcc/static/img/mail_compose_message_default_cc_bcc.png
+
+When select a template that has cc and/or bcc emails, the emails will be used
+to lookup partners then found partners will be added to corresponding mail
+composer's fields.
+
+ .. image:: https://raw.githubusercontent.com/OCA/social/15.0/mail_composer_cc_bcc/static/img/mail_compose_message_template_cc_bcc.png
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Camptocamp SA
+
+Contributors
+~~~~~~~~~~~~
+
+* `Trobz `_:
+
+ * Hai N. Le
+
+Other credits
+~~~~~~~~~~~~~
+
+The creation of this module was financially supported by Camptocamp.
+
+Maintainers
+~~~~~~~~~~~
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-hailangvn2023| image:: https://github.com/hailangvn2023.png?size=40px
+ :target: https://github.com/hailangvn2023
+ :alt: hailangvn2023
+
+Current `maintainer `__:
+
+|maintainer-hailangvn2023|
+
+This module is part of the `OCA/social `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/mail_composer_cc_bcc/__init__.py b/mail_composer_cc_bcc/__init__.py
new file mode 100644
index 000000000..aee8895e7
--- /dev/null
+++ b/mail_composer_cc_bcc/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizards
diff --git a/mail_composer_cc_bcc/__manifest__.py b/mail_composer_cc_bcc/__manifest__.py
new file mode 100644
index 000000000..549dc4c83
--- /dev/null
+++ b/mail_composer_cc_bcc/__manifest__.py
@@ -0,0 +1,30 @@
+# Copyright 2023 Camptocamp SA
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+{
+ "name": "Email CC and BCC",
+ "summary": "This module enables sending mail to CC and BCC partners in mail composer form.",
+ "version": "15.0.1.0.0",
+ "development_status": "Alpha",
+ "category": "Social",
+ "website": "https://github.com/OCA/social",
+ "author": "Camptocamp SA, Odoo Community Association (OCA)",
+ "maintainers": ["hailangvn2023"],
+ "license": "AGPL-3",
+ "application": False,
+ "installable": True,
+ "preloadable": True,
+ "depends": [
+ "mail",
+ "account",
+ ],
+ "data": [
+ "views/res_company_views.xml",
+ "views/mail_mail_views.xml",
+ "views/mail_message_views.xml",
+ "views/mail_template_views.xml",
+ "wizards/account_invoice_send_views.xml",
+ "wizards/mail_compose_message_view.xml",
+ ],
+ "demo": [],
+ "qweb": [],
+}
diff --git a/mail_composer_cc_bcc/models/__init__.py b/mail_composer_cc_bcc/models/__init__.py
new file mode 100644
index 000000000..3a18c06a2
--- /dev/null
+++ b/mail_composer_cc_bcc/models/__init__.py
@@ -0,0 +1,7 @@
+# These modules are sorted by calling sequence, i.e. mail_thread calls
+# mail_message, etc.
+from . import res_company
+from . import mail_template
+from . import mail_thread
+from . import mail_message
+from . import mail_mail
diff --git a/mail_composer_cc_bcc/models/mail_mail.py b/mail_composer_cc_bcc/models/mail_mail.py
new file mode 100644
index 000000000..95d4c3556
--- /dev/null
+++ b/mail_composer_cc_bcc/models/mail_mail.py
@@ -0,0 +1,269 @@
+import ast
+import base64
+import logging
+import re
+import smtplib
+
+import psycopg2
+
+from odoo import _, fields, models, tools
+
+from odoo.addons.base.models.ir_mail_server import MailDeliveryException
+
+_logger = logging.getLogger(__name__)
+
+
+def format_emails(partners):
+ emails = [
+ tools.formataddr((p.name or "False", p.email or "False")) for p in partners
+ ]
+ return ", ".join(emails)
+
+
+class MailMail(models.Model):
+ _inherit = "mail.mail"
+
+ email_bcc = fields.Char("Bcc", help="Blind Cc message recipients")
+
+ def _send( # noqa: max-complexity: 4
+ self, auto_commit=False, raise_exception=False, smtp_session=None
+ ):
+ env = self.env
+ IrMailServer = env["ir.mail_server"]
+ IrAttachment = env["ir.attachment"]
+ ICP = env["ir.config_parameter"].sudo()
+ # Mail composer only sends 1 mail at a time.
+ is_out_of_scope = len(self.ids) > 1
+ if is_out_of_scope or not (self.email_cc or self.email_bcc):
+ return super()._send(
+ auto_commit=auto_commit,
+ raise_exception=raise_exception,
+ smtp_session=smtp_session,
+ )
+ mail = self
+ success_pids = []
+ failure_type = None
+ # ===== Same with native Odoo =====
+ # https://github.com/odoo/odoo/blob/6ec4ba7ba22626219ddd09241c274b09a21fac0b
+ # /addons/mail/models/mail_mail.py#L375
+ try:
+ if mail.state != "outgoing":
+ if mail.state != "exception" and mail.auto_delete:
+ mail.sudo().unlink()
+ return True
+
+ # remove attachments if user send the link with the access_token
+ body = mail.body_html or ""
+ attachments = mail.attachment_ids
+ for link in re.findall(r"/web/(?:content|image)/([0-9]+)", body):
+ attachments = attachments - IrAttachment.browse(int(link))
+
+ # load attachment binary data with a separate read(), as
+ # prefetching all `datas` (binary field) could bloat the browse
+ # cache, triggerring soft/hard mem limits with temporary data.
+ attachments = [
+ (a["name"], base64.b64decode(a["datas"]), a["mimetype"])
+ for a in attachments.sudo().read(["name", "datas", "mimetype"])
+ if a["datas"] is not False
+ ]
+
+ # ===== Different than native Odoo =====
+ email = mail._send_prepare_values()
+ # ===== Same with native Odoo =====
+ # headers
+ headers = {}
+ bounce_alias = ICP.get_param("mail.bounce.alias")
+ catchall_domain = ICP.get_param("mail.catchall.domain")
+ if bounce_alias and catchall_domain:
+ headers["Return-Path"] = "%s@%s" % (bounce_alias, catchall_domain)
+ if mail.headers:
+ try:
+ headers.update(ast.literal_eval(mail.headers))
+ except Exception as e:
+ # ===== Different than native Odoo =====
+ _logger.warning("Error during update headers: %s" % e)
+
+ # ===== Same with native Odoo =====
+ # Writing on the mail object may fail (e.g. lock on user) which
+ # would trigger a rollback *after* actually sending the email.
+ # To avoid sending twice the same email, provoke the failure earlier
+ mail.write(
+ {
+ "state": "exception",
+ "failure_reason": _(
+ "Error without exception. Probably due do sending an email"
+ " without computed recipients."
+ ),
+ }
+ )
+ # Update notification in a transient exception state to avoid concurrent
+ # update in case an email bounces while sending all emails related to current
+ # mail record.
+ notifs = self.env["mail.notification"].search(
+ [
+ ("notification_type", "=", "email"),
+ ("mail_mail_id", "in", mail.ids),
+ ("notification_status", "not in", ("sent", "canceled")),
+ ]
+ )
+ if notifs:
+ notif_msg = _(
+ "Error without exception. Probably due do concurrent access"
+ " update of notification records. Please see with an administrator."
+ )
+ notifs.sudo().write(
+ {
+ "notification_status": "exception",
+ "failure_type": "unknown",
+ "failure_reason": notif_msg,
+ }
+ )
+ # `test_mail_bounce_during_send`, force immediate update to obtain the lock.
+ # see rev. 56596e5240ef920df14d99087451ce6f06ac6d36
+ notifs.flush(
+ fnames=["notification_status", "failure_type", "failure_reason"],
+ records=notifs,
+ )
+
+ # build an RFC2822 email.message.Message object and send it without queuing
+ res = None
+ # TDE note: could be great to pre-detect missing to/cc and skip sending it
+ # to go directly to failed state update
+ # ===== Different than native Odoo =====
+ msg = self.build_email(
+ email,
+ attachments=attachments,
+ headers=headers,
+ )
+ try:
+ res = IrMailServer.send_email(
+ msg,
+ mail_server_id=mail.mail_server_id.id,
+ smtp_session=smtp_session,
+ )
+ success_pids += mail.recipient_ids.ids
+ # ===== Same with native Odoo =====
+ except AssertionError as error:
+ if str(error) == IrMailServer.NO_VALID_RECIPIENT:
+ # if we have a list of void emails for email_list
+ # -> email missing, otherwise generic email
+ # failure
+ if (
+ not email.get("email_to")
+ and failure_type != "mail_email_invalid"
+ ):
+ failure_type = "mail_email_missing"
+ else:
+ failure_type = "mail_email_invalid"
+ # No valid recipient found for this particular
+ # mail item -> ignore error to avoid blocking
+ # delivery to next recipients, if any. If this is
+ # the only recipient, the mail will show as failed.
+ _logger.info(
+ "Ignoring invalid recipients for mail.mail %s: %s",
+ mail.message_id,
+ email.get("email_to"),
+ )
+ else:
+ raise
+ if res: # mail has been sent at least once, no major exception occurred
+ mail.write(
+ {"state": "sent", "message_id": res, "failure_reason": False}
+ )
+ _logger.info(
+ "Mail with ID %r and Message-Id %r successfully sent",
+ mail.id,
+ mail.message_id,
+ )
+ # /!\ can't use mail.state here, as mail.refresh() will cause an error
+ # see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
+ mail._postprocess_sent_message(
+ success_pids=success_pids, failure_type=failure_type
+ )
+ except MemoryError:
+ # prevent catching transient MemoryErrors, bubble up to
+ # notify user or abort cron job instead of marking the
+ # mail as failed
+ _logger.exception(
+ "MemoryError while processing mail with ID %r and Msg-Id %r."
+ " Consider raising the --limit-memory-hard startup option",
+ mail.id,
+ mail.message_id,
+ )
+ # mail status will stay on ongoing since transaction will be rollback
+ raise
+ except (psycopg2.Error, smtplib.SMTPServerDisconnected):
+ # If an error with the database or SMTP session occurs,
+ # chances are that the cursor or SMTP session are
+ # unusable, causing further errors when trying to save the
+ # state.
+ _logger.exception(
+ "Exception while processing mail with ID %r and Msg-Id %r.",
+ mail.id,
+ mail.message_id,
+ )
+ raise
+ except Exception as e:
+ failure_reason = tools.ustr(e)
+ _logger.exception(
+ "failed sending mail (id: %s) due to %s", mail.id, failure_reason
+ )
+ mail.write({"state": "exception", "failure_reason": failure_reason})
+ mail._postprocess_sent_message(
+ success_pids=success_pids,
+ failure_reason=failure_reason,
+ failure_type="unknown",
+ )
+ if raise_exception:
+ if isinstance(e, (AssertionError, UnicodeEncodeError)):
+ if isinstance(e, UnicodeEncodeError):
+ value = "Invalid text: %s" % e.object
+ else:
+ value = ". ".join(e.args)
+ raise MailDeliveryException(value) from e
+ raise
+
+ # ===== Different than native Odoo =====
+ # As we only send one email, auto_commit has no value
+ return True
+
+ def build_email(self, email, attachments=None, headers=None):
+ env = self.env
+ mail = self
+ IrMailServer = env["ir.mail_server"]
+ # ===== Same with native Odoo =====
+ # https://github.com/odoo/odoo/blob/6ec4ba7ba22626219ddd09241c274b09a21fac0b
+ # /addons/mail/models/mail_mail.py#L447
+ msg = IrMailServer.build_email(
+ email_from=mail.email_from,
+ email_to=email.get("email_to"),
+ subject=mail.subject,
+ body=email.get("body"),
+ body_alternative=email.get("body_alternative"),
+ # ===== Different than native Odoo =====
+ email_cc=mail.email_cc,
+ email_bcc=mail.email_bcc,
+ # ===== Same with native Odoo =====
+ reply_to=mail.reply_to,
+ attachments=attachments,
+ message_id=mail.message_id,
+ references=mail.references,
+ object_id=mail.res_id and ("%s-%s" % (mail.res_id, mail.model)),
+ subtype="html",
+ subtype_alternative="plain",
+ headers=headers,
+ )
+ return msg
+
+ def _send_prepare_values(self, partner=None):
+ res = super()._send_prepare_values(partner=partner)
+ is_from_composer = self.env.context.get("is_from_composer", False)
+ if not is_from_composer:
+ return res
+ partners_cc_bcc = self.recipient_cc_ids + self.recipient_bcc_ids
+ partner_to_ids = [r.id for r in self.recipient_ids if r not in partners_cc_bcc]
+ partner_to = self.env["res.partner"].browse(partner_to_ids)
+ res["email_to"] = format_emails(partner_to)
+ res["email_cc"] = format_emails(self.recipient_cc_ids)
+ res["email_bcc"] = format_emails(self.recipient_bcc_ids)
+ return res
diff --git a/mail_composer_cc_bcc/models/mail_message.py b/mail_composer_cc_bcc/models/mail_message.py
new file mode 100644
index 000000000..d4740f0ae
--- /dev/null
+++ b/mail_composer_cc_bcc/models/mail_message.py
@@ -0,0 +1,25 @@
+# Copyright 2023 Camptocamp
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models
+
+
+class MailMessage(models.Model):
+ _inherit = "mail.message"
+
+ recipient_cc_ids = fields.Many2many(
+ "res.partner",
+ "mail_message_res_partner_cc_rel",
+ "mail_message_id",
+ "parent_id",
+ string="Cc (Partners)",
+ context={"active_test": False},
+ )
+ recipient_bcc_ids = fields.Many2many(
+ "res.partner",
+ "mail_message_res_partner_bcc_rel",
+ "mail_message_id",
+ "parent_id",
+ string="Bcc (Partners)",
+ context={"active_test": False},
+ )
diff --git a/mail_composer_cc_bcc/models/mail_template.py b/mail_composer_cc_bcc/models/mail_template.py
new file mode 100644
index 000000000..4dff4140c
--- /dev/null
+++ b/mail_composer_cc_bcc/models/mail_template.py
@@ -0,0 +1,52 @@
+# Copyright 2023 Camptocamp
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models, tools
+
+from ..wizards.mail_compose_message import CC_BCC_FIELDS
+
+
+class MailTemplate(models.Model):
+ _inherit = "mail.template"
+
+ email_bcc = fields.Char(
+ "Bcc", help="Blind cc recipients (placeholders may be used here)"
+ )
+
+ def generate_recipients(self, results, res_ids):
+ res = super().generate_recipients(results, res_ids)
+ is_from_composer = self.env.context.get("is_from_composer", False)
+ if not is_from_composer or not (self.email_cc or self.email_bcc):
+ return res
+ ctx = {"tpl_partners_only": False}
+ ctx.update(self.env.context)
+ tmpl_ctx = super().with_context(**ctx)
+ template_values = {}
+ tmpl_ctx._render_fields(res_ids, CC_BCC_FIELDS.keys(), template_values)
+ for res_id, values in template_values.items():
+ email_cc_bcc = tools.email_split(values["email_cc"])
+ email_cc_bcc += tools.email_split(values["email_bcc"])
+ for_emails = [("email", "in", email_cc_bcc)]
+ partner_cc_bcc_ids = self.env["res.partner"].search(for_emails).ids
+ if not partner_cc_bcc_ids:
+ continue
+ res[res_id]["partner_ids"] = [
+ _id
+ for _id in res[res_id]["partner_ids"]
+ if _id not in partner_cc_bcc_ids
+ ]
+ return res
+
+ def _render_fields(self, res_ids, field_names, results):
+ template = self
+ template_res_ids = res_ids
+ for field in field_names:
+ generated_field_values = template._render_field(
+ field,
+ template_res_ids,
+ options={"render_safe": field == "subject"},
+ post_process=(field == "body_html"),
+ )
+ for res_id, field_value in generated_field_values.items():
+ results.setdefault(res_id, dict())[field] = field_value
+ return results
diff --git a/mail_composer_cc_bcc/models/mail_thread.py b/mail_composer_cc_bcc/models/mail_thread.py
new file mode 100644
index 000000000..0cb73eaf8
--- /dev/null
+++ b/mail_composer_cc_bcc/models/mail_thread.py
@@ -0,0 +1,117 @@
+# Copyright 2023 Camptocamp
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo import models
+
+from .mail_mail import format_emails
+
+
+class MailThread(models.AbstractModel):
+ _inherit = "mail.thread"
+
+ def _message_create(self, values_list):
+ context = self.env.context
+ res = super()._message_create(values_list)
+ partners_cc = context.get("partner_cc_ids", None)
+ if partners_cc:
+ res.recipient_cc_ids = partners_cc
+ partners_bcc = context.get("partner_bcc_ids", None)
+ if partners_bcc:
+ res.recipient_bcc_ids = partners_bcc
+ return res
+
+ def _notify_by_email_add_values(self, base_mail_values):
+ """
+ This is to add cc, bcc addresses to mail.mail objects so that email
+ can be sent to those addresses.
+ """
+ context = self.env.context
+
+ partners_cc = context.get("partner_cc_ids", None)
+ if partners_cc:
+ base_mail_values["email_cc"] = format_emails(partners_cc)
+ partners_bcc = context.get("partner_bcc_ids", None)
+ if partners_bcc:
+ base_mail_values["email_bcc"] = format_emails(partners_bcc)
+ res = super()._notify_by_email_add_values(base_mail_values)
+ return res
+
+ def _notify_compute_recipients(self, message, msg_vals):
+ """
+ This is to add cc, bcc recipients so that they can be grouped with
+ other recipients.
+ """
+ ResPartner = self.env["res.partner"]
+ MailFollowers = self.env["mail.followers"]
+ rdata = super()._notify_compute_recipients(message, msg_vals)
+ context = self.env.context
+ is_from_composer = context.get("is_from_composer", False)
+ if not is_from_composer:
+ return rdata
+ for pdata in rdata:
+ pdata["type"] = "customer"
+ partners_cc_bcc = context.get("partner_cc_ids", ResPartner)
+ partners_cc_bcc += context.get("partner_bcc_ids", ResPartner)
+ msg_sudo = message.sudo()
+ message_type = (
+ msg_vals.get("message_type") if msg_vals else msg_sudo.message_type
+ )
+ subtype_id = msg_vals.get("subtype_id") if msg_vals else msg_sudo.subtype_id.id
+ recipients_cc_bcc = MailFollowers._get_recipient_data(
+ None, message_type, subtype_id, partners_cc_bcc.ids
+ )
+ for pid, active, pshare, notif, groups in recipients_cc_bcc:
+ if not pid:
+ continue
+ if not notif: # notif is False, has no user, is therefore customer
+ notif = "email"
+ msg_type = "customer"
+ pdata = {
+ "id": pid,
+ "active": active,
+ "share": pshare,
+ "groups": groups or [],
+ "notif": notif,
+ "type": msg_type,
+ }
+ rdata.append(pdata)
+ return rdata
+
+ def _notify_email_recipient_values(self, recipient_ids):
+ """
+ This is to add cc, bcc recipients' ids to recipient_ids of mail.mail
+ """
+ res = super()._notify_email_recipient_values(recipient_ids)
+ context = self.env.context
+ r_ids = list(recipient_ids)
+ partners_cc = context.get("partner_cc_ids", None)
+ if partners_cc:
+ r_ids += partners_cc.ids
+ partners_bcc = context.get("partner_bcc_ids", None)
+ if partners_bcc:
+ r_ids += partners_bcc.ids
+ if partners_cc or partners_bcc:
+ res["recipient_ids"] = tuple(set(r_ids))
+ return res
+
+ def _notify_classify_recipients(self, recipient_data, model_name, msg_vals=None):
+ res = super()._notify_classify_recipients(
+ recipient_data, model_name, msg_vals=msg_vals
+ )
+ is_from_composer = self.env.context.get("is_from_composer", False)
+ if not is_from_composer:
+ return res
+ ids = []
+ customer_data = None
+ for rcpt_data in res:
+ if rcpt_data["notification_group_name"] == "customer":
+ customer_data = rcpt_data
+ else:
+ ids += rcpt_data["recipients"]
+ if not customer_data:
+ customer_data = res[0]
+ customer_data["notification_group_name"] = "customer"
+ customer_data["recipients"] = ids
+ else:
+ customer_data["recipients"] += ids
+ return [customer_data]
diff --git a/mail_composer_cc_bcc/models/res_company.py b/mail_composer_cc_bcc/models/res_company.py
new file mode 100644
index 000000000..47ea9e7ea
--- /dev/null
+++ b/mail_composer_cc_bcc/models/res_company.py
@@ -0,0 +1,23 @@
+# Copyright 2023 Camptocamp
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models
+
+
+class Company(models.Model):
+ _inherit = "res.company"
+
+ default_partner_cc_ids = fields.Many2many(
+ "res.partner",
+ "res_company_res_partner_cc_rel",
+ "company_id",
+ "partner_id",
+ string="Default Cc",
+ )
+ default_partner_bcc_ids = fields.Many2many(
+ "res.partner",
+ "res_company_res_partner_bcc_rel",
+ "company_id",
+ "partner_id",
+ string="Default Bcc",
+ )
diff --git a/mail_composer_cc_bcc/readme/CONFIGURE.rst b/mail_composer_cc_bcc/readme/CONFIGURE.rst
new file mode 100644
index 000000000..6011282f6
--- /dev/null
+++ b/mail_composer_cc_bcc/readme/CONFIGURE.rst
@@ -0,0 +1,8 @@
+In company form there are two fields to set default cc and bcc
+partners.
+
+ .. image:: ../static/img/res_company_form_default_cc_bcc.png
+
+In template form there are two fields to set cc and bcc emails.
+
+ .. image:: ../static/img/email_template_form_cc_bcc.png
diff --git a/mail_composer_cc_bcc/readme/CONTRIBUTORS.rst b/mail_composer_cc_bcc/readme/CONTRIBUTORS.rst
new file mode 100644
index 000000000..796a20add
--- /dev/null
+++ b/mail_composer_cc_bcc/readme/CONTRIBUTORS.rst
@@ -0,0 +1,3 @@
+* `Trobz `_:
+
+ * Hai N. Le
diff --git a/mail_composer_cc_bcc/readme/CREDITS.rst b/mail_composer_cc_bcc/readme/CREDITS.rst
new file mode 100644
index 000000000..ac19123b0
--- /dev/null
+++ b/mail_composer_cc_bcc/readme/CREDITS.rst
@@ -0,0 +1 @@
+The creation of this module was financially supported by Camptocamp.
diff --git a/mail_composer_cc_bcc/readme/DESCRIPTION.rst b/mail_composer_cc_bcc/readme/DESCRIPTION.rst
new file mode 100644
index 000000000..5bc4d650f
--- /dev/null
+++ b/mail_composer_cc_bcc/readme/DESCRIPTION.rst
@@ -0,0 +1,16 @@
+Odoo native does not support defining a Cc field in the Mail Composer
+by default; instead, it only has a unique Recipients fields, which is
+confusing for a lot of end users.
+
+This module allows to properly separate To:, Cc:, and Bcc: fields
+in the Mail Composer.
+
+Features
+~~~~~~~~
+
+* Add Cc and Bcc fields to mail composer form. Send only once to multiple email
+ addresses.
+* Add Cc and Bcc fields to company form to use them as default in mail composer
+ form.
+* Add Bcc field to mail template form. Use Cc and Bcc fields to lookup partners
+ by email then add them to corresponding fields in mail composer form.
diff --git a/mail_composer_cc_bcc/readme/USAGE.rst b/mail_composer_cc_bcc/readme/USAGE.rst
new file mode 100644
index 000000000..5f24f3804
--- /dev/null
+++ b/mail_composer_cc_bcc/readme/USAGE.rst
@@ -0,0 +1,10 @@
+The partners cc and bcc from company form will be used to fill in mail composer
+form.
+
+ .. image:: ../static/img/mail_compose_message_default_cc_bcc.png
+
+When select a template that has cc and/or bcc emails, the emails will be used
+to lookup partners then found partners will be added to corresponding mail
+composer's fields.
+
+ .. image:: ../static/img/mail_compose_message_template_cc_bcc.png
diff --git a/mail_composer_cc_bcc/static/description/index.html b/mail_composer_cc_bcc/static/description/index.html
new file mode 100644
index 000000000..13ad4cc7b
--- /dev/null
+++ b/mail_composer_cc_bcc/static/description/index.html
@@ -0,0 +1,477 @@
+
+
+
+
+
+
+Email CC and BCC
+
+
+
+
+
Email CC and BCC
+
+
+

+
Odoo native does not support defining a Cc field in the Mail Composer
+by default; instead, it only has a unique Recipients fields, which is
+confusing for a lot of end users.
+
This module allows to properly separate To:, Cc:, and Bcc: fields
+in the Mail Composer.
+
+
Features
+
+- Add Cc and Bcc fields to mail composer form. Send only once to multiple email
+addresses.
+- Add Cc and Bcc fields to company form to use them as default in mail composer
+form.
+- Add Bcc field to mail template form. Use Cc and Bcc fields to lookup partners
+by email then add them to corresponding fields in mail composer form.
+
+
+
Important
+
This is an alpha version, the data model and design can change at any time without warning.
+Only for development or testing purpose, do not use in production.
+More details on development status
+
+
Table of contents
+
+
+
+
In company form there are two fields to set default cc and bcc
+partners.
+
+
+
+
In template form there are two fields to set cc and bcc emails.
+
+
+
+
+
+
+
The partners cc and bcc from company form will be used to fill in mail composer
+form.
+
+
+
+
When select a template that has cc and/or bcc emails, the emails will be used
+to lookup partners then found partners will be added to corresponding mail
+composer’s fields.
+
+
+
+
+
+
+
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+
Other credits
+
The creation of this module was financially supported by Camptocamp.
+
+
+
Maintainers
+
This module is maintained by the OCA.
+

+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
Current maintainer:
+

+
This module is part of the OCA/social project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
+
+
+
+
diff --git a/mail_composer_cc_bcc/static/img/email_template_form_cc_bcc.png b/mail_composer_cc_bcc/static/img/email_template_form_cc_bcc.png
new file mode 100644
index 0000000000000000000000000000000000000000..336e4c1a9458900ef28322b65edf52b6cf709367
GIT binary patch
literal 57023
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