From c9f9f859ca896929fed87077a0a452d2aaef35b7 Mon Sep 17 00:00:00 2001 From: Laurent Smet Date: Mon, 1 Mar 2021 16:43:52 +0000 Subject: [PATCH] [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 --- addons/account_edi/data/cron.xml | 4 +- .../models/account_edi_document.py | 32 ++++++++++++---- addons/account_edi/models/account_move.py | 4 +- addons/account_edi/tests/test_edi.py | 37 +++++++++++++++++-- 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/addons/account_edi/data/cron.xml b/addons/account_edi/data/cron.xml index 1567942b8dc..1f47dbd7d02 100644 --- a/addons/account_edi/data/cron.xml +++ b/addons/account_edi/data/cron.xml @@ -3,9 +3,9 @@ EDI : Perform web services operations code - model.search([('state', 'in', ('to_send', 'to_cancel'))])._process_documents_web_services(job_count=20) + model._cron_process_documents_web_services(job_count=20) 1 - hours + days -1 False diff --git a/addons/account_edi/models/account_edi_document.py b/addons/account_edi/models/account_edi_document.py index 6a44e7f3ea7..57eb1db13f5 100644 --- a/addons/account_edi/models/account_edi_document.py +++ b/addons/account_edi/models/account_edi_document.py @@ -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() diff --git a/addons/account_edi/models/account_move.py b/addons/account_edi/models/account_move.py index 1db21d29058..44f24199779 100644 --- a/addons/account_edi/models/account_move.py +++ b/addons/account_edi/models/account_move.py @@ -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}) diff --git a/addons/account_edi/tests/test_edi.py b/addons/account_edi/tests/test_edi.py index bfd8f6f8518..04df5f12879 100644 --- a/addons/account_edi/tests/test_edi.py +++ b/addons/account_edi/tests/test_edi.py @@ -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")