feat: Permettre à l'enseignant de saisir les notes dans une grille inline
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

L'enseignant avait besoin d'un moyen rapide de saisir les notes après
une évaluation. La grille inline permet de compléter 30 élèves en moins
de 3 minutes grâce à la navigation clavier (Tab/Enter/Shift+Tab),
la validation temps réel, l'auto-save debounced (500ms) et les
raccourcis /abs et /disp pour marquer absents/dispensés.

Les notes restent en brouillon jusqu'à publication explicite (avec
confirmation modale). Une fois publiées, les élèves les voient
immédiatement ; les parents après un délai de 24h (VisibiliteNotesPolicy).
Le mode offline stocke les notes en IndexedDB et synchronise
automatiquement au retour de la connexion.

Chaque modification est auditée dans grade_events via un event
subscriber qui écoute NoteSaisie/NoteModifiee sur le bus d'événements.
This commit is contained in:
2026-03-29 09:55:45 +02:00
parent 98be1951bf
commit b70d5ec2ad
45 changed files with 3902 additions and 11 deletions

View File

@@ -10,8 +10,10 @@ use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Event\EvaluationCreee;
use App\Scolarite\Domain\Event\EvaluationModifiee;
use App\Scolarite\Domain\Event\EvaluationSupprimee;
use App\Scolarite\Domain\Event\NotesPubliees;
use App\Scolarite\Domain\Exception\BaremeNonModifiableException;
use App\Scolarite\Domain\Exception\EvaluationDejaSupprimeeException;
use App\Scolarite\Domain\Exception\NotesDejaPublieesException;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
@@ -19,6 +21,7 @@ use DateTimeImmutable;
final class Evaluation extends AggregateRoot
{
public private(set) DateTimeImmutable $updatedAt;
public private(set) ?DateTimeImmutable $gradesPublishedAt = null;
private function __construct(
public private(set) EvaluationId $id,
@@ -113,6 +116,30 @@ final class Evaluation extends AggregateRoot
));
}
public function publierNotes(DateTimeImmutable $now): void
{
if ($this->status === EvaluationStatus::DELETED) {
throw EvaluationDejaSupprimeeException::withId($this->id);
}
if ($this->gradesPublishedAt !== null) {
throw NotesDejaPublieesException::pourEvaluation($this->id);
}
$this->gradesPublishedAt = $now;
$this->updatedAt = $now;
$this->recordEvent(new NotesPubliees(
evaluationId: $this->id,
occurredOn: $now,
));
}
public function notesPubliees(): bool
{
return $this->gradesPublishedAt !== null;
}
public function supprimer(DateTimeImmutable $now): void
{
if ($this->status === EvaluationStatus::DELETED) {
@@ -145,6 +172,7 @@ final class Evaluation extends AggregateRoot
EvaluationStatus $status,
DateTimeImmutable $createdAt,
DateTimeImmutable $updatedAt,
?DateTimeImmutable $gradesPublishedAt = null,
): self {
$evaluation = new self(
id: $id,
@@ -162,6 +190,7 @@ final class Evaluation extends AggregateRoot
);
$evaluation->updatedAt = $updatedAt;
$evaluation->gradesPublishedAt = $gradesPublishedAt;
return $evaluation;
}