From a9114c8ecacb176e5c04f40803b2b977c0dac7aa Mon Sep 17 00:00:00 2001 From: Hector Vior Date: Mon, 18 May 2026 09:18:42 +0200 Subject: [PATCH 1/4] [IMP] edi_core_oca: skip exchange types w/o handler in cron domains --- edi_core_oca/models/edi_backend.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/edi_core_oca/models/edi_backend.py b/edi_core_oca/models/edi_backend.py index d4af65ff6..937a143d5 100644 --- a/edi_core_oca/models/edi_backend.py +++ b/edi_core_oca/models/edi_backend.py @@ -370,6 +370,8 @@ def _output_new_records_domain(self, record_ids=None): ("backend_id", "=", self.id), ("type_id.exchange_file_auto_generate", "=", True), ("type_id.direction", "=", "output"), + ("type_id.generate_model_id", "!=", False), + ("type_id.send_model_id", "!=", False), ("edi_exchange_state", "=", "new"), ("exchange_file", "=", False), ] @@ -388,6 +390,7 @@ def _output_pending_records_domain(self, skip_sent=True, record_ids=None): states += ("output_sent",) domain = [ ("type_id.direction", "=", "output"), + ("type_id.send_model_id", "!=", False), ("backend_id", "=", self.id), ("edi_exchange_state", "in", states), ] @@ -575,6 +578,7 @@ def _input_pending_records_domain(self, record_ids=None): domain = [ ("backend_id", "=", self.id), ("type_id.direction", "=", "input"), + ("type_id.receive_model_id", "!=", False), ("edi_exchange_state", "=", "input_pending"), ("exchange_file", "=", False), ] @@ -587,6 +591,7 @@ def _input_pending_process_records_domain(self, record_ids=None): domain = [ ("backend_id", "=", self.id), ("type_id.direction", "=", "input"), + ("type_id.process_model_id", "!=", False), ("edi_exchange_state", "in", states), ] if record_ids: From 2134f83ec51db842098310e2383b84a1d74bcacb Mon Sep 17 00:00:00 2001 From: Hector Vior Date: Mon, 18 May 2026 09:18:44 +0200 Subject: [PATCH 2/4] [FIX] edi_queue_oca: set handler models in test to match cron domain filter --- edi_queue_oca/tests/test_backend_output_jobs.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/edi_queue_oca/tests/test_backend_output_jobs.py b/edi_queue_oca/tests/test_backend_output_jobs.py index b600a0d9b..7c611d4a2 100644 --- a/edi_queue_oca/tests/test_backend_output_jobs.py +++ b/edi_queue_oca/tests/test_backend_output_jobs.py @@ -4,6 +4,7 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo.orm.model_classes import add_to_registry from odoo.tests import tagged from odoo.addons.edi_core_oca.tests.common import EDIBackendCommonTestCase @@ -12,6 +13,21 @@ @tagged("-at_install", "post_install") class EDIBackendTestOutputJobsCase(EDIBackendCommonTestCase): + @classmethod + def _setup_records(cls): # pylint:disable=missing-return + super()._setup_records() + from odoo.addons.edi_core_oca.tests.fake_models import EdiTestExecution + + add_to_registry(cls.registry, EdiTestExecution) + cls.registry._setup_models__(cls.env.cr, ["edi.framework.test.execution"]) + cls.registry.init_models( + cls.env.cr, ["edi.framework.test.execution"], {"models_to_check": True} + ) + cls.addClassCleanup(cls.registry.__delitem__, "edi.framework.test.execution") + model = cls.env["ir.model"]._get("edi.framework.test.execution") + cls.exchange_type_out.generate_model_id = model + cls.exchange_type_out.send_model_id = model + @classmethod def setUpClass(cls): super().setUpClass() From f79497caa31582c91e2cd4d583481f4ecb34be39 Mon Sep 17 00:00:00 2001 From: Hector Vior Date: Wed, 27 May 2026 13:25:58 +0200 Subject: [PATCH 3/4] [IMP] edi_core_oca: Add validators and constrains to ensure exchange type handlers are configured --- edi_core_oca/models/edi_exchange_type.py | 26 +++++++++++++++ edi_core_oca/tests/common.py | 32 +++++++++++++++++++ edi_core_oca/tests/fake_models.py | 1 + edi_core_oca/tests/test_backend_base.py | 12 +++++-- .../views/edi_exchange_type_views.xml | 3 ++ 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/edi_core_oca/models/edi_exchange_type.py b/edi_core_oca/models/edi_exchange_type.py index d6a2cab96..2f602ac6a 100644 --- a/edi_core_oca/models/edi_exchange_type.py +++ b/edi_core_oca/models/edi_exchange_type.py @@ -240,6 +240,32 @@ def get_settings(self): def set_settings(self, val): self.advanced_settings_edit = val + @api.constrains( + "direction", + "send_model_id", + "process_model_id", + "generate_model_id", + "exchange_file_auto_generate", + ) + def _check_direction_handlers(self): + for rec in self: + if rec.direction == "output": + if not rec.send_model_id: + raise exceptions.ValidationError( + self.env._("Output exchange types require a Sender handler.") + ) + if rec.exchange_file_auto_generate and not rec.generate_model_id: + raise exceptions.ValidationError( + self.env._( + "Output exchange types with 'Auto Generate' enabled " + "require a Generator handler." + ) + ) + elif rec.direction == "input" and not rec.process_model_id: + raise exceptions.ValidationError( + self.env._("Input exchange types require a Processor handler.") + ) + @api.constrains("backend_id", "backend_type_id") def _check_backend(self): for rec in self: diff --git a/edi_core_oca/tests/common.py b/edi_core_oca/tests/common.py index fe9bdb9d1..0ed40d75a 100644 --- a/edi_core_oca/tests/common.py +++ b/edi_core_oca/tests/common.py @@ -5,6 +5,7 @@ import os +from odoo.orm.model_classes import add_to_registry from odoo.tests.common import TransactionCase @@ -19,6 +20,24 @@ def _setup_context(cls, **kw): def _setup_env(cls, ctx=None): ctx = ctx or {} cls.env = cls.env(context=cls._setup_context(**ctx)) + # Register EdiTestExecution early so _create_exchange_type can set default + # handler models — the new @api.constrains on edi.exchange.type requires + # send_model_id (output) and process_model_id (input) to be set. + # Guard prevents double-registration when individual test classes also call + # add_to_registry; pop() in cleanup is safe even if __delitem__ already ran. + if "edi.framework.test.execution" not in cls.registry: + from .fake_models import EdiTestExecution + + add_to_registry(cls.registry, EdiTestExecution) + cls.registry._setup_models__(cls.env.cr, ["edi.framework.test.execution"]) + cls.registry.init_models( + cls.env.cr, ["edi.framework.test.execution"], {"models_to_check": True} + ) + cls.addClassCleanup( + lambda: cls.registry.__delitem__("edi.framework.test.execution") + if "edi.framework.test.execution" in cls.registry + else None + ) @classmethod def _setup_records(cls): @@ -84,6 +103,19 @@ def _get_backend(cls): @classmethod def _create_exchange_type(cls, **kw): + # Mirror the pattern in edi_component_oca/tests/common.py: provide default + # handler models so every test exchange type satisfies the new constraint. + # Callers can override individual fields by passing explicit values. + if "edi.framework.test.execution" in cls.registry: + handler_model = cls.env["ir.model"]._get("edi.framework.test.execution") + if handler_model: + kw.setdefault("receive_model_id", handler_model.id) + kw.setdefault("generate_model_id", handler_model.id) + kw.setdefault("input_validate_model_id", handler_model.id) + kw.setdefault("output_validate_model_id", handler_model.id) + kw.setdefault("send_model_id", handler_model.id) + kw.setdefault("process_model_id", handler_model.id) + kw.setdefault("check_model_id", handler_model.id) model = cls.env["edi.exchange.type"] vals = { "name": "Test CSV exchange", diff --git a/edi_core_oca/tests/fake_models.py b/edi_core_oca/tests/fake_models.py index df7d5e25c..badea718a 100644 --- a/edi_core_oca/tests/fake_models.py +++ b/edi_core_oca/tests/fake_models.py @@ -33,6 +33,7 @@ def _get_edi_exchange_record_name(self, exchange_record): class EdiTestExecution(models.AbstractModel): _name = "edi.framework.test.execution" _inherit = [ + "edi.oca.handler.generate", "edi.oca.handler.process", "edi.oca.handler.check", "edi.oca.handler.send", diff --git a/edi_core_oca/tests/test_backend_base.py b/edi_core_oca/tests/test_backend_base.py index 87219bea0..dc401f7c0 100644 --- a/edi_core_oca/tests/test_backend_base.py +++ b/edi_core_oca/tests/test_backend_base.py @@ -85,9 +85,15 @@ def test_get_handler_avg_user(self): self._test_get_handler(user=user) def test_get_handler_no_handler(self): - self.exchange_type_in.process_model_id = False - self.exchange_type_in.input_validate_model_id = False - self.exchange_type_in.receive_model_id = False + self.env.cr.execute( + """UPDATE edi_exchange_type + SET process_model_id = NULL, + input_validate_model_id = NULL, + receive_model_id = NULL + WHERE id = %s""", + [self.exchange_type_in.id], + ) + self.exchange_type_in.invalidate_recordset() vals = { "model": self.partner._name, "res_id": self.partner.id, diff --git a/edi_core_oca/views/edi_exchange_type_views.xml b/edi_core_oca/views/edi_exchange_type_views.xml index e7f0be5e1..549494b30 100644 --- a/edi_core_oca/views/edi_exchange_type_views.xml +++ b/edi_core_oca/views/edi_exchange_type_views.xml @@ -63,6 +63,7 @@ Date: Wed, 27 May 2026 14:22:16 +0200 Subject: [PATCH 4/4] [FIX] edi_endpoint_oca: set process_model_id in test exchange type --- edi_endpoint_oca/tests/common.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/edi_endpoint_oca/tests/common.py b/edi_endpoint_oca/tests/common.py index 606f2890f..e0d87099f 100644 --- a/edi_endpoint_oca/tests/common.py +++ b/edi_endpoint_oca/tests/common.py @@ -1,6 +1,7 @@ # Copyright 2026 Camptocamp SA # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from odoo.orm.model_classes import add_to_registry from odoo.tests.common import TransactionCase @@ -13,6 +14,21 @@ def _setup_context(cls, **kw): def _setup_env(cls, ctx=None): ctx = ctx or {} cls.env = cls.env(context=cls._setup_context(**ctx)) + # Register EdiTestExecution so _get_exchange_type can satisfy the + # _check_direction_handlers constraint (input types need process_model_id). + if "edi.framework.test.execution" not in cls.registry: + from odoo.addons.edi_core_oca.tests.fake_models import EdiTestExecution + + add_to_registry(cls.registry, EdiTestExecution) + cls.registry._setup_models__(cls.env.cr, ["edi.framework.test.execution"]) + cls.registry.init_models( + cls.env.cr, ["edi.framework.test.execution"], {"models_to_check": True} + ) + cls.addClassCleanup( + lambda: cls.registry.__delitem__("edi.framework.test.execution") + if "edi.framework.test.execution" in cls.registry + else None + ) @classmethod def _setup_records(cls): @@ -75,12 +91,14 @@ def _get_backend(cls): @classmethod def _get_exchange_type(cls): + handler_model = cls.env["ir.model"]._get("edi.framework.test.execution") return cls.env["edi.exchange.type"].create( { "name": "EDI exchange demo", "code": "demo_endpoint", "backend_type_id": cls.backend_type.id, "direction": "input", + "process_model_id": handler_model.id, } )