[IMP] account_edi: Replace high velocity CRON by CRON triggers

So, instead of waiting for the cron every hour, we create
a cron trigger, which is created/activated every time
we post/cancel an invoice.  When the CRON worker is available,
it can take the job immediately.

To be sure that we don't miss any crons or that events
might be needed that are not triggered, we still launch the
cron every day.

A test was added to check that the crons are correctly
triggered.

closes odoo/odoo#67088

Signed-off-by: Josse Colpaert <jco@openerp.com>
This commit is contained in:
Laurent Smet
2021-04-01 13:55:48 +00:00
committed by Josse Colpaert
parent 7faa4c3bc6
commit c9f9f859ca
4 changed files with 64 additions and 13 deletions
+2 -2
View File
@@ -3,9 +3,9 @@
<field name="name">EDI : Perform web services operations</field>
<field name="model_id" ref="model_account_edi_document"/>
<field name="state">code</field>
<field name="code">model.search([('state', 'in', ('to_send', 'to_cancel'))])._process_documents_web_services(job_count=20)</field>
<field name="code">model._cron_process_documents_web_services(job_count=20)</field>
<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="active">False</field>
</record>
@@ -190,13 +190,16 @@ class AccountEdiDocument(models.Model):
self._process_job(documents, doc_type)
def _process_documents_web_services(self, job_count=None, with_commit=True):
""" Post and cancel all the documents that need a web service. This is called by CRON.
''' Post and cancel all the documents that need a web service.
:param job_count: Limit to the number of jobs to process among the ones that are available for treatment.
"""
jobs = self.filtered(lambda d: d.edi_format_id._needs_web_services())._prepare_jobs()
jobs = jobs[0:job_count or len(jobs)]
for documents, doc_type in jobs:
:param job_count: The maximum number of jobs to process if specified.
:param with_commit: Flag indicating a commit should be made between each job.
:return: The number of remaining jobs to process.
'''
all_jobs = self.filtered(lambda d: d.edi_format_id._needs_web_services())._prepare_jobs()
jobs_to_process = all_jobs[0:job_count] if job_count else all_jobs
for documents, doc_type in jobs_to_process:
move_to_cancel = documents.filtered(lambda doc: doc.attachment_id \
and doc.state == 'to_cancel' \
and doc.move_id.is_invoice(include_receipts=True) \
@@ -220,5 +223,20 @@ class AccountEdiDocument(models.Model):
else:
raise e
else:
if with_commit and len(jobs) > 1:
if with_commit and len(jobs_to_process) > 1:
self.env.cr.commit()
return len(all_jobs) - len(jobs_to_process)
@api.model
def _cron_process_documents_web_services(self, job_count=None):
''' Method called by the EDI cron processing all web-services.
:param job_count: Limit explicitely the number of web service calls. If not provided, process all.
'''
edi_documents = self.search([('state', 'in', ('to_send', 'to_cancel'))])
nb_remaining_jobs = edi_documents._process_documents_web_services(job_count=job_count)
# Mark the CRON to be triggered again asap since there is some remaining jobs to process.
if nb_remaining_jobs > 0:
self.env.ref('account_edi.ir_cron_edi_network')._trigger()
+3 -1
View File
@@ -261,6 +261,7 @@ class AccountMove(models.Model):
self.env['account.edi.document'].create(edi_document_vals_list)
posted.edi_document_ids._process_documents_no_web_services()
self.env.ref('account_edi.ir_cron_edi_network')._trigger()
return posted
def button_cancel(self):
@@ -271,6 +272,7 @@ class AccountMove(models.Model):
self.edi_document_ids.filtered(lambda doc: doc.attachment_id).write({'state': 'to_cancel', 'error': False, 'blocking_level': False})
self.edi_document_ids.filtered(lambda doc: not doc.attachment_id).write({'state': 'cancelled', 'error': False, 'blocking_level': False})
self.edi_document_ids._process_documents_no_web_services()
self.env.ref('account_edi.ir_cron_edi_network')._trigger()
return res
@@ -353,7 +355,7 @@ class AccountMove(models.Model):
def action_process_edi_web_services(self):
docs = self.edi_document_ids.filtered(lambda d: d.state in ('to_send', 'to_cancel') and d.blocking_level != 'error')
docs._process_documents_web_services(with_commit=False)
docs._process_documents_web_services()
def action_retry_edi_documents_error(self):
self.edi_document_ids.write({'error': False, 'blocking_level': False})
+34 -3
View File
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.addons.account_edi.tests.common import AccountEdiTestCommon, _mocked_post_two_steps, _generate_mocked_needs_web_services, _mocked_cancel_failed, _generate_mocked_support_batching
from unittest.mock import patch
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
class TestAccountEdi(AccountEdiTestCommon):
class TestAccountEdi(AccountEdiTestCommon, CronMixinCase):
@classmethod
def setUpClass(cls, chart_template_ref=None, edi_format_ref=None):
@@ -93,7 +95,7 @@ class TestAccountEdi(AccountEdiTestCommon):
self.invoice.button_cancel_posted_moves()
self.assertEqual(doc.state, 'to_cancel')
self.assertEqual(self.invoice.state, 'posted')
doc._process_documents_web_services()
doc._process_documents_web_services(with_commit=False)
self.assertEqual(doc.state, 'cancelled')
self.assertEqual(self.invoice.state, 'cancel')
@@ -120,7 +122,7 @@ class TestAccountEdi(AccountEdiTestCommon):
self.invoice.button_cancel_posted_moves()
self.assertEqual(doc.state, 'to_cancel')
self.assertEqual(self.invoice.state, 'posted')
doc._process_documents_web_services()
doc._process_documents_web_services(with_commit=False)
self.assertEqual(doc.state, 'to_cancel')
self.assertEqual(self.invoice.state, 'posted')
@@ -196,3 +198,32 @@ class TestAccountEdi(AccountEdiTestCommon):
doc2.move_id.ref = 'batch1'
to_process = edi_docs._prepare_jobs()
self.assertEqual(len(to_process), 2)
def test_cron_triggers(self):
with self.capture_triggers('account_edi.ir_cron_edi_network') as capt, \
self.mock_edi(_needs_web_services_method=_generate_mocked_needs_web_services(True)):
self.invoice._get_edi_document(self.edi_format)
self.invoice.action_post()
capt.records.ensure_one()
def test_cron_self_trigger(self):
# Process single job by CRON call (and thus, disable the auto-commit).
edi_cron = self.env.ref('account_edi.ir_cron_edi_network')
edi_cron.code = 'model._cron_process_documents_web_services(job_count=1)'
# Create invoices.
invoices = self.env['account.move'].create([{
'move_type': 'out_invoice',
'invoice_date': '2019-01-01',
'date': '2019-01-01',
'partner_id': self.partner_a.id,
'invoice_line_ids': [Command.create({'product_id': self.product_a.id})],
} for i in range(4)])
with self.capture_triggers('account_edi.ir_cron_edi_network') as capt, \
self.mock_edi(_needs_web_services_method=_generate_mocked_needs_web_services(True)):
invoices.action_post()
self.env.ref('account_edi.ir_cron_edi_network').method_direct_trigger()
self.assertEqual(len(capt.records), 2, "Not all records have been processed in this run, the cron should "
"re-trigger itself to process some more later")