hello!
', + 'recurrence_update': 'this', + }) - with freeze_time("2020-01-13"): + with freeze_time(self.date_01_01 + relativedelta(day=13)): self.env['project.task.recurrence']._cron_create_recurring_tasks() task_c, task_b, task_a = self.env['project.task'].search([('project_id', '=', self.project_recurring.id)]) - self.set_task_create_date(task_a.id, datetime(2020, 1, 1)) - self.set_task_create_date(task_b.id, datetime(2020, 1, 6)) - self.set_task_create_date(task_c.id, datetime(2020, 1, 13)) - (task_a+task_b+task_c).invalidate_model() + self.assertEqual(task_a.name, 'my super updated task', 'This task has been directly updated') + self.assertEqual(task_b.name, 'test recurring task', 'As this task already existed, it should not have been updated') + self.assertEqual(task_c.name, 'my super updated task', 'As this task was created after template modification, it should be updated') - task_c.write({ - 'name': 'my super updated task', - 'recurrence_update': 'all', - }) - - self.assertEqual(task_a.name, 'my super updated task') - self.assertEqual(task_b.name, 'my super updated task') - self.assertEqual(task_c.name, 'my super updated task') - - task_a.write({ - 'name': 'don\'t you dare change my title', - 'recurrence_update': 'this', - }) - - self.assertEqual(task_a.name, 'don\'t you dare change my title') - self.assertEqual(task_b.name, 'my super updated task') - self.assertEqual(task_c.name, 'my super updated task') - - task_b.write({ - 'description': 'hello!', - 'recurrence_update': 'subsequent', - }) - - self.assertEqual(task_a.description, False) - self.assertEqual(task_b.description, 'hello!
') - self.assertEqual(task_c.description, 'hello!
') + self.assertEqual(task_a.description, 'hello!
', 'This task has been directly updated') + self.assertEqual(task_b.description, False, 'Future tasks should not be affected by "this" modifications') + self.assertEqual(task_c.description, False, 'Future tasks should not be affected by "this" modifications') def test_recurrence_fields_visibility(self): form = Form(self.env['project.task']) @@ -540,253 +544,43 @@ class TestProjectrecurrence(TransactionCase): self.assertEqual(dates[3], datetime(2024, 11, 30)) self.assertEqual(dates[4], datetime(2025, 11, 30)) - def test_recurrence_cron_repeat_after_subtasks(self): + def test_change_subtask_of_recurrent_task(self): + with freeze_time(self.date_01_01): + task = self.env['project.task'].create({ + 'name': 'recurring task', + 'project_id': self.project_recurring.id, + 'recurring_task': True, + 'repeat_interval': 2, + 'repeat_unit': 'week', + 'repeat_type': 'after', + 'repeat_number': 2, + 'mon': True, + 'child_ids': [ + Command.create({'name': 'subtask'}) + ], + }) + recurrence = task.recurrence_id + task.child_ids.write({ + 'name': 'subtask 1', + 'recurrence_update': 'future', + }) - def get_task_and_subtask_counts(domain): - tasks = self.env['project.task'].search(domain) - return tasks, len(tasks), len(tasks.filtered('parent_id')) - - # Required for `child_ids` to be visible in the view - # {'invisible': [('allow_subtasks', '=', False)]} - self.project_recurring.allow_subtasks = True - parent_task = self.env['project.task'].create({ - 'name': 'Parent Task', - 'project_id': self.project_recurring.id - }) - child_task = self.env['project.task'].create({ - 'name': 'Child Task', - 'parent_id': parent_task.id, - }) - domain = [('project_id', '=', self.project_recurring.id)] - with freeze_time("2020-01-01"): - with Form(child_task.with_context({'tracking_disable': True})) as form: - form.description = 'my super recurring task bla bla bla' - form.date_deadline = datetime(2020, 2, 1) - form.display_project_id = parent_task.project_id - - form.recurring_task = True - form.repeat_interval = 1 - form.repeat_unit = 'month' - form.repeat_type = 'after' - form.repeat_number = 2 - form.repeat_on_month = 'date' - form.repeat_day = '15' - subtask = form.save() - subtask.planned_hours = 2 - - self.assertEqual(subtask.recurrence_id.next_recurrence_date, date(2020, 1, 15)) - project_tasks, project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, 2) - self.assertEqual(project_subtask_count, 1) + with freeze_time(self.date_01_15): self.env['project.task.recurrence']._cron_create_recurring_tasks() - self.assertEqual(self.env['project.task'].search_count(domain), 2, 'no extra task should be created') - self.assertEqual(subtask.recurrence_id.recurrence_left, 2) - for task in project_tasks: - self.assertEqual(task.display_project_id, parent_task.project_id, "All tasks should have a display project id set") + child = recurrence.task_ids.sorted('create_date', reverse=True)[0].child_ids + self.assertEqual(child.name, 'subtask 1') + child.write({ + 'name': 'subtask 2', + 'recurrence_update': 'this', + }) - with freeze_time("2020-01-15"): - project_tasks, project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, 2) - self.assertEqual(project_subtask_count, 1) + with freeze_time(self.date_02_01): self.env['project.task.recurrence']._cron_create_recurring_tasks() - project_tasks, project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, 3) - self.assertEqual(project_subtask_count, 2) - self.assertEqual(subtask.recurrence_id.recurrence_left, 1) - for task in project_tasks: - self.assertEqual(task.display_project_id, parent_task.project_id, "All tasks should have a display project id set") + child = recurrence.task_ids.sorted('create_date', reverse=True)[0].child_ids + self.assertEqual(child.name, 'subtask 1') - with freeze_time("2020-02-15"): - self.env['project.task.recurrence']._cron_create_recurring_tasks() - project_tasks, project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, 4) - self.assertEqual(project_subtask_count, 3) - self.assertEqual(subtask.recurrence_id.recurrence_left, 0) - for task in project_tasks: - self.assertEqual(task.display_project_id, parent_task.project_id, "All tasks should have a display project id set") - self.env['project.task.recurrence']._cron_create_recurring_tasks() - _, project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, 4) - self.assertEqual(project_subtask_count, 3) - self.assertEqual(subtask.recurrence_id.recurrence_left, 0) - - tasks = self.env['project.task'].search(domain) - self.assertEqual(len(tasks), 4) - self.assertTrue(bool(tasks[0].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_after_subsubtasks(self): - """ - Tests how the recurrence is working when a task has subtasks that have recurrence too - We have at the beginning: - index Task name Recurrent parent - 0 Parent Task no no - 1 Subtask 1 no Parent task - 2 Subtask 2 Montly, 15, for 2 tasks Parent task - 3 Grand child task 1 Daily, 5 tasks Subtask 2 that has recurrence - 4 Grand child task 2 no Subtask 2 that has recurrence - 5 Grand child task 3 no Grand child task 2 - 6 Grand child task 4 no Grand child task 3 - 7 Grand child task 5 no Grand child task 4 - 1) After 5 days (including today), there will be 5 occurences of *task index 3*. - 2) After next 15th of the month, there will be 2 occurences of *task index 2* and a *copy of tasks 3, 4, 5, 6* (not 7) - 3) 5 days afterwards, there will be 5 occurences of the *copy of task index 3* - 4) The 15th of the next month, there won't be any other new occurence since all recurrences have been consumed. - """ - - def get_task_and_subtask_counts(domain): - tasks = self.env['project.task'].search(domain) - return len(tasks), len(tasks.filtered('parent_id')) - - # Phase 0 : Initialize test case - # Required for `child_ids` to be visible in the view - # {'invisible': [('allow_subtasks', '=', False)]} - self.project_recurring.allow_subtasks = True - parent_task = self.env['project.task'].create({ - 'name': 'Parent Task', - 'project_id': self.project_recurring.id - }) - domain = [('project_id', '=', self.project_recurring.id)] - child_task_1, child_task_2_recurrence = self.env['project.task'].create([ - {'name': 'Child task 1'}, - {'name': 'Child task 2 that have recurrence'}, - ]) - with Form(parent_task.with_context({'tracking_disable': True})) as task_form: - task_form.child_ids.add(child_task_1) - task_form.child_ids.add(child_task_2_recurrence) - - grand_child_task_1 = self.env['project.task'].create({ - 'name': 'Grandchild task 1 (recurrent)', - }) - grand_child_task_2_recurrence = self.env['project.task'].create({ - 'name': 'Grandchild task 2', - }) - with freeze_time("2020-01-01"): - recurrent_subtask = parent_task.child_ids[0] - with Form(recurrent_subtask.with_context(tracking_disable=True)) as task_form: - task_form.recurring_task = True - task_form.repeat_interval = 1 - task_form.repeat_unit = 'month' - task_form.repeat_type = 'after' - task_form.repeat_number = 1 - task_form.repeat_on_month = 'date' - task_form.repeat_day = '15' - task_form.date_deadline = datetime(2020, 2, 1) - task_form.child_ids.add(grand_child_task_1) - task_form.child_ids.add(grand_child_task_2_recurrence) - # configure recurring subtask - recurrent_subsubtask = recurrent_subtask.child_ids.filtered(lambda t: t.name == 'Grandchild task 1 (recurrent)') - non_recurrent_subsubtask = recurrent_subtask.child_ids.filtered(lambda t: t.name == 'Grandchild task 2') - with Form(recurrent_subsubtask.with_context(tracking_disable=True)) as subtask_form: - subtask_form.recurring_task = True - subtask_form.repeat_interval = 1 - subtask_form.repeat_unit = 'day' - subtask_form.repeat_type = 'after' - subtask_form.repeat_number = 4 - subtask_form.date_deadline = datetime(2020, 2, 3) - - grand_child_task_3, grand_child_task_4, grand_child_task_5 = self.env['project.task'].create([ - {'name': 'Grandchild task 3'}, - {'name': 'Grandchild task 4'}, - {'name': 'Grandchild task 5'}, - ]) - # create non-recurring grandchild subtasks - with Form(non_recurrent_subsubtask.with_context(tracking_disable=True)) as subtask_form: - subtask_form.child_ids.add(grand_child_task_3) - non_recurrent_subsubtask = non_recurrent_subsubtask.child_ids - with Form(non_recurrent_subsubtask.with_context(tracking_disable=True)) as subtask_form: - subtask_form.child_ids.add(grand_child_task_4) - non_recurrent_subsubtask = non_recurrent_subsubtask.child_ids - with Form(non_recurrent_subsubtask.with_context(tracking_disable=True)) as subtask_form: - subtask_form.child_ids.add(grand_child_task_5) - - self.assertTrue(recurrent_subtask.recurrence_id) - self.assertEqual(recurrent_subtask.recurrence_id.next_recurrence_date, date(2020, 1, 15)) - project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, 8) - self.assertEqual(project_subtask_count, 7) - self.env['project.task.recurrence']._cron_create_recurring_tasks() - self.assertEqual(self.env['project.task'].search_count(domain), 8, 'no extra task should be created') - all_tasks = self.env['project.task'].search(domain) - task_names = all_tasks.parent_id.mapped('name') - self.assertEqual(task_names.count('Parent Task'), 1) - self.assertEqual(task_names.count('Child task 2 that have recurrence'), 1) - self.assertEqual(task_names.count('Grandchild task 2'), 1) - self.assertEqual(task_names.count('Grandchild task 3'), 1) - self.assertEqual(task_names.count('Grandchild task 4'), 1) - - # Phase 1 : Verify recurrences of Grandchild task 1 (recurrent) - n = 8 - for i in range(1, 5): - with freeze_time("2020-01-%02d" % (i + 1)): - self.env['project.task.recurrence']._cron_create_recurring_tasks() - project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, n + i) # + 1 occurence of task 3 - self.assertEqual(project_subtask_count, n + i - 1) - with freeze_time("2020-01-11"): - self.env['project.task.recurrence']._cron_create_recurring_tasks() - project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, 12) # total = 5 occurences of task 3 - self.assertEqual(project_subtask_count, 11) - - # Phase 2 : Verify recurrences of Child task 2 that have recurrence - with freeze_time("2020-01-15"): - self.env['project.task.recurrence']._cron_create_recurring_tasks() - project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - all_tasks = self.env['project.task'].search(domain) - task_names = all_tasks.parent_id.mapped('name') - self.assertEqual(task_names.count('Parent Task'), 1) - self.assertEqual(task_names.count('Child task 2 that have recurrence'), 2) - self.assertEqual(task_names.count('Grandchild task 2'), 2) - self.assertEqual(task_names.count('Grandchild task 3'), 2) - self.assertEqual(task_names.count('Grandchild task 4'), 1) - self.assertEqual(len(task_names), 8) - self.assertEqual(project_task_count, 12 + 1 + 4) # 12 + the recurring task 5 + the 2 childs (3, 4) + 1 grandchild (5) + 1 grandgrandchild (6) - self.assertEqual(project_subtask_count, 16) - bottom_genealogy = all_tasks.filtered(lambda t: not t.child_ids.exists()) - bottom_genealogy_name = bottom_genealogy.mapped('name') - self.assertEqual(bottom_genealogy_name.count('Child task 1'), 1) - self.assertEqual(bottom_genealogy_name.count('Grandchild task 1 (recurrent)'), 6) - # Grandchild task 5 should not be copied ! - self.assertEqual(bottom_genealogy_name.count('Grandchild task 5'), 1) - - # Phase 3 : Verify recurrences of the copy of Grandchild task 1 (recurrent) - n = 17 - for i in range(1, 5): - with freeze_time("2020-01-%02d" % (i + 15)): - self.env['project.task.recurrence']._cron_create_recurring_tasks() - project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, n + i) - self.assertEqual(project_subtask_count, n + i - 1) - with freeze_time("2020-01-25"): - self.env['project.task.recurrence']._cron_create_recurring_tasks() - project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, 21) - self.assertEqual(project_subtask_count, 20) - - # Phase 4 : No more recurrence - with freeze_time("2020-02-15"): - self.env['project.task.recurrence']._cron_create_recurring_tasks() - project_task_count, project_subtask_count = get_task_and_subtask_counts(domain) - self.assertEqual(project_task_count, 21) - self.assertEqual(project_subtask_count, 20) - - all_tasks = self.env['project.task'].search(domain) - self.assertEqual(len(all_tasks), 21) - deadlines = all_tasks.sorted('create_date').mapped('date_deadline') - self.assertTrue(bool(deadlines[0])) - self.assertTrue(bool(deadlines[1])) - del deadlines[1] - del deadlines[0] - self.assertTrue(not any(deadlines), "Deadline should not be copied") - - bottom_genealogy = all_tasks.filtered(lambda t: not t.child_ids.exists()) - bottom_genealogy_name = bottom_genealogy.mapped('name') - self.assertEqual(bottom_genealogy_name.count('Child task 1'), 1) - self.assertEqual(bottom_genealogy_name.count('Grandchild task 1 (recurrent)'), 10) - self.assertEqual(bottom_genealogy_name.count('Grandchild task 5'), 1) - for f in self.env['project.task.recurrence']._get_recurring_fields(): - self.assertTrue(all_tasks[2][f] == all_tasks[3][f] == all_tasks[4][f], "Field %s should have been copied" % f) + with self.assertRaises(ValidationError): + child.write({ + 'recurring_task': True, + 'repeat_unit': 'week', + }) diff --git a/addons/project/views/project_sharing_views.xml b/addons/project/views/project_sharing_views.xml index 3a50cdf5b54..29c2467c542 100644 --- a/addons/project/views/project_sharing_views.xml +++ b/addons/project/views/project_sharing_views.xml @@ -249,7 +249,12 @@
No tasks found. Let's create one!
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index b83008ef31b..28664242cee 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -144,7 +144,12 @@
Edit recurring task Edit recurring task Edit recurring task's child