fix: correct lock guards, flush batching, and clearQuiz atomicity in question bank

- syncUsagesAfterEdit: use isLocked() instead of !isFinalized() to avoid
  syncing quizzes with started candidates (would have destroyed GivenAnswer records)
- syncToQuiz action: use isLocked() instead of hasStartedCandidates() to block
  syncing into finalized quizzes that have no started candidates
- QuestionBankService::syncToQuiz: remove internal flush(); callers now
  flush once (syncUsagesAfterEdit after the loop, syncToQuiz action after the call)
- QuizRepository::clearQuiz: delete BankQuestionUsage rows and reset
  finalized_at inside the transaction (previously orphaned usages blocked
  bank question reassignment; finalizedAt reset was a separate non-atomic flush)
- QuizController::finalizeQuiz: add flash when quiz is already finalized
- QuestionBankController::delete: block deletion when any usage references a locked quiz
- BankQuestion::__toString: remove dead null-coalescing on non-nullable string
- SeasonController::addBlankQuiz: align form field with UploadQuizFormType
  (add translation_domain: false, use translator for label)
This commit is contained in:
2026-07-05 11:54:29 +02:00
parent 3d6bb88837
commit 829028c807
6 changed files with 38 additions and 10 deletions
+2 -2
View File
@@ -297,8 +297,6 @@ class QuizController extends AbstractController
{
try {
$this->quizRepository->clearQuiz($quiz);
$quiz->finalizedAt = null;
$this->em->flush();
$this->addFlash(FlashType::Success, $this->translator->trans('Quiz cleared and no longer finalized'));
} catch (ErrorClearingQuizException) {
$this->addFlash(FlashType::Danger, $this->translator->trans('Error clearing quiz'));
@@ -323,6 +321,8 @@ class QuizController extends AbstractController
$quiz->finalizedAt = new DateTimeImmutable();
$this->em->flush();
$this->addFlash(FlashType::Success, $this->translator->trans('Quiz finalized'));
} else {
$this->addFlash(FlashType::Warning, $this->translator->trans('The quiz is already finalized'));
}
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->season->seasonCode, 'quiz' => $quiz->id]);