L'administration a besoin de construire et maintenir les emplois du temps hebdomadaires pour chaque classe, en s'assurant que les enseignants ne sont pas en conflit (même créneau, classes différentes) et que les affectations enseignant-matière-classe sont respectées. Cette implémentation couvre le CRUD complet des créneaux (ScheduleSlot), la détection de conflits (classe, enseignant, salle) avec possibilité de forcer, la validation des affectations côté serveur (AC2), l'intégration calendrier pour les jours bloqués, une vue mobile-first avec onglets jour par jour, et le drag-and-drop pour réorganiser les créneaux sur desktop.
177 lines
5.9 KiB
PHP
177 lines
5.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Scolarite\Application\Query\GetScheduleSlots;
|
|
|
|
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\Query\GetScheduleSlots\GetScheduleSlotsHandler;
|
|
use App\Scolarite\Application\Query\GetScheduleSlots\GetScheduleSlotsQuery;
|
|
use App\Scolarite\Domain\Model\Schedule\DayOfWeek;
|
|
use App\Scolarite\Domain\Model\Schedule\ScheduleSlot;
|
|
use App\Scolarite\Domain\Model\Schedule\TimeSlot;
|
|
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryScheduleSlotRepository;
|
|
use App\Shared\Domain\Tenant\TenantId;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class GetScheduleSlotsHandlerTest extends TestCase
|
|
{
|
|
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
|
|
private const string CLASS_A = '550e8400-e29b-41d4-a716-446655440020';
|
|
private const string CLASS_B = '550e8400-e29b-41d4-a716-446655440021';
|
|
private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
|
|
private const string TEACHER_A = '550e8400-e29b-41d4-a716-446655440010';
|
|
private const string TEACHER_B = '550e8400-e29b-41d4-a716-446655440011';
|
|
|
|
private InMemoryScheduleSlotRepository $repository;
|
|
private GetScheduleSlotsHandler $handler;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->repository = new InMemoryScheduleSlotRepository();
|
|
$this->handler = new GetScheduleSlotsHandler($this->repository);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsSlotsFilteredByClass(): void
|
|
{
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
|
|
$this->createSlot(classId: self::CLASS_B, teacherId: self::TEACHER_A);
|
|
|
|
$result = ($this->handler)(new GetScheduleSlotsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
classId: self::CLASS_A,
|
|
));
|
|
|
|
self::assertCount(1, $result);
|
|
self::assertSame(self::CLASS_A, $result[0]->classId);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsSlotsFilteredByTeacher(): void
|
|
{
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_B, day: DayOfWeek::TUESDAY);
|
|
|
|
$result = ($this->handler)(new GetScheduleSlotsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
teacherId: self::TEACHER_A,
|
|
));
|
|
|
|
self::assertCount(1, $result);
|
|
self::assertSame(self::TEACHER_A, $result[0]->teacherId);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsSlotsFilteredByClassAndTeacher(): void
|
|
{
|
|
// Classe A, enseignant A
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
|
|
// Classe A, enseignant B
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_B, day: DayOfWeek::TUESDAY);
|
|
// Classe B, enseignant A
|
|
$this->createSlot(classId: self::CLASS_B, teacherId: self::TEACHER_A, day: DayOfWeek::WEDNESDAY);
|
|
|
|
$result = ($this->handler)(new GetScheduleSlotsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
classId: self::CLASS_A,
|
|
teacherId: self::TEACHER_A,
|
|
));
|
|
|
|
self::assertCount(1, $result);
|
|
self::assertSame(self::CLASS_A, $result[0]->classId);
|
|
self::assertSame(self::TEACHER_A, $result[0]->teacherId);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsEmptyWhenClassAndTeacherDontMatch(): void
|
|
{
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
|
|
|
|
$result = ($this->handler)(new GetScheduleSlotsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
classId: self::CLASS_A,
|
|
teacherId: self::TEACHER_B,
|
|
));
|
|
|
|
self::assertCount(0, $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsEmptyWhenNoFilters(): void
|
|
{
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
|
|
|
|
$result = ($this->handler)(new GetScheduleSlotsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
));
|
|
|
|
self::assertCount(0, $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsEmptyForInvalidClassIdUuid(): void
|
|
{
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
|
|
|
|
$result = ($this->handler)(new GetScheduleSlotsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
classId: 'not-a-valid-uuid',
|
|
));
|
|
|
|
self::assertCount(0, $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function returnsEmptyForInvalidTeacherIdUuid(): void
|
|
{
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
|
|
|
|
$result = ($this->handler)(new GetScheduleSlotsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
teacherId: 'invalid',
|
|
));
|
|
|
|
self::assertCount(0, $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function teacherFilterReturnsAllClassesForTeacher(): void
|
|
{
|
|
$this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
|
|
$this->createSlot(classId: self::CLASS_B, teacherId: self::TEACHER_A, day: DayOfWeek::TUESDAY);
|
|
|
|
$result = ($this->handler)(new GetScheduleSlotsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
teacherId: self::TEACHER_A,
|
|
));
|
|
|
|
self::assertCount(2, $result);
|
|
}
|
|
|
|
private function createSlot(
|
|
string $classId,
|
|
string $teacherId,
|
|
DayOfWeek $day = DayOfWeek::MONDAY,
|
|
): ScheduleSlot {
|
|
$slot = ScheduleSlot::creer(
|
|
tenantId: TenantId::fromString(self::TENANT_ID),
|
|
classId: ClassId::fromString($classId),
|
|
subjectId: SubjectId::fromString(self::SUBJECT_ID),
|
|
teacherId: UserId::fromString($teacherId),
|
|
dayOfWeek: $day,
|
|
timeSlot: new TimeSlot('08:00', '09:00'),
|
|
room: null,
|
|
isRecurring: true,
|
|
now: new DateTimeImmutable('2026-03-01 10:00:00'),
|
|
);
|
|
$this->repository->save($slot);
|
|
|
|
return $slot;
|
|
}
|
|
}
|