commit f64fbb5e067dc28237f7d54b2e203d8513f793d5 Author: Kristof Bernaert Date: Mon Jan 26 00:44:43 2026 +0100 Fix allow_mail_archived_partner causing system notifications to send emails diff --git a/allow_mail_archived_partner/__init__.py b/allow_mail_archived_partner/__init__.py new file mode 100755 index 0000000..9a7e03e --- /dev/null +++ b/allow_mail_archived_partner/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/allow_mail_archived_partner/__manifest__.py b/allow_mail_archived_partner/__manifest__.py new file mode 100755 index 0000000..b48d767 --- /dev/null +++ b/allow_mail_archived_partner/__manifest__.py @@ -0,0 +1,10 @@ +{ + 'name': 'Allow Emails to Archived Contacts', + 'version': '1.0', + 'category': 'Tools', + 'summary': 'Allow sending emails to archived contacts for Sales and Invoices', + 'depends': ['base', 'mail', 'sale', 'account'], + 'data': [], + 'installable': True, + 'application': False, +} \ No newline at end of file diff --git a/allow_mail_archived_partner/models/__init__.py b/allow_mail_archived_partner/models/__init__.py new file mode 100755 index 0000000..761c16e --- /dev/null +++ b/allow_mail_archived_partner/models/__init__.py @@ -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 \ No newline at end of file diff --git a/allow_mail_archived_partner/models/account_move_send.py b/allow_mail_archived_partner/models/account_move_send.py new file mode 100755 index 0000000..590e925 --- /dev/null +++ b/allow_mail_archived_partner/models/account_move_send.py @@ -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) diff --git a/allow_mail_archived_partner/models/mail_compose_message.py b/allow_mail_archived_partner/models/mail_compose_message.py new file mode 100755 index 0000000..aa01a9b --- /dev/null +++ b/allow_mail_archived_partner/models/mail_compose_message.py @@ -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) diff --git a/allow_mail_archived_partner/models/mail_template.py b/allow_mail_archived_partner/models/mail_template.py new file mode 100755 index 0000000..9284329 --- /dev/null +++ b/allow_mail_archived_partner/models/mail_template.py @@ -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) diff --git a/allow_mail_archived_partner/models/mail_thread.py b/allow_mail_archived_partner/models/mail_thread.py new file mode 100755 index 0000000..b7a49ab --- /dev/null +++ b/allow_mail_archived_partner/models/mail_thread.py @@ -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 diff --git a/allow_mail_archived_partner/models/res_partner.py b/allow_mail_archived_partner/models/res_partner.py new file mode 100755 index 0000000..e6484b8 --- /dev/null +++ b/allow_mail_archived_partner/models/res_partner.py @@ -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 + ) diff --git a/archived_partner_show_warning/__init__.py b/archived_partner_show_warning/__init__.py new file mode 100755 index 0000000..0650744 --- /dev/null +++ b/archived_partner_show_warning/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/archived_partner_show_warning/__manifest__.py b/archived_partner_show_warning/__manifest__.py new file mode 100755 index 0000000..6a216a3 --- /dev/null +++ b/archived_partner_show_warning/__manifest__.py @@ -0,0 +1,18 @@ +{ + "name": "Archived Contact Warning", + "version": "1.0", + "category": "Sales", + "summary": "Show warning when using archived contacts", + "depends": ["sale", "account"], + "data": [ + "views/sale_order_views.xml", + "views/account_move_views.xml", + ], + "assets": { + "web.assets_backend": [ + "archived_partner_show_warning/static/src/css/style.css", + ], + }, + "installable": True, + "application": False, +} diff --git a/archived_partner_show_warning/i18n/nl_BE.po b/archived_partner_show_warning/i18n/nl_BE.po new file mode 100755 index 0000000..4c9eb5b --- /dev/null +++ b/archived_partner_show_warning/i18n/nl_BE.po @@ -0,0 +1,50 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * archived_partner_show_warning +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-01-15 18:10+0000\n" +"PO-Revision-Date: 2026-01-15 19:15+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.6\n" + +#. module: archived_partner_show_warning +#: model_terms:ir.ui.view,arch_db:archived_partner_show_warning.view_move_form_archived_warning +#: model_terms:ir.ui.view,arch_db:archived_partner_show_warning.view_order_form_archived_warning +msgid "This customer is archived." +msgstr "" +"De klant is gearchiveerd (geïmporteerd van vorige Odoo). Ga na of dit " +"aangepast moet worden naar een nieuwe klantenfiche." + +#. module: archived_partner_show_warning +#: model_terms:ir.ui.view,arch_db:archived_partner_show_warning.view_move_form_archived_warning +#: model_terms:ir.ui.view,arch_db:archived_partner_show_warning.view_order_form_archived_warning +msgid "Warning: " +msgstr "Opgelet: " + +#. module: archived_partner_show_warning +#: model:ir.model,name:archived_partner_show_warning.model_account_move +msgid "Journal Entry" +msgstr "Boeking" + +#. module: archived_partner_show_warning +#: model:ir.model.fields,field_description:archived_partner_show_warning.field_account_bank_statement_line__partner_is_archived +#: model:ir.model.fields,field_description:archived_partner_show_warning.field_account_move__partner_is_archived +#: model:ir.model.fields,field_description:archived_partner_show_warning.field_account_payment__partner_is_archived +#: model:ir.model.fields,field_description:archived_partner_show_warning.field_sale_order__partner_is_archived +msgid "Partner Archived" +msgstr "" + +#. module: archived_partner_show_warning +#: model:ir.model,name:archived_partner_show_warning.model_sale_order +msgid "Sales Order" +msgstr "Verkooporder" diff --git a/archived_partner_show_warning/models/__init__.py b/archived_partner_show_warning/models/__init__.py new file mode 100755 index 0000000..2eade25 --- /dev/null +++ b/archived_partner_show_warning/models/__init__.py @@ -0,0 +1,2 @@ +from . import sale_order +from . import account_move diff --git a/archived_partner_show_warning/models/account_move.py b/archived_partner_show_warning/models/account_move.py new file mode 100755 index 0000000..05e9e08 --- /dev/null +++ b/archived_partner_show_warning/models/account_move.py @@ -0,0 +1,15 @@ +from odoo import models, fields, api + +class AccountMove(models.Model): + _inherit = 'account.move' + + partner_is_archived = fields.Boolean( + string='Partner Archived', + compute='_compute_partner_is_archived', + store=False + ) + + @api.depends('partner_id', 'partner_id.active') + def _compute_partner_is_archived(self): + for invoice in self: + invoice.partner_is_archived = invoice.partner_id and not invoice.partner_id.active diff --git a/archived_partner_show_warning/models/sale_order.py b/archived_partner_show_warning/models/sale_order.py new file mode 100755 index 0000000..b0c27bd --- /dev/null +++ b/archived_partner_show_warning/models/sale_order.py @@ -0,0 +1,15 @@ +from odoo import models, fields, api + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + partner_is_archived = fields.Boolean( + string='Partner Archived', + compute='_compute_partner_is_archived', + store=False + ) + + @api.depends('partner_id', 'partner_id.active') + def _compute_partner_is_archived(self): + for order in self: + order.partner_is_archived = order.partner_id and not order.partner_id.active diff --git a/archived_partner_show_warning/static/src/css/style.css b/archived_partner_show_warning/static/src/css/style.css new file mode 100755 index 0000000..3c1643d --- /dev/null +++ b/archived_partner_show_warning/static/src/css/style.css @@ -0,0 +1,18 @@ +/* Custom styling for archived partner warning */ +.archived-warning-custom { + margin: -20px 00 5px 0; + + padding-top: 13px; + border-left: 4px solid #f0ad4e; + background: linear-gradient(135deg, #fcf8e3 0%, #faf2cc 100%); + +} + +.archived-warning-custom i { + font-size: 18px; + margin-right: 5px; +} + +.archived-warning-custom strong { + font-size: 14px; +} \ No newline at end of file diff --git a/archived_partner_show_warning/views/account_move_views.xml b/archived_partner_show_warning/views/account_move_views.xml new file mode 100755 index 0000000..44ad78d --- /dev/null +++ b/archived_partner_show_warning/views/account_move_views.xml @@ -0,0 +1,31 @@ + + + + account.move.form.archived.warning + account.move + + + + + + + + + +
+
+
+ + + Warning: + + This customer is archived. +
+ + +
+
+
+
+
+
\ No newline at end of file diff --git a/archived_partner_show_warning/views/sale_order_views.xml b/archived_partner_show_warning/views/sale_order_views.xml new file mode 100755 index 0000000..6176f43 --- /dev/null +++ b/archived_partner_show_warning/views/sale_order_views.xml @@ -0,0 +1,31 @@ + + + + sale.order.form.archived.warning + sale.order + + + + + + + + + +
+
+
+ + + Warning: + + This customer is archived. +
+ + +
+
+
+
+
+
\ No newline at end of file diff --git a/partner_hierarchical_name/__init__.py b/partner_hierarchical_name/__init__.py new file mode 100755 index 0000000..0650744 --- /dev/null +++ b/partner_hierarchical_name/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/partner_hierarchical_name/__manifest__.py b/partner_hierarchical_name/__manifest__.py new file mode 100755 index 0000000..ed9a972 --- /dev/null +++ b/partner_hierarchical_name/__manifest__.py @@ -0,0 +1,17 @@ +{ + "name": "Partner Hierarchical Name", + "version": "16.0.1.0.0", + "summary": "Show full partner hierarchy in dropdowns (Company / Delivery / Contact)", + "category": "Contacts", + "author": "Van Bernaert (Developer), Domus La Vila (Distributor)", + "license": "LGPL-3", + "depends": ["base"], + "installable": True, + "application": False, + + "assets": { + "web.assets_backend": [ + "partner_hierarchical_name/static/src/css/many2one_dropdown.css", + ], + }, +} diff --git a/partner_hierarchical_name/__pycache__/__init__.cpython-312.pyc b/partner_hierarchical_name/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..2851438 Binary files /dev/null and b/partner_hierarchical_name/__pycache__/__init__.cpython-312.pyc differ diff --git a/partner_hierarchical_name/models/__init__.py b/partner_hierarchical_name/models/__init__.py new file mode 100755 index 0000000..4c58886 --- /dev/null +++ b/partner_hierarchical_name/models/__init__.py @@ -0,0 +1 @@ +from . import res_partner \ No newline at end of file diff --git a/partner_hierarchical_name/models/__pycache__/__init__.cpython-312.pyc b/partner_hierarchical_name/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..8fab5e6 Binary files /dev/null and b/partner_hierarchical_name/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/partner_hierarchical_name/models/__pycache__/res_partner.cpython-312.pyc b/partner_hierarchical_name/models/__pycache__/res_partner.cpython-312.pyc new file mode 100644 index 0000000..b9fc5fd Binary files /dev/null and b/partner_hierarchical_name/models/__pycache__/res_partner.cpython-312.pyc differ diff --git a/partner_hierarchical_name/models/res_partner.py b/partner_hierarchical_name/models/res_partner.py new file mode 100755 index 0000000..aeb8e56 --- /dev/null +++ b/partner_hierarchical_name/models/res_partner.py @@ -0,0 +1,25 @@ +from odoo import models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + def name_get(self): + result = [] + + for partner in self: + names = [] + current = partner + + # Walk up the hierarchy + while current: + if current.name: + names.append(current.name) + current = current.parent_id + + # Reverse to show top-down (L1 / L2 / L3) + full_name = " / ".join(reversed(names)) + + result.append((partner.id, full_name)) + + return result diff --git a/partner_hierarchical_name/static/src/css/many2one_dropdown.css b/partner_hierarchical_name/static/src/css/many2one_dropdown.css new file mode 100755 index 0000000..4a1a829 --- /dev/null +++ b/partner_hierarchical_name/static/src/css/many2one_dropdown.css @@ -0,0 +1,24 @@ +/* jQuery UI autocomplete dropdown (authoritative override) */ +.ui-autocomplete { + min-width: 650px !important; + max-width: 900px !important; + + /* Float above form & right column */ + z-index: 1100 !important; + + /* Allow multi-line hierarchy */ + white-space: normal !important; +} + +/* Ensure individual items wrap */ +.ui-autocomplete .ui-menu-item-wrapper { + white-space: normal !important; + word-break: break-word; +} + +/* Prevent clipping by form containers */ +.o_form_view, +.o_form_sheet, +.o_form_sheet_bg { + overflow: visible !important; +}