diff --git a/bemade_fsm/models/task.py b/bemade_fsm/models/task.py index 3cc7219..296a7c6 100644 --- a/bemade_fsm/models/task.py +++ b/bemade_fsm/models/task.py @@ -184,17 +184,21 @@ class Task(models.Model): rec.allow_billable = rec.project_id.allow_billable def action_fsm_validate(self): - visits = self.filtered(lambda t: t.visit_id) - non_visits = self - visits + # Override to make sure all descendant tasks are validated as well + full_hierarchy = self._get_full_hierarchy() + visits = self.filtered(lambda t: bool(t.visit_id)) + non_visits = full_hierarchy - visits super(Task, non_visits).action_fsm_validate() - visits._stop_all_timers_and_create_timesheets() closed_stage_by_project = visits._get_closed_stage_by_project() - super(Task, visits.child_ids).action_fsm_validate() for visit in visits: stage = closed_stage_by_project[visit.project_id] visits.write({'stage_id': stage.id, 'fsm_done': True}) + def _get_full_hierarchy(self): + if self.child_ids: + return self | self.child_ids._get_full_hierarchy() + return self def synchronize_name_fsm(self): """ Applies naming to the entire task tree for tasks that are part of this recordset. Root tasks are named: diff --git a/bemade_fsm/tests/test_bemade_fsm_common.py b/bemade_fsm/tests/test_bemade_fsm_common.py index 613db7b..a0fef55 100644 --- a/bemade_fsm/tests/test_bemade_fsm_common.py +++ b/bemade_fsm/tests/test_bemade_fsm_common.py @@ -192,3 +192,16 @@ class BemadeFSMBaseTest(TransactionCase): sol1.sequence = 2 sol2.sequence = 3 return so, visit, sol1, sol2 + + def _generate_so_with_one_visit_two_lines_and_descendants(self): + so = self._generate_sale_order() + visit = self._generate_visit(sale_order=so) + task_template = self._generate_task_template(structure=[2, 2, 2], + names=['Parent', 'Child', 'Grandchild', 'Great-grandchild']) + product = self._generate_product(task_template=task_template) + sol1 = self._generate_sale_order_line(sale_order=so, product=product) + sol2 = self._generate_sale_order_line(sale_order=so, product=product) + visit.so_section_id.sequence = 1 + sol1.sequence = 2 + sol2.sequence = 3 + return so, visit, sol1, sol2 diff --git a/bemade_fsm/tests/test_fsm_visit.py b/bemade_fsm/tests/test_fsm_visit.py index 137410e..0121d9e 100644 --- a/bemade_fsm/tests/test_fsm_visit.py +++ b/bemade_fsm/tests/test_fsm_visit.py @@ -102,18 +102,19 @@ class FSMVisitTest(BemadeFSMBaseTest): self.assertEqual(len(so.order_line), 3) def test_marking_visit_task_done_completes_descendants(self): - so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines() + so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines_and_descendants() so.action_confirm() - parent, child1, child2 = visit.task_id, sol1.task_id, sol2.task_id + parent = visit.task_id parent.action_fsm_validate() - self.assertTrue(parent.is_closed) - self.assertTrue(child1.is_closed) - self.assertTrue(child2.is_closed) - self.assertEqual(sol1.qty_to_deliver, 0) - self.assertEqual(sol2.qty_to_deliver, 0) - self.assertTrue(visit.is_completed) + self._assert_is_done(parent) + + def _assert_is_done(self, task): + """ Recursively assert all tasks in a hierarchy are complete """ + self.assertTrue(task.is_closed) + for child in task.child_ids: + self._assert_is_done(child) def test_marking_visit_task_done_does_not_create_sale_order_line(self): so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines()