[FIX] mass_mailing: More accurate next_departure taking into account cron.triggers
When the user schedules a datetime for the emailing campaign, it creates
a ir.cron.trigger entry that should run the Mail Marketing: Process queue.
This fix tries to take this into account to calculate a next_departure
datetime closer to the true moment job will be run.
When the schedule datetime is between now() and the next call datetime
for the cron job (nextcall field), current logic will always chose
nextcall given the use of max(). This can be confusing for the end-user
as the information banner in the form view will indicate the nextcall date,
which in some cases can be way farther in the futur.
While a ir.cron.trigger is not a 100% guarantee that the cron job will
run at that specific time, it will give better feedback to the user for
when he should expected his mailing campaign to be send out.
Main changes:
simplified logic in _compute_next_departure to take into account
the implicit creation of cron.triggers (methods action_launch and
action_schedule)
added test_mailing_next_departure to simulate "Send", "Cancel" and
"Schedule Date" buttons on mass_mailing form view. Check ir.cron.trigger
model for presence of triggers for cron job "Mail Marketing: Process queue"
and assert if cron.trigger was created as expected
opw-3137819
original commit b98494d
some trailing whitespace got linted from commit 4cebf5
closes odoo/odoo#115068
X-original-commit: f82dec9e6433dc027391978420237efaa1d0be7d
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
This commit is contained in:
@@ -329,15 +329,19 @@ class MassMailing(models.Model):
|
||||
self.browse(row.pop('mailing_id')).update(row)
|
||||
|
||||
def _compute_next_departure(self):
|
||||
cron_next_call = self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo().nextcall
|
||||
str2dt = fields.Datetime.from_string
|
||||
cron_time = str2dt(cron_next_call)
|
||||
# Schedule_date should only be False if schedule_type = "now" or
|
||||
# mass_mailing is canceled.
|
||||
# A cron.trigger is created when mailing is put "in queue"
|
||||
# so we can reasonably expect that the cron worker will
|
||||
# execute this based on the cron.trigger's call_at which should
|
||||
# be now() when clicking "Send" or schedule_date if scheduled
|
||||
|
||||
for mass_mailing in self:
|
||||
if mass_mailing.schedule_date:
|
||||
schedule_date = str2dt(mass_mailing.schedule_date)
|
||||
mass_mailing.next_departure = max(schedule_date, cron_time)
|
||||
# max in case the user schedules a date in the past
|
||||
mass_mailing.next_departure = max(mass_mailing.schedule_date, fields.datetime.now())
|
||||
else:
|
||||
mass_mailing.next_departure = cron_time
|
||||
mass_mailing.next_departure = fields.datetime.now()
|
||||
|
||||
@api.depends('email_from', 'mail_server_id')
|
||||
def _compute_warning_message(self):
|
||||
@@ -931,7 +935,7 @@ class MassMailing(models.Model):
|
||||
JOIN %(target)s t ON (s.res_id = t.id)
|
||||
%(join_domain)s
|
||||
WHERE substring(t.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
|
||||
%(where_domain)s
|
||||
%(where_domain)s
|
||||
"""
|
||||
|
||||
# Apply same 'get email field' rule from mail_thread.message_get_default_recipients
|
||||
@@ -942,9 +946,9 @@ class MassMailing(models.Model):
|
||||
FROM mailing_trace s
|
||||
JOIN %(target)s t ON (s.res_id = t.id)
|
||||
JOIN res_partner p ON (t.partner_id = p.id)
|
||||
%(join_domain)s
|
||||
%(join_domain)s
|
||||
WHERE substring(p.%(mail_field)s, '([^ ,;<@]+@[^> ,;]+)') IS NOT NULL
|
||||
%(where_domain)s
|
||||
%(where_domain)s
|
||||
"""
|
||||
elif issubclass(type(target), self.pool['mail.thread.blacklist']):
|
||||
mail_field = 'email_normalized'
|
||||
|
||||
@@ -2,16 +2,118 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.addons.base.tests.test_ir_cron import CronMixinCase
|
||||
from odoo.addons.mass_mailing.tests.common import MassMailCommon
|
||||
from odoo.tests import users, Form
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestMailingScheduleDateWizard(MassMailCommon):
|
||||
class TestMailingScheduleDateWizard(MassMailCommon, CronMixinCase):
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
@users('user_marketing')
|
||||
def test_mailing_next_departure(self):
|
||||
# test if mailing.mailing.next_departure is correctly set taking into account
|
||||
# presence of implicitly created cron triggers (since odoo v15). These should
|
||||
# launch cron job before its schedule nextcall datetime (if scheduled_date < nextcall)
|
||||
|
||||
cron_job = self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo()
|
||||
cron_job.write({'nextcall' : datetime(2023, 2, 18, 9, 0)})
|
||||
cron_job_id = cron_job.id
|
||||
|
||||
# case where user click on "Send" button (action_launch)
|
||||
with freeze_time(datetime(2023, 2, 17, 9, 0)):
|
||||
with self.capture_triggers(cron_job_id) as capt:
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'mailing',
|
||||
'subject': 'some subject',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'state' : 'draft'
|
||||
})
|
||||
mailing.action_launch()
|
||||
capt.records.ensure_one()
|
||||
|
||||
# assert that the schedule_date and schedule_type fields are correct and that the mailing is put in queue
|
||||
self.assertEqual(mailing.next_departure, datetime(2023, 2, 17, 9, 0))
|
||||
self.assertIsNot(mailing.schedule_date, cron_job.nextcall)
|
||||
self.assertEqual(mailing.schedule_type, 'now')
|
||||
self.assertEqual(mailing.state, 'in_queue')
|
||||
self.assertEqual(capt.records.call_at, datetime(2023, 2, 17, 9, 0)) #verify that cron.trigger exists
|
||||
|
||||
# case where client uses schedule wizard to chose a date between now and cron.job nextcall
|
||||
with freeze_time(datetime(2023, 2, 17, 9, 0)):
|
||||
with self.capture_triggers(cron_job_id) as capt:
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'mailing',
|
||||
'subject': 'some subject',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'state' : 'draft',
|
||||
'schedule_date' : datetime(2023, 2, 17, 11, 0),
|
||||
'schedule_type' : 'scheduled'
|
||||
})
|
||||
mailing.action_schedule()
|
||||
capt.records.ensure_one()
|
||||
|
||||
self.assertEqual(mailing.schedule_date, datetime(2023, 2, 17, 11, 0))
|
||||
self.assertEqual(mailing.next_departure, datetime(2023, 2, 17, 11, 0))
|
||||
self.assertEqual(mailing.schedule_type, 'scheduled')
|
||||
self.assertEqual(mailing.state, 'in_queue')
|
||||
self.assertEqual(capt.records.call_at, datetime(2023, 2, 17, 11, 0)) #verify that cron.trigger exists
|
||||
|
||||
# case where client uses schedule wizard to chose a date after cron.job nextcall
|
||||
# which means mails will get send after that date (datetime(2023, 2, 18, 9, 0))
|
||||
with freeze_time(datetime(2023, 2, 17, 9, 0)):
|
||||
with self.capture_triggers(cron_job_id) as capt:
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'mailing',
|
||||
'subject': 'some subject',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'state' : 'draft',
|
||||
'schedule_date' : datetime(2024, 2, 17, 11, 0),
|
||||
'schedule_type' : 'scheduled'
|
||||
})
|
||||
mailing.action_schedule()
|
||||
capt.records.ensure_one()
|
||||
|
||||
self.assertEqual(mailing.schedule_date, datetime(2024, 2, 17, 11, 0))
|
||||
self.assertEqual(mailing.next_departure, datetime(2024, 2, 17, 11, 0))
|
||||
self.assertEqual(mailing.schedule_type, 'scheduled')
|
||||
self.assertEqual(mailing.state, 'in_queue')
|
||||
self.assertEqual(capt.records.call_at, datetime(2024, 2, 17, 11, 0)) #verify that cron.trigger exists
|
||||
|
||||
# case where client uses schedule wizard to chose a date in the past
|
||||
with freeze_time(datetime(2023, 2, 17, 9, 0)):
|
||||
with self.capture_triggers(cron_job_id) as capt:
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'mailing',
|
||||
'subject': 'some subject',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'state' : 'draft',
|
||||
'schedule_date' : datetime(2024, 2, 17, 11, 0),
|
||||
'schedule_type' : 'scheduled'
|
||||
})
|
||||
# create a schedule date wizard
|
||||
# Have to use wizard for this case to simulate schedule date in the past
|
||||
# Otherwise "state" doesn't get update from draft to'in_queue'
|
||||
# in test env vs production env (see mailing.mailing.schedule.date wizard)
|
||||
|
||||
wizard_form = Form(
|
||||
self.env['mailing.mailing.schedule.date'].with_context(default_mass_mailing_id=mailing.id))
|
||||
|
||||
# set a schedule date
|
||||
wizard_form.schedule_date = datetime(2022, 2, 17, 11, 0)
|
||||
wizard = wizard_form.save()
|
||||
wizard.action_schedule_date()
|
||||
capt.records.ensure_one()
|
||||
|
||||
self.assertEqual(mailing.schedule_date, datetime(2022, 2, 17, 11, 0))
|
||||
self.assertEqual(mailing.next_departure, datetime(2023, 2, 17, 9, 0)) #now
|
||||
self.assertEqual(mailing.schedule_type, 'scheduled')
|
||||
self.assertEqual(mailing.state, 'in_queue')
|
||||
self.assertEqual(capt.records.call_at, datetime(2022, 2, 17, 11, 0)) #verify that cron.trigger exists
|
||||
|
||||
def test_mailing_schedule_date(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'mailing',
|
||||
|
||||
Reference in New Issue
Block a user