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: