From 485e55eec097cf9e60bf78ac818c688cfe4e7b46 Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Fri, 29 Aug 2025 19:18:20 +0200 Subject: [PATCH 01/17] [ADD] edi_queue_oca: adds all functionality that integrates queue and edi --- edi_queue_oca/README.rst | 1 + edi_queue_oca/__init__.py | 1 + edi_queue_oca/__manifest__.py | 20 +++ edi_queue_oca/data/job_channel.xml | 10 ++ edi_queue_oca/data/job_function.xml | 48 ++++++ edi_queue_oca/models/__init__.py | 3 + edi_queue_oca/models/edi_backend.py | 22 +++ edi_queue_oca/models/edi_exchange_record.py | 93 ++++++++++ edi_queue_oca/models/edi_exchange_type.py | 14 ++ edi_queue_oca/pyproject.toml | 3 + edi_queue_oca/readme/CONTRIBUTORS.md | 5 + edi_queue_oca/readme/DESCRIPTION.md | 3 + edi_queue_oca/security/ir_model_access.xml | 13 ++ edi_queue_oca/static/description/icon.png | Bin 0 -> 9455 bytes edi_queue_oca/tests/__init__.py | 14 ++ edi_queue_oca/tests/test_backend_jobs.py | 159 ++++++++++++++++++ .../tests/test_backend_output_jobs.py | 56 ++++++ edi_queue_oca/tests/test_record.py | 37 ++++ edi_queue_oca/utils.py | 16 ++ edi_queue_oca/views/edi_exchange_record.xml | 28 +++ edi_queue_oca/views/edi_exchange_type.xml | 15 ++ 21 files changed, 561 insertions(+) create mode 100644 edi_queue_oca/README.rst create mode 100644 edi_queue_oca/__init__.py create mode 100644 edi_queue_oca/__manifest__.py create mode 100644 edi_queue_oca/data/job_channel.xml create mode 100644 edi_queue_oca/data/job_function.xml create mode 100644 edi_queue_oca/models/__init__.py create mode 100644 edi_queue_oca/models/edi_backend.py create mode 100644 edi_queue_oca/models/edi_exchange_record.py create mode 100644 edi_queue_oca/models/edi_exchange_type.py create mode 100644 edi_queue_oca/pyproject.toml create mode 100644 edi_queue_oca/readme/CONTRIBUTORS.md create mode 100644 edi_queue_oca/readme/DESCRIPTION.md create mode 100644 edi_queue_oca/security/ir_model_access.xml create mode 100644 edi_queue_oca/static/description/icon.png create mode 100644 edi_queue_oca/tests/__init__.py create mode 100644 edi_queue_oca/tests/test_backend_jobs.py create mode 100644 edi_queue_oca/tests/test_backend_output_jobs.py create mode 100644 edi_queue_oca/tests/test_record.py create mode 100644 edi_queue_oca/utils.py create mode 100644 edi_queue_oca/views/edi_exchange_record.xml create mode 100644 edi_queue_oca/views/edi_exchange_type.xml diff --git a/edi_queue_oca/README.rst b/edi_queue_oca/README.rst new file mode 100644 index 000000000..85954498e --- /dev/null +++ b/edi_queue_oca/README.rst @@ -0,0 +1 @@ +TO BE GENERATED \ No newline at end of file diff --git a/edi_queue_oca/__init__.py b/edi_queue_oca/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/edi_queue_oca/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/edi_queue_oca/__manifest__.py b/edi_queue_oca/__manifest__.py new file mode 100644 index 000000000..cb9cc7e73 --- /dev/null +++ b/edi_queue_oca/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2025 Dixmit +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +{ + "name": "Edi Queue Oca", + "summary": """Set Queue Jobs on EDI""", + "version": "18.0.1.0.0", + "license": "LGPL-3", + "author": "Dixmit,Camptocamp,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/edi-framework", + "depends": ["edi_core_oca", "queue_job"], + "data": [ + "views/edi_exchange_type.xml", + "security/ir_model_access.xml", + "data/job_channel.xml", + "data/job_function.xml", + "views/edi_exchange_record.xml", + ], + "demo": [], +} diff --git a/edi_queue_oca/data/job_channel.xml b/edi_queue_oca/data/job_channel.xml new file mode 100644 index 000000000..7718ea76f --- /dev/null +++ b/edi_queue_oca/data/job_channel.xml @@ -0,0 +1,10 @@ + + + edi + + + + edi_exchange + + + diff --git a/edi_queue_oca/data/job_function.xml b/edi_queue_oca/data/job_function.xml new file mode 100644 index 000000000..aedc76dec --- /dev/null +++ b/edi_queue_oca/data/job_function.xml @@ -0,0 +1,48 @@ + + + + action_exchange_generate + + + + + action_exchange_send + + + + + action_exchange_receive + + + + + action_exchange_process + + + + + exchange_create_ack_record + + + + + + exchange_generate + + + + + exchange_send + + + + + exchange_receive + + + + + exchange_process + + + diff --git a/edi_queue_oca/models/__init__.py b/edi_queue_oca/models/__init__.py new file mode 100644 index 000000000..e1a8e889b --- /dev/null +++ b/edi_queue_oca/models/__init__.py @@ -0,0 +1,3 @@ +from . import edi_exchange_record +from . import edi_exchange_type +from . import edi_backend diff --git a/edi_queue_oca/models/edi_backend.py b/edi_queue_oca/models/edi_backend.py new file mode 100644 index 000000000..b06cda1b7 --- /dev/null +++ b/edi_queue_oca/models/edi_backend.py @@ -0,0 +1,22 @@ +# Copyright 2020 ACSONE SA +# Copyright 2021 Camptocamp +# Copyright 2025 Dixmit +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + + +from odoo import models + +from odoo.addons.queue_job.exception import RetryableJobError + + +class EDIBackend(models.Model): + _inherit = "edi.backend" + + def _send_retryable_exceptions(self): + # IOError is a base class for all connection errors + # OSError is a base class for all errors + # when dealing w/ internal or external systems or filesystems + return (IOError, OSError) + + def _retryable_exception(self): + return RetryableJobError diff --git a/edi_queue_oca/models/edi_exchange_record.py b/edi_queue_oca/models/edi_exchange_record.py new file mode 100644 index 000000000..58915fa5a --- /dev/null +++ b/edi_queue_oca/models/edi_exchange_record.py @@ -0,0 +1,93 @@ +# Copyright 2021 Camptocamp SA +# Copyright 2025 Dixmit +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from ast import literal_eval + +from odoo import fields, models + +from ..utils import exchange_record_job_identity_exact + + +class EdiExchangeRecord(models.Model): + _inherit = "edi.exchange.record" + + related_queue_jobs_count = fields.Integer( + compute="_compute_related_queue_jobs_count" + ) + + def _register_hook(self): + for function in [ + "action_exchange_send", + "action_exchange_receive", + "action_exchange_process", + "action_exchange_generate", + ]: + self._patch_method(function, self._patch_job_auto_delay(function)) + return super()._register_hook() + + def action_exchange_send_job_options(self): + return {"priority": 0} + + def _job_delay_params(self): + params = {} + exchange_type = self.type_id.sudo() + channel = exchange_type.job_channel_id + if channel: + params["channel"] = channel.complete_name + priority = exchange_type.job_priority + if priority: + params["priority"] = priority + # Avoid generating the same job for the same record if existing + params["identity_key"] = exchange_record_job_identity_exact + return params + + def with_delay(self, **kw): + params = self._job_delay_params() + params.update(kw) + return super().with_delay(**params) + + def delayable(self, **kw): + params = self._job_delay_params() + params.update(kw) + return super().delayable(**params) + + def _job_retry_params(self): + return {} + + def _compute_related_queue_jobs_count(self): + for rec in self: + # TODO: We should refactor the object field on queue_job to use jsonb field + # so that we can search directly into it. + rec.related_queue_jobs_count = rec.env["queue.job"].search_count( + [("func_string", "like", str(rec))] + ) + + def action_view_related_queue_jobs(self): + self.ensure_one() + xmlid = "queue_job.action_queue_job" + action = self.env["ir.actions.act_window"]._for_xml_id(xmlid) + # Searching based on task name: + # Ex: `edi.exchange.record(1,).action_exchange_send()` + # TODO: We should refactor the object field on queue_job to use jsonb field + # so that we can search directly into it. + action["domain"] = [("func_string", "like", str(self))] + # Purge default search filters from ctx to avoid hiding records + ctx = action.get("context", {}) + if isinstance(ctx, str): + ctx = literal_eval(ctx) + # Update the current contexts + ctx.update(self.env.context) + action["context"] = { + k: v for k, v in ctx.items() if not k.startswith("search_default_") + } + # Drop ID otherwise the context will be loaded from the action's record + action.pop("id") + return action + + def action_exchange_generate_send_chained(self): + job1 = self.delayable().action_exchange_generate() + # Chain send job. + # Raise prio to max to send the record out as fast as possible. + job1.on_done(self.delayable(priority=0).action_exchange_send()) + job1.delay() diff --git a/edi_queue_oca/models/edi_exchange_type.py b/edi_queue_oca/models/edi_exchange_type.py new file mode 100644 index 000000000..407279f66 --- /dev/null +++ b/edi_queue_oca/models/edi_exchange_type.py @@ -0,0 +1,14 @@ +# Copyright 2021 Camptocamp SA +# Copyright 2025 Dixmit +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class EdiExchangeType(models.Model): + _inherit = "edi.exchange.type" + + job_channel_id = fields.Many2one( + comodel_name="queue.job.channel", + ) + job_priority = fields.Integer() diff --git a/edi_queue_oca/pyproject.toml b/edi_queue_oca/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/edi_queue_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/edi_queue_oca/readme/CONTRIBUTORS.md b/edi_queue_oca/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..405f579f4 --- /dev/null +++ b/edi_queue_oca/readme/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +- Simone Orsi \<\> +- Enric Tobella \<\> +- Manuel Regidor \<\> +- Thien Vo \<\> +- Jordi Masvidal \<\> diff --git a/edi_queue_oca/readme/DESCRIPTION.md b/edi_queue_oca/readme/DESCRIPTION.md new file mode 100644 index 000000000..14715261c --- /dev/null +++ b/edi_queue_oca/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module integrates EDI with Queue Job and now the edi exchange records are generated using queue. + +No need of doing a configuration on it, however, we can specify priority and channel in exchange type. diff --git a/edi_queue_oca/security/ir_model_access.xml b/edi_queue_oca/security/ir_model_access.xml new file mode 100644 index 000000000..527c77cbf --- /dev/null +++ b/edi_queue_oca/security/ir_model_access.xml @@ -0,0 +1,13 @@ + + + + + access_queue_job edi user + + + + + + + + diff --git a/edi_queue_oca/static/description/icon.png b/edi_queue_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_queue_oca/tests/__init__.py b/edi_queue_oca/tests/__init__.py new file mode 100644 index 000000000..1dad186b6 --- /dev/null +++ b/edi_queue_oca/tests/__init__.py @@ -0,0 +1,14 @@ +from . import test_record +from . import test_backend_jobs +from . import test_backend_output_jobs + + +# We want to execute the same tests from the original modules to ensure +# that everything works as expected. +from odoo.addons.edi_core_oca.tests import ( + test_backend_input, + test_backend_output, + test_backend_process, + test_edi_backend_cron, + test_quick_exec, +) diff --git a/edi_queue_oca/tests/test_backend_jobs.py b/edi_queue_oca/tests/test_backend_jobs.py new file mode 100644 index 000000000..6998d08eb --- /dev/null +++ b/edi_queue_oca/tests/test_backend_jobs.py @@ -0,0 +1,159 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from unittest import mock + +from odoo_test_helper import FakeModelLoader +from requests.exceptions import ConnectionError as ReqConnectionError + +from odoo.tests import tagged + +from odoo.addons.edi_core_oca.tests.common import EDIBackendCommonTestCase +from odoo.addons.queue_job.exception import RetryableJobError +from odoo.addons.queue_job.tests.common import JobMixin + + +@tagged("-at_install", "post_install") +class EDIBackendTestJobsCase(EDIBackendCommonTestCase, JobMixin): + @classmethod + def _setup_context(cls): + return dict(super()._setup_context(), queue_job__no_delay=None) + + @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 EdiTestExecution + + cls.loader.update_registry((EdiTestExecution,)) + cls.ExecutionAbstractModel = cls.env["edi.framework.test.execution"] + cls.model = cls.env["ir.model"].search( + [("model", "=", "edi.framework.test.execution")] + ) + cls.exchange_type_out.generate_model_id = cls.model + cls.exchange_type_out.send_model_id = cls.model + cls.exchange_type_out.output_validate_model_id = cls.model + cls.exchange_type_in.receive_model_id = cls.model + cls.exchange_type_in.process_model_id = cls.model + cls.exchange_type_in.input_validate_model_id = cls.model + + def _get_related_jobs(self, record): + # Use domain in action to find all related jobs + record.ensure_one() + action = record.action_view_related_queue_jobs() + return self.env["queue.job"].search(action["domain"]) + + def test_output(self): + job_counter = self.job_counter() + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record = self.backend.create_record("test_csv_output", vals) + self.assertEqual(record.edi_exchange_state, "new") + job = self.backend.with_delay().exchange_generate(record) + created = job_counter.search_created() + self.assertEqual(len(created), 1) + self.assertEqual( + created.name, "Generate output content for given exchange record." + ) + # Check related jobs + self.assertEqual(created, self._get_related_jobs(record)) + with ( + mock.patch.object( + type(self.backend), "_exchange_generate" + ) as mocked_generate, + mock.patch.object(type(self.backend), "_validate_data") as mocked_validate, + ): + mocked_generate.return_value = "filecontent" + mocked_validate.return_value = None + res = job.perform() + self.assertEqual(res, "Exchange data generated") + self.assertEqual(record.edi_exchange_state, "output_pending") + job = self.backend.with_delay().exchange_send(record) + created = job_counter.search_created() + with mock.patch.object(type(self.backend), "_exchange_send") as mocked: + mocked.return_value = "ok" + res = job.perform() + self.assertEqual(res, "Exchange sent") + self.assertEqual(record.edi_exchange_state, "output_sent") + self.assertEqual(created[0].name, "Send exchange file.") + # Check related jobs + record.invalidate_recordset() + self.assertEqual(created, self._get_related_jobs(record)) + + def test_output_fail_retry(self): + job_counter = self.job_counter() + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + "edi_exchange_state": "output_pending", + } + record = self.backend.create_record("test_csv_output", vals) + record._set_file_content("ABC") + job = self.backend.with_delay().exchange_send(record) + job_counter.search_created() + with mock.patch.object(type(self.backend), "_exchange_send") as mocked: + mocked.side_effect = ReqConnectionError("Connection broken") + with self.assertRaises(RetryableJobError): + job.perform() + + def test_input(self): + job_counter = self.job_counter() + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record = self.backend.create_record("test_csv_input", vals) + job = self.backend.with_delay().exchange_receive(record) + created = job_counter.search_created() + self.assertEqual(len(created), 1) + self.assertEqual(created.name, "Retrieve an incoming document.") + # Check related jobs + self.assertEqual(created, self._get_related_jobs(record)) + with ( + mock.patch.object( + type(self.backend), "_exchange_receive" + ) as mocked_receive, + mock.patch.object(type(self.backend), "_validate_data") as mocked_validate, + ): + mocked_receive.return_value = "filecontent" + mocked_validate.return_value = None + res = job.perform() + # the state is not input_pending hence there's nothing to do + self.assertEqual(res, "Nothing to do. Likely already received.") + record.edi_exchange_state = "input_pending" + res = job.perform() + # the state is not input_pending hence there's nothing to do + self.assertEqual(res, "Exchange received successfully") + self.assertEqual(record.edi_exchange_state, "input_received") + job = self.backend.with_delay().exchange_process(record) + created = job_counter.search_created() + self.assertEqual(created[0].name, "Process an incoming document.") + + def test_input_processed_error(self): + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + "edi_exchange_state": "input_received", + } + record = self.backend.create_record("test_csv_input", vals) + record._set_file_content("ABC") + # Process `input_received` records + job_counter = self.job_counter() + self.backend._check_input_exchange_sync() + created = job_counter.search_created() + # Create job + self.assertEqual(len(created), 1) + record.edi_exchange_state = "input_processed_error" + # Don't re-process `input_processed_error` records + self.backend._check_input_exchange_sync() + new_created = job_counter.search_created() - created + # Should not create new job + self.assertEqual(len(new_created), 0) + # Check related jobs + record.invalidate_recordset() + self.assertEqual(created, self._get_related_jobs(record)) diff --git a/edi_queue_oca/tests/test_backend_output_jobs.py b/edi_queue_oca/tests/test_backend_output_jobs.py new file mode 100644 index 000000000..b600a0d9b --- /dev/null +++ b/edi_queue_oca/tests/test_backend_output_jobs.py @@ -0,0 +1,56 @@ +# Copyright 2020 ACSONE +# Copyright 2021 Camptocamp +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + + +from odoo.tests import tagged + +from odoo.addons.edi_core_oca.tests.common import EDIBackendCommonTestCase +from odoo.addons.queue_job.tests.common import trap_jobs + + +@tagged("-at_install", "post_install") +class EDIBackendTestOutputJobsCase(EDIBackendCommonTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + vals = { + "model": cls.partner._name, + "res_id": cls.partner.id, + } + cls.record = cls.backend.create_record("test_csv_output", vals) + cls.record.type_id.exchange_file_auto_generate = True + + @classmethod + def _setup_context(cls): + # Re-enable jobs + return dict(super()._setup_context(), queue_job__no_delay=False) + + def test_job(self): + with trap_jobs() as trap: + self.backend._check_output_exchange_sync(record_ids=self.record.ids) + trap.assert_jobs_count(2) + trap.assert_enqueued_job( + self.record.action_exchange_generate, + ) + trap.assert_enqueued_job( + self.record.action_exchange_send, + ) + # No matter how many times we schedule jobs + self.record.action_exchange_generate() + self.record.action_exchange_generate() + self.record.action_exchange_generate() + # identity key should prevent having new jobs for same record same file + trap.assert_jobs_count(2) + # but if we change the content + self.record._set_file_content("something different") + # 1st call will schedule another job + self.record.action_exchange_generate() + # the 2nd one not + self.record.action_exchange_generate() + trap.assert_jobs_count(3) + job = self.record.action_exchange_send() + self.assertEqual(0, job.priority) + trap.assert_jobs_count(4) + # TODO: test input in the same way diff --git a/edi_queue_oca/tests/test_record.py b/edi_queue_oca/tests/test_record.py new file mode 100644 index 000000000..d290b9497 --- /dev/null +++ b/edi_queue_oca/tests/test_record.py @@ -0,0 +1,37 @@ +# Copyright 2020 ACSONE +# Copyright 2022 Camptocamp SA +# @author: Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + + +from odoo.addons.edi_core_oca.tests.common import EDIBackendCommonTestCase +from odoo.addons.queue_job.delay import DelayableRecordset + + +class EDIRecordTestCase(EDIBackendCommonTestCase): + def test_with_delay_override(self): + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record = self.backend.create_record("test_csv_input", vals) + parent_channel = self.env["queue.job.channel"].create( + { + "name": "parent_test_chan", + "parent_id": self.env.ref("queue_job.channel_root").id, + } + ) + channel = self.env["queue.job.channel"].create( + {"name": "test_chan", "parent_id": parent_channel.id} + ) + self.exchange_type_in.job_channel_id = channel + self.exchange_type_in.job_priority = 5 + # re-enable job delayed feature + delayed = record.with_context(queue_job__no_delay=False).with_delay() + # Silent useless warning + # `Delayable Delayable(edi.exchange.record*) was prepared but never delayed` + delayed.delayable._generated_job = object() + self.assertTrue(isinstance(delayed, DelayableRecordset)) + self.assertEqual(delayed.recordset, record) + self.assertEqual(delayed.delayable.channel, "root.parent_test_chan.test_chan") + self.assertEqual(delayed.delayable.priority, 5) diff --git a/edi_queue_oca/utils.py b/edi_queue_oca/utils.py new file mode 100644 index 000000000..3b100bfd6 --- /dev/null +++ b/edi_queue_oca/utils.py @@ -0,0 +1,16 @@ +# Copyright 2020 ACSONE SA +# Copyright 2023 Camptocamp +# @author Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + + +from odoo.addons.queue_job.job import identity_exact_hasher + + +def exchange_record_job_identity_exact(job_): + hasher = identity_exact_hasher(job_) + # Include files checksum + hasher.update( + str(sorted(job_.recordset.mapped("exchange_filechecksum"))).encode("utf-8") + ) + return hasher.hexdigest() diff --git a/edi_queue_oca/views/edi_exchange_record.xml b/edi_queue_oca/views/edi_exchange_record.xml new file mode 100644 index 000000000..800c445d0 --- /dev/null +++ b/edi_queue_oca/views/edi_exchange_record.xml @@ -0,0 +1,28 @@ + + + + + edi.exchange.record + + +
+ +
+
+
+
diff --git a/edi_queue_oca/views/edi_exchange_type.xml b/edi_queue_oca/views/edi_exchange_type.xml new file mode 100644 index 000000000..f42622e0e --- /dev/null +++ b/edi_queue_oca/views/edi_exchange_type.xml @@ -0,0 +1,15 @@ + + + + + edi.exchange.type + + + + + + + + + From 89d8c42c975da735757ad4a08e096b21529b7b89 Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Thu, 25 Sep 2025 08:22:10 +0200 Subject: [PATCH 02/17] [IMP] edi_queue_oca: Pass view elements to a new tab to improve the view --- edi_queue_oca/README.rst | 90 +++- edi_queue_oca/__manifest__.py | 2 +- edi_queue_oca/i18n/edi_queue_oca.pot | 54 +++ edi_queue_oca/static/description/index.html | 437 ++++++++++++++++++++ edi_queue_oca/views/edi_exchange_type.xml | 12 +- 5 files changed, 589 insertions(+), 6 deletions(-) create mode 100644 edi_queue_oca/i18n/edi_queue_oca.pot create mode 100644 edi_queue_oca/static/description/index.html diff --git a/edi_queue_oca/README.rst b/edi_queue_oca/README.rst index 85954498e..4f0005b07 100644 --- a/edi_queue_oca/README.rst +++ b/edi_queue_oca/README.rst @@ -1 +1,89 @@ -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 Queue Oca +============= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e3afeef7814c93bbb02342bbea9a025fdac06fcbf906dcc4eb2b221aa4757a1e + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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_queue_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_queue_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 integrates EDI with Queue Job and now the edi exchange +records are generated using queue. + +No need of doing a configuration on it, however, we can specify priority +and channel in exchange type. + +**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 +------- + +* 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. + +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_queue_oca/__manifest__.py b/edi_queue_oca/__manifest__.py index cb9cc7e73..28121311c 100644 --- a/edi_queue_oca/__manifest__.py +++ b/edi_queue_oca/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Edi Queue Oca", "summary": """Set Queue Jobs on EDI""", - "version": "18.0.1.0.0", + "version": "18.0.1.0.1", "license": "LGPL-3", "author": "Dixmit,Camptocamp,Odoo Community Association (OCA)", "website": "https://github.com/OCA/edi-framework", diff --git a/edi_queue_oca/i18n/edi_queue_oca.pot b/edi_queue_oca/i18n/edi_queue_oca.pot new file mode 100644 index 000000000..3af0c2486 --- /dev/null +++ b/edi_queue_oca/i18n/edi_queue_oca.pot @@ -0,0 +1,54 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_queue_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_queue_oca +#: model:ir.model,name:edi_queue_oca.model_edi_backend +msgid "EDI Backend" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model,name:edi_queue_oca.model_edi_exchange_type +msgid "EDI Exchange Type" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model,name:edi_queue_oca.model_edi_exchange_record +msgid "EDI exchange Record" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_type__job_channel_id +msgid "Job Channel" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_type__job_priority +msgid "Job Priority" +msgstr "" + +#. module: edi_queue_oca +#: model_terms:ir.ui.view,arch_db:edi_queue_oca.edi_exchange_record_form_view +msgid "Jobs" +msgstr "" + +#. module: edi_queue_oca +#: model_terms:ir.ui.view,arch_db:edi_queue_oca.edi_exchange_type_form_view +msgid "Queue" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_record__related_queue_jobs_count +msgid "Related Queue Jobs Count" +msgstr "" diff --git a/edi_queue_oca/static/description/index.html b/edi_queue_oca/static/description/index.html new file mode 100644 index 000000000..94c3d5675 --- /dev/null +++ b/edi_queue_oca/static/description/index.html @@ -0,0 +1,437 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Edi Queue Oca

+ +

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

+

This module integrates EDI with Queue Job and now the edi exchange +records are generated using queue.

+

No need of doing a configuration on it, however, we can specify priority +and channel in exchange type.

+

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

+
    +
  • 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.

+

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_queue_oca/views/edi_exchange_type.xml b/edi_queue_oca/views/edi_exchange_type.xml index f42622e0e..14eb0d22a 100644 --- a/edi_queue_oca/views/edi_exchange_type.xml +++ b/edi_queue_oca/views/edi_exchange_type.xml @@ -6,10 +6,14 @@ edi.exchange.type - - - - + + + + + + + + From 845c06adc4d90f75245ab888c375140a11653e0b Mon Sep 17 00:00:00 2001 From: mymage Date: Mon, 6 Oct 2025 07:26:37 +0000 Subject: [PATCH 03/17] Added translation using Weblate (Italian) --- edi_queue_oca/i18n/it.po | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 edi_queue_oca/i18n/it.po diff --git a/edi_queue_oca/i18n/it.po b/edi_queue_oca/i18n/it.po new file mode 100644 index 000000000..601ef2580 --- /dev/null +++ b/edi_queue_oca/i18n/it.po @@ -0,0 +1,55 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_queue_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\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" + +#. module: edi_queue_oca +#: model:ir.model,name:edi_queue_oca.model_edi_backend +msgid "EDI Backend" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model,name:edi_queue_oca.model_edi_exchange_type +msgid "EDI Exchange Type" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model,name:edi_queue_oca.model_edi_exchange_record +msgid "EDI exchange Record" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_type__job_channel_id +msgid "Job Channel" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_type__job_priority +msgid "Job Priority" +msgstr "" + +#. module: edi_queue_oca +#: model_terms:ir.ui.view,arch_db:edi_queue_oca.edi_exchange_record_form_view +msgid "Jobs" +msgstr "" + +#. module: edi_queue_oca +#: model_terms:ir.ui.view,arch_db:edi_queue_oca.edi_exchange_type_form_view +msgid "Queue" +msgstr "" + +#. module: edi_queue_oca +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_record__related_queue_jobs_count +msgid "Related Queue Jobs Count" +msgstr "" From dd8294385c31062f09cf123010331b344ec486f8 Mon Sep 17 00:00:00 2001 From: mymage Date: Mon, 6 Oct 2025 07:26:58 +0000 Subject: [PATCH 04/17] Translated using Weblate (Italian) Currently translated at 100.0% (8 of 8 strings) Translation: edi-framework-18.0/edi-framework-18.0-edi_queue_oca Translate-URL: https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_queue_oca/it/ --- edi_queue_oca/i18n/it.po | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/edi_queue_oca/i18n/it.po b/edi_queue_oca/i18n/it.po index 601ef2580..900add755 100644 --- a/edi_queue_oca/i18n/it.po +++ b/edi_queue_oca/i18n/it.po @@ -6,50 +6,52 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2025-10-06 07:42+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_queue_oca #: model:ir.model,name:edi_queue_oca.model_edi_backend msgid "EDI Backend" -msgstr "" +msgstr "Backend EDI" #. module: edi_queue_oca #: model:ir.model,name:edi_queue_oca.model_edi_exchange_type msgid "EDI Exchange Type" -msgstr "" +msgstr "Tipo scambio EDI" #. module: edi_queue_oca #: model:ir.model,name:edi_queue_oca.model_edi_exchange_record msgid "EDI exchange Record" -msgstr "" +msgstr "Record di scambio EDI" #. module: edi_queue_oca #: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_type__job_channel_id msgid "Job Channel" -msgstr "" +msgstr "Canale lavoro" #. module: edi_queue_oca #: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_type__job_priority msgid "Job Priority" -msgstr "" +msgstr "Priorità lavoro" #. module: edi_queue_oca #: model_terms:ir.ui.view,arch_db:edi_queue_oca.edi_exchange_record_form_view msgid "Jobs" -msgstr "" +msgstr "Lavori" #. module: edi_queue_oca #: model_terms:ir.ui.view,arch_db:edi_queue_oca.edi_exchange_type_form_view msgid "Queue" -msgstr "" +msgstr "Coda" #. module: edi_queue_oca #: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_record__related_queue_jobs_count msgid "Related Queue Jobs Count" -msgstr "" +msgstr "Conteggio lavori in coda relativo" From d3da7442d8715afe35e234ff7ac18161f11e0b54 Mon Sep 17 00:00:00 2001 From: Ricardoalso Date: Fri, 20 Feb 2026 15:24:21 +0100 Subject: [PATCH 05/17] [FIX] edi_*_oca: New way for FakeModelLoader --- edi_queue_oca/README.rst | 2 +- edi_queue_oca/__manifest__.py | 2 +- edi_queue_oca/static/description/index.html | 2 +- edi_queue_oca/tests/test_backend_jobs.py | 32 +++++++++++---------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/edi_queue_oca/README.rst b/edi_queue_oca/README.rst index 4f0005b07..c9b6f4945 100644 --- a/edi_queue_oca/README.rst +++ b/edi_queue_oca/README.rst @@ -11,7 +11,7 @@ Edi Queue Oca !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:e3afeef7814c93bbb02342bbea9a025fdac06fcbf906dcc4eb2b221aa4757a1e + !! source digest: sha256:273ae337ce810a27ea20810513190a340066e443fdb5d1b3d00065665bf8c6aa !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/edi_queue_oca/__manifest__.py b/edi_queue_oca/__manifest__.py index 28121311c..dec27cca5 100644 --- a/edi_queue_oca/__manifest__.py +++ b/edi_queue_oca/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Edi Queue Oca", "summary": """Set Queue Jobs on EDI""", - "version": "18.0.1.0.1", + "version": "18.0.1.0.2", "license": "LGPL-3", "author": "Dixmit,Camptocamp,Odoo Community Association (OCA)", "website": "https://github.com/OCA/edi-framework", diff --git a/edi_queue_oca/static/description/index.html b/edi_queue_oca/static/description/index.html index 94c3d5675..c65aff333 100644 --- a/edi_queue_oca/static/description/index.html +++ b/edi_queue_oca/static/description/index.html @@ -372,7 +372,7 @@

Edi Queue Oca

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:e3afeef7814c93bbb02342bbea9a025fdac06fcbf906dcc4eb2b221aa4757a1e +!! source digest: sha256:273ae337ce810a27ea20810513190a340066e443fdb5d1b3d00065665bf8c6aa !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

This module integrates EDI with Queue Job and now the edi exchange diff --git a/edi_queue_oca/tests/test_backend_jobs.py b/edi_queue_oca/tests/test_backend_jobs.py index 6998d08eb..44c90eac7 100644 --- a/edi_queue_oca/tests/test_backend_jobs.py +++ b/edi_queue_oca/tests/test_backend_jobs.py @@ -20,25 +20,27 @@ class EDIBackendTestJobsCase(EDIBackendCommonTestCase, JobMixin): def _setup_context(cls): return dict(super()._setup_context(), queue_job__no_delay=None) - @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() + 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 EdiTestExecution - cls.loader.update_registry((EdiTestExecution,)) - cls.ExecutionAbstractModel = cls.env["edi.framework.test.execution"] - cls.model = cls.env["ir.model"].search( + self.loader.update_registry((EdiTestExecution,)) + self.ExecutionAbstractModel = self.env["edi.framework.test.execution"] + self.model = self.env["ir.model"].search( [("model", "=", "edi.framework.test.execution")] ) - cls.exchange_type_out.generate_model_id = cls.model - cls.exchange_type_out.send_model_id = cls.model - cls.exchange_type_out.output_validate_model_id = cls.model - cls.exchange_type_in.receive_model_id = cls.model - cls.exchange_type_in.process_model_id = cls.model - cls.exchange_type_in.input_validate_model_id = cls.model + self.exchange_type_out.generate_model_id = self.model + self.exchange_type_out.send_model_id = self.model + self.exchange_type_out.output_validate_model_id = self.model + self.exchange_type_in.receive_model_id = self.model + self.exchange_type_in.process_model_id = self.model + self.exchange_type_in.input_validate_model_id = self.model + + def tearDown(self): + self.loader.restore_registry() + super().tearDown() def _get_related_jobs(self, record): # Use domain in action to find all related jobs From 5bca5f85aa16cc79420bfc28c0d747dbe37dddc1 Mon Sep 17 00:00:00 2001 From: Sergij Pfaifer Date: Thu, 26 Mar 2026 11:19:40 +0200 Subject: [PATCH 06/17] [MIG] edi_queue_oca: Migration to 19.0 --- edi_queue_oca/README.rst | 12 ++++----- edi_queue_oca/__manifest__.py | 2 +- edi_queue_oca/i18n/edi_queue_oca.pot | 16 +++++++++++- edi_queue_oca/models/edi_exchange_record.py | 11 +++++++- edi_queue_oca/static/description/index.html | 8 +++--- edi_queue_oca/tests/test_backend_jobs.py | 28 +++++++++++++-------- 6 files changed, 53 insertions(+), 24 deletions(-) diff --git a/edi_queue_oca/README.rst b/edi_queue_oca/README.rst index c9b6f4945..df0d5456e 100644 --- a/edi_queue_oca/README.rst +++ b/edi_queue_oca/README.rst @@ -11,7 +11,7 @@ Edi Queue Oca !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:273ae337ce810a27ea20810513190a340066e443fdb5d1b3d00065665bf8c6aa + !! source digest: sha256:186ea8dc51c0216b5067f4cc29ed5c478a91fad07c648bcb9d0ac21d303da33b !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -21,13 +21,13 @@ Edi Queue 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_queue_oca + :target: https://github.com/OCA/edi-framework/tree/19.0/edi_queue_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_queue_oca + :target: https://translation.odoo-community.org/projects/edi-framework-19-0/edi-framework-19-0-edi_queue_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| @@ -49,7 +49,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. @@ -84,6 +84,6 @@ 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. +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_queue_oca/__manifest__.py b/edi_queue_oca/__manifest__.py index dec27cca5..9ef8b39f6 100644 --- a/edi_queue_oca/__manifest__.py +++ b/edi_queue_oca/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Edi Queue Oca", "summary": """Set Queue Jobs on EDI""", - "version": "18.0.1.0.2", + "version": "19.0.1.0.0", "license": "LGPL-3", "author": "Dixmit,Camptocamp,Odoo Community Association (OCA)", "website": "https://github.com/OCA/edi-framework", diff --git a/edi_queue_oca/i18n/edi_queue_oca.pot b/edi_queue_oca/i18n/edi_queue_oca.pot index 3af0c2486..030c37dd1 100644 --- a/edi_queue_oca/i18n/edi_queue_oca.pot +++ b/edi_queue_oca/i18n/edi_queue_oca.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 18.0\n" +"Project-Id-Version: Odoo Server 19.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -13,6 +13,13 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: edi_queue_oca +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_backend__display_name +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_record__display_name +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_type__display_name +msgid "Display Name" +msgstr "" + #. module: edi_queue_oca #: model:ir.model,name:edi_queue_oca.model_edi_backend msgid "EDI Backend" @@ -28,6 +35,13 @@ msgstr "" msgid "EDI exchange Record" msgstr "" +#. module: edi_queue_oca +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_backend__id +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_record__id +#: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_type__id +msgid "ID" +msgstr "" + #. module: edi_queue_oca #: model:ir.model.fields,field_description:edi_queue_oca.field_edi_exchange_type__job_channel_id msgid "Job Channel" diff --git a/edi_queue_oca/models/edi_exchange_record.py b/edi_queue_oca/models/edi_exchange_record.py index 58915fa5a..d62ac6bdb 100644 --- a/edi_queue_oca/models/edi_exchange_record.py +++ b/edi_queue_oca/models/edi_exchange_record.py @@ -2,6 +2,7 @@ # Copyright 2025 Dixmit # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +import functools from ast import literal_eval from odoo import fields, models @@ -23,7 +24,15 @@ def _register_hook(self): "action_exchange_process", "action_exchange_generate", ]: - self._patch_method(function, self._patch_job_auto_delay(function)) + patched = self._patch_job_auto_delay(function) + setattr( + type(self), + function, + functools.update_wrapper( + patched, + getattr(type(self), function), + ), + ) return super()._register_hook() def action_exchange_send_job_options(self): diff --git a/edi_queue_oca/static/description/index.html b/edi_queue_oca/static/description/index.html index c65aff333..e850cd986 100644 --- a/edi_queue_oca/static/description/index.html +++ b/edi_queue_oca/static/description/index.html @@ -372,9 +372,9 @@

Edi Queue Oca

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:273ae337ce810a27ea20810513190a340066e443fdb5d1b3d00065665bf8c6aa +!! source digest: sha256:186ea8dc51c0216b5067f4cc29ed5c478a91fad07c648bcb9d0ac21d303da33b !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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 integrates EDI with Queue Job and now the edi exchange records are generated using queue.

No need of doing a configuration on it, however, we can specify priority @@ -396,7 +396,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.

@@ -427,7 +427,7 @@

Maintainers

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.

+

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_queue_oca/tests/test_backend_jobs.py b/edi_queue_oca/tests/test_backend_jobs.py index 44c90eac7..61ac106bd 100644 --- a/edi_queue_oca/tests/test_backend_jobs.py +++ b/edi_queue_oca/tests/test_backend_jobs.py @@ -4,9 +4,9 @@ from unittest import mock -from odoo_test_helper import FakeModelLoader from requests.exceptions import ConnectionError as ReqConnectionError +from odoo.orm.model_classes import add_to_registry from odoo.tests import tagged from odoo.addons.edi_core_oca.tests.common import EDIBackendCommonTestCase @@ -20,14 +20,24 @@ class EDIBackendTestJobsCase(EDIBackendCommonTestCase, JobMixin): def _setup_context(cls): return dict(super()._setup_context(), queue_job__no_delay=None) - def setUp(self): - super().setUp() - self.loader = FakeModelLoader(self.env, self.__module__) - self.loader.backup_registry() + @classmethod + def _setup_env(cls): + super()._setup_env() + # Load fake models ->/ from odoo.addons.edi_core_oca.tests.fake_models import EdiTestExecution - self.loader.update_registry((EdiTestExecution,)) - self.ExecutionAbstractModel = self.env["edi.framework.test.execution"] + add_to_registry(cls.registry, EdiTestExecution) + cls.registry._setup_models__(cls.env.cr, ["edi.framework.test.execution"]) + cls.registry.init_models( + cls.env.cr, + ["edi.framework.test.execution"], + {"models_to_check": True}, + ) + cls.addClassCleanup(cls.registry.__delitem__, "edi.framework.test.execution") + return super()._setup_env() + + def setUp(self): + super().setUp() self.model = self.env["ir.model"].search( [("model", "=", "edi.framework.test.execution")] ) @@ -38,10 +48,6 @@ def setUp(self): self.exchange_type_in.process_model_id = self.model self.exchange_type_in.input_validate_model_id = self.model - def tearDown(self): - self.loader.restore_registry() - super().tearDown() - def _get_related_jobs(self, record): # Use domain in action to find all related jobs record.ensure_one() From 8a1da4164d259245e2515e0d69f4f5fbe185647b Mon Sep 17 00:00:00 2001 From: Ricardoalso Date: Fri, 19 Jun 2026 16:20:02 +0200 Subject: [PATCH 07/17] [IMP] edi_queue_oca: add timezone-aware daily ETA scheduling Add a configurable daily execution time on EDI exchange types and inject the corresponding ETA into queued exchange record jobs. Added fields to `edi.exchange.type` for enabling ETA scheduling, configuring execution hour, minute, and timezone, and logic to compute the next scheduled runtime in UTC. This allows all jobs for a given exchange type to be held and released together at a fixed time each day, supporting off-peak processing and trading partner requirements. Enabling ETA scheduling on an exchange type causes every job created for that type to be held in the queue until the configured time of day, rather than being processed immediately. All jobs that arrive during the day accumulate and are released together at that scheduled time. When ETA scheduling is disabled, jobs are dispatched immediately as usual --- edi_queue_oca/README.rst | 5 ++ edi_queue_oca/models/edi_exchange_record.py | 2 + edi_queue_oca/models/edi_exchange_type.py | 70 ++++++++++++++++++++- edi_queue_oca/readme/DESCRIPTION.md | 5 ++ edi_queue_oca/static/description/index.html | 4 ++ edi_queue_oca/tests/__init__.py | 1 + edi_queue_oca/tests/common.py | 19 ++++++ edi_queue_oca/tests/test_exchange_type.py | 60 ++++++++++++++++++ edi_queue_oca/tests/test_record.py | 49 +++++++++++---- edi_queue_oca/views/edi_exchange_type.xml | 16 +++++ 10 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 edi_queue_oca/tests/common.py create mode 100644 edi_queue_oca/tests/test_exchange_type.py diff --git a/edi_queue_oca/README.rst b/edi_queue_oca/README.rst index df0d5456e..465d772f9 100644 --- a/edi_queue_oca/README.rst +++ b/edi_queue_oca/README.rst @@ -38,6 +38,11 @@ records are generated using queue. No need of doing a configuration on it, however, we can specify priority and channel in exchange type. +Exchange types can also define a daily execution time with ``eta_time``. +The value is entered as a ``float_time`` in the current user's timezone +(``22.5`` means 22:30 local time) and is converted at runtime to the +next matching UTC datetime used as the queue job ETA. + **Table of contents** .. contents:: diff --git a/edi_queue_oca/models/edi_exchange_record.py b/edi_queue_oca/models/edi_exchange_record.py index d62ac6bdb..c8ddeff7d 100644 --- a/edi_queue_oca/models/edi_exchange_record.py +++ b/edi_queue_oca/models/edi_exchange_record.py @@ -47,6 +47,8 @@ def _job_delay_params(self): priority = exchange_type.job_priority if priority: params["priority"] = priority + if exchange_type.job_eta_enabled and (eta := exchange_type._get_job_eta()): + params["eta"] = eta # Avoid generating the same job for the same record if existing params["identity_key"] = exchange_record_job_identity_exact return params diff --git a/edi_queue_oca/models/edi_exchange_type.py b/edi_queue_oca/models/edi_exchange_type.py index 407279f66..adfcab9e6 100644 --- a/edi_queue_oca/models/edi_exchange_type.py +++ b/edi_queue_oca/models/edi_exchange_type.py @@ -2,7 +2,14 @@ # Copyright 2025 Dixmit # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). -from odoo import fields, models +from datetime import datetime + +import pytz +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models + +from odoo.addons.base.models.res_partner import _tz_get as timezone_selection class EdiExchangeType(models.Model): @@ -12,3 +19,64 @@ class EdiExchangeType(models.Model): comodel_name="queue.job.channel", ) job_priority = fields.Integer() + job_eta_enabled = fields.Boolean( + string="Enable ETA Scheduling", + help="Accumulate all queue jobs for this exchange type and release them " + "at the daily time configured below, instead of dispatching each job " + "immediately. Use this when a trading partner only processes files at a " + "fixed daily window, or to concentrate resource-intensive EDI work in " + "off-peak hours.", + ) + job_eta_hour = fields.Selection( + [(str(x).zfill(2), str(x).zfill(2)) for x in range(24)], + string="Execution time - hour", + help="Hour of the day at which jobs for this type should be scheduled.", + default="00", + ) + job_eta_minute = fields.Selection( + [(str(x).zfill(2), str(x).zfill(2)) for x in range(60)], + string="Execution time - minute", + help="Minute of the hour at which jobs for this type should be scheduled.", + default="00", + ) + job_eta_tz = fields.Selection( + timezone_selection, + string="Execution time - timezone", + help="ETA's timezone for jobs of this type", + default=lambda self: self._get_default_eta_tz(), + ) + + @api.model + def _get_default_eta_tz(self): + tz_list = timezone_selection(self) + return self.env.user.tz or (tz_list and tz_list[0][0]) or "UTC" + + def _get_job_eta(self): + """Returns the ETA for the current type as timezone-naive datetime""" + self.ensure_one() + if ( + not self.job_eta_enabled + or not self.job_eta_tz + or not self.job_eta_hour + or not self.job_eta_minute + ): + return None + + self.ensure_one() + now = pytz.UTC.localize(datetime.utcnow()) + eta = ( + pytz.timezone(self.job_eta_tz) + .localize( + datetime( + now.year, + now.month, + now.day, + int(self.job_eta_hour), + int(self.job_eta_minute), + ) + ) + .astimezone(pytz.UTC) + ) + if eta < now: + eta += relativedelta(days=1) + return eta.replace(tzinfo=None) diff --git a/edi_queue_oca/readme/DESCRIPTION.md b/edi_queue_oca/readme/DESCRIPTION.md index 14715261c..cbc92e389 100644 --- a/edi_queue_oca/readme/DESCRIPTION.md +++ b/edi_queue_oca/readme/DESCRIPTION.md @@ -1,3 +1,8 @@ This module integrates EDI with Queue Job and now the edi exchange records are generated using queue. No need of doing a configuration on it, however, we can specify priority and channel in exchange type. + +Exchange types can also define a daily execution time with `eta_time`. +The value is entered as a `float_time` in the current user's timezone +(`22.5` means 22:30 local time) and is converted at runtime to the next +matching UTC datetime used as the queue job ETA. diff --git a/edi_queue_oca/static/description/index.html b/edi_queue_oca/static/description/index.html index e850cd986..7105e55e8 100644 --- a/edi_queue_oca/static/description/index.html +++ b/edi_queue_oca/static/description/index.html @@ -379,6 +379,10 @@

Edi Queue Oca

records are generated using queue.

No need of doing a configuration on it, however, we can specify priority and channel in exchange type.

+

Exchange types can also define a daily execution time with eta_time. +The value is entered as a float_time in the current user’s timezone +(22.5 means 22:30 local time) and is converted at runtime to the +next matching UTC datetime used as the queue job ETA.

Table of contents