From db47e51516a91e26f0237b9ae78e4e30e230f252 Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Fri, 29 Aug 2025 19:19:11 +0200 Subject: [PATCH 1/7] [ADD] edi_component_oca: Adds all functionality that integrates EDI and components Co-authored-by: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= --- edi_component_oca/README.rst | 1 + edi_component_oca/__init__.py | 2 + edi_component_oca/__manifest__.py | 15 ++ edi_component_oca/components/__init__.py | 4 + edi_component_oca/components/base.py | 72 +++++++ edi_component_oca/components/base_input.py | 23 +++ edi_component_oca/components/base_output.py | 36 ++++ edi_component_oca/components/base_validate.py | 20 ++ edi_component_oca/models/__init__.py | 3 + edi_component_oca/models/edi_backend.py | 171 +++++++++++++++++ .../models/edi_exchange_consumer_mixin.py | 30 +++ .../models/edi_exchange_record.py | 22 +++ edi_component_oca/pyproject.toml | 3 + edi_component_oca/readme/CONFIGURE.md | 16 ++ edi_component_oca/readme/CONTRIBUTORS.md | 5 + edi_component_oca/readme/DESCRIPTION.md | 1 + edi_component_oca/static/description/icon.png | Bin 0 -> 9455 bytes edi_component_oca/tests/__init__.py | 10 + edi_component_oca/tests/common.py | 35 ++++ edi_component_oca/tests/fake_components.py | 155 ++++++++++++++++ edi_component_oca/tests/test_backend_base.py | 72 +++++++ edi_component_oca/tests/test_backend_input.py | 72 +++++++ .../tests/test_backend_output.py | 111 +++++++++++ .../tests/test_backend_process.py | 95 ++++++++++ .../tests/test_backend_validate.py | 121 ++++++++++++ .../tests/test_component_match.py | 71 +++++++ .../tests/test_edi_backend_cron.py | 98 ++++++++++ .../tests/test_edi_configuration.py | 175 ++++++++++++++++++ .../tests/test_exchange_type_encoding.py | 84 +++++++++ edi_component_oca/tests/test_quick_exec.py | 117 ++++++++++++ 30 files changed, 1640 insertions(+) create mode 100644 edi_component_oca/README.rst create mode 100644 edi_component_oca/__init__.py create mode 100644 edi_component_oca/__manifest__.py create mode 100644 edi_component_oca/components/__init__.py create mode 100644 edi_component_oca/components/base.py create mode 100644 edi_component_oca/components/base_input.py create mode 100644 edi_component_oca/components/base_output.py create mode 100644 edi_component_oca/components/base_validate.py create mode 100644 edi_component_oca/models/__init__.py create mode 100644 edi_component_oca/models/edi_backend.py create mode 100644 edi_component_oca/models/edi_exchange_consumer_mixin.py create mode 100644 edi_component_oca/models/edi_exchange_record.py create mode 100644 edi_component_oca/pyproject.toml create mode 100644 edi_component_oca/readme/CONFIGURE.md create mode 100644 edi_component_oca/readme/CONTRIBUTORS.md create mode 100644 edi_component_oca/readme/DESCRIPTION.md create mode 100644 edi_component_oca/static/description/icon.png create mode 100644 edi_component_oca/tests/__init__.py create mode 100644 edi_component_oca/tests/common.py create mode 100644 edi_component_oca/tests/fake_components.py create mode 100644 edi_component_oca/tests/test_backend_base.py create mode 100644 edi_component_oca/tests/test_backend_input.py create mode 100644 edi_component_oca/tests/test_backend_output.py create mode 100644 edi_component_oca/tests/test_backend_process.py create mode 100644 edi_component_oca/tests/test_backend_validate.py create mode 100644 edi_component_oca/tests/test_component_match.py create mode 100644 edi_component_oca/tests/test_edi_backend_cron.py create mode 100644 edi_component_oca/tests/test_edi_configuration.py create mode 100644 edi_component_oca/tests/test_exchange_type_encoding.py create mode 100644 edi_component_oca/tests/test_quick_exec.py diff --git a/edi_component_oca/README.rst b/edi_component_oca/README.rst new file mode 100644 index 000000000..85954498e --- /dev/null +++ b/edi_component_oca/README.rst @@ -0,0 +1 @@ +TO BE GENERATED \ No newline at end of file diff --git a/edi_component_oca/__init__.py b/edi_component_oca/__init__.py new file mode 100644 index 000000000..f24d3e242 --- /dev/null +++ b/edi_component_oca/__init__.py @@ -0,0 +1,2 @@ +from . import components +from . import models diff --git a/edi_component_oca/__manifest__.py b/edi_component_oca/__manifest__.py new file mode 100644 index 000000000..52b6062c4 --- /dev/null +++ b/edi_component_oca/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2025 Dixmit +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +{ + "name": "Edi Connector Oca", + "summary": """Allow to use Connector as a source in EDI""", + "version": "18.0.1.0.0", + "license": "LGPL-3", + "author": "ACSONE,Dixmit,Camptocamp,Odoo Community Association (OCA)", + "maintainers": ["simahawk", "etobella"], + "website": "https://github.com/OCA/edi-framework", + "depends": ["base_edi", "component_event", "edi_core_oca"], + "data": [], + "demo": [], +} diff --git a/edi_component_oca/components/__init__.py b/edi_component_oca/components/__init__.py new file mode 100644 index 000000000..94e4ac140 --- /dev/null +++ b/edi_component_oca/components/__init__.py @@ -0,0 +1,4 @@ +from . import base +from . import base_output +from . import base_input +from . import base_validate diff --git a/edi_component_oca/components/base.py b/edi_component_oca/components/base.py new file mode 100644 index 000000000..1b4c1e6b0 --- /dev/null +++ b/edi_component_oca/components/base.py @@ -0,0 +1,72 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.addons.component.core import AbstractComponent + + +class EDIBackendComponentMixin(AbstractComponent): + """Generic mixin for all EDI components.""" + + _name = "edi.component.base.mixin" + _collection = "edi.backend" + _usage = None + _backend_type = None + _exchange_type = None + + def __init__(self, work_context): + super().__init__(work_context) + self.backend = work_context.backend + + @staticmethod + def _match_attrs(): + """Attributes to be used for matching this component. + + By default, match by backend and exchange type. + + NOTE: the class attribute must have an underscore, the name here not. + """ + return ("backend_type", "exchange_type") + + @classmethod + def _component_match(cls, work, usage=None, model_name=None, **kw): + """Override to customize match. + + Registry lookup filtered by usage and model_name when landing here. + Now, narrow match to `_match_attrs` attributes. + """ + match_attrs = cls._match_attrs() + if not any([kw.get(k) for k in match_attrs]): + # No attr to check + return True + + backend_type = kw.get("backend_type") + exchange_type = kw.get("exchange_type") + + if cls._backend_type and cls._exchange_type: + # They must match both + return ( + cls._backend_type == backend_type + and cls._exchange_type == exchange_type + ) + + if cls._backend_type not in (None, kw.get("backend_type")): + return False + + if cls._exchange_type not in (None, kw.get("exchange_type")): + return False + + return True + + +class EDIBackendRecordComponentMixin(AbstractComponent): + """Generic mixin for record-bound components.""" + + _name = "edi.component.mixin" + _inherit = "edi.component.base.mixin" + + def __init__(self, work_context): + super().__init__(work_context) + self.exchange_record = work_context.exchange_record + self.record = self.exchange_record.record + self.type_settings = self.exchange_record.type_id.get_settings() diff --git a/edi_component_oca/components/base_input.py b/edi_component_oca/components/base_input.py new file mode 100644 index 000000000..14e84f7b3 --- /dev/null +++ b/edi_component_oca/components/base_input.py @@ -0,0 +1,23 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.addons.component.core import AbstractComponent + + +class EDIBackendInputComponentMixin(AbstractComponent): + """Generate input content.""" + + _name = "edi.component.input.mixin" + _inherit = "edi.component.mixin" + + def process(self): + raise NotImplementedError() + + +class EDIBackendReceiveComponentMixin(AbstractComponent): + _name = "edi.component.receive.mixin" + _inherit = "edi.component.mixin" + + def receive(self): + raise NotImplementedError() diff --git a/edi_component_oca/components/base_output.py b/edi_component_oca/components/base_output.py new file mode 100644 index 000000000..9a97f1f2b --- /dev/null +++ b/edi_component_oca/components/base_output.py @@ -0,0 +1,36 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.addons.component.core import AbstractComponent + + +class EDIBackendOutputComponentMixin(AbstractComponent): + """Generate output content.""" + + _name = "edi.component.output.mixin" + _inherit = "edi.component.mixin" + _usage = "edi.output.generate.*" + + def generate(self): + raise NotImplementedError() + + +class EDIBackendSendComponentMixin(AbstractComponent): + """Send output records.""" + + _name = "edi.component.send.mixin" + _inherit = "edi.component.mixin" + _usage = "edi.output.send.*" + + def send(self): + raise NotImplementedError() + + +class EDIBackendCheckComponentMixin(AbstractComponent): + _name = "edi.component.check.mixin" + _inherit = "edi.component.mixin" + _usage = "edi.output.check.*" + + def check(self): + raise NotImplementedError() diff --git a/edi_component_oca/components/base_validate.py b/edi_component_oca/components/base_validate.py new file mode 100644 index 000000000..324f0401a --- /dev/null +++ b/edi_component_oca/components/base_validate.py @@ -0,0 +1,20 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.addons.component.core import AbstractComponent + + +class EDIBackendValidateComponentMixin(AbstractComponent): + """Validate exchange data.""" + + _name = "edi.component.validate.mixin" + _inherit = "edi.component.mixin" + _usage = "edi.validate.*" + + def validate(self, value=None): + self._validate(value) + + def _validate(self, value=None): + """Return None validated, raise `edi.exceptions.EDIValidationError` if not.""" + raise NotImplementedError() diff --git a/edi_component_oca/models/__init__.py b/edi_component_oca/models/__init__.py new file mode 100644 index 000000000..f86bfa873 --- /dev/null +++ b/edi_component_oca/models/__init__.py @@ -0,0 +1,3 @@ +from . import edi_backend +from . import edi_exchange_record +from . import edi_exchange_consumer_mixin diff --git a/edi_component_oca/models/edi_backend.py b/edi_component_oca/models/edi_backend.py new file mode 100644 index 000000000..23025b063 --- /dev/null +++ b/edi_component_oca/models/edi_backend.py @@ -0,0 +1,171 @@ +# Copyright 2025 Dixmit +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +import logging + +from odoo import models + +from odoo.addons.component.exception import NoComponentError +from odoo.addons.edi_core_oca.exceptions import EDINotImplementedError + +_logger = logging.getLogger(__name__) + + +class EdiBackend(models.Model): + _name = "edi.backend" + _inherit = ["edi.backend", "collection.base"] + + def _exchange_generate(self, exchange_record, **kw): + try: + return super()._exchange_generate(exchange_record, **kw) + except EDINotImplementedError: + component = self._get_component(exchange_record, "generate") + if component: + return component.generate() + raise + + def _exchange_validate_data(self, exchange_record, value=None, **kw): + try: + return super()._exchange_validate_data(exchange_record, value=value, **kw) + except EDINotImplementedError: + component = self._get_component(exchange_record, "validate") + if component: + return component.validate(value) + raise + + def _exchange_send(self, exchange_record): + try: + return super()._exchange_send(exchange_record) + except EDINotImplementedError: + component = self._get_component(exchange_record, "send") + if component: + return component.send() + raise + + def _exchange_output_check_state(self, exchange_record): + try: + return super()._exchange_output_check_state(exchange_record) + except EDINotImplementedError: + component = self._get_component(exchange_record, "check") + if component: + return component.check() + raise + + def _exchange_process(self, exchange_record): + try: + return super()._exchange_process(exchange_record) + except EDINotImplementedError: + component = self._get_component(exchange_record, "process") + if component: + return component.process() + raise + + def _exchange_receive(self, exchange_record): + try: + return super()._exchange_receive(exchange_record) + except EDINotImplementedError: + component = self._get_component(exchange_record, "receive") + if component: + return component.receive() + raise + + def _get_component(self, exchange_record, key): + record_conf = self._get_component_conf_for_record(exchange_record, key) + # Load additional ctx keys if any + collection = self + # TODO: document/test this + env_ctx = self._get_component_env_ctx(record_conf, key) + collection = collection.with_context(**env_ctx) + exchange_record = exchange_record.with_context(**env_ctx) + work_ctx = {"exchange_record": exchange_record} + # Inject work context from advanced settings + work_ctx.update(record_conf.get("work_ctx", {})) + # Model is not granted to be there + model = exchange_record.model or self._name + candidates = self._get_component_usage_candidates(exchange_record, key) + match_attrs = self._component_match_attrs(exchange_record, key) + return collection._find_component( + model, + candidates, + work_ctx=work_ctx, + **match_attrs, + ) + + def _component_match_attrs(self, exchange_record, key): + """Attributes that will be used to lookup components. + + They will be set in the work context and propagated to components. + """ + return { + "backend_type": self.backend_type_id.code, + "exchange_type": exchange_record.type_id.code, + } + + def _component_sort_key(self, component_class): + """Determine the order of matched components. + + The order can be very important if your implementation + allow generic / default components to be registered. + """ + return ( + 1 if component_class._backend_type else 0, + 1 if component_class._exchange_type else 0, + ) + + def _find_component(self, model, usage_candidates, safe=True, work_ctx=None, **kw): + """Retrieve component for current backend. + + :param usage_candidates: + list of usage to try by priority. 1st found, 1st returned + :param safe: boolean, if true does not break if component is not found + :param work_ctx: dictionary with work context params + :param kw: keyword args to lookup for components (eg: usage) + """ + component = None + work_ctx = work_ctx or {} + if "backend" not in work_ctx: + work_ctx["backend"] = self + with self.work_on(model, **work_ctx) as work: + for usage in usage_candidates: + components, c_work_ctx = work._matching_components(usage=usage, **kw) + if not components: + continue + # Sort components and pick the 1st one matching. + # In this way we support generic components registration + # and specific components registrations + components = sorted( + components, key=lambda x: self._component_sort_key(x), reverse=True + ) + component = components[0](c_work_ctx) + _logger.debug("using component %s", component._name) + break + if not component and not safe: + raise NoComponentError( + f"No component found matching any of: {usage_candidates}" + ) + return component or None + + def _get_component_usage_candidates(self, exchange_record, key): + """Retrieve usage candidates for components.""" + # fmt:off + base_usage = ".".join([ + exchange_record.direction, + key, + ]) + # fmt:on + record_conf = self._get_component_conf_for_record(exchange_record, key) + candidates = [record_conf["usage"]] if record_conf else [] + candidates += [ + base_usage, + ] + return candidates + + def _get_component_conf_for_record(self, exchange_record, key): + settings = exchange_record.type_id.get_settings() + return settings.get("components", {}).get(key, {}) + + def _get_component_env_ctx(self, record_conf, key): + env_ctx = record_conf.get("env_ctx", {}) + # You can use `edi_session` down in the stack to control logics. + env_ctx.update(dict(edi_framework_action=key)) + return env_ctx diff --git a/edi_component_oca/models/edi_exchange_consumer_mixin.py b/edi_component_oca/models/edi_exchange_consumer_mixin.py new file mode 100644 index 000000000..83a20a70e --- /dev/null +++ b/edi_component_oca/models/edi_exchange_consumer_mixin.py @@ -0,0 +1,30 @@ +# Copyright 2025 Camptocamp +# Copyright 2025 Dixmit +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import models + + +class EdiExchangeConsumerMixin(models.AbstractModel): + _inherit = "edi.exchange.consumer.mixin" + + def _manual_notify_edi_generation(self, exchange_record): + self._event("on_edi_generate_manual").notify(self, exchange_record) + return super()._manual_notify_edi_generation(exchange_record) + + def write(self, vals): + # Generic event to match a state change + # TODO: this can be added to component_event for models having the state field + state_change = "state" in vals and "state" in self._fields + if state_change: + for rec in self: + rec._event(f"on_edi_{self._table}_before_state_change").notify( + rec, state=vals["state"] + ) + res = super().write(vals) + if state_change: + for rec in self: + rec._event(f"on_edi_{self._table}_state_change").notify( + rec, state=vals["state"] + ) + return res diff --git a/edi_component_oca/models/edi_exchange_record.py b/edi_component_oca/models/edi_exchange_record.py new file mode 100644 index 000000000..551d1fa6b --- /dev/null +++ b/edi_component_oca/models/edi_exchange_record.py @@ -0,0 +1,22 @@ +# Copyright 2025 Camptocamp +# Copyright 2025 Dixmit +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import models + + +class EdiExchangeRecord(models.Model): + _inherit = "edi.exchange.record" + + def _trigger_edi_event_make_name(self, name, suffix=None): + return "on_edi_exchange_{name}{suffix}".format( + name=name, + suffix=("_" + suffix) if suffix else "", + ) + + def _trigger_edi_event(self, name, suffix=None, target=None, **kw): + """Trigger a component event linked to this backend and edi exchange.""" + name = self._trigger_edi_event_make_name(name, suffix=suffix) + target = target or self + target._event(name).notify(self, **kw) + return super()._trigger_edi_event(name, suffix=suffix, target=target, **kw) diff --git a/edi_component_oca/pyproject.toml b/edi_component_oca/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/edi_component_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/edi_component_oca/readme/CONFIGURE.md b/edi_component_oca/readme/CONFIGURE.md new file mode 100644 index 000000000..b1369af2d --- /dev/null +++ b/edi_component_oca/readme/CONFIGURE.md @@ -0,0 +1,16 @@ +## Component definition + +The component used on edi must inherit from: + +- `edi.component.input.mixin` for processing and implement the process function +- `edi.component.receive.mixin` for reception and implement the receive function +- `edi.component.output.mixin` for generation and implement the generate function +- `edi.component.send.mixin` for sending and implement the send function +- `edi.component.check.mixin` for checking and implement the check function +- `edi.component.validate.mixin` for validation and implement the validate function + +Also, the components may have the following elements that will be used to use the right component: + +- `_backend_type`: code of the backend type +- `_exchange_type`: code of the exchange type +- `_usage`: Automatically set by the inherited component diff --git a/edi_component_oca/readme/CONTRIBUTORS.md b/edi_component_oca/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..405f579f4 --- /dev/null +++ b/edi_component_oca/readme/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +- Simone Orsi \<\> +- Enric Tobella \<\> +- Manuel Regidor \<\> +- Thien Vo \<\> +- Jordi Masvidal \<\> diff --git a/edi_component_oca/readme/DESCRIPTION.md b/edi_component_oca/readme/DESCRIPTION.md new file mode 100644 index 000000000..4c38a75cd --- /dev/null +++ b/edi_component_oca/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module allows to use components to handle code to execute on EDI Exchanges. diff --git a/edi_component_oca/static/description/icon.png b/edi_component_oca/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/edi_component_oca/tests/__init__.py b/edi_component_oca/tests/__init__.py new file mode 100644 index 000000000..6f2143c72 --- /dev/null +++ b/edi_component_oca/tests/__init__.py @@ -0,0 +1,10 @@ +from . import test_backend_base +from . import test_backend_input +from . import test_backend_output +from . import test_backend_process +from . import test_backend_validate +from . import test_component_match +from . import test_edi_backend_cron +from . import test_edi_configuration +from . import test_exchange_type_encoding +from . import test_quick_exec diff --git a/edi_component_oca/tests/common.py b/edi_component_oca/tests/common.py new file mode 100644 index 000000000..412a5ac3f --- /dev/null +++ b/edi_component_oca/tests/common.py @@ -0,0 +1,35 @@ +# Copyright 2020 ACSONE +# Copyright 2020 Dixmit +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + + +from odoo.tests.common import tagged + +from odoo.addons.component.tests.common import ( + TransactionComponentCase, + TransactionComponentRegistryCase, +) +from odoo.addons.edi_core_oca.tests.common import EDIBackendTestMixin + + +@tagged("-at_install", "post_install") +class EDIBackendCommonComponentTestCase(TransactionComponentCase, EDIBackendTestMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_env() + cls._setup_records() + + +@tagged("-at_install", "post_install") +class EDIBackendCommonComponentRegistryTestCase( + TransactionComponentRegistryCase, EDIBackendTestMixin +): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_env() + cls._setup_records() + cls._setup_registry(cls) + cls._load_module_components(cls, "edi_component_oca") diff --git a/edi_component_oca/tests/fake_components.py b/edi_component_oca/tests/fake_components.py new file mode 100644 index 000000000..6ab994792 --- /dev/null +++ b/edi_component_oca/tests/fake_components.py @@ -0,0 +1,155 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.addons.component.core import Component + + +class FakeComponentMixin(Component): + FAKED_COLLECTOR = [] + + # only for testing + _action = "" + + def _fake_it(self): + self.FAKED_COLLECTOR.append(self._call_key(self.exchange_record)) + if self.env.context.get("test_break_" + self._action): + exception = self.env.context.get( + "test_break_" + self._action, "YOU BROKE IT!" + ) + if not isinstance(exception, Exception): + exception = ValueError(exception) + raise exception + update_values = self.env.context.get("fake_update_values") + if update_values: + self.exchange_record.write(update_values) + return self.env.context.get("fake_output", self._call_key(self.exchange_record)) + + @classmethod + def _call_key(cls, rec): + return f"{cls._name}: {rec.id}" + + @classmethod + def reset_faked(cls): + cls.FAKED_COLLECTOR = [] + + @classmethod + def check_called_for(cls, rec): + return cls._call_key(rec) in cls.FAKED_COLLECTOR + + @classmethod + def check_not_called_for(cls, rec): + return cls._call_key(rec) not in cls.FAKED_COLLECTOR + + +class FakeOutputGenerator(FakeComponentMixin): + _name = "fake.output.generator" + _inherit = "edi.component.output.mixin" + _usage = "output.generate" + _backend_type = "demo_backend" + _exchange_type = "test_csv_output" + + _action = "generate" + + def generate(self): + return self._fake_it() + + +class FakeOutputSender(FakeComponentMixin): + _name = "fake.output.sender" + _inherit = "edi.component.send.mixin" + _usage = "output.send" + _backend_type = "demo_backend" + _exchange_type = "test_csv_output" + + _action = "send" + + def send(self): + return self._fake_it() + + +class FakeOutputChecker(FakeComponentMixin): + _name = "fake.output.checker" + _inherit = "edi.component.check.mixin" + _usage = "output.check" + _backend_type = "demo_backend" + _exchange_type = "test_csv_output" + + _action = "check" + + def check(self): + return self._fake_it() + + +class FakeInputProcess(FakeComponentMixin): + _name = "fake.input.process" + _inherit = "edi.component.input.mixin" + _usage = "input.process" + _backend_type = "demo_backend" + _exchange_type = "test_csv_input" + + _action = "process" + + def process(self): + return self._fake_it() + + +class FakeInputReceive(FakeComponentMixin): + _name = "fake.input.receive" + _inherit = "edi.component.input.mixin" + _usage = "input.receive" + _backend_type = "demo_backend" + _exchange_type = "test_csv_input" + + _action = "receive" + + def receive(self): + return self._fake_it() + + +class FakeOutputValidate(FakeComponentMixin): + _name = "fake.out.validate" + _inherit = "edi.component.validate.mixin" + _usage = "output.validate" + _backend_type = "demo_backend" + _exchange_type = "test_csv_output" + + _action = "validate" + + def validate(self, value=None): + self._fake_it() + return + + +class FakeInputValidate(FakeComponentMixin): + _name = "fake.in.validate" + _inherit = "edi.component.validate.mixin" + _usage = "input.validate" + _backend_type = "demo_backend" + _exchange_type = "test_csv_input" + + _action = "validate" + + def validate(self, value=None): + self._fake_it() + return + + +class FakeConfigurationListener(FakeComponentMixin): + _name = "fake.configuration.listener" + _inherit = "base.event.listener" + _apply_on = ["edi.exchange.consumer.test"] + + def on_record_write(self, record, fields=None, **kwargs): + trigger = "on_record_write" + confs = record.edi_config_ids.edi_get_conf(trigger) + for conf in confs: + conf.edi_exec_snippet_do(record, **kwargs) + return True + + def on_record_create(self, record, fields=None, **kwargs): + trigger = "on_record_create" + confs = record.edi_config_ids.edi_get_conf(trigger) + for conf in confs: + conf.edi_exec_snippet_do(record, **kwargs) + return True diff --git a/edi_component_oca/tests/test_backend_base.py b/edi_component_oca/tests/test_backend_base.py new file mode 100644 index 000000000..d3b5ac40b --- /dev/null +++ b/edi_component_oca/tests/test_backend_base.py @@ -0,0 +1,72 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from freezegun import freeze_time + +from odoo.addons.edi_core_oca.tests.common import EDIBackendCommonTestCase + + +class EDIBackendTestCaseBase(EDIBackendCommonTestCase): + @freeze_time("2020-10-21 10:00:00") + def test_create_record(self): + self.env.user.tz = None # Have no timezone used in generated filename + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record = self.backend.create_record("test_csv_input", vals) + expected = { + "type_id": self.exchange_type_in.id, + "edi_exchange_state": "new", + "exchange_filename": "EDI_EXC_TEST-test_csv_" + "input-2020-10-21-10-00-00.csv", + } + self.assertRecordValues(record, [expected]) + self.assertEqual(record.record, self.partner) + self.assertEqual(record.edi_exchange_state, "new") + + def test_get_component_usage(self): + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record = self.backend.create_record("test_csv_input", vals) + candidates = self.backend._get_component_usage_candidates(record, "process") + self.assertEqual( + candidates, + ["input.process"], + ) + record = self.backend.create_record("test_csv_output", vals) + candidates = self.backend._get_component_usage_candidates(record, "generate") + self.assertEqual( + candidates, + ["output.generate"], + ) + # set advanced settings on type + settings = """ + components: + generate: + usage: my.special.generate + send: + usage: my.special.send + """ + record.type_id.advanced_settings_edit = settings + candidates = self.backend._get_component_usage_candidates(record, "generate") + self.assertEqual( + candidates, + ["my.special.generate", "output.generate"], + ) + candidates = self.backend._get_component_usage_candidates(record, "send") + self.assertEqual( + candidates, + ["my.special.send", "output.send"], + ) + + def test_action_view_exchanges(self): + # Just testing is not broken + self.assertTrue(self.backend.action_view_exchanges()) + + def test_action_view_exchange_types(self): + # Just testing is not broken + self.assertTrue(self.backend.action_view_exchange_types()) diff --git a/edi_component_oca/tests/test_backend_input.py b/edi_component_oca/tests/test_backend_input.py new file mode 100644 index 000000000..9bb149010 --- /dev/null +++ b/edi_component_oca/tests/test_backend_input.py @@ -0,0 +1,72 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from .common import EDIBackendCommonComponentRegistryTestCase +from .fake_components import FakeInputReceive + + +class EDIBackendTestInputCase(EDIBackendCommonComponentRegistryTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._build_components( + # TODO: test all components lookup + cls, + FakeInputReceive, + ) + vals = { + "model": cls.partner._name, + "res_id": cls.partner.id, + } + cls.record = cls.backend.create_record("test_csv_input", vals) + + @classmethod + def _setup_context(cls): + return dict( + super()._setup_context(), + _edi_receive_break_on_error=True, + _edi_process_break_on_error=True, + ) + + def setUp(self): + super().setUp() + FakeInputReceive.reset_faked() + + def test_receive_record_nothing_todo(self): + self.backend.with_context(fake_output="yeah!").exchange_receive(self.record) + self.assertEqual(self.record._get_file_content(), "") + self.assertRecordValues(self.record, [{"edi_exchange_state": "new"}]) + + def test_receive_record(self): + self.record.edi_exchange_state = "input_pending" + self.backend.with_context(fake_output="yeah!").exchange_receive(self.record) + self.assertEqual(self.record._get_file_content(), "yeah!") + self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}]) + + def test_receive_no_allow_empty_file_record(self): + self.record.edi_exchange_state = "input_pending" + self.backend.with_context( + fake_output="", _edi_receive_break_on_error=False + ).exchange_receive(self.record) + # Check the record + msg = ( + "Empty files are not allowed for exchange type " + f"{self.exchange_type_in.name} ({self.exchange_type_in.code})" + ) + self.assertEqual(msg, self.record.exchange_error) + self.assertIn(msg, self.record.exchange_error_traceback) + self.assertEqual(self.record._get_file_content(), "") + self.assertRecordValues( + self.record, [{"edi_exchange_state": "input_receive_error"}] + ) + + def test_receive_allow_empty_file_record(self): + self.record.edi_exchange_state = "input_pending" + self.record.type_id.allow_empty_files_on_receive = True + self.backend.with_context( + fake_output="", _edi_receive_break_on_error=False + ).exchange_receive(self.record) + # Check the record + self.assertEqual(self.record._get_file_content(), "") + self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}]) diff --git a/edi_component_oca/tests/test_backend_output.py b/edi_component_oca/tests/test_backend_output.py new file mode 100644 index 000000000..00a9f26cc --- /dev/null +++ b/edi_component_oca/tests/test_backend_output.py @@ -0,0 +1,111 @@ +# Copyright 2020 ACSONE +# Copyright 2021 Camptocamp +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from unittest import mock + +from freezegun import freeze_time + +from odoo import fields, tools +from odoo.exceptions import UserError + +from .common import EDIBackendCommonComponentRegistryTestCase +from .fake_components import FakeOutputChecker, FakeOutputGenerator, FakeOutputSender + + +class EDIBackendTestOutputCase(EDIBackendCommonComponentRegistryTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._build_components( + cls, + FakeOutputGenerator, + FakeOutputSender, + FakeOutputChecker, + ) + vals = { + "model": cls.partner._name, + "res_id": cls.partner.id, + } + cls.record = cls.backend.create_record("test_csv_output", vals) + + def setUp(self): + super().setUp() + FakeOutputGenerator.reset_faked() + FakeOutputSender.reset_faked() + FakeOutputChecker.reset_faked() + + def test_generate_record_output(self): + self.record.with_context(fake_output="yeah!").action_exchange_generate() + self.assertEqual(self.record._get_file_content(), "yeah!") + + def test_generate_record_output_pdf(self): + pdf_content = tools.file_open( + "addons/edi_core_oca/tests/result.pdf", mode="rb" + ).read() + self.record.with_context(fake_output=pdf_content).action_exchange_generate() + + def test_send_record(self): + self.record.write({"edi_exchange_state": "output_pending"}) + self.record._set_file_content("TEST %d" % self.record.id) + self.assertFalse(self.record.exchanged_on) + with freeze_time("2020-10-21 10:00:00"): + self.record.action_exchange_send() + self.assertTrue(FakeOutputSender.check_called_for(self.record)) + self.assertRecordValues(self.record, [{"edi_exchange_state": "output_sent"}]) + self.assertEqual( + fields.Datetime.to_string(self.record.exchanged_on), "2020-10-21 10:00:00" + ) + + def test_send_record_with_error(self): + self.record.write({"edi_exchange_state": "output_pending"}) + self.record._set_file_content("TEST %d" % self.record.id) + self.assertFalse(self.record.exchanged_on) + self.record.with_context( + test_break_send="OOPS! Something went wrong :(" + ).action_exchange_send() + self.assertTrue(FakeOutputSender.check_called_for(self.record)) + self.assertRecordValues( + self.record, + [ + { + "edi_exchange_state": "output_error_on_send", + "exchange_error": "OOPS! Something went wrong :(", + } + ], + ) + self.assertIn( + "OOPS! Something went wrong :(", self.record.exchange_error_traceback + ) + + def test_send_invalid_direction(self): + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record = self.backend.create_record("test_csv_input", vals) + with mock.patch.object(type(self.backend), "_exchange_send") as mocked: + mocked.return_value = "AAA" + with self.assertRaises(UserError) as err: + record.action_exchange_send() + self.assertEqual( + err.exception.args[0], + "Record ID=%d is not meant to be sent!" % record.id, + ) + mocked.assert_not_called() + + def test_send_not_generated_record(self): + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record = self.backend.create_record("test_csv_output", vals) + with mock.patch.object(type(self.backend), "_exchange_send") as mocked: + mocked.return_value = "AAA" + with self.assertRaises(UserError) as err: + record.action_exchange_send() + self.assertEqual( + err.exception.args[0], "Record ID=%d has no file to send!" % record.id + ) + mocked.assert_not_called() diff --git a/edi_component_oca/tests/test_backend_process.py b/edi_component_oca/tests/test_backend_process.py new file mode 100644 index 000000000..b2e8fa057 --- /dev/null +++ b/edi_component_oca/tests/test_backend_process.py @@ -0,0 +1,95 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import base64 + +from freezegun import freeze_time + +from odoo import fields +from odoo.exceptions import UserError +from odoo.tools import mute_logger + +from .common import EDIBackendCommonComponentRegistryTestCase +from .fake_components import FakeInputProcess + + +class EDIBackendTestProcessCase(EDIBackendCommonComponentRegistryTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._build_components( + # TODO: test all components lookup + cls, + FakeInputProcess, + ) + vals = { + "model": cls.partner._name, + "res_id": cls.partner.id, + "exchange_file": base64.b64encode(b"1234"), + } + cls.record = cls.backend.create_record("test_csv_input", vals) + + def setUp(self): + super().setUp() + FakeInputProcess.reset_faked() + + def test_process_record(self): + self.record.write({"edi_exchange_state": "input_received"}) + with freeze_time("2020-10-22 10:00:00"): + self.record.action_exchange_process() + self.assertTrue(FakeInputProcess.check_called_for(self.record)) + self.assertRecordValues( + self.record, [{"edi_exchange_state": "input_processed"}] + ) + self.assertEqual( + fields.Datetime.to_string(self.record.exchanged_on), "2020-10-22 10:00:00" + ) + + def test_process_record_with_error(self): + self.record.write({"edi_exchange_state": "input_received"}) + self.record._set_file_content("TEST %d" % self.record.id) + self.record.with_context( + test_break_process="OOPS! Something went wrong :(" + ).action_exchange_process() + self.assertTrue(FakeInputProcess.check_called_for(self.record)) + self.assertRecordValues( + self.record, + [ + { + "edi_exchange_state": "input_processed_error", + "exchange_error": "OOPS! Something went wrong :(", + } + ], + ) + self.assertIn( + "OOPS! Something went wrong :(", self.record.exchange_error_traceback + ) + + @mute_logger("odoo.models.unlink") + def test_process_no_file_record(self): + self.record.write({"edi_exchange_state": "input_received"}) + self.record.exchange_file = False + self.exchange_type_in.allow_empty_files_on_receive = False + with self.assertRaises(UserError): + self.record.action_exchange_process() + + @mute_logger("odoo.models.unlink") + def test_process_allow_no_file_record(self): + self.record.write({"edi_exchange_state": "input_received"}) + self.record.exchange_file = False + self.exchange_type_in.allow_empty_files_on_receive = True + self.record.action_exchange_process() + self.assertEqual(self.record.edi_exchange_state, "input_processed") + + def test_process_outbound_record(self): + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record = self.backend.create_record("test_csv_output", vals) + record._set_file_content("TEST %d" % record.id) + with self.assertRaises(UserError): + record.action_exchange_process() + + # TODO: test ack file are processed diff --git a/edi_component_oca/tests/test_backend_validate.py b/edi_component_oca/tests/test_backend_validate.py new file mode 100644 index 000000000..ea7a0f836 --- /dev/null +++ b/edi_component_oca/tests/test_backend_validate.py @@ -0,0 +1,121 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import base64 + +from odoo.addons.edi_core_oca.exceptions import EDIValidationError + +from .common import EDIBackendCommonComponentRegistryTestCase +from .fake_components import ( + FakeInputReceive, + FakeInputValidate, + FakeOutputGenerator, + FakeOutputValidate, +) + + +class EDIBackendTestValidateCase(EDIBackendCommonComponentRegistryTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._build_components( + # TODO: test all components lookup + cls, + FakeInputValidate, + FakeOutputValidate, + FakeInputReceive, + FakeOutputGenerator, + ) + vals = { + "model": cls.partner._name, + "res_id": cls.partner.id, + "exchange_file": base64.b64encode(b"1234"), + } + cls.record_in = cls.backend.create_record("test_csv_input", vals) + vals.pop("exchange_file") + cls.record_out = cls.backend.create_record("test_csv_output", vals) + + def setUp(self): + super().setUp() + FakeInputValidate.reset_faked() + FakeOutputValidate.reset_faked() + FakeInputReceive.reset_faked() + FakeOutputGenerator.reset_faked() + + def test_receive_validate_record(self): + self.record_in.write({"edi_exchange_state": "input_pending"}) + self.backend.exchange_receive(self.record_in) + self.assertTrue(FakeInputValidate.check_called_for(self.record_in)) + self.assertRecordValues( + self.record_in, [{"edi_exchange_state": "input_received"}] + ) + + def test_receive_validate_record_error(self): + self.record_in.write({"edi_exchange_state": "input_pending"}) + exc = EDIValidationError("Data seems wrong!") + self.backend.with_context(test_break_validate=exc).exchange_receive( + self.record_in + ) + self.assertTrue(FakeInputValidate.check_called_for(self.record_in)) + self.assertRecordValues( + self.record_in, + [ + { + "edi_exchange_state": "validate_error", + "exchange_error": "Data seems wrong!", + } + ], + ) + self.assertIn("Data seems wrong!", self.record_in.exchange_error_traceback) + + def test_generate_validate_record(self): + self.record_out.write({"edi_exchange_state": "new"}) + self.backend.exchange_generate(self.record_out) + self.assertTrue(FakeOutputValidate.check_called_for(self.record_out)) + self.assertRecordValues( + self.record_out, [{"edi_exchange_state": "output_pending"}] + ) + + def test_generate_validate_record_error(self): + self.record_out.write({"edi_exchange_state": "new"}) + exc = EDIValidationError("Data seems wrong!") + self.backend.with_context(test_break_validate=exc).exchange_generate( + self.record_out + ) + self.assertTrue(FakeOutputValidate.check_called_for(self.record_out)) + self.assertRecordValues( + self.record_out, + [ + { + "edi_exchange_state": "validate_error", + "exchange_error": "Data seems wrong!", + } + ], + ) + self.assertIn("Data seems wrong!", self.record_out.exchange_error_traceback) + + def test_validate_record_error_regenerate(self): + self.record_out.write({"edi_exchange_state": "new"}) + exc = EDIValidationError("Data seems wrong!") + self.backend.with_context(test_break_validate=exc).exchange_generate( + self.record_out + ) + self.assertRecordValues( + self.record_out, + [ + { + "edi_exchange_state": "validate_error", + } + ], + ) + self.record_out.with_context(fake_output="yeah!").action_regenerate() + self.assertEqual(self.record_out._get_file_content(), "yeah!") + self.assertRecordValues( + self.record_out, + [ + { + "edi_exchange_state": "output_pending", + } + ], + ) diff --git a/edi_component_oca/tests/test_component_match.py b/edi_component_oca/tests/test_component_match.py new file mode 100644 index 000000000..7fcc5b08f --- /dev/null +++ b/edi_component_oca/tests/test_component_match.py @@ -0,0 +1,71 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.addons.component.core import Component + +from .common import EDIBackendCommonComponentRegistryTestCase + + +class EDIBackendTestMatchCase(EDIBackendCommonComponentRegistryTestCase): + def test_component_match(self): + """Lookup with special match method.""" + + class MatchByBackendTypeOnly(Component): + _name = "backend_type.only" + _inherit = "edi.component.mixin" + _usage = "generate" + _backend_type = "demo_backend" + _apply_on = ["res.partner"] + + class MatchByExchangeTypeOnly(Component): + _name = "exchange_type.only" + _inherit = "edi.component.mixin" + _usage = "generate" + _exchange_type = "test_csv_output" + _apply_on = ["res.partner"] + + class MatchByBackendExchangeType(Component): + _name = "backend_type.and.exchange_type" + _inherit = "edi.component.mixin" + _usage = "generate" + _backend_type = "demo_backend" + _exchange_type = "test_csv_output" + _apply_on = ["res.partner"] + + self._build_components( + MatchByBackendTypeOnly, + MatchByExchangeTypeOnly, + # This one is registered last but since edi.backend + # is going to sort them by priority, we'll get the right one + # when looking for both match + MatchByBackendExchangeType, + ) + + # Record not relevant for these tests + work_ctx = {"exchange_record": self.env["edi.exchange.record"].browse()} + + # Search by both backend and exchange type + component = self.backend._find_component( + "res.partner", + ["generate"], + work_ctx=work_ctx, + backend_type="demo_backend", + exchange_type="test_csv_output", + ) + self.assertEqual(component._name, MatchByBackendExchangeType._name) + + # Search by backend type only + component = self.backend._find_component( + "res.partner", ["generate"], work_ctx=work_ctx, backend_type="demo_backend" + ) + self.assertEqual(component._name, MatchByBackendTypeOnly._name) + + # Search by exchange type only + component = self.backend._find_component( + "res.partner", + ["generate"], + work_ctx=work_ctx, + exchange_type="test_csv_output", + ) + self.assertEqual(component._name, MatchByExchangeTypeOnly._name) diff --git a/edi_component_oca/tests/test_edi_backend_cron.py b/edi_component_oca/tests/test_edi_backend_cron.py new file mode 100644 index 000000000..f28c41934 --- /dev/null +++ b/edi_component_oca/tests/test_edi_backend_cron.py @@ -0,0 +1,98 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.tools import mute_logger + +from .common import EDIBackendCommonComponentRegistryTestCase +from .fake_components import FakeOutputChecker, FakeOutputGenerator, FakeOutputSender + +LOGGERS = ( + "odoo.addons.edi_core_oca.models.edi_backend", + "odoo.addons.queue_job.delay", + "odoo.addons.edi_exchange_template_oca.models.edi_backend", +) + + +class EDIBackendTestCronCase(EDIBackendCommonComponentRegistryTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._build_components( + cls, FakeOutputGenerator, FakeOutputSender, FakeOutputChecker + ) + cls.partner2 = cls.env.ref("base.res_partner_10") + cls.partner3 = cls.env.ref("base.res_partner_12") + cls.record1 = cls.backend.create_record( + "test_csv_output", {"model": cls.partner._name, "res_id": cls.partner.id} + ) + cls.record2 = cls.backend.create_record( + "test_csv_output", {"model": cls.partner._name, "res_id": cls.partner2.id} + ) + cls.record3 = cls.backend.create_record( + "test_csv_output", {"model": cls.partner._name, "res_id": cls.partner3.id} + ) + cls.records = cls.record1 + cls.record1 + cls.record3 + + def setUp(self): + super().setUp() + FakeOutputGenerator.reset_faked() + FakeOutputSender.reset_faked() + FakeOutputChecker.reset_faked() + + @mute_logger(*LOGGERS) + def test_exchange_generate_new_no_auto(self): + # No content ready to be sent, no auto-generate, nothing happens + for rec in self.records: + self.assertEqual(rec.edi_exchange_state, "new") + self.backend._cron_check_output_exchange_sync() + for rec in self.records: + self.assertEqual(rec.edi_exchange_state, "new") + + @mute_logger(*LOGGERS) + def test_exchange_generate_new_auto_skip_send(self): + self.exchange_type_out.exchange_file_auto_generate = True + # No content ready to be sent, will get the content but not send it + for rec in self.records: + self.assertEqual(rec.edi_exchange_state, "new") + self.backend._cron_check_output_exchange_sync(skip_send=True) + for rec in self.records: + self.assertEqual(rec.edi_exchange_state, "output_pending") + self.assertTrue(FakeOutputGenerator.check_called_for(rec)) + self.assertEqual( + rec._get_file_content(), FakeOutputGenerator._call_key(rec) + ) + # TODO: test better? + self.assertFalse(rec.ack_exchange_id) + + @mute_logger(*LOGGERS) + def test_exchange_generate_new_auto_send(self): + self.exchange_type_out.exchange_file_auto_generate = True + # No content ready to be sent, will get the content and send it + for rec in self.records: + self.assertEqual(rec.edi_exchange_state, "new") + self.backend._cron_check_output_exchange_sync() + for rec in self.records: + self.assertEqual(rec.edi_exchange_state, "output_sent") + self.assertTrue(FakeOutputGenerator.check_called_for(rec)) + self.assertEqual( + rec._get_file_content(), FakeOutputGenerator._call_key(rec) + ) + self.assertTrue(FakeOutputSender.check_called_for(rec)) + + @mute_logger(*LOGGERS) + def test_exchange_generate_output_ready_auto_send(self): + # No content ready to be sent, will get the content and send it + for rec in self.records: + self.assertEqual(rec.edi_exchange_state, "new") + self.record1._set_file_content("READY") + self.record1.edi_exchange_state = "output_sent" + self.backend.with_context( + fake_update_values={"edi_exchange_state": "output_sent_and_processed"} + )._cron_check_output_exchange_sync(skip_sent=False) + for rec in self.records - self.record1: + self.assertEqual(rec.edi_exchange_state, "new") + self.assertEqual(self.record1.edi_exchange_state, "output_sent_and_processed") + self.assertTrue(FakeOutputGenerator.check_not_called_for(self.record1)) + self.assertTrue(FakeOutputSender.check_not_called_for(self.record1)) + self.assertTrue(FakeOutputChecker.check_called_for(self.record1)) diff --git a/edi_component_oca/tests/test_edi_configuration.py b/edi_component_oca/tests/test_edi_configuration.py new file mode 100644 index 000000000..509b3e2ab --- /dev/null +++ b/edi_component_oca/tests/test_edi_configuration.py @@ -0,0 +1,175 @@ +# Copyright 2024 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import os +import unittest + +from odoo_test_helper import FakeModelLoader + +from odoo.tests.common import tagged + +from .common import EDIBackendCommonComponentRegistryTestCase +from .fake_components import ( + FakeConfigurationListener, + FakeOutputChecker, + FakeOutputGenerator, + FakeOutputSender, +) + + +# This clashes w/ some setup (eg: run tests w/ pytest when edi_storage is installed) +# If you still want to run `edi` tests w/ pytest when this happens, set this env var. +@unittest.skipIf(os.getenv("SKIP_EDI_CONSUMER_CASE"), "Consumer test case disabled.") +@tagged("at_install", "-post_install") +class TestEDIConfigurations(EDIBackendCommonComponentRegistryTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._build_components( + cls, + FakeOutputGenerator, + FakeOutputSender, + FakeOutputChecker, + FakeConfigurationListener, + ) + vals = { + "model": cls.partner._name, + "res_id": cls.partner.id, + } + cls.record = cls.backend.create_record("test_csv_output", vals) + + def setUp(self): + super().setUp() + FakeOutputGenerator.reset_faked() + FakeOutputSender.reset_faked() + FakeOutputChecker.reset_faked() + self.consumer_record = self.env["edi.exchange.consumer.test"].create( + { + "name": "Test Consumer", + "edi_config_ids": [ + (4, self.create_config.id), + (4, self.write_config.id), + ], + } + ) + + @classmethod + def _setup_records(cls): # pylint:disable=missing-return + super()._setup_records() + # Load fake models ->/ + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + from odoo.addons.edi_core_oca.tests.fake_models import EdiExchangeConsumerTest + + EdiExchangeConsumerTest._edi_config_field_relation = lambda self: self.env[ + "edi.configuration" + ] + # We need to override it, as we want to test the usage with components + + cls.loader.update_registry((EdiExchangeConsumerTest,)) + cls.exchange_type_out.exchange_filename_pattern = "{record.id}" + cls.edi_configuration = cls.env["edi.configuration"] + cls.create_config = cls.edi_configuration.create( + { + "name": "Create Config", + "active": True, + "backend_id": cls.backend.id, + "type_id": cls.exchange_type_out.id, + "trigger_id": cls.env.ref( + "edi_core_oca.edi_conf_trigger_record_create" + ).id, + "model_id": cls.env["ir.model"]._get_id("edi.exchange.consumer.test"), + "snippet_do": "record._edi_send_via_edi(conf.type_id)", + } + ) + cls.write_config = cls.edi_configuration.create( + { + "name": "Write Config 1", + "active": True, + "backend_id": cls.backend.id, + "type_id": cls.exchange_type_out.id, + "trigger_id": cls.env.ref( + "edi_core_oca.edi_conf_trigger_record_write" + ).id, + "model_id": cls.env["ir.model"]._get_id("edi.exchange.consumer.test"), + "snippet_do": "record._edi_send_via_edi(conf.type_id)", + } + ) + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + super().tearDownClass() + + def test_edi_send_via_edi_config(self): + # Check configuration on create + self.consumer_record.invalidate_recordset() + exchange_record = self.consumer_record.exchange_record_ids + self.assertEqual(len(exchange_record), 1) + self.assertEqual(exchange_record.type_id, self.exchange_type_out) + self.assertEqual(exchange_record.edi_exchange_state, "output_sent") + # Write the existed consumer record + self.consumer_record.name = "Fixed Consumer" + # check Configuration on write + self.consumer_record.invalidate_recordset() + exchange_record = self.consumer_record.exchange_record_ids - exchange_record + self.assertEqual(len(exchange_record), 1) + self.assertEqual(exchange_record.type_id, self.exchange_type_out) + self.assertEqual(exchange_record.edi_exchange_state, "output_sent") + + def test_edi_code_snippet(self): + expected_value = { + "todo": True, + "snippet_do_vars": { + "a": 1, + "b": 2, + }, + "event_only": True, + "tracked_fields": ["state"], + "edi_action": "new_action", + } + # Simulate the snippet_before_do + self.write_config.snippet_before_do = "result = " + str(expected_value) + # Execute with the raw data + vals = self.write_config.edi_exec_snippet_before_do( + self.consumer_record, + tracked_fields=[], + edi_action="generate", + ) + # Check the new vals after execution + expected_value["conf"] = self.write_config + self.assertEqual(vals, expected_value) + + # Check the snippet_do + expected_value = { + "change_state": True, + "snippet_do_vars": { + "a": 1, + "b": 2, + }, + "record": self.consumer_record, + "tracked_fields": ["state"], + } + snippet_do = """\n +old_state = old_value.get("state", False)\n +new_state = vals.get("state", False)\n +change_state = True if old_state and new_state and old_state != new_state else False +result = {\n + "change_state": change_state,\n + "snippet_do_vars": snippet_do_vars,\n + "record": record,\n + "tracked_fields": tracked_fields,\n +} + """ + self.write_config.snippet_do = snippet_do + # Execute with the raw data + record_id = self.consumer_record.id + vals = self.write_config.edi_exec_snippet_do( + self.consumer_record, + tracked_fields=[], + edi_action="generate", + old_vals={record_id: dict(state="draft")}, + vals={record_id: dict(state="confirmed")}, + ) + # Check the new vals after execution + self.assertEqual(vals, expected_value) diff --git a/edi_component_oca/tests/test_exchange_type_encoding.py b/edi_component_oca/tests/test_exchange_type_encoding.py new file mode 100644 index 000000000..21a3f54dd --- /dev/null +++ b/edi_component_oca/tests/test_exchange_type_encoding.py @@ -0,0 +1,84 @@ +# Copyright 2024 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +import base64 + +import chardet + +from .common import EDIBackendCommonComponentRegistryTestCase +from .fake_components import FakeOutputGenerator + + +class EDIBackendTestOutputCase(EDIBackendCommonComponentRegistryTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._build_components( + cls, + FakeOutputGenerator, + ) + vals = { + "model": cls.partner._name, + "res_id": cls.partner.id, + } + cls.record = cls.backend.create_record("test_csv_output", vals) + + def setUp(self): + super().setUp() + FakeOutputGenerator.reset_faked() + + def test_encoding_default(self): + """ + Test default output/input encoding (UTF-8). Use string with special + character to test the encoding applied. + """ + self.backend.with_context(fake_output="Palmotićeva").exchange_generate( + self.record + ) + # Test decoding is applied correctly + self.assertEqual(self.record._get_file_content(), "Palmotićeva") + # Test encoding used + content = base64.b64decode(self.record.exchange_file) + encoding = chardet.detect(content)["encoding"].lower() + self.assertEqual(encoding, "utf-8") + + def test_encoding(self): + """ + Test specific output/input encoding. Use string with special + character to test the encoding applied. + """ + self.exchange_type_out.write({"encoding": "UTF-16"}) + self.backend.with_context(fake_output="Palmotićeva").exchange_generate( + self.record + ) + # Test decoding is applied correctly + self.assertEqual(self.record._get_file_content(), "Palmotićeva") + # Test encoding used + content = base64.b64decode(self.record.exchange_file) + encoding = chardet.detect(content)["encoding"].lower() + self.assertEqual(encoding, "utf-16") + + def test_encoding_error_handler(self): + self.exchange_type_out.write({"encoding": "ascii"}) + # By default, error handling raises error + with self.assertRaises(UnicodeEncodeError): + self.backend.with_context(fake_output="Palmotićeva").exchange_generate( + self.record + ) + self.exchange_type_out.write({"encoding_out_error_handler": "ignore"}) + self.backend.with_context(fake_output="Palmotićeva").exchange_generate( + self.record + ) + self.assertEqual(self.record._get_file_content(), "Palmotieva") + + def test_decoding_error_handler(self): + self.backend.with_context(fake_output="Palmotićeva").exchange_generate( + self.record + ) + # Change encoding to ascii to check the decoding + self.exchange_type_out.write({"encoding": "ascii"}) + # By default, error handling raises error + with self.assertRaises(UnicodeDecodeError): + content = self.record._get_file_content() + self.exchange_type_out.write({"encoding_in_error_handler": "ignore"}) + content = self.record._get_file_content() + self.assertEqual(content, "Palmotieva") diff --git a/edi_component_oca/tests/test_quick_exec.py b/edi_component_oca/tests/test_quick_exec.py new file mode 100644 index 000000000..4e6a22101 --- /dev/null +++ b/edi_component_oca/tests/test_quick_exec.py @@ -0,0 +1,117 @@ +# Copyright 2022 Camptocamp SA +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import base64 +from unittest import mock + +from odoo.tools import mute_logger + +from .common import EDIBackendCommonComponentRegistryTestCase +from .fake_components import ( + FakeInputProcess, + FakeOutputChecker, + FakeOutputGenerator, + FakeOutputSender, +) + +LOGGERS = ( + "odoo.addons.edi_core_oca.models.edi_backend", + "odoo.addons.queue_job.delay", + "odoo.addons.edi_exchange_template_oca.models.edi_backend", +) + + +class EDIQuickExecTestCase(EDIBackendCommonComponentRegistryTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._build_components( + cls, + FakeOutputGenerator, + FakeOutputSender, + FakeOutputChecker, + FakeInputProcess, + ) + cls.partner2 = cls.env.ref("base.res_partner_10") + cls.partner3 = cls.env.ref("base.res_partner_12") + + def setUp(self): + super().setUp() + FakeOutputGenerator.reset_faked() + FakeOutputSender.reset_faked() + FakeOutputChecker.reset_faked() + FakeInputProcess.reset_faked() + + @mute_logger(*LOGGERS) + def test_quick_exec_on_create_no_call(self): + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + model = self.env["edi.exchange.record"] + # quick exec is off, we should not get any call + with mock.patch.object(type(model), "_execute_next_action") as mocked: + record0 = self.backend.create_record("test_csv_output", vals) + mocked.assert_not_called() + self.assertEqual(record0.edi_exchange_state, "new") + # enabled but bypassed + self.exchange_type_out.exchange_file_auto_generate = True + self.exchange_type_out.quick_exec = True + with mock.patch.object(type(model), "_execute_next_action") as mocked: + record0 = self.backend.with_context( + edi__skip_quick_exec=True + ).create_record("test_csv_output", vals) + # quick exec is off, we should not get any call + mocked.assert_not_called() + self.assertEqual(record0.edi_exchange_state, "new") + + @mute_logger(*LOGGERS) + def test_quick_exec_on_create_out(self): + self.exchange_type_out.exchange_file_auto_generate = True + self.exchange_type_out.quick_exec = True + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record0 = self.backend.create_record("test_csv_output", vals) + # File generated and sent! + self.assertEqual(record0.edi_exchange_state, "output_sent") + self.assertTrue(FakeOutputGenerator.check_called_for(record0)) + self.assertEqual( + record0._get_file_content(), FakeOutputGenerator._call_key(record0) + ) + + @mute_logger(*LOGGERS) + def test_quick_exec_on_create_in(self): + self.exchange_type_in.quick_exec = True + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + "exchange_file": base64.b64encode(b"1234"), + "edi_exchange_state": "input_received", + } + record0 = self.backend.create_record("test_csv_input", vals) + self.assertEqual(record0.edi_exchange_state, "input_processed") + self.assertTrue(FakeInputProcess.check_called_for(record0)) + + @mute_logger(*LOGGERS) + def test_quick_exec_on_retry(self): + self.exchange_type_in.quick_exec = True + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + "edi_exchange_state": "input_processed_error", + "exchange_file": base64.b64encode(b"1234"), + } + record0 = self.backend.with_context(edi__skip_quick_exec=True).create_record( + "test_csv_input", vals + ) + self.assertEqual(record0.edi_exchange_state, "input_processed_error") + self.assertTrue(record0.retryable) + # get record w/ a clean context + record0 = self.backend.exchange_record_model.browse(record0.id) + record0.action_retry() + # The file has been rolled back and processed right away + self.assertEqual(record0.edi_exchange_state, "input_processed") + self.assertTrue(FakeInputProcess.check_called_for(record0)) From ac2b4817c8463cce7a512a38d495a865b13e330c Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Wed, 24 Sep 2025 07:39:52 +0200 Subject: [PATCH 2/7] [IMP] edi_component_oca: Pass logic to a new component handler --- edi_component_oca/README.rst | 127 ++++- edi_component_oca/__manifest__.py | 2 +- edi_component_oca/i18n/edi_component_oca.pot | 34 ++ edi_component_oca/models/__init__.py | 1 + edi_component_oca/models/edi_backend.py | 55 -- .../models/edi_oca_component_handler.py | 84 ++++ .../static/description/index.html | 470 ++++++++++++++++++ edi_component_oca/tests/common.py | 24 + edi_component_oca/tests/test_backend_input.py | 16 +- .../tests/test_backend_validate.py | 12 +- .../tests/test_exchange_type_encoding.py | 20 +- 11 files changed, 764 insertions(+), 81 deletions(-) create mode 100644 edi_component_oca/i18n/edi_component_oca.pot create mode 100644 edi_component_oca/models/edi_oca_component_handler.py create mode 100644 edi_component_oca/static/description/index.html diff --git a/edi_component_oca/README.rst b/edi_component_oca/README.rst index 85954498e..03ff57157 100644 --- a/edi_component_oca/README.rst +++ b/edi_component_oca/README.rst @@ -1 +1,126 @@ -TO BE GENERATED \ No newline at end of file +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +================= +Edi Connector Oca +================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:36123ef4bec6a65cf0c713c08d02e70737c74d515719d8becc269908ad9ccf08 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github + :target: https://github.com/OCA/edi-framework/tree/18.0/edi_component_oca + :alt: OCA/edi-framework +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_component_oca + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/edi-framework&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to use components to handle code to execute on EDI +Exchanges. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Component definition +-------------------- + +The component used on edi must inherit from: + +- ``edi.component.input.mixin`` for processing and implement the process + function +- ``edi.component.receive.mixin`` for reception and implement the + receive function +- ``edi.component.output.mixin`` for generation and implement the + generate function +- ``edi.component.send.mixin`` for sending and implement the send + function +- ``edi.component.check.mixin`` for checking and implement the check + function +- ``edi.component.validate.mixin`` for validation and implement the + validate function + +Also, the components may have the following elements that will be used +to use the right component: + +- ``_backend_type``: code of the backend type +- ``_exchange_type``: code of the exchange type +- ``_usage``: Automatically set by the inherited component + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* ACSONE +* Dixmit +* Camptocamp + +Contributors +------------ + +- Simone Orsi +- Enric Tobella +- Manuel Regidor +- Thien Vo +- Jordi Masvidal + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-simahawk| image:: https://github.com/simahawk.png?size=40px + :target: https://github.com/simahawk + :alt: simahawk +.. |maintainer-etobella| image:: https://github.com/etobella.png?size=40px + :target: https://github.com/etobella + :alt: etobella + +Current `maintainers `__: + +|maintainer-simahawk| |maintainer-etobella| + +This module is part of the `OCA/edi-framework `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/edi_component_oca/__manifest__.py b/edi_component_oca/__manifest__.py index 52b6062c4..ec2cb3513 100644 --- a/edi_component_oca/__manifest__.py +++ b/edi_component_oca/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Edi Connector Oca", "summary": """Allow to use Connector as a source in EDI""", - "version": "18.0.1.0.0", + "version": "18.0.1.0.1", "license": "LGPL-3", "author": "ACSONE,Dixmit,Camptocamp,Odoo Community Association (OCA)", "maintainers": ["simahawk", "etobella"], diff --git a/edi_component_oca/i18n/edi_component_oca.pot b/edi_component_oca/i18n/edi_component_oca.pot new file mode 100644 index 000000000..dc4613309 --- /dev/null +++ b/edi_component_oca/i18n/edi_component_oca.pot @@ -0,0 +1,34 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_component_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: edi_component_oca +#: model:ir.model,name:edi_component_oca.model_edi_exchange_consumer_mixin +msgid "Abstract record where exchange records can be assigned" +msgstr "" + +#. module: edi_component_oca +#: model:ir.model,name:edi_component_oca.model_edi_oca_component_handler +msgid "Component Handler for EDI" +msgstr "" + +#. module: edi_component_oca +#: model:ir.model,name:edi_component_oca.model_edi_backend +msgid "EDI Backend" +msgstr "" + +#. module: edi_component_oca +#: model:ir.model,name:edi_component_oca.model_edi_exchange_record +msgid "EDI exchange Record" +msgstr "" diff --git a/edi_component_oca/models/__init__.py b/edi_component_oca/models/__init__.py index f86bfa873..0aeb0d076 100644 --- a/edi_component_oca/models/__init__.py +++ b/edi_component_oca/models/__init__.py @@ -1,3 +1,4 @@ from . import edi_backend from . import edi_exchange_record from . import edi_exchange_consumer_mixin +from . import edi_oca_component_handler diff --git a/edi_component_oca/models/edi_backend.py b/edi_component_oca/models/edi_backend.py index 23025b063..fc2a85d68 100644 --- a/edi_component_oca/models/edi_backend.py +++ b/edi_component_oca/models/edi_backend.py @@ -6,7 +6,6 @@ from odoo import models from odoo.addons.component.exception import NoComponentError -from odoo.addons.edi_core_oca.exceptions import EDINotImplementedError _logger = logging.getLogger(__name__) @@ -15,60 +14,6 @@ class EdiBackend(models.Model): _name = "edi.backend" _inherit = ["edi.backend", "collection.base"] - def _exchange_generate(self, exchange_record, **kw): - try: - return super()._exchange_generate(exchange_record, **kw) - except EDINotImplementedError: - component = self._get_component(exchange_record, "generate") - if component: - return component.generate() - raise - - def _exchange_validate_data(self, exchange_record, value=None, **kw): - try: - return super()._exchange_validate_data(exchange_record, value=value, **kw) - except EDINotImplementedError: - component = self._get_component(exchange_record, "validate") - if component: - return component.validate(value) - raise - - def _exchange_send(self, exchange_record): - try: - return super()._exchange_send(exchange_record) - except EDINotImplementedError: - component = self._get_component(exchange_record, "send") - if component: - return component.send() - raise - - def _exchange_output_check_state(self, exchange_record): - try: - return super()._exchange_output_check_state(exchange_record) - except EDINotImplementedError: - component = self._get_component(exchange_record, "check") - if component: - return component.check() - raise - - def _exchange_process(self, exchange_record): - try: - return super()._exchange_process(exchange_record) - except EDINotImplementedError: - component = self._get_component(exchange_record, "process") - if component: - return component.process() - raise - - def _exchange_receive(self, exchange_record): - try: - return super()._exchange_receive(exchange_record) - except EDINotImplementedError: - component = self._get_component(exchange_record, "receive") - if component: - return component.receive() - raise - def _get_component(self, exchange_record, key): record_conf = self._get_component_conf_for_record(exchange_record, key) # Load additional ctx keys if any diff --git a/edi_component_oca/models/edi_oca_component_handler.py b/edi_component_oca/models/edi_oca_component_handler.py new file mode 100644 index 000000000..3d67150ba --- /dev/null +++ b/edi_component_oca/models/edi_oca_component_handler.py @@ -0,0 +1,84 @@ +# Copyright 2025 Dixmit +# @author Enric Tobella +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +# Copyright 2025 Dixmit +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +import logging + +from odoo import models + +from odoo.addons.edi_core_oca.exceptions import EDINotImplementedError + +_logger = logging.getLogger(__name__) + + +class EdiOcaHandlerGenerate(models.AbstractModel): + _name = "edi.oca.component.handler" + _inherit = [ + "edi.oca.handler.generate", + "edi.oca.handler.input.validate", + "edi.oca.handler.output.validate", + "edi.oca.handler.send", + "edi.oca.handler.receive", + "edi.oca.handler.process", + "edi.oca.handler.check", + ] + _description = "Component Handler for EDI" + + def generate(self, exchange_record): + component = exchange_record.backend_id._get_component( + exchange_record, "generate" + ) + if component: + return component.generate() + raise EDINotImplementedError("No component found to generate EDI document.") + + def input_validate(self, exchange_record, value=None, **kw): + component = exchange_record.backend_id._get_component( + exchange_record, "validate" + ) + if component: + return component.validate(value) + raise EDINotImplementedError( + "No component found to validate EDI document input." + ) + + def output_validate(self, exchange_record, value=None, **kw): + component = exchange_record.backend_id._get_component( + exchange_record, "validate" + ) + if component: + return component.validate(value) + raise EDINotImplementedError( + "No component found to validate EDI document output." + ) + + def send(self, exchange_record): + component = exchange_record.backend_id._get_component(exchange_record, "send") + if component: + return component.send() + raise EDINotImplementedError("No component found to send EDI document.") + + def receive(self, exchange_record): + component = exchange_record.backend_id._get_component( + exchange_record, "receive" + ) + if component: + return component.receive() + raise EDINotImplementedError("No component found to receive EDI document.") + + def process(self, exchange_record): + component = exchange_record.backend_id._get_component( + exchange_record, "process" + ) + if component: + return component.process() + raise EDINotImplementedError("No component found to process EDI document.") + + def check(self, exchange_record): + component = exchange_record.backend_id._get_component(exchange_record, "check") + if component: + return component.check() + raise EDINotImplementedError("No component found to check EDI document.") diff --git a/edi_component_oca/static/description/index.html b/edi_component_oca/static/description/index.html new file mode 100644 index 000000000..edd048f69 --- /dev/null +++ b/edi_component_oca/static/description/index.html @@ -0,0 +1,470 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Edi Connector Oca

