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
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

+

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 @@
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 @@
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.