From 0f86dfb018108144bcecbe5b2b40af15a896131d Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Mon, 6 Apr 2026 18:06:04 +0200 Subject: [PATCH 1/6] [IMP] purchase_exception: Use hook to popup exception after rollback With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call _popup_exception as we used to, but we can use the new hook to have the popup displayed smoothly. --- purchase_exception/models/purchase.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/purchase_exception/models/purchase.py b/purchase_exception/models/purchase.py index e376696c722..16880a55b80 100644 --- a/purchase_exception/models/purchase.py +++ b/purchase_exception/models/purchase.py @@ -47,9 +47,12 @@ def purchase_check_exception(self): if orders: orders._check_exception() + def _must_popup_exception(self): + return True + def button_confirm(self): if self.detect_exceptions() and not self.ignore_exception: - return self._popup_exceptions() + return self.action_popup_exceptions() return super().button_confirm() def button_draft(self): From 7042ef9455b670a1c86f41df850262b91fe12f57 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Mon, 6 Apr 2026 18:07:04 +0200 Subject: [PATCH 2/6] [IMP] purchase_request_exception: Use hook to popup exception after rollback With OCA/server-tools#3590 changing the way exceptions are detected, the error raising doesn't allow to call _popup_exception as we used to, but we can use the new hook to have the popup displayed smoothly. --- purchase_request_exception/models/purchase_request.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/purchase_request_exception/models/purchase_request.py b/purchase_request_exception/models/purchase_request.py index 6667e4e5633..a28d3829b72 100644 --- a/purchase_request_exception/models/purchase_request.py +++ b/purchase_request_exception/models/purchase_request.py @@ -36,9 +36,12 @@ def onchange_ignore_exception(self): if self.state == "to_approve": self.ignore_exception = False + def _must_popup_exception(self): + return True + def button_to_approve(self): if self.detect_exceptions() and not self.ignore_exception: - return self._popup_exceptions() + return self.action_popup_exceptions() return super().button_to_approve() def button_draft(self): From b0390582d8180df94d1f7f523323fa0fccdd2d1d Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Wed, 29 Apr 2026 17:53:35 +0200 Subject: [PATCH 3/6] fixup! [IMP] purchase_exception: Use hook to popup exception after rollback --- purchase_exception/models/purchase_line.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/purchase_exception/models/purchase_line.py b/purchase_exception/models/purchase_line.py index cbd50a2036f..4c07e5d0f01 100644 --- a/purchase_exception/models/purchase_line.py +++ b/purchase_exception/models/purchase_line.py @@ -24,3 +24,7 @@ def _reverse_field(self): def _detect_exceptions(self, rule): records = super()._detect_exceptions(rule) return records.mapped("order_id") + + def _detect_exception_get_exc_class_values(self): + res = super()._detect_exception_get_exc_class_values() + return dict(res, target_model="purchase.order") From 7ee40070e20bf8c99b59ca3b643fe5c095405b4e Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Wed, 29 Apr 2026 17:54:52 +0200 Subject: [PATCH 4/6] fixup! [IMP] purchase_request_exception: Use hook to popup exception after rollback --- purchase_request_exception/models/purchase_request.py | 2 +- .../models/purchase_request_line.py | 4 ++++ .../tests/test_purchase_request_exception.py | 9 ++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/purchase_request_exception/models/purchase_request.py b/purchase_request_exception/models/purchase_request.py index a28d3829b72..fbed6dc5c0d 100644 --- a/purchase_request_exception/models/purchase_request.py +++ b/purchase_request_exception/models/purchase_request.py @@ -41,7 +41,7 @@ def _must_popup_exception(self): def button_to_approve(self): if self.detect_exceptions() and not self.ignore_exception: - return self.action_popup_exceptions() + return return super().button_to_approve() def button_draft(self): diff --git a/purchase_request_exception/models/purchase_request_line.py b/purchase_request_exception/models/purchase_request_line.py index 9706647cacf..5321bfe4074 100644 --- a/purchase_request_exception/models/purchase_request_line.py +++ b/purchase_request_exception/models/purchase_request_line.py @@ -22,3 +22,7 @@ def _reverse_field(self): def _detect_exceptions(self, rule): records = super()._detect_exceptions(rule) return records.mapped("request_id") + + def _detect_exception_get_exc_class_values(self): + res = super()._detect_exception_get_exc_class_values() + return dict(res, target_model="purchase.request") diff --git a/purchase_request_exception/tests/test_purchase_request_exception.py b/purchase_request_exception/tests/test_purchase_request_exception.py index f3a2879e665..47cf38fec7c 100644 --- a/purchase_request_exception/tests/test_purchase_request_exception.py +++ b/purchase_request_exception/tests/test_purchase_request_exception.py @@ -7,12 +7,17 @@ from odoo.tests.common import TransactionCase from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +from odoo.addons.base_exception.tests.common import ( + patch_base_exception_method_env, + swallow_base_exception_error, +) + class TestPurchaseRequestException(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() - + cls.env = cls.env(context=dict(cls.env.context, test_base_exception=True)) # Useful models cls.PurchaseRequest = cls.env["purchase.request"] cls.PurchaseRequestLine = cls.env["purchase.request.line"] @@ -49,6 +54,8 @@ def setUpClass(cls): ], } + @patch_base_exception_method_env + @swallow_base_exception_error def test_purchase_request_exception(self): self.exception_noapprover.active = True self.exception_qtycheck.active = True From 14445e149269f025d214fa93107503419afa6dce Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Wed, 29 Apr 2026 18:01:22 +0200 Subject: [PATCH 5/6] fixup! fixup! [IMP] purchase_exception: Use hook to popup exception after rollback --- purchase_exception/tests/test_purchase_exception.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/purchase_exception/tests/test_purchase_exception.py b/purchase_exception/tests/test_purchase_exception.py index f80f0c1b9c1..fa928d2ece0 100644 --- a/purchase_exception/tests/test_purchase_exception.py +++ b/purchase_exception/tests/test_purchase_exception.py @@ -7,12 +7,17 @@ from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT from odoo.addons.base.tests.common import BaseCommon +from odoo.addons.base_exception.tests.common import ( + patch_base_exception_method_env, + swallow_base_exception_error, +) class TestPurchaseException(BaseCommon): @classmethod def setUpClass(cls): super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, test_base_exception=True)) # Useful models cls.PurchaseOrder = cls.env["purchase.order"] cls.PurchaseOrderLine = cls.env["purchase.order.line"] @@ -71,6 +76,8 @@ def setUpClass(cls): ], } + @patch_base_exception_method_env + @swallow_base_exception_error def test_purchase_order_exception(self): self.exception_noemail.active = True self.exception_qtycheck.active = True From d4cb26e2476e5e3f1d41388fa30431eba9778e60 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Wed, 29 Apr 2026 18:03:05 +0200 Subject: [PATCH 6/6] [DROPME] test_requirements.txt --- test-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test-requirements.txt b/test-requirements.txt index 66bc2cbae3f..2e74964cab0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,2 @@ odoo_test_helper +odoo-addon-base_exception @ git+https://github.com/OCA/server-tools.git@refs/pull/3590/head#subdirectory=base_exception