diff --git a/addons/project/data/project_demo.xml b/addons/project/data/project_demo.xml
index 2bba5e2e0a3..aaa602f29aa 100644
--- a/addons/project/data/project_demo.xml
+++ b/addons/project/data/project_demo.xml
@@ -7,6 +7,11 @@
+
+
+
+
+
Bug
@@ -875,5 +880,94 @@ Send it ASAP, its urgent.
at_risk
+
+
+
+ 3
+ month
+ date
+ after
+ 4
+ 1
+ mon
+
+
+
+
+
+
+ 20
+
+
+
+
+
+ after
+ 20
+
+
+
+
+
+ 20
+ 20.0
+
+
+ Customer review
+
+
+
+
+
+
+ 20
+ 0.25
+
+
+
+ Daily stand-up meeting - Send minutes
+
+
+
+
+
+
+ 20
+ 8.0
+
+
+
+ Customer Meeting
+
+
+
+ 10
+ 2.0
+
+
+
+ Daily Meetings summary
+
+
+
+ 20
+ 2.0
+
+
+
+ Preparation
+
+
+
+ 30
+ 2.0
+
+
+
+ Minutes
+
+
+
+
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 6be16a3c627..c9828112dd2 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -1419,8 +1419,9 @@ class Task(models.Model):
@api.constrains('recurring_task')
def _check_recurring_task(self):
- if self.filtered(lambda task: task.parent_id and task.recurring_task):
- raise ValidationError(_("A sub-task cannot be a recurring task. Please consider making its parent task, a recurring task."))
+ # Deprecated method
+ # TODO : Remove me in master
+ return
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
diff --git a/addons/project/tests/test_project_recurrence.py b/addons/project/tests/test_project_recurrence.py
index 561bd17447b..6f7c2470fa4 100644
--- a/addons/project/tests/test_project_recurrence.py
+++ b/addons/project/tests/test_project_recurrence.py
@@ -458,3 +458,71 @@ class TestProjectrecurrence(TransactionCase):
self.assertEqual(dates[2], datetime(2023, 11, 30))
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 get_task_and_subtask_counts(domain):
+ tasks = self.env['project.task'].search(domain)
+ return len(tasks), len(tasks.filtered('parent_id'))
+
+ parent_task = self.env['project.task'].create({
+ 'name': 'Parent Task',
+ 'project_id': self.project_recurring.id
+ })
+ domain = [('project_id', '=', self.project_recurring.id)]
+ with Form(parent_task.with_context({'tracking_disable': True})) as task_form:
+ with task_form.child_ids.new() as subtask_form:
+ subtask_form.name = 'Test Subtask 1'
+ with freeze_time("2020-01-01"):
+ with Form(parent_task.child_ids.with_context({'tracking_disable': True})) as form:
+ form.description = 'my super recurring task bla bla bla'
+ form.date_deadline = datetime(2020, 2, 1)
+
+ 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_task_count, project_subtask_count = get_task_and_subtask_counts(domain)
+ self.assertEqual(project_task_count, 2)
+ self.assertEqual(project_subtask_count, 1)
+ 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)
+
+ with freeze_time("2020-01-15"):
+ project_task_count, project_subtask_count = get_task_and_subtask_counts(domain)
+ self.assertEqual(project_task_count, 2)
+ self.assertEqual(project_subtask_count, 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, 3)
+ self.assertEqual(project_subtask_count, 2)
+ self.assertEqual(subtask.recurrence_id.recurrence_left, 1)
+
+ 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, 4)
+ self.assertEqual(project_subtask_count, 3)
+ self.assertEqual(subtask.recurrence_id.recurrence_left, 0)
+ 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[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)
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index 64403a217a7..4e2fce8e87a 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -852,7 +852,7 @@
-
+