+ +

Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

+

This module allows to use components to handle code to execute on EDI +Exchanges.

+

Table of contents

+ +
+

Configuration

+
+

Component definition

+

The component used on edi must inherit from:

+
    +
  • edi.component.input.mixin for processing and implement the process +function
  • +
  • edi.component.receive.mixin for reception and implement the +receive function
  • +
  • edi.component.output.mixin for generation and implement the +generate function
  • +
  • edi.component.send.mixin for sending and implement the send +function
  • +
  • edi.component.check.mixin for checking and implement the check +function
  • +
  • edi.component.validate.mixin for validation and implement the +validate function
  • +
+

Also, the components may have the following elements that will be used +to use the right component:

+
    +
  • _backend_type: code of the backend type
  • +
  • _exchange_type: code of the exchange type
  • +
  • _usage: Automatically set by the inherited component
  • +
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE
  • +
  • Dixmit
  • +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainers:

+

simahawk etobella

+

This module is part of the OCA/edi-framework project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/edi_component_oca/tests/common.py b/edi_component_oca/tests/common.py index 412a5ac3f..a5148cdf4 100644 --- a/edi_component_oca/tests/common.py +++ b/edi_component_oca/tests/common.py @@ -21,6 +21,18 @@ def setUpClass(cls): cls._setup_env() cls._setup_records() + @classmethod + def _create_exchange_type(cls, **kw): + model = cls.env.ref("edi_component_oca.model_edi_oca_component_handler") + kw.setdefault("receive_model_id", model.id) + kw.setdefault("generate_model_id", model.id) + kw.setdefault("input_validate_model_id", model.id) + kw.setdefault("output_validate_model_id", model.id) + kw.setdefault("send_model_id", model.id) + kw.setdefault("process_model_id", model.id) + kw.setdefault("check_model_id", model.id) + return super()._create_exchange_type(**kw) + @tagged("-at_install", "post_install") class EDIBackendCommonComponentRegistryTestCase( @@ -33,3 +45,15 @@ def setUpClass(cls): cls._setup_records() cls._setup_registry(cls) cls._load_module_components(cls, "edi_component_oca") + + @classmethod + def _create_exchange_type(cls, **kw): + model = cls.env.ref("edi_component_oca.model_edi_oca_component_handler") + kw.setdefault("receive_model_id", model.id) + kw.setdefault("generate_model_id", model.id) + kw.setdefault("input_validate_model_id", model.id) + kw.setdefault("output_validate_model_id", model.id) + kw.setdefault("send_model_id", model.id) + kw.setdefault("process_model_id", model.id) + kw.setdefault("check_model_id", model.id) + return super()._create_exchange_type(**kw) diff --git a/edi_component_oca/tests/test_backend_input.py b/edi_component_oca/tests/test_backend_input.py index 9bb149010..f0108e1ab 100644 --- a/edi_component_oca/tests/test_backend_input.py +++ b/edi_component_oca/tests/test_backend_input.py @@ -34,21 +34,21 @@ def setUp(self): FakeInputReceive.reset_faked() def test_receive_record_nothing_todo(self): - self.backend.with_context(fake_output="yeah!").exchange_receive(self.record) + self.backend.exchange_receive(self.record.with_context(fake_output="yeah!")) self.assertEqual(self.record._get_file_content(), "") self.assertRecordValues(self.record, [{"edi_exchange_state": "new"}]) def test_receive_record(self): self.record.edi_exchange_state = "input_pending" - self.backend.with_context(fake_output="yeah!").exchange_receive(self.record) + self.backend.exchange_receive(self.record.with_context(fake_output="yeah!")) self.assertEqual(self.record._get_file_content(), "yeah!") self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}]) def test_receive_no_allow_empty_file_record(self): self.record.edi_exchange_state = "input_pending" - self.backend.with_context( - fake_output="", _edi_receive_break_on_error=False - ).exchange_receive(self.record) + self.backend.with_context(_edi_receive_break_on_error=False).exchange_receive( + self.record.with_context(fake_output="") + ) # Check the record msg = ( "Empty files are not allowed for exchange type " @@ -64,9 +64,9 @@ def test_receive_no_allow_empty_file_record(self): def test_receive_allow_empty_file_record(self): self.record.edi_exchange_state = "input_pending" self.record.type_id.allow_empty_files_on_receive = True - self.backend.with_context( - fake_output="", _edi_receive_break_on_error=False - ).exchange_receive(self.record) + self.backend.exchange_receive( + self.record.with_context(fake_output="", _edi_receive_break_on_error=False) + ) # Check the record self.assertEqual(self.record._get_file_content(), "") self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}]) diff --git a/edi_component_oca/tests/test_backend_validate.py b/edi_component_oca/tests/test_backend_validate.py index ea7a0f836..5107e0d88 100644 --- a/edi_component_oca/tests/test_backend_validate.py +++ b/edi_component_oca/tests/test_backend_validate.py @@ -54,8 +54,8 @@ def test_receive_validate_record(self): def test_receive_validate_record_error(self): self.record_in.write({"edi_exchange_state": "input_pending"}) exc = EDIValidationError("Data seems wrong!") - self.backend.with_context(test_break_validate=exc).exchange_receive( - self.record_in + self.backend.exchange_receive( + self.record_in.with_context(test_break_validate=exc) ) self.assertTrue(FakeInputValidate.check_called_for(self.record_in)) self.assertRecordValues( @@ -80,8 +80,8 @@ def test_generate_validate_record(self): def test_generate_validate_record_error(self): self.record_out.write({"edi_exchange_state": "new"}) exc = EDIValidationError("Data seems wrong!") - self.backend.with_context(test_break_validate=exc).exchange_generate( - self.record_out + self.backend.exchange_generate( + self.record_out.with_context(test_break_validate=exc) ) self.assertTrue(FakeOutputValidate.check_called_for(self.record_out)) self.assertRecordValues( @@ -98,8 +98,8 @@ def test_generate_validate_record_error(self): def test_validate_record_error_regenerate(self): self.record_out.write({"edi_exchange_state": "new"}) exc = EDIValidationError("Data seems wrong!") - self.backend.with_context(test_break_validate=exc).exchange_generate( - self.record_out + self.backend.exchange_generate( + self.record_out.with_context(test_break_validate=exc) ) self.assertRecordValues( self.record_out, diff --git a/edi_component_oca/tests/test_exchange_type_encoding.py b/edi_component_oca/tests/test_exchange_type_encoding.py index 21a3f54dd..60af3ca08 100644 --- a/edi_component_oca/tests/test_exchange_type_encoding.py +++ b/edi_component_oca/tests/test_exchange_type_encoding.py @@ -31,8 +31,8 @@ def test_encoding_default(self): Test default output/input encoding (UTF-8). Use string with special character to test the encoding applied. """ - self.backend.with_context(fake_output="Palmotićeva").exchange_generate( - self.record + self.backend.exchange_generate( + self.record.with_context(fake_output="Palmotićeva") ) # Test decoding is applied correctly self.assertEqual(self.record._get_file_content(), "Palmotićeva") @@ -47,8 +47,8 @@ def test_encoding(self): character to test the encoding applied. """ self.exchange_type_out.write({"encoding": "UTF-16"}) - self.backend.with_context(fake_output="Palmotićeva").exchange_generate( - self.record + self.backend.exchange_generate( + self.record.with_context(fake_output="Palmotićeva") ) # Test decoding is applied correctly self.assertEqual(self.record._get_file_content(), "Palmotićeva") @@ -61,18 +61,18 @@ def test_encoding_error_handler(self): self.exchange_type_out.write({"encoding": "ascii"}) # By default, error handling raises error with self.assertRaises(UnicodeEncodeError): - self.backend.with_context(fake_output="Palmotićeva").exchange_generate( - self.record + self.backend.exchange_generate( + self.record.with_context(fake_output="Palmotićeva") ) self.exchange_type_out.write({"encoding_out_error_handler": "ignore"}) - self.backend.with_context(fake_output="Palmotićeva").exchange_generate( - self.record + self.backend.exchange_generate( + self.record.with_context(fake_output="Palmotićeva") ) self.assertEqual(self.record._get_file_content(), "Palmotieva") def test_decoding_error_handler(self): - self.backend.with_context(fake_output="Palmotićeva").exchange_generate( - self.record + self.backend.exchange_generate( + self.record.with_context(fake_output="Palmotićeva") ) # Change encoding to ascii to check the decoding self.exchange_type_out.write({"encoding": "ascii"}) From 53eb03d4031b868bc0362e6283c60bb289fd43d3 Mon Sep 17 00:00:00 2001 From: mymage Date: Mon, 6 Oct 2025 07:42:18 +0000 Subject: [PATCH 3/7] Added translation using Weblate (Italian) --- edi_component_oca/i18n/it.po | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 edi_component_oca/i18n/it.po diff --git a/edi_component_oca/i18n/it.po b/edi_component_oca/i18n/it.po new file mode 100644 index 000000000..a36207c60 --- /dev/null +++ b/edi_component_oca/i18n/it.po @@ -0,0 +1,37 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_component_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-10-14 06:34+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: edi_component_oca +#: model:ir.model,name:edi_component_oca.model_edi_exchange_consumer_mixin +msgid "Abstract record where exchange records can be assigned" +msgstr "Record astratto dove i record di scambio possono essere assegnati" + +#. module: edi_component_oca +#: model:ir.model,name:edi_component_oca.model_edi_oca_component_handler +msgid "Component Handler for EDI" +msgstr "Gestore componente per EDI" + +#. module: edi_component_oca +#: model:ir.model,name:edi_component_oca.model_edi_backend +msgid "EDI Backend" +msgstr "Backend EDI" + +#. module: edi_component_oca +#: model:ir.model,name:edi_component_oca.model_edi_exchange_record +msgid "EDI exchange Record" +msgstr "Record di scambio EDI" From 988b6aa22255f9b45b6ce7a2b5a13551717e7b13 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 1 Dec 2025 17:55:32 +0100 Subject: [PATCH 4/7] edi_component_oca: adapt tests --- edi_component_oca/README.rst | 2 +- edi_component_oca/__manifest__.py | 2 +- edi_component_oca/static/description/index.html | 2 +- edi_component_oca/tests/test_backend_output.py | 13 ++++++++----- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/edi_component_oca/README.rst b/edi_component_oca/README.rst index 03ff57157..8cd40f4f6 100644 --- a/edi_component_oca/README.rst +++ b/edi_component_oca/README.rst @@ -11,7 +11,7 @@ Edi Connector Oca !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:36123ef4bec6a65cf0c713c08d02e70737c74d515719d8becc269908ad9ccf08 + !! source digest: sha256:3bf081843ed5a121210f466d0a354fe495798e66f75d76f6958176548d647356 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/edi_component_oca/__manifest__.py b/edi_component_oca/__manifest__.py index ec2cb3513..245d468d5 100644 --- a/edi_component_oca/__manifest__.py +++ b/edi_component_oca/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Edi Connector Oca", "summary": """Allow to use Connector as a source in EDI""", - "version": "18.0.1.0.1", + "version": "18.0.1.0.2", "license": "LGPL-3", "author": "ACSONE,Dixmit,Camptocamp,Odoo Community Association (OCA)", "maintainers": ["simahawk", "etobella"], diff --git a/edi_component_oca/static/description/index.html b/edi_component_oca/static/description/index.html index edd048f69..b82405afa 100644 --- a/edi_component_oca/static/description/index.html +++ b/edi_component_oca/static/description/index.html @@ -372,7 +372,7 @@

