diff options
| author | Victor Feyens <vfe@odoo.com> | 2020-12-17 11:16:36 +0000 |
|---|---|---|
| committer | Victor Feyens <vfe@odoo.com> | 2020-12-17 11:39:56 +0000 |
| commit | e4943afef0f66703bafd78780764ff6f0b039d3e (patch) | |
| tree | 7b92f3fac946f65a5a9e2c77f2bae50d0b40a545 /addons/project | |
| parent | e7be857bea964628b80d4d3c1314dc231e0da947 (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.py | 3 | ||||
| -rw-r--r-- | addons/project/tests/test_project_recurrence.py | 105 |
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({ |
