diff --git a/edi_storage_component_oca/README.rst b/edi_storage_component_oca/README.rst new file mode 100644 index 000000000..393f7bdb2 --- /dev/null +++ b/edi_storage_component_oca/README.rst @@ -0,0 +1,80 @@ +========================= +EDI Storage Component OCA +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:929f63d432944d358bc315e8419381157ce9ed930aa5c68995ae6f66c85feea6 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-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_storage_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_storage_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| + +Glue module between edi_storage_oca and edi_component_oca. + +It implements the EDI processing listener using the OCA component +framework, allowing custom behaviors to be executed when EDI records are +processed. + +**Table of contents** + +.. contents:: + :local: + +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 +------- + +* ForgeFlow + +Contributors +------------ + +- Arnau Cruz + +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. + +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_storage_component_oca/__init__.py b/edi_storage_component_oca/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/edi_storage_component_oca/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/edi_storage_component_oca/__manifest__.py b/edi_storage_component_oca/__manifest__.py new file mode 100644 index 000000000..ff2594e6b --- /dev/null +++ b/edi_storage_component_oca/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + "name": "EDI Storage Component OCA", + "summary": """ + Glue module between edi_storage_oca and edi_component_oca. + """, + "version": "18.0.1.0.0", + "development_status": "Beta", + "license": "LGPL-3", + "website": "https://github.com/OCA/edi-framework", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "depends": ["edi_storage_oca", "edi_component_oca"], + "data": [], +} diff --git a/edi_storage_component_oca/models/__init__.py b/edi_storage_component_oca/models/__init__.py new file mode 100644 index 000000000..324a67a5f --- /dev/null +++ b/edi_storage_component_oca/models/__init__.py @@ -0,0 +1 @@ +from . import edi_event_listener diff --git a/edi_storage_component_oca/models/edi_event_listener.py b/edi_storage_component_oca/models/edi_event_listener.py new file mode 100644 index 000000000..c0e6c4673 --- /dev/null +++ b/edi_storage_component_oca/models/edi_event_listener.py @@ -0,0 +1,78 @@ +# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import functools +import os +from pathlib import PurePath + +from odoo.addons.component.core import Component +from odoo.addons.edi_storage_oca import utils + + +class EdiStorageListener(Component): + _name = "edi.storage.component.listener" + _inherit = "base.event.listener" + + def _move_file(self, storage, from_dir_str, to_dir_str, filename): + from_dir = PurePath(from_dir_str) + to_dir = PurePath(to_dir_str) + # - storage.list_files now includes path in fs_storage, breaking change + # - we remove path + files = utils.list_files(storage, from_dir.as_posix()) + files = [os.path.basename(f) for f in files] + if filename not in files: + return False + self._add_post_commit_hook( + utils.move_files, + storage, + [(from_dir / filename).as_posix()], + to_dir.as_posix(), + ) + return True + + def _add_post_commit_hook( + self, move_func, storage, sftp_filepath, sftp_destination_path + ): + """Add hook after commit to move the file when transaction is over.""" + self.env.cr.postcommit.add( + functools.partial(move_func, storage, sftp_filepath, sftp_destination_path) + ) + + def on_edi_exchange_done(self, record): + storage = record.storage_id + res = False + if record.direction == "input" and storage: + file = record.exchange_filename + pending_dir = record.type_id._storage_fullpath( + record.backend_id.input_dir_pending + ).as_posix() + done_dir = record.type_id._storage_fullpath( + record.backend_id.input_dir_done + ).as_posix() + error_dir = record.type_id._storage_fullpath( + record.backend_id.input_dir_error + ).as_posix() + if not done_dir: + return res + res = self._move_file(storage, pending_dir, done_dir, file) + if not res: + # If a file previously failed it should have been previously + # moved to the error dir, therefore it is not present in the + # pending dir and we need to retry from error dir. + res = self._move_file(storage, error_dir, done_dir, file) + return res + + def on_edi_exchange_error(self, record): + storage = record.storage_id + res = False + if record.direction == "input" and storage: + file = record.exchange_filename + pending_dir = record.type_id._storage_fullpath( + record.backend_id.input_dir_pending + ).as_posix() + error_dir = record.type_id._storage_fullpath( + record.backend_id.input_dir_error + ).as_posix() + if error_dir: + res = self._move_file(storage, pending_dir, error_dir, file) + return res diff --git a/edi_storage_component_oca/pyproject.toml b/edi_storage_component_oca/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/edi_storage_component_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/edi_storage_component_oca/readme/CONTRIBUTORS.md b/edi_storage_component_oca/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..05cf88689 --- /dev/null +++ b/edi_storage_component_oca/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Arnau Cruz \ diff --git a/edi_storage_component_oca/readme/DESCRIPTION.md b/edi_storage_component_oca/readme/DESCRIPTION.md new file mode 100644 index 000000000..362a22f22 --- /dev/null +++ b/edi_storage_component_oca/readme/DESCRIPTION.md @@ -0,0 +1,4 @@ +Glue module between edi_storage_oca and edi_component_oca. + +It implements the EDI processing listener using the OCA component framework, +allowing custom behaviors to be executed when EDI records are processed. diff --git a/edi_storage_component_oca/static/description/index.html b/edi_storage_component_oca/static/description/index.html new file mode 100644 index 000000000..ce58a02f5 --- /dev/null +++ b/edi_storage_component_oca/static/description/index.html @@ -0,0 +1,426 @@ + + + + + +EDI Storage Component OCA + + + +
+

