From 2daa9a1673f5ef501ad6f294f7730810c356718d Mon Sep 17 00:00:00 2001 From: Thibault Francois Date: Mon, 6 Dec 2021 14:35:33 +0000 Subject: [PATCH] [FIX] sale: send invoice only when ready to be send Issues ------ In the module sale, with the automatic invoicing configured. Payment transaction generate, post and send the invoice when they are post process. In some country the invoice is not directly ready to be sent due to some edi document Solution -------- use the mechanism introduce with https://github.com/odoo/odoo/commit/3a29371eb70309f46e3b8938434287fefc23b351 to delay the sending. Introduce a cron that check invoice to send closes odoo/odoo#81526 X-original-commit: 97b74d8304337d901d9422418cbd0dcdcc46fcf5 Signed-off-by: Antoine Vandevenne (anv) --- addons/sale/__init__.py | 10 +++++ addons/sale/__manifest__.py | 1 + addons/sale/data/sale_data.xml | 12 ++++++ addons/sale/models/account_move.py | 16 ++++++++ addons/sale/models/payment_transaction.py | 50 +++++++++++++++++++---- addons/sale/models/res_config_settings.py | 4 ++ 6 files changed, 84 insertions(+), 9 deletions(-) diff --git a/addons/sale/__init__.py b/addons/sale/__init__.py index b91ad4c9d06..a67265753e7 100644 --- a/addons/sale/__init__.py +++ b/addons/sale/__init__.py @@ -6,3 +6,13 @@ from . import controllers from . import report from . import wizard from . import populate + +from odoo.api import Environment, SUPERUSER_ID + + +def _synchronize_cron(cr, registry): + env = Environment(cr, SUPERUSER_ID, {'active_test': False}) + send_invoice_cron = env.ref('sale.send_invoice_cron', raise_if_not_found=False) + if send_invoice_cron: + config = env['ir.config_parameter'].get_param('sale.automatic_invoice', False) + send_invoice_cron.active = bool(config) diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py index 94035204a25..ddc91098081 100644 --- a/addons/sale/__manifest__.py +++ b/addons/sale/__manifest__.py @@ -72,5 +72,6 @@ This module contains all the common features of Sales Management and eCommerce. 'sale/static/tests/sales_team_dashboard_tests.js', ], }, + 'post_init_hook': '_synchronize_cron', 'license': 'LGPL-3', } diff --git a/addons/sale/data/sale_data.xml b/addons/sale/data/sale_data.xml index 0f3abd5937b..8c7037993fb 100644 --- a/addons/sale/data/sale_data.xml +++ b/addons/sale/data/sale_data.xml @@ -16,5 +16,17 @@ sale.default_confirmation_template + + + automatic invoicing: send ready invoice + + code + model._cron_send_invoice() + + 1 + days + -1 + + diff --git a/addons/sale/models/account_move.py b/addons/sale/models/account_move.py index 3552fe478f8..c854192372a 100644 --- a/addons/sale/models/account_move.py +++ b/addons/sale/models/account_move.py @@ -109,3 +109,19 @@ class AccountMove(models.Model): for (order, name) in todo: order.message_post(body=_("Invoice %s paid", name)) return res + + def _get_invoice_delivery_partner_id(self): + # OVERRIDE + self.ensure_one() + return self.partner_shipping_id.id or super(AccountMove, self)._get_invoice_delivery_partner_id() + + def _action_invoice_ready_to_be_sent(self): + # OVERRIDE + # Make sure the send invoice CRON is called when an invoice becomes ready to be sent by mail. + res = super()._action_invoice_ready_to_be_sent() + + send_invoice_cron = self.env.ref('sale.send_invoice_cron', raise_if_not_found=False) + if send_invoice_cron: + send_invoice_cron._trigger() + + return res diff --git a/addons/sale/models/payment_transaction.py b/addons/sale/models/payment_transaction.py index f7e5d83c5a4..efaf2de2545 100644 --- a/addons/sale/models/payment_transaction.py +++ b/addons/sale/models/payment_transaction.py @@ -3,6 +3,9 @@ import logging import re +from datetime import datetime +from dateutil import relativedelta + from odoo import api, fields, models, _, SUPERUSER_ID from odoo.tools import float_compare @@ -98,17 +101,46 @@ class PaymentTransaction(models.Model): self._invoice_sale_orders() res = super()._reconcile_after_done() if self.env['ir.config_parameter'].sudo().get_param('sale.automatic_invoice') and any(so.state in ('sale', 'done') for so in self.sale_order_ids): - default_template = self.env['ir.config_parameter'].sudo().get_param('sale.default_invoice_email_template') - if default_template: - for trans in self.filtered(lambda t: t.sale_order_ids.filtered(lambda so: so.state in ('sale', 'done'))): - trans = trans.with_company(trans.acquirer_id.company_id).with_context( - mark_invoice_as_sent=True, - company_id=trans.acquirer_id.company_id.id, - ) - for invoice in trans.invoice_ids.with_user(SUPERUSER_ID): - invoice.message_post_with_template(int(default_template), email_layout_xmlid="mail.mail_notification_paynow") + self.filtered(lambda t: t.sale_order_ids.filtered(lambda so: so.state in ('sale', 'done')))._send_invoice() return res + def _send_invoice(self): + default_template = self.env['ir.config_parameter'].sudo().get_param('sale.default_invoice_email_template') + if not default_template: + return + + for trans in self: + trans = trans.with_company(trans.acquirer_id.company_id).with_context( + company_id=trans.acquirer_id.company_id.id, + ) + invoice_to_send = trans.invoice_ids.filtered( + lambda i: not i.is_move_sent and i.state == 'posted' and i._is_ready_to_be_sent() + ) + invoice_to_send.is_move_sent = True # Mark invoice as sent + for invoice in invoice_to_send.with_user(SUPERUSER_ID): + invoice.message_post_with_template(int(default_template), email_layout_xmlid="mail.mail_notification_paynow") + + def _cron_send_invoice(self): + """ + Cron to send invoice that where not ready to be send directly after posting + """ + if not self.env['ir.config_parameter'].sudo().get_param('sale.automatic_invoice'): + return + + # No need to retrieve old transactions + retry_limit_date = datetime.now() - relativedelta.relativedelta(days=2) + # Retrieve all transactions matching the criteria for post-processing + self.search([ + ('state', '=', 'done'), + ('is_post_processed', '=', True), + ('invoice_ids', 'in', self.env['account.move']._search([ + ('is_move_sent', '=', False), + ('state', '=', 'posted'), + ])), + ('sale_order_ids.state', 'in', ('sale', 'done')), + ('last_state_change', '>=', retry_limit_date), + ])._send_invoice() + def _invoice_sale_orders(self): if self.env['ir.config_parameter'].sudo().get_param('sale.automatic_invoice'): for trans in self.filtered(lambda t: t.sale_order_ids): diff --git a/addons/sale/models/res_config_settings.py b/addons/sale/models/res_config_settings.py index df4482fe99a..242c97d508e 100644 --- a/addons/sale/models/res_config_settings.py +++ b/addons/sale/models/res_config_settings.py @@ -75,6 +75,10 @@ class ResConfigSettings(models.TransientModel): if self.default_invoice_policy != 'order': self.env['ir.config_parameter'].set_param('sale.automatic_invoice', False) + send_invoice_cron = self.env.ref('sale.send_invoice_cron', raise_if_not_found=False) + if send_invoice_cron: + send_invoice_cron.active = self.automatic_invoice + @api.onchange('use_quotation_validity_days') def _onchange_use_quotation_validity_days(self): if self.quotation_validity_days <= 0: