Fix allow_mail_archived_partner causing system notifications to send emails

This commit is contained in:
Kristof Bernaert
2026-01-26 00:44:43 +01:00
commit f64fbb5e06
25 changed files with 453 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
# models/__init__.py
from . import mail_thread
from . import res_partner
from . import mail_template
# from . import mail_compose_message
from . import account_move_send
+14
View File
@@ -0,0 +1,14 @@
from odoo import models, api
class AccountMoveSend(models.TransientModel):
_inherit = "account.move.send"
@api.model
def _get_default_mail_partner_ids(self, move, mail_template, mail_lang):
"""
The invoice 'Send by Email' wizard uses this to compute partner_ids (To:).
We must disable active_test here to include archived partners.
"""
wiz = self.with_context(active_test=False, include_archived_partners=True)
return super(AccountMoveSend, wiz)._get_default_mail_partner_ids(move, mail_template, mail_lang)
+48
View File
@@ -0,0 +1,48 @@
from odoo import models
class MailComposeMessage(models.TransientModel):
_inherit = "mail.compose.message"
def _get_default_recipients(self):
"""
This is the ONLY place that fills the 'To' field in Odoo 16.
We explicitly allow archived partners here for user-initiated sends.
"""
recipients = super()._get_default_recipients()
model = self.env.context.get("active_model")
active_ids = self.env.context.get("active_ids") or []
if model == "account.move" and active_ids:
moves = self.env["account.move"].with_context(
active_test=False
).browse(active_ids)
partners = moves.mapped("partner_id").filtered(
lambda p: p.email
)
if partners:
recipients["partner_ids"] = [(6, 0, partners.ids)]
recipients["email_to"] = ", ".join(partners.mapped("email"))
return recipients
def _prepare_mail_values(self, res_ids):
"""
Explicit user send → allow archived partners internally.
"""
model = self.env.context.get("active_model") or self.model
if model in ["sale.order", "account.move"]:
return super(
MailComposeMessage,
self.with_context(
active_test=False,
include_archived_partners=True,
mail_notify_force=True,
)
)._prepare_mail_values(res_ids)
return super()._prepare_mail_values(res_ids)
+27
View File
@@ -0,0 +1,27 @@
import logging
_logger = logging.getLogger(__name__)
from odoo import models
class MailTemplate(models.Model):
_inherit = "mail.template"
def generate_email(self, res_ids, fields=None):
"""
Only allow archived partners when *explicitly* sending through a flow
that sets:
context['mail_notify_force'] = True
This prevents system notifications from inheriting your relaxed rules.
"""
if self.model in ["sale.order", "account.move"] and self.env.context.get("mail_notify_force"):
return super(
MailTemplate,
self.with_context(
active_test=False,
include_archived_partners=True
)
).generate_email(res_ids, fields)
return super().generate_email(res_ids, fields)
+50
View File
@@ -0,0 +1,50 @@
import logging
_logger = logging.getLogger(__name__)
from odoo import models
class MailThread(models.AbstractModel):
_inherit = "mail.thread"
def _notify_thread(self, message, msg_vals=False, **kwargs):
"""
Do NOT force notifications, do NOT set mail_notification=True,
do NOT globally disable active_test here.
The parent method decides whether a message should notify.
"""
return super()._notify_thread(message, msg_vals=msg_vals, **kwargs)
def _notify_get_recipients(self, message, msg_vals, **kwargs):
"""
CRITICAL RULES:
- Never turn system notifications into outgoing emails.
- Never fabricate recipients when Odoo returns none.
- Never force notif='email'.
If archived partners should be allowed, that must be opt-in
via context['include_archived_partners'] and only for explicit user sends,
not tracking/system messages.
"""
# 1) Never email system notifications (tracking, auto messages, etc.)
# These are the ones that caused "Re: Draft Bill ..." and odoobot@example.com.
if getattr(message, "message_type", None) == "notification":
return super()._notify_get_recipients(message, msg_vals, **kwargs)
# 2) Never auto-notify supplier bills. Vendor bills created from email/EDI
# must not trigger replies to vendors via chatter notifications.
# (If you DO want it for customer invoices, leave those alone.)
if self._name == "account.move":
# self can be multi; if any are supplier bills, just don't customize at all
# (safer than partial overriding recipient lists)
if any(m.move_type == "in_invoice" for m in self):
return super()._notify_get_recipients(message, msg_vals, **kwargs)
# 3) Default behavior first
recipients = super()._notify_get_recipients(message, msg_vals, **kwargs)
# 4) Do NOT fabricate recipients if empty.
# If you need archived partner support, handle it in explicit sending flows
# (compose + template), not here.
return recipients
+48
View File
@@ -0,0 +1,48 @@
import logging
_logger = logging.getLogger(__name__)
from odoo import models, api
class ResPartner(models.Model):
_inherit = "res.partner"
@api.model
def _search(
self,
args,
offset=0,
limit=None,
order=None,
count=False,
access_rights_uid=None
):
"""
Only include archived partners when the caller explicitly opts in via:
context['include_archived_partners'] = True
Never key off fragile string matches like 'mail'/'notify'/'message'.
"""
if self.env.context.get("include_archived_partners"):
# Remove explicit active=True filters only (do not touch other domains)
args = [
arg for arg in args
if not (
isinstance(arg, (list, tuple))
and len(arg) == 3
and arg[0] == "active"
and arg[1] == "="
and arg[2] is True
)
]
# Also ensure active_test is off so ORM won't auto-filter archived partners
self = self.with_context(active_test=False)
return super()._search(
args,
offset=offset,
limit=limit,
order=order,
count=count,
access_rights_uid=access_rights_uid
)