Edi Connector Oca

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:36123ef4bec6a65cf0c713c08d02e70737c74d515719d8becc269908ad9ccf08 +!! source digest: sha256:3bf081843ed5a121210f466d0a354fe495798e66f75d76f6958176548d647356 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

This module allows to use components to handle code to execute on EDI diff --git a/edi_component_oca/tests/test_backend_output.py b/edi_component_oca/tests/test_backend_output.py index 00a9f26cc..b93f68219 100644 --- a/edi_component_oca/tests/test_backend_output.py +++ b/edi_component_oca/tests/test_backend_output.py @@ -52,11 +52,14 @@ def test_send_record(self): self.assertFalse(self.record.exchanged_on) with freeze_time("2020-10-21 10:00:00"): self.record.action_exchange_send() - self.assertTrue(FakeOutputSender.check_called_for(self.record)) - self.assertRecordValues(self.record, [{"edi_exchange_state": "output_sent"}]) - self.assertEqual( - fields.Datetime.to_string(self.record.exchanged_on), "2020-10-21 10:00:00" - ) + self.assertTrue(FakeOutputSender.check_called_for(self.record)) + self.assertRecordValues( + self.record, [{"edi_exchange_state": "output_sent"}] + ) + self.assertEqual( + fields.Datetime.to_string(self.record.exchanged_on), + "2020-10-21 10:00:00", + ) def test_send_record_with_error(self): self.record.write({"edi_exchange_state": "output_pending"}) From 639d7fc1f88a3a7622374bde12b42eaffb52d623 Mon Sep 17 00:00:00 2001 From: Ricardoalso Date: Fri, 20 Feb 2026 15:24:21 +0100 Subject: [PATCH 5/7] [FIX] edi_*_oca: New way for FakeModelLoader --- edi_component_oca/README.rst | 2 +- edi_component_oca/__manifest__.py | 2 +- .../static/description/index.html | 2 +- .../tests/test_edi_configuration.py | 70 +++++++++---------- 4 files changed, 37 insertions(+), 39 deletions(-) diff --git a/edi_component_oca/README.rst b/edi_component_oca/README.rst index 8cd40f4f6..db2cffdd7 100644 --- a/edi_component_oca/README.rst +++ b/edi_component_oca/README.rst @@ -11,7 +11,7 @@ Edi Connector Oca !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:3bf081843ed5a121210f466d0a354fe495798e66f75d76f6958176548d647356 + !! source digest: sha256:81c11c0d670f363513d25e5d2d6cb038f1fc56580f20c837c2d2a7665798018d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/edi_component_oca/__manifest__.py b/edi_component_oca/__manifest__.py index 245d468d5..fa8111718 100644 --- a/edi_component_oca/__manifest__.py +++ b/edi_component_oca/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Edi Connector Oca", "summary": """Allow to use Connector as a source in EDI""", - "version": "18.0.1.0.2", + "version": "18.0.1.0.3", "license": "LGPL-3", "author": "ACSONE,Dixmit,Camptocamp,Odoo Community Association (OCA)", "maintainers": ["simahawk", "etobella"], diff --git a/edi_component_oca/static/description/index.html b/edi_component_oca/static/description/index.html index b82405afa..e6128b6fa 100644 --- a/edi_component_oca/static/description/index.html +++ b/edi_component_oca/static/description/index.html @@ -372,7 +372,7 @@

Edi Connector Oca

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:3bf081843ed5a121210f466d0a354fe495798e66f75d76f6958176548d647356 +!! source digest: sha256:81c11c0d670f363513d25e5d2d6cb038f1fc56580f20c837c2d2a7665798018d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

This module allows to use components to handle code to execute on EDI diff --git a/edi_component_oca/tests/test_edi_configuration.py b/edi_component_oca/tests/test_edi_configuration.py index 509b3e2ab..acb9267d9 100644 --- a/edi_component_oca/tests/test_edi_configuration.py +++ b/edi_component_oca/tests/test_edi_configuration.py @@ -40,66 +40,64 @@ def setUpClass(cls): def setUp(self): super().setUp() - FakeOutputGenerator.reset_faked() - FakeOutputSender.reset_faked() - FakeOutputChecker.reset_faked() - self.consumer_record = self.env["edi.exchange.consumer.test"].create( - { - "name": "Test Consumer", - "edi_config_ids": [ - (4, self.create_config.id), - (4, self.write_config.id), - ], - } - ) - - @classmethod - def _setup_records(cls): # pylint:disable=missing-return - super()._setup_records() - # Load fake models ->/ - cls.loader = FakeModelLoader(cls.env, cls.__module__) - cls.loader.backup_registry() + self.loader = FakeModelLoader(self.env, self.__module__) + self.loader.backup_registry() from odoo.addons.edi_core_oca.tests.fake_models import EdiExchangeConsumerTest EdiExchangeConsumerTest._edi_config_field_relation = lambda self: self.env[ "edi.configuration" ] - # We need to override it, as we want to test the usage with components + self.loader.update_registry((EdiExchangeConsumerTest,)) - cls.loader.update_registry((EdiExchangeConsumerTest,)) - cls.exchange_type_out.exchange_filename_pattern = "{record.id}" - cls.edi_configuration = cls.env["edi.configuration"] - cls.create_config = cls.edi_configuration.create( + FakeOutputGenerator.reset_faked() + FakeOutputSender.reset_faked() + FakeOutputChecker.reset_faked() + + self.create_config = self.env["edi.configuration"].create( { "name": "Create Config", "active": True, - "backend_id": cls.backend.id, - "type_id": cls.exchange_type_out.id, - "trigger_id": cls.env.ref( + "backend_id": self.backend.id, + "type_id": self.exchange_type_out.id, + "trigger_id": self.env.ref( "edi_core_oca.edi_conf_trigger_record_create" ).id, - "model_id": cls.env["ir.model"]._get_id("edi.exchange.consumer.test"), + "model_id": self.env["ir.model"]._get_id("edi.exchange.consumer.test"), "snippet_do": "record._edi_send_via_edi(conf.type_id)", } ) - cls.write_config = cls.edi_configuration.create( + self.write_config = self.env["edi.configuration"].create( { "name": "Write Config 1", "active": True, - "backend_id": cls.backend.id, - "type_id": cls.exchange_type_out.id, - "trigger_id": cls.env.ref( + "backend_id": self.backend.id, + "type_id": self.exchange_type_out.id, + "trigger_id": self.env.ref( "edi_core_oca.edi_conf_trigger_record_write" ).id, - "model_id": cls.env["ir.model"]._get_id("edi.exchange.consumer.test"), + "model_id": self.env["ir.model"]._get_id("edi.exchange.consumer.test"), "snippet_do": "record._edi_send_via_edi(conf.type_id)", } ) + self.consumer_record = self.env["edi.exchange.consumer.test"].create( + { + "name": "Test Consumer", + "edi_config_ids": [ + (4, self.create_config.id), + (4, self.write_config.id), + ], + } + ) + @classmethod - def tearDownClass(cls): - cls.loader.restore_registry() - super().tearDownClass() + def _setup_records(cls): # pylint:disable=missing-return + super()._setup_records() + cls.exchange_type_out.exchange_filename_pattern = "{record.id}" + + def tearDown(self): + self.loader.restore_registry() + super().tearDown() def test_edi_send_via_edi_config(self): # Check configuration on create From 9e40ebd5e44441023f906a5e3b5be1a3932b7511 Mon Sep 17 00:00:00 2001 From: AaronHForgeFlow Date: Tue, 20 Jan 2026 10:26:52 +0100 Subject: [PATCH 6/7] [IMP] edi_component_oca: pre-commit auto fixes --- edi_component_oca/tests/test_backend_base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/edi_component_oca/tests/test_backend_base.py b/edi_component_oca/tests/test_backend_base.py index d3b5ac40b..3a3c18610 100644 --- a/edi_component_oca/tests/test_backend_base.py +++ b/edi_component_oca/tests/test_backend_base.py @@ -19,8 +19,7 @@ def test_create_record(self): expected = { "type_id": self.exchange_type_in.id, "edi_exchange_state": "new", - "exchange_filename": "EDI_EXC_TEST-test_csv_" - "input-2020-10-21-10-00-00.csv", + "exchange_filename": "EDI_EXC_TEST-test_csv_input-2020-10-21-10-00-00.csv", } self.assertRecordValues(record, [expected]) self.assertEqual(record.record, self.partner) From 903e94a712280def86f469a86a4efbbfee6d1c22 Mon Sep 17 00:00:00 2001 From: AaronHForgeFlow Date: Tue, 20 Jan 2026 10:27:51 +0100 Subject: [PATCH 7/7] [MIG] edi_component_oca: Migration to 19.0 --- edi_component_oca/README.rst | 10 ++++----- edi_component_oca/__manifest__.py | 2 +- .../static/description/index.html | 6 ++--- edi_component_oca/tests/common.py | 22 +++++++++++++++---- edi_component_oca/tests/test_backend_base.py | 6 +++++ .../tests/test_backend_output.py | 8 +++---- .../tests/test_backend_process.py | 4 ++-- .../tests/test_edi_backend_cron.py | 4 ++-- .../tests/test_edi_configuration.py | 20 +++-------------- edi_component_oca/tests/test_quick_exec.py | 2 -- 10 files changed, 44 insertions(+), 40 deletions(-) diff --git a/edi_component_oca/README.rst b/edi_component_oca/README.rst index db2cffdd7..4af284595 100644 --- a/edi_component_oca/README.rst +++ b/edi_component_oca/README.rst @@ -21,13 +21,13 @@ Edi Connector Oca :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github - :target: https://github.com/OCA/edi-framework/tree/18.0/edi_component_oca + :target: https://github.com/OCA/edi-framework/tree/19.0/edi_component_oca :alt: OCA/edi-framework .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_component_oca + :target: https://translation.odoo-community.org/projects/edi-framework-19-0/edi-framework-19-0-edi_component_oca :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/edi-framework&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/edi-framework&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -74,7 +74,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -121,6 +121,6 @@ Current `maintainers `__: |maintainer-simahawk| |maintainer-etobella| -This module is part of the `OCA/edi-framework `_ project on GitHub. +This module is part of the `OCA/edi-framework `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/edi_component_oca/__manifest__.py b/edi_component_oca/__manifest__.py index fa8111718..e3ba0c0f4 100644 --- a/edi_component_oca/__manifest__.py +++ b/edi_component_oca/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Edi Connector Oca", "summary": """Allow to use Connector as a source in EDI""", - "version": "18.0.1.0.3", + "version": "19.0.1.0.0", "license": "LGPL-3", "author": "ACSONE,Dixmit,Camptocamp,Odoo Community Association (OCA)", "maintainers": ["simahawk", "etobella"], diff --git a/edi_component_oca/static/description/index.html b/edi_component_oca/static/description/index.html index e6128b6fa..3eef4e3f9 100644 --- a/edi_component_oca/static/description/index.html +++ b/edi_component_oca/static/description/index.html @@ -374,7 +374,7 @@

Edi Connector Oca

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:81c11c0d670f363513d25e5d2d6cb038f1fc56580f20c837c2d2a7665798018d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

+

Beta License: LGPL-3 OCA/edi-framework Translate me on Weblate Try me on Runboat

This module allows to use components to handle code to execute on EDI Exchanges.

Table of contents

@@ -426,7 +426,7 @@

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -460,7 +460,7 @@

Maintainers

promote its widespread use.

Current maintainers:

simahawk etobella

-

This module is part of the OCA/edi-framework project on GitHub.

+

This module is part of the OCA/edi-framework project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/edi_component_oca/tests/common.py b/edi_component_oca/tests/common.py index a5148cdf4..4690f8bae 100644 --- a/edi_component_oca/tests/common.py +++ b/edi_component_oca/tests/common.py @@ -2,13 +2,12 @@ # Copyright 2020 Dixmit # @author: Simone Orsi # 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 tagged from odoo.addons.component.tests.common import ( + ComponentRegistryCase, TransactionComponentCase, - TransactionComponentRegistryCase, ) from odoo.addons.edi_core_oca.tests.common import EDIBackendTestMixin @@ -36,7 +35,7 @@ def _create_exchange_type(cls, **kw): @tagged("-at_install", "post_install") class EDIBackendCommonComponentRegistryTestCase( - TransactionComponentRegistryCase, EDIBackendTestMixin + ComponentRegistryCase, EDIBackendTestMixin ): @classmethod def setUpClass(cls): @@ -44,6 +43,21 @@ def setUpClass(cls): cls._setup_env() cls._setup_records() cls._setup_registry(cls) + # Load fake models ->/ + from odoo.addons.edi_core_oca.tests.fake_models import EdiExchangeConsumerTest + + EdiExchangeConsumerTest._edi_config_field_relation = lambda self: self.env[ + "edi.configuration" + ] + # We need to override it, as we want to test the usage with components + + add_to_registry(cls.registry, EdiExchangeConsumerTest) + cls.registry._setup_models__(cls.env.cr, ["edi.exchange.consumer.test"]) + cls.registry.init_models( + cls.env.cr, ["edi.exchange.consumer.test"], {"models_to_check": True} + ) + cls.addClassCleanup(cls.registry.__delitem__, "edi.exchange.consumer.test") + # Load the EDI component module components into the isolated test registry cls._load_module_components(cls, "edi_component_oca") @classmethod diff --git a/edi_component_oca/tests/test_backend_base.py b/edi_component_oca/tests/test_backend_base.py index 3a3c18610..3d0b2bd9a 100644 --- a/edi_component_oca/tests/test_backend_base.py +++ b/edi_component_oca/tests/test_backend_base.py @@ -16,6 +16,12 @@ def test_create_record(self): "res_id": self.partner.id, } record = self.backend.create_record("test_csv_input", vals) + # Force date pattern and timezone on advanced settings + self.exchange_type_in.advanced_settings_edit = """ + filename_pattern: + force_tz: UTC + date_pattern: '%Y-%m-%d-%H-%M-%S' + """ expected = { "type_id": self.exchange_type_in.id, "edi_exchange_state": "new", diff --git a/edi_component_oca/tests/test_backend_output.py b/edi_component_oca/tests/test_backend_output.py index b93f68219..034b51484 100644 --- a/edi_component_oca/tests/test_backend_output.py +++ b/edi_component_oca/tests/test_backend_output.py @@ -48,7 +48,7 @@ def test_generate_record_output_pdf(self): def test_send_record(self): self.record.write({"edi_exchange_state": "output_pending"}) - self.record._set_file_content("TEST %d" % self.record.id) + self.record._set_file_content(f"TEST {self.record.id}") self.assertFalse(self.record.exchanged_on) with freeze_time("2020-10-21 10:00:00"): self.record.action_exchange_send() @@ -63,7 +63,7 @@ def test_send_record(self): def test_send_record_with_error(self): self.record.write({"edi_exchange_state": "output_pending"}) - self.record._set_file_content("TEST %d" % self.record.id) + self.record._set_file_content(f"TEST {self.record.id}") self.assertFalse(self.record.exchanged_on) self.record.with_context( test_break_send="OOPS! Something went wrong :(" @@ -94,7 +94,7 @@ def test_send_invalid_direction(self): record.action_exchange_send() self.assertEqual( err.exception.args[0], - "Record ID=%d is not meant to be sent!" % record.id, + f"Record ID={record.id} is not meant to be sent!", ) mocked.assert_not_called() @@ -109,6 +109,6 @@ def test_send_not_generated_record(self): with self.assertRaises(UserError) as err: record.action_exchange_send() self.assertEqual( - err.exception.args[0], "Record ID=%d has no file to send!" % record.id + err.exception.args[0], f"Record ID={record.id} has no file to send!" ) mocked.assert_not_called() diff --git a/edi_component_oca/tests/test_backend_process.py b/edi_component_oca/tests/test_backend_process.py index b2e8fa057..72e8411ba 100644 --- a/edi_component_oca/tests/test_backend_process.py +++ b/edi_component_oca/tests/test_backend_process.py @@ -48,7 +48,7 @@ def test_process_record(self): def test_process_record_with_error(self): self.record.write({"edi_exchange_state": "input_received"}) - self.record._set_file_content("TEST %d" % self.record.id) + self.record._set_file_content(f"TEST {self.record.id}") self.record.with_context( test_break_process="OOPS! Something went wrong :(" ).action_exchange_process() @@ -88,7 +88,7 @@ def test_process_outbound_record(self): "res_id": self.partner.id, } record = self.backend.create_record("test_csv_output", vals) - record._set_file_content("TEST %d" % record.id) + record._set_file_content(f"TEST {record.id}") with self.assertRaises(UserError): record.action_exchange_process() diff --git a/edi_component_oca/tests/test_edi_backend_cron.py b/edi_component_oca/tests/test_edi_backend_cron.py index f28c41934..8d1ab2eea 100644 --- a/edi_component_oca/tests/test_edi_backend_cron.py +++ b/edi_component_oca/tests/test_edi_backend_cron.py @@ -21,8 +21,8 @@ def setUpClass(cls): cls._build_components( cls, FakeOutputGenerator, FakeOutputSender, FakeOutputChecker ) - cls.partner2 = cls.env.ref("base.res_partner_10") - cls.partner3 = cls.env.ref("base.res_partner_12") + cls.partner2 = cls.env["res.partner"].create({"name": "EDI Test Partner 2"}) + cls.partner3 = cls.env["res.partner"].create({"name": "EDI Test Partner 3"}) cls.record1 = cls.backend.create_record( "test_csv_output", {"model": cls.partner._name, "res_id": cls.partner.id} ) diff --git a/edi_component_oca/tests/test_edi_configuration.py b/edi_component_oca/tests/test_edi_configuration.py index acb9267d9..e35968528 100644 --- a/edi_component_oca/tests/test_edi_configuration.py +++ b/edi_component_oca/tests/test_edi_configuration.py @@ -4,8 +4,7 @@ import os import unittest -from odoo_test_helper import FakeModelLoader - +from odoo import Command from odoo.tests.common import tagged from .common import EDIBackendCommonComponentRegistryTestCase @@ -40,15 +39,6 @@ def setUpClass(cls): def setUp(self): super().setUp() - self.loader = FakeModelLoader(self.env, self.__module__) - self.loader.backup_registry() - from odoo.addons.edi_core_oca.tests.fake_models import EdiExchangeConsumerTest - - EdiExchangeConsumerTest._edi_config_field_relation = lambda self: self.env[ - "edi.configuration" - ] - self.loader.update_registry((EdiExchangeConsumerTest,)) - FakeOutputGenerator.reset_faked() FakeOutputSender.reset_faked() FakeOutputChecker.reset_faked() @@ -84,8 +74,8 @@ def setUp(self): { "name": "Test Consumer", "edi_config_ids": [ - (4, self.create_config.id), - (4, self.write_config.id), + Command.link(self.create_config.id), + Command.link(self.write_config.id), ], } ) @@ -95,10 +85,6 @@ def _setup_records(cls): # pylint:disable=missing-return super()._setup_records() cls.exchange_type_out.exchange_filename_pattern = "{record.id}" - def tearDown(self): - self.loader.restore_registry() - super().tearDown() - def test_edi_send_via_edi_config(self): # Check configuration on create self.consumer_record.invalidate_recordset() diff --git a/edi_component_oca/tests/test_quick_exec.py b/edi_component_oca/tests/test_quick_exec.py index 4e6a22101..2e7f5d6a6 100644 --- a/edi_component_oca/tests/test_quick_exec.py +++ b/edi_component_oca/tests/test_quick_exec.py @@ -33,8 +33,6 @@ def setUpClass(cls): FakeOutputChecker, FakeInputProcess, ) - cls.partner2 = cls.env.ref("base.res_partner_10") - cls.partner3 = cls.env.ref("base.res_partner_12") def setUp(self): super().setUp()