From 7a87bccbd9625472af0ae52991f23ba6f0432b6d Mon Sep 17 00:00:00 2001 From: b0ink <40929320+b0ink@users.noreply.github.com> Date: Wed, 4 Feb 2026 12:25:22 +1100 Subject: [PATCH 1/3] feat: recursively assess dependent tasks if prerequisite changed to fix and resubmit --- app/models/task.rb | 14 +++++ test/models/task_test.rb | 115 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/app/models/task.rb b/app/models/task.rb index e6a166fc6f..f196297d46 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -659,6 +659,20 @@ def assess(task_status, assessor, assess_date = Time.zone.now) # Save the task if save! + if task_status == TaskStatus.fix_and_resubmit + # Look for other submitted tasks from this student that has this task as a prerequisite + # If they are ready for feedback, automatically assess them to fix and resubmit + dependents = TaskPrerequisite.where(prerequisite_id: task_definition.id) + dependents.each do |prereq| + td = prereq.task_definition + task = project.task_for_task_definition(td) + next unless task.task_status == TaskStatus.ready_for_feedback + # Since we are re-calling this assess method, we will recursively check for more prerequisites + task.assess(TaskStatus.fix_and_resubmit, assessor, assess_date) + task.add_text_comment(assessor, "**Automated comment**: A prerequisite task was updated to Fix and Resubmit, so this task was updated as well. You may need to review and update the prerequisite before resubmitting.") + end + end + TaskEngagement.create!(task: self, engagement_time: Time.zone.now, engagement: task_status.name) # Grab the submission for the task if the user made one diff --git a/test/models/task_test.rb b/test/models/task_test.rb index 0e0c5b1c86..cab193b681 100644 --- a/test/models/task_test.rb +++ b/test/models/task_test.rb @@ -1424,4 +1424,119 @@ def test_assessment_lock_to_tutorial_stream assert_not_nil result, "Task should be able to marked complete by tutor" assert_equal TaskStatus.complete, task_hd.task_status, 'Task status should be complete from tutor assessment' end + + def test_prerequisite_tasks_change_to_fix_and_resubmit + unit = FactoryBot.create(:unit, student_count: 1, task_count: 4) + tutor = FactoryBot.create(:user, :tutor) + unit.employ_staff(tutor, Role.tutor) + + td1 = unit.task_definitions.first + td2 = unit.task_definitions.second + td3 = unit.task_definitions.third + td4 = unit.task_definitions.fourth + + [td1, td2, td3, td4].each do |td| + td.update!(start_date: Time.zone.now - 2.weeks, target_date: Time.zone.now + 2.weeks, due_date: Time.zone.now + 3.weeks, target_grade: 0) + end + + TaskPrerequisite.create!( + task_definition: td3, + prerequisite: td2, + task_status_id: TaskStatus.ready_for_feedback.id + ) + + TaskPrerequisite.create!( + task_definition: td2, + prerequisite: td1, + task_status_id: TaskStatus.ready_for_feedback.id + ) + + project = unit.active_projects.first + task1 = project.task_for_task_definition(td1) + task2 = project.task_for_task_definition(td2) + task3 = project.task_for_task_definition(td3) + task4 = project.task_for_task_definition(td4) + + task1.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + task2.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + task3.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + task4.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + task1.reload + task2.reload + task3.reload + task4.reload + + assert_equal TaskStatus.ready_for_feedback, task1.task_status + assert_equal TaskStatus.ready_for_feedback, task2.task_status + assert_equal TaskStatus.ready_for_feedback, task3.task_status + assert_equal TaskStatus.ready_for_feedback, task4.task_status + + # Test case 1: Ensure parent prerequisite is not affected + task2.assess(TaskStatus.fix_and_resubmit, tutor) + + task1.reload + task2.reload + task3.reload + task4.reload + + # Task 1 should not be affected if the dependent task is assessed to fix and resubmit + assert_equal TaskStatus.ready_for_feedback, task1.task_status, "Parent prerequisite should not be affected" + assert_equal TaskStatus.fix_and_resubmit, task2.task_status, "Task should have updated to Fix and Resubmit" + assert_equal TaskStatus.fix_and_resubmit, task3.task_status, "Dependent task should have automatically moved to Fix and Resubmit" + assert_equal TaskStatus.ready_for_feedback, task4.task_status # Task 4 has no prerequsite links + + # Reset status + task1.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + task2.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + task3.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + task4.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + + task2.comments.delete_all + + task1.reload + task2.reload + task3.reload + task4.reload + + # Test case 2: Ensure dependent tasks are recursively moved to fix and resubmit + task1.assess(TaskStatus.fix_and_resubmit, tutor) + + task1.reload + task2.reload + task3.reload + task4.reload + + assert_equal TaskStatus.fix_and_resubmit, task1.task_status, "Task should have updated to Fix and Resubmit" + assert_equal TaskStatus.fix_and_resubmit, task2.task_status, "Dependent task should have automatically moved to Fix and Resubmit" + assert_equal TaskStatus.fix_and_resubmit, task3.task_status, "Dependent task should have automatically moved to Fix and Resubmit" + assert_equal TaskStatus.ready_for_feedback, task4.task_status # Task 4 has no prerequsite links + + lc = task2.comments.last + assert_not lc.nil?, "Automated comment should have been created" + assert lc.comment.start_with?("**Automated comment**: A prerequisite task") + + # Reset status + task1.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + task2.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + task3.trigger_transition(trigger: 'complete', by_user: unit.main_convenor_user) + task4.trigger_transition(trigger: 'ready_for_feedback', by_user: unit.main_convenor_user) + + task1.reload + task2.reload + task3.reload + task4.reload + + # Test case 3: Ensure tasks that are not Ready for Feedback are not moved to Fix and resubmit + task1.assess(TaskStatus.fix_and_resubmit, tutor) + + task1.reload + task2.reload + task3.reload + task4.reload + + assert_equal TaskStatus.fix_and_resubmit, task1.task_status, "Task should have updated to Fix and Resubmit" + assert_equal TaskStatus.fix_and_resubmit, task2.task_status, "Dependent task should have automatically moved to Fix and Resubmit" + assert_equal TaskStatus.complete, task3.task_status, "Task not Ready for Feedback should not be affected" + assert_equal TaskStatus.ready_for_feedback, task4.task_status # Task 4 has no prerequsite links + end end From 054ef7d07d4c72573f6d39978e5c23289408004f Mon Sep 17 00:00:00 2001 From: b0ink <40929320+b0ink@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:39:13 +1100 Subject: [PATCH 2/3] chore: fix wording --- app/models/task.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/task.rb b/app/models/task.rb index f196297d46..f3bf182d9a 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -667,7 +667,7 @@ def assess(task_status, assessor, assess_date = Time.zone.now) td = prereq.task_definition task = project.task_for_task_definition(td) next unless task.task_status == TaskStatus.ready_for_feedback - # Since we are re-calling this assess method, we will recursively check for more prerequisites + # Since we are calling this assess method again, we recursively check for more dependent tasks that need to be updated task.assess(TaskStatus.fix_and_resubmit, assessor, assess_date) task.add_text_comment(assessor, "**Automated comment**: A prerequisite task was updated to Fix and Resubmit, so this task was updated as well. You may need to review and update the prerequisite before resubmitting.") end From a8ff91254107810d6f7574a1085797223abbef30 Mon Sep 17 00:00:00 2001 From: b0ink <40929320+b0ink@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:26:25 +1100 Subject: [PATCH 3/3] chore: avoid infinite loop --- app/models/task.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/task.rb b/app/models/task.rb index f3bf182d9a..d89792002c 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -666,6 +666,10 @@ def assess(task_status, assessor, assess_date = Time.zone.now) dependents.each do |prereq| td = prereq.task_definition task = project.task_for_task_definition(td) + + # Avoid infinite loop + next if task.id == id + next unless task.task_status == TaskStatus.ready_for_feedback # Since we are calling this assess method again, we recursively check for more dependent tasks that need to be updated task.assess(TaskStatus.fix_and_resubmit, assessor, assess_date)