diff --git a/allow_mail_archived_partner/models/account_move_send.py b/allow_mail_archived_partner/models/account_move_send.py index 5f76071..a760faa 100755 --- a/allow_mail_archived_partner/models/account_move_send.py +++ b/allow_mail_archived_partner/models/account_move_send.py @@ -1,3 +1,6 @@ +import logging +_logger = logging.getLogger(__name__) + from odoo import models, api, fields @@ -15,9 +18,8 @@ class AccountInvoiceSend(models.TransientModel): @api.model def default_get(self, fields): - """ - Pre-fill invoice email wizard with archived partners. - """ + _logger.error("🔥 ACCOUNT.INVOICE.SEND default_get") + res = super().default_get(fields) active_ids = self.env.context.get('active_ids', []) @@ -29,13 +31,16 @@ class AccountInvoiceSend(models.TransientModel): partners = moves.mapped('partner_id').filtered(lambda p: p.email) if partners: res['partner_ids'] = [(6, 0, partners.ids)] + _logger.error(f"🔥 Set partner_ids: {res['partner_ids']}") return res def action_send_and_print(self): - """ - Pass context to allow archived partners during email sending. - """ + _logger.error("🔥🔥🔥 ACCOUNT.INVOICE.SEND action_send_and_print CALLED 🔥🔥🔥") + _logger.error(f"🔥 Wizard ID: {self.id}") + _logger.error(f"🔥 Partner IDs: {self.partner_ids.ids}") # This returns a list of IDs + + # Call parent with context AND ensure partner_ids are passed return super( AccountInvoiceSend, self.with_context( @@ -44,13 +49,15 @@ class AccountInvoiceSend(models.TransientModel): mail_notify_force=True, force_email=True, mark_invoice_as_sent=True, + # Pass simple list of IDs, NOT ORM tuple format + invoice_partner_ids=self.partner_ids.ids if self.partner_ids else [], ) ).action_send_and_print() def _get_composer_values(self, res_ids, template): - """ - Pass context to mail composer to allow archived partners. - """ + _logger.error("🔥 ACCOUNT.INVOICE.SEND _get_composer_values") + + # Also pass context to composer return super( AccountInvoiceSend, self.with_context( diff --git a/allow_mail_archived_partner/models/mail_compose_message.py b/allow_mail_archived_partner/models/mail_compose_message.py index 739cc85..e408b13 100755 --- a/allow_mail_archived_partner/models/mail_compose_message.py +++ b/allow_mail_archived_partner/models/mail_compose_message.py @@ -1,3 +1,6 @@ +import logging +_logger = logging.getLogger(__name__) + from odoo import models, api @@ -10,6 +13,8 @@ class MailComposeMessage(models.TransientModel): Pre-fill email wizard with archived partners for sales orders. For invoices, this is handled by account.invoice.send. """ + _logger.debug("=== MAIL.COMPOSE.MESSAGE DEFAULT_GET ===") + res = super().default_get(fields) model = self.env.context.get("active_model") @@ -17,6 +22,8 @@ class MailComposeMessage(models.TransientModel): # Only handle sales orders here - invoices use account.invoice.send if model == "sale.order" and res_id: + _logger.debug(f"Processing sales order {res_id}") + # Find order with archived partners allowed order = self.env["sale.order"].with_context( active_test=False @@ -24,6 +31,7 @@ class MailComposeMessage(models.TransientModel): 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.id} for sales order") return res @@ -32,11 +40,14 @@ class MailComposeMessage(models.TransientModel): Set context flags for manual email sends. This ensures archived partners are allowed during email sending. """ + _logger.debug("=== _prepare_mail_values ===") + model = self.env.context.get("active_model") or self.model # Only for sales orders and account moves (invoices) # Note: account.invoice.send should handle invoices, but keep this as fallback if model in ["sale.order", "account.move"]: + _logger.debug(f"Setting context for {model} to allow archived partners") return super( MailComposeMessage, self.with_context( @@ -63,6 +74,7 @@ class MailComposeMessage(models.TransientModel): # If it's a manual send and partner is archived, include them if is_manual_send and partner and not partner.active: + _logger.debug(f"Including archived partner {partner.name} in email") return { "partner_id": partner.id, "email": partner.email, @@ -71,4 +83,21 @@ class MailComposeMessage(models.TransientModel): } # Default behavior for active partners or non-manual sends - return super()._prepare_recipient_values(partner) \ No newline at end of file + return super()._prepare_recipient_values(partner) + + + def action_send_mail(self): + """ + Log when email is actually sent. + """ + _logger.info("🔥 MAIL.COMPOSE.MESSAGE action_send_mail") + _logger.info(f"Partner IDs: {self.partner_ids.ids}") + _logger.info(f"Context: {dict(self.env.context)}") + return super().action_send_mail() + + def _action_send_mail(self, auto_commit=False): + """ + Another possible send method. + """ + _logger.info("🔥 MAIL.COMPOSE.MESSAGE _action_send_mail") + return super()._action_send_mail(auto_commit=auto_commit) \ No newline at end of file diff --git a/allow_mail_archived_partner/models/mail_template.py b/allow_mail_archived_partner/models/mail_template.py index cd6ec44..b61b744 100755 --- a/allow_mail_archived_partner/models/mail_template.py +++ b/allow_mail_archived_partner/models/mail_template.py @@ -1,6 +1,25 @@ +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,from odoo import models + + class MailTemplate(models.Model): _inherit = "mail.template" @@ -20,4 +39,11 @@ class MailTemplate(models.Model): ) ).generate_email(res_ids, fields) - return super().generate_email(res_ids, fields) \ No newline at end of file + return super().generate_email(res_ids, fields) + self.with_context( + active_test=False, + include_archived_partners=True + ) + ).generate_email(res_ids, fields) + + return super().generate_email(res_ids, fields) diff --git a/allow_mail_archived_partner/models/mail_thread.py b/allow_mail_archived_partner/models/mail_thread.py index 8c3c6cf..ddbd5ab 100755 --- a/allow_mail_archived_partner/models/mail_thread.py +++ b/allow_mail_archived_partner/models/mail_thread.py @@ -1,50 +1,152 @@ +import logging +_logger = logging.getLogger(__name__) + from odoo import models class MailThread(models.AbstractModel): _inherit = "mail.thread" + _logger.info("=== MAILTHREAD CLASS LOADED (allow_mail_archived_partner) ===") + + def _notify_thread(self, message, msg_vals=False, **kwargs): + """ + Log notification thread calls. + """ + _logger.error("🔥🔥🔥 MailThread._notify_thread CALLED 🔥🔥🔥") + _logger.error(f"🔥 Model: {self._name}") + _logger.error(f"🔥 Message type: {getattr(message, 'message_type', 'Unknown')}") + _logger.error(f"🔥 Message subject: {getattr(message, 'subject', 'No subject')}") + _logger.error(f"🔥 Context: {dict(self.env.context)}") + + # Check message attributes + _logger.error(f"🔥 Message author_id: {getattr(message, 'author_id', None)}") + _logger.error(f"🔥 Message partner_ids: {getattr(message, 'partner_ids', None)}") + _logger.error(f"🔥 Message record: {message.model if hasattr(message, 'model') else 'N/A'} {message.res_id if hasattr(message, 'res_id') else 'N/A'}") + + return super()._notify_thread(message, msg_vals=msg_vals, **kwargs) + def _notify_get_recipients(self, message, msg_vals, **kwargs): """ - Ensure archived recipients are included for manual email sends. + Force inclusion of archived partners for manual sends. """ - # Get normal recipients first - recipients = super()._notify_get_recipients(message, msg_vals, **kwargs) + _logger.error("🔥🔥🔥🔥🔥 MailThread._notify_get_recipients CALLED 🔥🔥🔥🔥🔥") - # Check if this is a manual email send (invoice or sales order) - is_manual_email = ( - self.env.context.get("force_email") or # Invoice wizard - self.env.context.get("mark_invoice_as_sent") or # Invoice wizard - self.env.context.get("mail_notify_force") or # Manual compose - self.env.context.get("default_composition_mode") == "comment" # Email composer + # Check if this is a manual send + is_manual_send = ( + self.env.context.get("mail_notify_force") or + self.env.context.get("include_archived_partners") or + self.env.context.get("force_email") or + self.env.context.get("mark_invoice_as_sent") ) - # If manual email AND message has partner_ids, ensure they're in recipients - if is_manual_email and hasattr(message, 'partner_ids') and message.partner_ids: - for partner in message.partner_ids: - # Check if partner is already in recipients - partner_found = False - for recipient in recipients: - if recipient.get('partner_id') == partner.id: - partner_found = True - # Ensure email notification is enabled - if recipient.get('notif') != 'email': - recipient['notif'] = 'email' - break - - # If partner not found, add them (even if archived!) - if not partner_found and partner.email: - recipients.append({ - 'id': partner.id, - 'partner_id': partner.id, - 'email': partner.email, - 'name': partner.name, - 'notif': 'email', # Force email - 'lang': partner.lang or 'en_US', - 'type': 'customer', - 'is_follower': False, - 'groups': [], - 'notifications': [], - }) + _logger.error(f"🔥 Is manual send? {is_manual_send}") + _logger.error(f"🔥 Message partner_ids: {getattr(message, 'partner_ids', None)}") - return recipients \ No newline at end of file + # For manual sends, we need to handle archived partners + if is_manual_send and hasattr(message, 'partner_ids') and message.partner_ids: + _logger.error("🔥🔥🔥 MANUAL SEND WITH PARTNERS - Handling archived partners 🔥🔥🔥") + + # Get the partner IDs from the message + partner_ids = message.partner_ids.ids + _logger.error(f"🔥 Message has partners: {partner_ids}") + + # Force the lookup of THESE partners (the recipients), not the author + # We need to ensure these partners are found even if archived + + # Call parent with context that allows archived partners + recipients = super( + MailThread, + self.with_context( + active_test=False, + include_archived_partners=True, + mail_notify_force=True, + # Add the partner IDs we want to look up + force_notification_partner_ids=partner_ids, + ) + )._notify_get_recipients(message, msg_vals, **kwargs) + + _logger.error(f"🔥 Number of recipients found: {len(recipients)}") + + # If no recipients found, create them manually + if len(recipients) == 0 and partner_ids: + _logger.error("🔥 No recipients found, creating manually") + recipients = [] + for partner_id in partner_ids: + partner = self.env['res.partner'].with_context( + active_test=False + ).browse(partner_id) + + if partner.exists() and partner.email: + recipients.append({ + 'id': partner.id, + 'partner_id': partner.id, + 'email': partner.email, + 'name': partner.name, + 'notif': 'email', # Force email notification + 'lang': partner.lang or 'en_US', + 'type': 'customer', + 'is_follower': False, + 'groups': [], + 'notifications': [], + }) + _logger.error(f"🔥 Created recipient for archived partner: {partner.id} - {partner.email}") + + for i, recipient in enumerate(recipients): + _logger.error(f"🔥 Recipient {i}: partner_id={recipient.get('partner_id')}, " + f"notif={recipient.get('notif')}, email={recipient.get('email')}") + + return recipients + + + def _message_post(self, **kwargs): + """ + Log message post calls to trace email flow. + """ + _logger.error("🔥🔥🔥 MailThread._message_post CALLED 🔥🔥🔥") + _logger.error(f"🔥 Model: {self._name}") + _logger.error(f"🔥 Kwargs keys: {kwargs.keys()}") + _logger.error(f"🔥 Context: {dict(self.env.context)}") + + return super()._message_post(**kwargs) + + def message_post(self, **kwargs): + """ + Override to ensure partner_ids are passed correctly. + """ + _logger.error("🔥🔥🔥 MailThread.message_post CALLED 🔥🔥🔥") + _logger.error(f"🔥 Model: {self._name}") + _logger.error(f"🔥 Kwargs keys: {kwargs.keys()}") + _logger.error(f"🔥 Context: {dict(self.env.context)}") + _logger.error(f"🔥 Partner IDs in kwargs: {kwargs.get('partner_ids', [])}") + + # Check if we have partner_ids from context (passed from account.invoice.send) + context_partner_ids = self.env.context.get('invoice_partner_ids', []) + if context_partner_ids and not kwargs.get('partner_ids'): + _logger.error(f"🔥 Using partner_ids from context: {context_partner_ids}") + # Convert to simple list of IDs, NOT ORM tuple format + kwargs['partner_ids'] = context_partner_ids + + # Also check for other sources of partner_ids + if not kwargs.get('partner_ids'): + # Try to get from the record itself + if self and hasattr(self, 'partner_id') and self.partner_id: + _logger.error(f"🔥 Getting partner_id from record: {self.partner_id.id}") + kwargs['partner_ids'] = [self.partner_id.id] + + # Convert ORM tuple format to simple list if needed + if kwargs.get('partner_ids') and isinstance(kwargs['partner_ids'], list): + # Check if it's in ORM format [(6, 0, [id1, id2])] + if (len(kwargs['partner_ids']) == 1 and + isinstance(kwargs['partner_ids'][0], (list, tuple)) and + len(kwargs['partner_ids'][0]) == 3 and + kwargs['partner_ids'][0][0] == 6): + + # Extract IDs from ORM format + partner_ids = kwargs['partner_ids'][0][2] + _logger.error(f"🔥 Converting ORM format to simple list: {kwargs['partner_ids']} -> {partner_ids}") + kwargs['partner_ids'] = partner_ids + + _logger.error(f"🔥 Final partner_ids being passed: {kwargs.get('partner_ids', [])}") + + return super().message_post(**kwargs) \ No newline at end of file diff --git a/allow_mail_archived_partner/models/res_partner.py b/allow_mail_archived_partner/models/res_partner.py index 9b14033..b6e7d40 100755 --- a/allow_mail_archived_partner/models/res_partner.py +++ b/allow_mail_archived_partner/models/res_partner.py @@ -1,3 +1,6 @@ +import logging +_logger = logging.getLogger(__name__) + from odoo import models, api @@ -7,9 +10,13 @@ class ResPartner(models.Model): @api.model def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): """ - Remove active filters when context indicates manual email send - (to allow finding archived partners). + SIMPLE: Just remove active filters when context says to include archived. """ + _logger.error("🔥 RES.PARTNER._search - Checking for archived context") + _logger.error(f"🔥 Context force_email: {self.env.context.get('force_email')}") + _logger.error(f"🔥 Context mark_invoice_as_sent: {self.env.context.get('mark_invoice_as_sent')}") + _logger.error(f"🔥 Context mail_notify_force: {self.env.context.get('mail_notify_force')}") + # Check for manual email context include_archived = ( self.env.context.get("force_email") or @@ -17,8 +24,11 @@ class ResPartner(models.Model): self.env.context.get("mail_notify_force") ) + _logger.error(f"🔥 Should include archived? {include_archived}") + if include_archived: - # Remove active filters to include archived partners + _logger.error("🔥 Removing active filters for archived partners") + # Simple: remove active filters args = [ arg for arg in args if not (isinstance(arg, (list, tuple)) and @@ -26,5 +36,12 @@ class ResPartner(models.Model): arg[0] == "active") ] self = self.with_context(active_test=False) + _logger.error(f"🔥 Search args after: {args}") - return super()._search(args, offset, limit, order, count, access_rights_uid) \ No newline at end of file + result = super()._search(args, offset, limit, order, count, access_rights_uid) + + if not count: + result_ids = list(result) + _logger.error(f"🔥 Search returned {len(result_ids)} results") + + return result \ No newline at end of file