feat: add question bank management, quiz finalization, and related backend/frontend functionality

This commit is contained in:
2026-07-04 20:10:03 +02:00
parent d1d1eb3a24
commit c34c25dff7
37 changed files with 2493 additions and 206 deletions
+43
View File
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Tvdt\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Tvdt\Entity\BankQuestion;
use Tvdt\Entity\QuestionLabel;
use Tvdt\Entity\Season;
/** @extends ServiceEntityRepository<BankQuestion> */
class BankQuestionRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, BankQuestion::class);
}
/** @return list<BankQuestion> */
public function findBySeason(Season $season, ?QuestionLabel $label = null): array
{
$queryBuilder = $this->createQueryBuilder('bq')
->select('bq', 'ba', 'l', 'u', 'uq')
->leftJoin('bq.answers', 'ba')
->leftJoin('bq.labels', 'l')
->leftJoin('bq.usages', 'u')
->leftJoin('u.quiz', 'uq')
->where('bq.season = :season')
->orderBy('bq.question', 'ASC')
->setParameter('season', $season);
if ($label instanceof QuestionLabel) {
$queryBuilder
->andWhere(':label member of bq.labels')
->setParameter('label', $label);
}
/* @var list<BankQuestion> */
return $queryBuilder->getQuery()->getResult();
}
}
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Tvdt\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Tvdt\Entity\QuestionLabel;
/** @extends ServiceEntityRepository<QuestionLabel> */
class QuestionLabelRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, QuestionLabel::class);
}
}
+24
View File
@@ -12,6 +12,7 @@ use Safe\Exceptions\DatetimeException;
use Symfony\Component\Uid\Uuid;
use Tvdt\Dto\Result;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\Season;
use Tvdt\Exception\ErrorClearingQuizException;
/** @extends ServiceEntityRepository<Quiz> */
@@ -22,6 +23,29 @@ class QuizRepository extends ServiceEntityRepository
parent::__construct($registry, Quiz::class);
}
/**
* Quizzes of the season that can still receive bank questions:
* not finalized and not started by any candidate.
*
* @return list<Quiz>
*/
public function findAssignableForSeason(Season $season): array
{
/* @var list<Quiz> */
return $this->getEntityManager()->createQuery(<<<DQL
select q from Tvdt\Entity\Quiz q
where q.season = :season
and q.finalizedAt is null
and not exists (
select 1 from Tvdt\Entity\QuizCandidate qc
where qc.quiz = q and qc.started is not null
)
order by q.id asc
DQL)
->setParameter('season', $season)
->getResult();
}
/** @throws ErrorClearingQuizException */
public function clearQuiz(Quiz $quiz): void
{