diff --git a/queue_job_batch/models/queue_job.py b/queue_job_batch/models/queue_job.py
index ddc3efe879..3c41c9b6a3 100644
--- a/queue_job_batch/models/queue_job.py
+++ b/queue_job_batch/models/queue_job.py
@@ -25,7 +25,7 @@ def write(self, vals):
for record in self:
if record.state != "done" and record.job_batch_id:
batches |= record.job_batch_id
- for batch in batches:
+ for batch in batches.with_context(job_batch=None):
# We need to make it with delay in order to prevent two jobs
# to work with the same batch
batch.with_delay(identity_key=identity_exact).check_state()
diff --git a/queue_job_batch/models/queue_job_batch.py b/queue_job_batch/models/queue_job_batch.py
index 998f7a5ac2..10812bcbc9 100644
--- a/queue_job_batch/models/queue_job_batch.py
+++ b/queue_job_batch/models/queue_job_batch.py
@@ -5,6 +5,7 @@
from odoo import api, fields, models
from odoo.addons.mail.tools.discuss import Store
+from odoo.addons.queue_job.exception import RetryableJobError
class QueueJobBatch(models.Model):
@@ -58,6 +59,7 @@ class QueueJobBatch(models.Model):
completeness = fields.Float(
compute="_compute_job_count",
)
+ execution_time = fields.Float(compute="_compute_job_count")
failed_percentage = fields.Float(
compute="_compute_job_count",
)
@@ -107,8 +109,21 @@ def _compute_job_count(self):
rec.failed_job_count = len(jobs_by_state.get("failed", []))
rec.finished_job_count = len(jobs_by_state.get("done", []))
rec.completeness = rec.finished_job_count / max(1, rec.job_count)
+ rec.execution_time = sum(rec.job_ids.mapped("exec_time"))
rec.failed_percentage = rec.failed_job_count / max(1, rec.job_count)
+ def _on_done(self):
+ if self.job_count != self.finished_job_count + self.failed_job_count:
+ raise RetryableJobError(
+ "%s: %d total jobs != %d finished + %d failed"
+ % (
+ self.name,
+ self.job_count,
+ self.finished_job_count,
+ self.failed_job_count,
+ )
+ )
+
@api.model
def _to_store_fnames(self):
return (
diff --git a/queue_job_batch/tests/__init__.py b/queue_job_batch/tests/__init__.py
new file mode 100644
index 0000000000..39cec46423
--- /dev/null
+++ b/queue_job_batch/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_queue_job_batch
diff --git a/queue_job_batch/tests/test_queue_job_batch.py b/queue_job_batch/tests/test_queue_job_batch.py
new file mode 100644
index 0000000000..688b7d59f8
--- /dev/null
+++ b/queue_job_batch/tests/test_queue_job_batch.py
@@ -0,0 +1,50 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.tests import TransactionCase
+
+from odoo.addons.queue_job.exception import RetryableJobError
+
+
+class TestJobBatch(TransactionCase):
+ def setUp(self):
+ super().setUp()
+ self.job_batch = self.env["queue.job.batch"].create(
+ {
+ "name": "test",
+ "user_id": self.env.user.id,
+ }
+ )
+ partners = self.env.ref("base.res_partner_1") + self.env.ref(
+ "base.res_partner_2"
+ )
+ self.jobs = [
+ p.with_context(job_batch=self.job_batch).with_delay()._get_complete_name()
+ for p in partners
+ ]
+ self.assertEqual(len(self.job_batch.job_ids), len(self.jobs))
+
+ def test_execution_time(self):
+ self.assertEqual(self.job_batch.execution_time, 0)
+ for job in self.jobs:
+ job.set_started()
+ job.perform()
+ job.set_done()
+ job.store()
+ self.assertGreater(job.exec_time, 0)
+ self.assertEqual(
+ self.job_batch.execution_time,
+ self.jobs[0].exec_time + self.jobs[1].exec_time,
+ )
+
+ def test_on_done(self):
+ self.jobs[0].set_started()
+ self.jobs[0].perform()
+ self.jobs[0].set_done()
+ self.jobs[0].store()
+ with self.assertRaises(RetryableJobError):
+ self.job_batch._on_done()
+ self.jobs[1].set_started()
+ self.jobs[1].perform()
+ self.jobs[1].set_done()
+ self.jobs[1].store()
+ self.job_batch._on_done()
diff --git a/queue_job_batch/views/queue_job_batch_views.xml b/queue_job_batch/views/queue_job_batch_views.xml
index 34bdc1fee3..b06f383e19 100644
--- a/queue_job_batch/views/queue_job_batch_views.xml
+++ b/queue_job_batch/views/queue_job_batch_views.xml
@@ -14,6 +14,7 @@
options="{'current_value': 'finished_job_count', 'max_value': 'job_count'}"
/>
+