From 7d9e08fbe581100f6d804d948e9296b2fe446b28 Mon Sep 17 00:00:00 2001 From: Kristof Bernaert Date: Mon, 26 Jan 2026 01:50:07 +0100 Subject: [PATCH] Fix allow_mail_archived_partner causing system notifications to send emails --- .../models/account_move_send.py | 52 ++++----------- .../models/mail_compose_message.py | 65 +++++++++++++++++-- .../models/mail_thread.py | 65 +++++++++---------- 3 files changed, 101 insertions(+), 81 deletions(-) diff --git a/allow_mail_archived_partner/models/account_move_send.py b/allow_mail_archived_partner/models/account_move_send.py index 3e19c2d..07de6e4 100755 --- a/allow_mail_archived_partner/models/account_move_send.py +++ b/allow_mail_archived_partner/models/account_move_send.py @@ -1,3 +1,4 @@ +# account_move_send.py from odoo import models, api @@ -7,55 +8,24 @@ class AccountInvoiceSend(models.TransientModel): @api.model def default_get(self, fields): """ - Override default_get to pre-fill archived partners. - This is often called BEFORE _get_default_mail_partner_ids. + Pre-fill invoice email wizard with archived partner. """ res = super().default_get(fields) - # Check if we're in the right context - active_model = self.env.context.get('active_model') active_ids = self.env.context.get('active_ids', []) - if active_model == 'account.move' and active_ids: - # Get the moves with archived partners allowed - moves = self.env['account.move'].with_context( + if active_ids: + # Find invoices WITH archived partners + invoices = self.env['account.move'].with_context( active_test=False ).browse(active_ids) - # Collect all unique partners from the moves - partner_ids = set() - for move in moves: - if move.partner_id and move.partner_id.email: - partner_ids.add(move.partner_id.id) + partner_ids = [] + for inv in invoices: + if inv.partner_id: + partner_ids.append(inv.partner_id.id) if partner_ids: - res['partner_ids'] = [(6, 0, list(partner_ids))] + res['partner_ids'] = [(6, 0, partner_ids)] - return res - - @api.model - def _get_default_mail_partner_ids(self, move, mail_template, mail_lang): - """ - Also fix this method to include archived partners. - """ - wiz = self.with_context( - active_test=False, - include_archived_partners=True, - ) - return super( - AccountInvoiceSend, - wiz - )._get_default_mail_partner_ids(move, mail_template, mail_lang) - - def _get_mail_composer_values(self, move, template, partner_ids): - """ - Ensure context is passed to mail composer. - """ - return super( - AccountInvoiceSend, - self.with_context( - active_test=False, - include_archived_partners=True, - mail_notify_force=True, - ) - )._get_mail_composer_values(move, template, partner_ids) \ No newline at end of file + return res \ No newline at end of file diff --git a/allow_mail_archived_partner/models/mail_compose_message.py b/allow_mail_archived_partner/models/mail_compose_message.py index 12cd4fb..90ff3ce 100755 --- a/allow_mail_archived_partner/models/mail_compose_message.py +++ b/allow_mail_archived_partner/models/mail_compose_message.py @@ -1,24 +1,77 @@ +import logging +_logger = logging.getLogger(__name__) + from odoo import models, api class MailComposeMessage(models.TransientModel): _inherit = "mail.compose.message" + @api.model + def default_get(self, fields): + """ + Pre-fill the email wizard with archived partner's email. + This makes the archived partner appear in the "To" field. + """ + res = super().default_get(fields) + + model = self.env.context.get("active_model") + res_id = self.env.context.get("active_id") + + # For invoices + if model == "account.move" and res_id: + invoice = self.env["account.move"].with_context( + active_test=False # CRITICAL: Find archived partner + ).browse(res_id) + + if invoice.exists() and invoice.partner_id: + res["partner_ids"] = [(6, 0, [invoice.partner_id.id])] + _logger.debug(f"Pre-filled archived partner {invoice.partner_id.name} for invoice") + + # For sales orders + elif model == "sale.order" and res_id: + order = self.env["sale.order"].with_context( + active_test=False # CRITICAL: Find archived partner + ).browse(res_id) + + if order.exists() and order.partner_id: + res["partner_ids"] = [(6, 0, [order.partner_id.id])] + _logger.debug(f"Pre-filled archived partner {order.partner_id.name} for sales order") + + return res + + def _prepare_mail_values(self, res_ids): + """ + Set context to allow archived partners during email sending. + This ensures the email can actually be sent to archived partners. + """ + model = self.env.context.get("active_model") or self.model + + if model in ["sale.order", "account.move"]: + _logger.debug(f"Setting context for {model} to allow archived partners") + 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) + def _prepare_recipient_values(self, partner): """ Handle archived partners when context allows. + This ensures archived partner data is properly formatted for email. """ - # Check if we should include archived partners - # Look for context flags from account.invoice.send OR direct context include_archived = ( self.env.context.get("include_archived_partners") or - self.env.context.get("mail_notify_force") or - (self.env.context.get("active_model") in ["account.move", "sale.order"] and - not getattr(partner, 'active', True)) + self.env.context.get("mail_notify_force") ) if include_archived and partner and not partner.active: - # Return complete data for archived partner + _logger.debug(f"Including archived partner {partner.name} in email") return { "partner_id": partner.id, "email": partner.email, diff --git a/allow_mail_archived_partner/models/mail_thread.py b/allow_mail_archived_partner/models/mail_thread.py index b7a49ab..9c61018 100755 --- a/allow_mail_archived_partner/models/mail_thread.py +++ b/allow_mail_archived_partner/models/mail_thread.py @@ -8,43 +8,40 @@ 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. + FIXED: Allow archived partners ONLY for explicit manual sends """ - # 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) + # 1) BLOCK system notifications completely + is_system_notification = ( + getattr(message, "message_type", None) == "notification" or + getattr(message, "author_id", False) and + message.author_id == self.env.ref("base.partner_root", raise_if_not_found=False) + ) + + if is_system_notification: + _logger.debug("Blocking system notification email") + recipients = super()._notify_get_recipients(message, msg_vals, **kwargs) + # Force inbox only, no email + for recipient in recipients: + recipient["notif"] = "inbox" + return recipients - # 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 + # 2) Check if this is an EXPLICIT manual send + is_explicit_send = ( + self.env.context.get("mail_notify_force") or + self.env.context.get("include_archived_partners") + ) + + # 3) For explicit sends, allow archived partners + if is_explicit_send: + _logger.debug(f"Explicit send detected for {self._name}, allowing archived partners") + return super( + MailThread, + self.with_context(active_test=False) + )._notify_get_recipients(message, msg_vals, **kwargs) + + # 4) Default behavior for non-explicit sends + return super()._notify_get_recipients(message, msg_vals, **kwargs) \ No newline at end of file