summaryrefslogtreecommitdiff
path: root/addons/project
diff options
context:
space:
mode:
authorVictor Feyens <vfe@odoo.com>2020-12-17 11:16:36 +0000
committerVictor Feyens <vfe@odoo.com>2020-12-17 11:39:56 +0000
commite4943afef0f66703bafd78780764ff6f0b039d3e (patch)
tree7b92f3fac946f65a5a9e2c77f2bae50d0b40a545 /addons/project
parente7be857bea964628b80d4d3c1314dc231e0da947 (diff)
[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#63518 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
Diffstat (limited to 'addons/project')
-rw-r--r--addons/project/models/project_task_recurrence.py3
-rw-r--r--addons/project/tests/test_project_recurrence.py105
2 files changed, 107 insertions, 1 deletions
diff --git a/addons/project/models/project_task_recurrence.py b/addons/project/models/project_task_recurrence.py
index 14be6ca224c..0202a12f878 100644
--- a/addons/project/models/project_task_recurrence.py
+++ b/addons/project/models/project_task_recurrence.py
@@ -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
diff --git a/addons/project/tests/test_project_recurrence.py b/addons/project/tests/test_project_recurrence.py
index 4cf13a730af..63090189070 100644
--- a/addons/project/tests/test_project_recurrence.py
+++ b/addons/project/tests/test_project_recurrence.py
@@ -56,7 +56,7 @@ class TestProjectrecurrence(SavepointCase):
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(SavepointCase):
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({