feat: Gestion des classes scolaires

Permet aux administrateurs de créer, modifier et supprimer des classes
pour organiser les élèves par niveau. L'archivage soft-delete préserve
l'historique tout en masquant les classes obsolètes.

Inclut la validation des noms (2-50 caractères), les niveaux scolaires
du CP à la Terminale, et les contrôles d'accès par rôle.
This commit is contained in:
2026-02-05 15:24:29 +01:00
parent b45ef735db
commit 8e09e0abf1
54 changed files with 5099 additions and 5 deletions

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace App\Administration\Infrastructure\Persistence\InMemory;
use App\Administration\Domain\Exception\ClasseNotFoundException;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\SchoolClass\ClassName;
use App\Administration\Domain\Model\SchoolClass\ClassStatus;
use App\Administration\Domain\Model\SchoolClass\SchoolClass;
use App\Administration\Domain\Repository\ClassRepository;
use App\Shared\Domain\Tenant\TenantId;
use Override;
final class InMemoryClassRepository implements ClassRepository
{
/** @var array<string, SchoolClass> Indexed by ID */
private array $byId = [];
/** @var array<string, SchoolClass> Indexed by tenant:year:name */
private array $byTenantYearName = [];
#[Override]
public function save(SchoolClass $class): void
{
// If class already exists, remove the old name key (handles renames)
$existingClass = $this->byId[(string) $class->id] ?? null;
if ($existingClass !== null) {
$oldKey = $this->nameKey($existingClass->name, $existingClass->tenantId, $existingClass->academicYearId);
unset($this->byTenantYearName[$oldKey]);
}
$this->byId[(string) $class->id] = $class;
$this->byTenantYearName[$this->nameKey($class->name, $class->tenantId, $class->academicYearId)] = $class;
}
#[Override]
public function get(ClassId $id): SchoolClass
{
$class = $this->findById($id);
if ($class === null) {
throw ClasseNotFoundException::withId($id);
}
return $class;
}
#[Override]
public function findById(ClassId $id): ?SchoolClass
{
return $this->byId[(string) $id] ?? null;
}
#[Override]
public function findByName(
ClassName $name,
TenantId $tenantId,
AcademicYearId $academicYearId,
): ?SchoolClass {
return $this->byTenantYearName[$this->nameKey($name, $tenantId, $academicYearId)] ?? null;
}
#[Override]
public function findActiveByTenantAndYear(
TenantId $tenantId,
AcademicYearId $academicYearId,
): array {
$result = [];
foreach ($this->byId as $class) {
if ($class->tenantId->equals($tenantId)
&& $class->academicYearId->equals($academicYearId)
&& $class->status === ClassStatus::ACTIVE
) {
$result[] = $class;
}
}
return $result;
}
#[Override]
public function delete(ClassId $id): void
{
$class = $this->byId[(string) $id] ?? null;
if ($class !== null) {
unset($this->byId[(string) $id]);
unset($this->byTenantYearName[$this->nameKey($class->name, $class->tenantId, $class->academicYearId)]);
}
}
private function nameKey(ClassName $name, TenantId $tenantId, AcademicYearId $academicYearId): string
{
return $tenantId . ':' . $academicYearId . ':' . mb_strtolower((string) $name, 'UTF-8');
}
}