From bda63bd98ca144c6e25fa15c2a13f1ec914f7a00 Mon Sep 17 00:00:00 2001 From: Mathias STRASSER Date: Mon, 9 Mar 2026 11:20:29 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20Permettre=20l'affectation=20de=20classe?= =?UTF-8?q?=20pour=20les=20=C3=A9l=C3=A8ves=20sans=20affectation=20existan?= =?UTF-8?q?te?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le handler ChangeStudentClass exigeait une affectation existante pour l'année scolaire en cours avant de pouvoir changer la classe. Un élève créé sans ClassAssignment (import direct, année précédente) provoquait une erreur "Élève non trouvé" au lieu d'être simplement affecté. Le handler crée désormais une nouvelle affectation quand aucune n'existe, et l'erreur de changement de classe s'affiche dans la modale au lieu de la page principale. --- .../ChangeStudentClassHandler.php | 19 +++++++++++------ .../AffectationEleveNonTrouveeException.php | 21 ------------------- .../Processor/ChangeStudentClassProcessor.php | 3 --- .../ChangeStudentClassHandlerTest.php | 13 +++++++----- .../src/routes/admin/students/+page.svelte | 16 ++++++++++++-- 5 files changed, 35 insertions(+), 37 deletions(-) delete mode 100644 backend/src/Administration/Domain/Exception/AffectationEleveNonTrouveeException.php diff --git a/backend/src/Administration/Application/Command/ChangeStudentClass/ChangeStudentClassHandler.php b/backend/src/Administration/Application/Command/ChangeStudentClass/ChangeStudentClassHandler.php index f881069..af5e21d 100644 --- a/backend/src/Administration/Application/Command/ChangeStudentClass/ChangeStudentClassHandler.php +++ b/backend/src/Administration/Application/Command/ChangeStudentClass/ChangeStudentClassHandler.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Administration\Application\Command\ChangeStudentClass; -use App\Administration\Domain\Exception\AffectationEleveNonTrouveeException; use App\Administration\Domain\Exception\ClasseNotFoundException; use App\Administration\Domain\Model\ClassAssignment\ClassAssignment; use App\Administration\Domain\Model\SchoolClass\AcademicYearId; @@ -44,15 +43,23 @@ final readonly class ChangeStudentClassHandler throw ClasseNotFoundException::withId($newClassId); } - // Trouver l'affectation existante + $now = $this->clock->now(); + + // Trouver l'affectation existante ou en créer une nouvelle $assignment = $this->classAssignmentRepository->findByStudent($studentId, $academicYearId, $tenantId); - if ($assignment === null) { - throw AffectationEleveNonTrouveeException::pourEleve($studentId); + if ($assignment !== null) { + $assignment->changerClasse($newClassId, $now); + } else { + $assignment = ClassAssignment::affecter( + tenantId: $tenantId, + studentId: $studentId, + classId: $newClassId, + academicYearId: $academicYearId, + assignedAt: $now, + ); } - $assignment->changerClasse($newClassId, $this->clock->now()); - $this->classAssignmentRepository->save($assignment); return $assignment; diff --git a/backend/src/Administration/Domain/Exception/AffectationEleveNonTrouveeException.php b/backend/src/Administration/Domain/Exception/AffectationEleveNonTrouveeException.php deleted file mode 100644 index 4ff90c5..0000000 --- a/backend/src/Administration/Domain/Exception/AffectationEleveNonTrouveeException.php +++ /dev/null @@ -1,21 +0,0 @@ -classLevel = $newClass->level?->value; return $data; - } catch (AffectationEleveNonTrouveeException) { - throw new NotFoundHttpException('Élève non trouvé.'); } catch (ClasseNotFoundException $e) { throw new NotFoundHttpException($e->getMessage()); } diff --git a/backend/tests/Unit/Administration/Application/Command/ChangeStudentClass/ChangeStudentClassHandlerTest.php b/backend/tests/Unit/Administration/Application/Command/ChangeStudentClass/ChangeStudentClassHandlerTest.php index 3a7216f..509a776 100644 --- a/backend/tests/Unit/Administration/Application/Command/ChangeStudentClass/ChangeStudentClassHandlerTest.php +++ b/backend/tests/Unit/Administration/Application/Command/ChangeStudentClass/ChangeStudentClassHandlerTest.php @@ -6,7 +6,6 @@ namespace App\Tests\Unit\Administration\Application\Command\ChangeStudentClass; use App\Administration\Application\Command\ChangeStudentClass\ChangeStudentClassCommand; use App\Administration\Application\Command\ChangeStudentClass\ChangeStudentClassHandler; -use App\Administration\Domain\Exception\AffectationEleveNonTrouveeException; use App\Administration\Domain\Exception\ClasseNotFoundException; use App\Administration\Domain\Model\ClassAssignment\ClassAssignment; use App\Administration\Domain\Model\SchoolClass\AcademicYearId; @@ -74,13 +73,17 @@ final class ChangeStudentClassHandlerTest extends TestCase } #[Test] - public function itThrowsWhenAssignmentNotFound(): void + public function itCreatesAssignmentWhenNoneExists(): void { $handler = $this->createHandler(); - $command = $this->createCommand(studentId: '550e8400-e29b-41d4-a716-446655440070'); + $newStudentId = '550e8400-e29b-41d4-a716-446655440070'; + $command = $this->createCommand(studentId: $newStudentId); - $this->expectException(AffectationEleveNonTrouveeException::class); - $handler($command); + $assignment = $handler($command); + + self::assertTrue($assignment->studentId->equals(UserId::fromString($newStudentId))); + self::assertTrue($assignment->classId->equals(ClassId::fromString(self::NEW_CLASS_ID))); + self::assertTrue($assignment->academicYearId->equals(AcademicYearId::fromString(self::ACADEMIC_YEAR_ID))); } #[Test] diff --git a/frontend/src/routes/admin/students/+page.svelte b/frontend/src/routes/admin/students/+page.svelte index d31c0a2..676bccd 100644 --- a/frontend/src/routes/admin/students/+page.svelte +++ b/frontend/src/routes/admin/students/+page.svelte @@ -62,6 +62,7 @@ let changeClassTarget = $state(null); let newClassForChange = $state(''); let isChangingClass = $state(false); + let changeClassError = $state(null); // Classes grouped by level for optgroup let classesByLevel = $derived.by(() => { @@ -300,7 +301,7 @@ changeClassTarget = student; newClassForChange = ''; showChangeClassModal = true; - error = null; + changeClassError = null; } function closeChangeClassModal() { @@ -338,7 +339,7 @@ successMessage = `${changeClassTarget.firstName} ${changeClassTarget.lastName} a été transféré vers ${targetClass?.name ?? 'la nouvelle classe'}.`; closeChangeClassModal(); } catch (e) { - error = e instanceof Error ? e.message : 'Erreur lors du changement de classe'; + changeClassError = e instanceof Error ? e.message : 'Erreur lors du changement de classe'; } finally { isChangingClass = false; } @@ -720,6 +721,13 @@ + {#if changeClassError} + + {/if} + {#if newClassForChange} {@const targetClass = classes.find((c) => c.id === newClassForChange)}
@@ -1244,6 +1252,10 @@ justify-content: flex-end; } + .modal-error { + margin-bottom: 0; + } + .change-confirm-info { padding: 0.75rem 1rem; background: #eff6ff;