feat: Permettre à l'enseignant de créer et gérer ses évaluations
Some checks failed
CI / Backend Tests (push) Has been cancelled
CI / Frontend Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Naming Conventions (push) Has been cancelled
CI / Build Check (push) Has been cancelled

Les enseignants avaient besoin de définir les critères de notation
(barème, coefficient) avant de pouvoir saisir des notes. Sans cette
brique, le module Notes & Évaluations (Epic 6) ne pouvait pas démarrer.

L'évaluation est un agrégat du bounded context Scolarité avec deux
Value Objects (GradeScale 1-100, Coefficient 0.1-10). Le barème est
verrouillé dès qu'une note existe pour éviter les incohérences.
Un port EvaluationGradesChecker (stub pour l'instant) sera branché
sur le repository de notes dans la story 6.2.
This commit is contained in:
2026-03-23 23:56:37 +01:00
parent 8d950b0f3c
commit 93baeb1eaa
43 changed files with 4312 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Application\Command\CreateEvaluation;
final readonly class CreateEvaluationCommand
{
public function __construct(
public string $tenantId,
public string $classId,
public string $subjectId,
public string $teacherId,
public string $title,
public ?string $description,
public string $evaluationDate,
public int $gradeScale = 20,
public float $coefficient = 1.0,
) {
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Application\Command\CreateEvaluation;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Port\EnseignantAffectationChecker;
use App\Scolarite\Domain\Exception\EnseignantNonAffecteException;
use App\Scolarite\Domain\Model\Evaluation\Coefficient;
use App\Scolarite\Domain\Model\Evaluation\Evaluation;
use App\Scolarite\Domain\Model\Evaluation\GradeScale;
use App\Scolarite\Domain\Repository\EvaluationRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler(bus: 'command.bus')]
final readonly class CreateEvaluationHandler
{
public function __construct(
private EvaluationRepository $evaluationRepository,
private EnseignantAffectationChecker $affectationChecker,
private Clock $clock,
) {
}
public function __invoke(CreateEvaluationCommand $command): Evaluation
{
$tenantId = TenantId::fromString($command->tenantId);
$classId = ClassId::fromString($command->classId);
$subjectId = SubjectId::fromString($command->subjectId);
$teacherId = UserId::fromString($command->teacherId);
$now = $this->clock->now();
if (!$this->affectationChecker->estAffecte($teacherId, $classId, $subjectId, $tenantId)) {
throw EnseignantNonAffecteException::pourClasseEtMatiere($teacherId, $classId, $subjectId);
}
$evaluation = Evaluation::creer(
tenantId: $tenantId,
classId: $classId,
subjectId: $subjectId,
teacherId: $teacherId,
title: $command->title,
description: $command->description,
evaluationDate: new DateTimeImmutable($command->evaluationDate),
gradeScale: new GradeScale($command->gradeScale),
coefficient: new Coefficient($command->coefficient),
now: $now,
);
$this->evaluationRepository->save($evaluation);
return $evaluation;
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Application\Command\DeleteEvaluation;
final readonly class DeleteEvaluationCommand
{
public function __construct(
public string $tenantId,
public string $evaluationId,
public string $teacherId,
) {
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Application\Command\DeleteEvaluation;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Exception\NonProprietaireDeLEvaluationException;
use App\Scolarite\Domain\Model\Evaluation\Evaluation;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Scolarite\Domain\Repository\EvaluationRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler(bus: 'command.bus')]
final readonly class DeleteEvaluationHandler
{
public function __construct(
private EvaluationRepository $evaluationRepository,
private Clock $clock,
) {
}
public function __invoke(DeleteEvaluationCommand $command): Evaluation
{
$tenantId = TenantId::fromString($command->tenantId);
$evaluationId = EvaluationId::fromString($command->evaluationId);
$evaluation = $this->evaluationRepository->get($evaluationId, $tenantId);
$teacherId = UserId::fromString($command->teacherId);
if ((string) $evaluation->teacherId !== (string) $teacherId) {
throw NonProprietaireDeLEvaluationException::withId($evaluationId);
}
$evaluation->supprimer($this->clock->now());
$this->evaluationRepository->save($evaluation);
return $evaluation;
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Application\Command\UpdateEvaluation;
final readonly class UpdateEvaluationCommand
{
public function __construct(
public string $tenantId,
public string $evaluationId,
public string $teacherId,
public string $title,
public ?string $description,
public string $evaluationDate,
public float $coefficient = 1.0,
public ?int $gradeScale = null,
) {
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Application\Command\UpdateEvaluation;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Port\EvaluationGradesChecker;
use App\Scolarite\Domain\Exception\NonProprietaireDeLEvaluationException;
use App\Scolarite\Domain\Model\Evaluation\Coefficient;
use App\Scolarite\Domain\Model\Evaluation\Evaluation;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Scolarite\Domain\Model\Evaluation\GradeScale;
use App\Scolarite\Domain\Repository\EvaluationRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler(bus: 'command.bus')]
final readonly class UpdateEvaluationHandler
{
public function __construct(
private EvaluationRepository $evaluationRepository,
private EvaluationGradesChecker $gradesChecker,
private Clock $clock,
) {
}
public function __invoke(UpdateEvaluationCommand $command): Evaluation
{
$tenantId = TenantId::fromString($command->tenantId);
$evaluationId = EvaluationId::fromString($command->evaluationId);
$now = $this->clock->now();
$evaluation = $this->evaluationRepository->get($evaluationId, $tenantId);
$teacherId = UserId::fromString($command->teacherId);
if ((string) $evaluation->teacherId !== (string) $teacherId) {
throw NonProprietaireDeLEvaluationException::withId($evaluationId);
}
$hasGrades = $this->gradesChecker->hasGrades($evaluationId, $tenantId);
$evaluation->modifier(
title: $command->title,
description: $command->description,
coefficient: new Coefficient($command->coefficient),
evaluationDate: new DateTimeImmutable($command->evaluationDate),
gradeScale: $command->gradeScale !== null ? new GradeScale($command->gradeScale) : null,
hasGrades: $hasGrades,
now: $now,
);
$this->evaluationRepository->save($evaluation);
return $evaluation;
}
}