diff --git a/sale_exception/models/sale_order.py b/sale_exception/models/sale_order.py index 73e69c3d16f..bdcadfc5957 100644 --- a/sale_exception/models/sale_order.py +++ b/sale_exception/models/sale_order.py @@ -2,6 +2,8 @@ # Copyright 2018 Akretion # Copyright 2019 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.api import Environment +from odoo.modules.registry import Registry from odoo import api, models @@ -48,10 +50,18 @@ def sale_check_exception(self): orders._check_exception() def action_confirm(self): - if self.detect_exceptions(): - if not self.env.company.sale_exception_show_popup: - return - return self._popup_exceptions() + breakpoint() + with Registry(self.env.cr.dbname).cursor() as new_cr: + new_env = Environment(new_cr, self.env.uid, self.env.context) + exception_ids = self.with_env(new_env).detect_exceptions() + if exception_ids: + new_cr.commit() + + if exception_ids: + # FIXME: As ValidationError is raised, the client is not refreshed to + # display the exception summary. Should we catch the ValidationError + # and use another error class to force that? + self._check_exception() return super().action_confirm() def action_draft(self): diff --git a/test_sale_exception_confirm/__init__.py b/test_sale_exception_confirm/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/test_sale_exception_confirm/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/test_sale_exception_confirm/__manifest__.py b/test_sale_exception_confirm/__manifest__.py new file mode 100644 index 00000000000..5cc6e942433 --- /dev/null +++ b/test_sale_exception_confirm/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2025 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +{ + "name": "Test sale exception confirm", + "summary": "Ensure any action in action confirm is rolled back upon exception", + "version": "18.0.1.0.0", + "category": "Uncategorized", + "website": "https://github.com/OCA/sale-workflow", + "author": "Camptocamp, Odoo Community Association (OCA)", + "maintainers": ["grindtildeath"], + "license": "AGPL-3", + "depends": [ + "sale_exception", + ], + "demo": [ + "demo/sale_exception_demo.xml", + ], +} diff --git a/test_sale_exception_confirm/demo/sale_exception_demo.xml b/test_sale_exception_confirm/demo/sale_exception_demo.xml new file mode 100644 index 00000000000..20e9ba67994 --- /dev/null +++ b/test_sale_exception_confirm/demo/sale_exception_demo.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/test_sale_exception_confirm/models/__init__.py b/test_sale_exception_confirm/models/__init__.py new file mode 100644 index 00000000000..6aacb753131 --- /dev/null +++ b/test_sale_exception_confirm/models/__init__.py @@ -0,0 +1 @@ +from . import sale_order diff --git a/test_sale_exception_confirm/models/sale_order.py b/test_sale_exception_confirm/models/sale_order.py new file mode 100644 index 00000000000..b1eeef8ee8c --- /dev/null +++ b/test_sale_exception_confirm/models/sale_order.py @@ -0,0 +1,16 @@ +# Copyright 2025 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + before_confirm = fields.Char() + after_confirm = fields.Char() + + def action_confirm(self): + self.write({"before_confirm": "Write before confirm"}) + res = super().action_confirm() + self.write({"after_confirm": "Write after confirm"}) + return res diff --git a/test_sale_exception_confirm/tests/__init__.py b/test_sale_exception_confirm/tests/__init__.py new file mode 100644 index 00000000000..b39bbfd4042 --- /dev/null +++ b/test_sale_exception_confirm/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_exception_confirm diff --git a/test_sale_exception_confirm/tests/test_sale_exception_confirm.py b/test_sale_exception_confirm/tests/test_sale_exception_confirm.py new file mode 100644 index 00000000000..d4ba6c2ba26 --- /dev/null +++ b/test_sale_exception_confirm/tests/test_sale_exception_confirm.py @@ -0,0 +1,39 @@ +# Copyright 2025 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo.fields import Command +from odoo.tests import TransactionCase + + +class TestSaleExceptionConfirm(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.no_free_exception = cls.env.ref("sale_exception.excep_no_free") + # cls.no_free_exception.active = True + cls.partner = cls.env.ref("base.res_partner_12") + cls.product = cls.env.ref("product.product_product_9") + + def test_action_confirm_rollback(self): + sale_order = self.env["sale.order"].create( + { + "partner_id": self.partner.id, + "order_line": [ + Command.create( + { + "product_id": self.product.id, + "name": self.product.name, + "product_uom_qty": 1.0, + "price_unit": 0.0, + }, + ) + ], + } + ) + self.assertFalse(sale_order.before_confirm) + self.assertFalse(sale_order.after_confirm) + self.assertTrue(self.no_free_exception.active) + sale_order.action_confirm() + self.assertEqual(sale_order.exception_ids, self.no_free_exception) + self.assertEqual(sale_order.state, "draft") + self.assertFalse(sale_order.before_confirm) + self.assertFalse(sale_order.after_confirm)