[FIX] project: fix infinite recurring tasks creation
for repeat_type = 'forever' recurrences. Consider a recurrence creating a task every 15th day of the month. When the cron runs, it updates the next recurrence dates of the recurrence. But currently, the updated "next_recurrence_date" can be set to an earlier date than today, if the fixed day of the recurring task is earlier than the current day. This means that the recurring task to create every 15th of the month will be created the 15, the 16, the 17, the 18, ... until the next month begins (no task will be created when the cron runs before the 15th of a month). This commit enforces the next recurrence_date to be set after the current cron run, to ensure a task isn't wrongly created multiple times. This commit also includes the tests that led to the discovery of this bug. closes odoo/odoo#63989 X-original-commit: e4943afef0f66703bafd78780764ff6f0b039d3e Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com> Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
This commit is contained in:
@@ -182,6 +182,9 @@ class ProjectTaskRecurrence(models.Model):
|
||||
count = delta.years * 12 + delta.months
|
||||
for i in range(count):
|
||||
start = start.replace(day=min(repeat_day, monthrange(start.year, start.month)[1]))
|
||||
if i == 0 and start < date_start:
|
||||
# Ensure the next recurrence is in the future
|
||||
start += relativedelta(months=repeat_interval)
|
||||
dates.append(start)
|
||||
start += relativedelta(months=repeat_interval)
|
||||
return dates
|
||||
|
||||
@@ -56,7 +56,7 @@ class TestProjectrecurrence(TransactionCase):
|
||||
task.recurring_task = False
|
||||
self.assertFalse(bool(task.recurrence_id), 'the recurrence should be deleted')
|
||||
|
||||
def test_recurrence_cron(self):
|
||||
def test_recurrence_cron_repeat_after(self):
|
||||
domain = [('project_id', '=', self.project_recurring.id)]
|
||||
with freeze_time("2020-01-01"):
|
||||
form = Form(self.env['project.task'])
|
||||
@@ -105,6 +105,109 @@ class TestProjectrecurrence(TransactionCase):
|
||||
for f in self.env['project.task.recurrence']._get_recurring_fields():
|
||||
self.assertTrue(tasks[0][f] == tasks[1][f] == tasks[2][f], "Field %s should have been copied" % f)
|
||||
|
||||
def test_recurrence_cron_repeat_until(self):
|
||||
domain = [('project_id', '=', self.project_recurring.id)]
|
||||
with freeze_time("2020-01-01"):
|
||||
form = Form(self.env['project.task'])
|
||||
form.name = 'test recurring task'
|
||||
form.description = 'my super recurring task bla bla bla'
|
||||
form.project_id = self.project_recurring
|
||||
form.date_deadline = datetime(2020, 2, 1)
|
||||
|
||||
form.recurring_task = True
|
||||
form.repeat_interval = 1
|
||||
form.repeat_unit = 'month'
|
||||
form.repeat_type = 'until'
|
||||
form.repeat_until = date(2020, 2, 20)
|
||||
form.repeat_on_month = 'date'
|
||||
form.repeat_day = '15'
|
||||
task = form.save()
|
||||
task.planned_hours = 2
|
||||
|
||||
self.assertEqual(task.recurrence_id.next_recurrence_date, date(2020, 1, 15))
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 1)
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 1, 'no extra task should be created')
|
||||
|
||||
with freeze_time("2020-01-15"):
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 1)
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 2)
|
||||
|
||||
with freeze_time("2020-02-15"):
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 3)
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 3)
|
||||
|
||||
tasks = self.env['project.task'].search(domain)
|
||||
self.assertEqual(len(tasks), 3)
|
||||
|
||||
self.assertTrue(bool(tasks[2].date_deadline))
|
||||
self.assertFalse(tasks[1].date_deadline, "Deadline should not be copied")
|
||||
|
||||
for f in self.env['project.task.recurrence']._get_recurring_fields():
|
||||
self.assertTrue(tasks[0][f] == tasks[1][f] == tasks[2][f], "Field %s should have been copied" % f)
|
||||
|
||||
def test_recurrence_cron_repeat_forever(self):
|
||||
domain = [('project_id', '=', self.project_recurring.id)]
|
||||
with freeze_time("2020-01-01"):
|
||||
form = Form(self.env['project.task'])
|
||||
form.name = 'test recurring task'
|
||||
form.description = 'my super recurring task bla bla bla'
|
||||
form.project_id = self.project_recurring
|
||||
form.date_deadline = datetime(2020, 2, 1)
|
||||
|
||||
form.recurring_task = True
|
||||
form.repeat_interval = 1
|
||||
form.repeat_unit = 'month'
|
||||
form.repeat_type = 'forever'
|
||||
form.repeat_on_month = 'date'
|
||||
form.repeat_day = '15'
|
||||
task = form.save()
|
||||
task.planned_hours = 2
|
||||
|
||||
self.assertEqual(task.recurrence_id.next_recurrence_date, date(2020, 1, 15))
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 1)
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 1, 'no extra task should be created')
|
||||
|
||||
with freeze_time("2020-01-15"):
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 1)
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 2)
|
||||
|
||||
with freeze_time("2020-02-15"):
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 3)
|
||||
|
||||
with freeze_time("2020-02-16"):
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 3)
|
||||
|
||||
with freeze_time("2020-02-17"):
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 3)
|
||||
|
||||
with freeze_time("2020-02-17"):
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 3)
|
||||
|
||||
with freeze_time("2020-03-15"):
|
||||
self.env['project.task.recurrence']._cron_create_recurring_tasks()
|
||||
self.assertEqual(self.env['project.task'].search_count(domain), 4)
|
||||
|
||||
tasks = self.env['project.task'].search(domain)
|
||||
self.assertEqual(len(tasks), 4)
|
||||
|
||||
self.assertTrue(bool(tasks[3].date_deadline))
|
||||
self.assertFalse(tasks[1].date_deadline, "Deadline should not be copied")
|
||||
|
||||
for f in self.env['project.task.recurrence']._get_recurring_fields():
|
||||
self.assertTrue(
|
||||
tasks[0][f] == tasks[1][f] == tasks[2][f] == tasks[3][f],
|
||||
"Field %s should have been copied" % f)
|
||||
|
||||
def test_recurrence_update_task(self):
|
||||
with freeze_time("2020-01-01"):
|
||||
task = self.env['project.task'].create({
|
||||
|
||||
Reference in New Issue
Block a user