EDI Storage Component OCA

+ + +

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

+

Glue module between edi_storage_oca and edi_component_oca.

+

It implements the EDI processing listener using the OCA component +framework, allowing custom behaviors to be executed when EDI records are +processed.

+

Table of contents

+ +
+

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

+
    +
  • ForgeFlow
  • +
+
+ +
+

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.

+

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_storage_component_oca/tests/__init__.py b/edi_storage_component_oca/tests/__init__.py new file mode 100644 index 000000000..8b2e9384e --- /dev/null +++ b/edi_storage_component_oca/tests/__init__.py @@ -0,0 +1 @@ +from . import test_edi_event_listenner diff --git a/edi_storage_component_oca/tests/test_edi_event_listenner.py b/edi_storage_component_oca/tests/test_edi_event_listenner.py new file mode 100644 index 000000000..9b3a664fb --- /dev/null +++ b/edi_storage_component_oca/tests/test_edi_event_listenner.py @@ -0,0 +1,83 @@ +# Copyright 2026 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import base64 +from unittest import mock + +from odoo.addons.edi_component_oca.tests.common import ( + EDIBackendCommonComponentRegistryTestCase, +) +from odoo.addons.edi_component_oca.tests.fake_components import FakeInputProcess + +LISTENER_MOCK_PATH = ( + "odoo.addons.edi_storage_component_oca.models." + "edi_event_listener.EdiStorageListener" +) + + +class EDIBackendTestCase(EDIBackendCommonComponentRegistryTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._load_module_components(cls, "edi_storage_component_oca") + cls._build_components( + 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) + cls.fake_move_args = None + + @classmethod + def _get_backend(cls): + return cls.env.ref("edi_storage_oca.demo_edi_backend_storage") + + def setUp(self): + super().setUp() + FakeInputProcess.reset_faked() + + def _move_file_mocked(self, *args): + self.fake_move_args = [*args] + if not all([*args]): + return False + return True + + def _mock_listener_move_file(self): + return mock.patch(LISTENER_MOCK_PATH + "._move_file", self._move_file_mocked) + + def test_01_process_record_success(self): + with self._mock_listener_move_file(): + self.record.write( + { + "edi_exchange_state": "input_received", + "storage_id": self.backend.storage_id.id, + } + ) + self.record.action_exchange_process() + storage, from_dir_str, to_dir_str, filename = self.fake_move_args + self.assertEqual(storage, self.backend.storage_id) + self.assertEqual(from_dir_str, self.backend.input_dir_pending) + self.assertEqual(to_dir_str, self.backend.input_dir_done) + self.assertEqual(filename, self.record.exchange_filename) + + def test_02_process_record_with_error(self): + with self._mock_listener_move_file(): + self.record.write( + { + "edi_exchange_state": "input_received", + "storage_id": self.backend.storage_id.id, + } + ) + self.record._set_file_content("TEST %d" % self.record.id) + self.record.with_context( + test_break_process="OOPS! Something went wrong :(" + ).action_exchange_process() + storage, from_dir_str, to_dir_str, filename = self.fake_move_args + self.assertEqual(storage, self.backend.storage_id) + self.assertEqual(from_dir_str, self.backend.input_dir_pending) + self.assertEqual(to_dir_str, self.backend.input_dir_error) + self.assertEqual(filename, self.record.exchange_filename)