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,210 @@
<?php
declare(strict_types=1);
namespace App\Administration\Domain\Model\SchoolClass;
use App\Administration\Domain\Event\ClasseArchivee;
use App\Administration\Domain\Event\ClasseCreee;
use App\Administration\Domain\Event\ClasseModifiee;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
/**
* Aggregate Root représentant une classe scolaire.
*
* Une classe appartient à un établissement (tenant), une école et une année scolaire.
* Elle peut recevoir des élèves et est organisée par niveau scolaire.
*
* @see FR73: Organiser les élèves par groupes pédagogiques
*/
final class SchoolClass extends AggregateRoot
{
public private(set) ?string $description = null;
public private(set) DateTimeImmutable $updatedAt;
public private(set) ?DateTimeImmutable $deletedAt = null;
private function __construct(
public private(set) ClassId $id,
public private(set) TenantId $tenantId,
public private(set) SchoolId $schoolId,
public private(set) AcademicYearId $academicYearId,
public private(set) ClassName $name,
public private(set) ?SchoolLevel $level,
public private(set) ?int $capacity,
public private(set) ClassStatus $status,
public private(set) DateTimeImmutable $createdAt,
) {
$this->updatedAt = $createdAt;
}
/**
* Crée une nouvelle classe scolaire.
*/
public static function creer(
TenantId $tenantId,
SchoolId $schoolId,
AcademicYearId $academicYearId,
ClassName $name,
?SchoolLevel $level,
?int $capacity,
DateTimeImmutable $createdAt,
): self {
$class = new self(
id: ClassId::generate(),
tenantId: $tenantId,
schoolId: $schoolId,
academicYearId: $academicYearId,
name: $name,
level: $level,
capacity: $capacity,
status: ClassStatus::ACTIVE,
createdAt: $createdAt,
);
$class->recordEvent(new ClasseCreee(
classId: $class->id,
tenantId: $class->tenantId,
name: $class->name,
level: $class->level,
occurredOn: $createdAt,
));
return $class;
}
/**
* Renomme la classe.
*/
public function renommer(ClassName $nouveauNom, DateTimeImmutable $at): void
{
if ($this->name->equals($nouveauNom)) {
return;
}
$ancienNom = $this->name;
$this->name = $nouveauNom;
$this->updatedAt = $at;
$this->recordEvent(new ClasseModifiee(
classId: $this->id,
tenantId: $this->tenantId,
ancienNom: $ancienNom,
nouveauNom: $nouveauNom,
occurredOn: $at,
));
}
/**
* Modifie le niveau scolaire de la classe.
*/
public function changerNiveau(?SchoolLevel $niveau, DateTimeImmutable $at): void
{
if ($this->level === $niveau) {
return;
}
$this->level = $niveau;
$this->updatedAt = $at;
}
/**
* Modifie la capacité maximale de la classe.
*/
public function changerCapacite(?int $capacity, DateTimeImmutable $at): void
{
if ($this->capacity === $capacity) {
return;
}
$this->capacity = $capacity;
$this->updatedAt = $at;
}
/**
* Ajoute ou modifie la description de la classe.
*/
public function decrire(?string $description, DateTimeImmutable $at): void
{
$this->description = $description;
$this->updatedAt = $at;
}
/**
* Archive la classe (soft delete).
*
* Note: La vérification des élèves affectés doit être faite par l'Application Layer
* via une Query avant d'appeler cette méthode.
*/
public function archiver(DateTimeImmutable $at): void
{
if ($this->status === ClassStatus::ARCHIVED) {
return;
}
$this->status = ClassStatus::ARCHIVED;
$this->deletedAt = $at;
$this->updatedAt = $at;
$this->recordEvent(new ClasseArchivee(
classId: $this->id,
tenantId: $this->tenantId,
occurredOn: $at,
));
}
/**
* Vérifie si la classe est active.
*/
public function estActive(): bool
{
return $this->status === ClassStatus::ACTIVE;
}
/**
* Vérifie si la classe peut recevoir des élèves.
*/
public function peutRecevoirEleves(): bool
{
return $this->status->peutRecevoirEleves();
}
/**
* Reconstitue une SchoolClass depuis le stockage.
*
* @internal Pour usage Infrastructure uniquement
*/
public static function reconstitute(
ClassId $id,
TenantId $tenantId,
SchoolId $schoolId,
AcademicYearId $academicYearId,
ClassName $name,
?SchoolLevel $level,
?int $capacity,
ClassStatus $status,
?string $description,
DateTimeImmutable $createdAt,
DateTimeImmutable $updatedAt,
?DateTimeImmutable $deletedAt,
): self {
$class = new self(
id: $id,
tenantId: $tenantId,
schoolId: $schoolId,
academicYearId: $academicYearId,
name: $name,
level: $level,
capacity: $capacity,
status: $status,
createdAt: $createdAt,
);
$class->description = $description;
$class->updatedAt = $updatedAt;
$class->deletedAt = $deletedAt;
return $class;
}
}