feat: Désignation de remplaçants temporaires avec corrections sécurité
Permet aux administrateurs de désigner un enseignant remplaçant pour un autre enseignant absent, sur des classes et matières précises, pour une période donnée. Le dashboard enseignant affiche les remplacements actifs avec les noms de classes/matières au lieu des identifiants bruts. Inclut les corrections de la code review : - Requête findActiveByTenant qui excluait les remplacements en cours mais incluait les futurs (manquait start_date <= :at) - Validation tenant et rôle enseignant dans le handler de désignation pour empêcher l'affectation cross-tenant ou de non-enseignants - Validation structurée du payload classes (Assert\Collection + UUID) pour éviter les erreurs serveur sur payloads malformés - API replaced-classes enrichie avec les noms classe/matière
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Scolarite\Infrastructure\Api\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
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\Model\SchoolClass\SchoolId;
|
||||
use App\Administration\Domain\Model\Subject\Subject;
|
||||
use App\Administration\Domain\Model\Subject\SubjectCode;
|
||||
use App\Administration\Domain\Model\Subject\SubjectId;
|
||||
use App\Administration\Domain\Model\Subject\SubjectName;
|
||||
use App\Administration\Domain\Model\Subject\SubjectStatus;
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryClassRepository;
|
||||
use App\Administration\Infrastructure\Persistence\InMemory\InMemorySubjectRepository;
|
||||
use App\Administration\Infrastructure\Security\SecurityUser;
|
||||
use App\Scolarite\Application\Query\GetReplacedClassesForTeacher\GetReplacedClassesForTeacherHandler;
|
||||
use App\Scolarite\Domain\Model\TeacherReplacement\ClassSubjectPair;
|
||||
use App\Scolarite\Domain\Model\TeacherReplacement\TeacherReplacement;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\ReplacedClassesProvider;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\ReplacedClassResource;
|
||||
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryTeacherReplacementRepository;
|
||||
use App\Shared\Domain\Clock;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use App\Shared\Infrastructure\Tenant\TenantConfig;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use App\Shared\Infrastructure\Tenant\TenantId as InfraTenantId;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
|
||||
final class ReplacedClassesProviderTest extends TestCase
|
||||
{
|
||||
private const string TENANT_UUID = '550e8400-e29b-41d4-a716-446655440001';
|
||||
private const string REPLACED_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
|
||||
private const string REPLACEMENT_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440011';
|
||||
private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
|
||||
private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
|
||||
private const string CREATED_BY_ID = '550e8400-e29b-41d4-a716-446655440099';
|
||||
private const string SCHOOL_ID = '550e8400-e29b-41d4-a716-446655440040';
|
||||
private const string ACADEMIC_YEAR_ID = '550e8400-e29b-41d4-a716-446655440041';
|
||||
|
||||
private InMemoryTeacherReplacementRepository $repository;
|
||||
private InMemoryClassRepository $classRepository;
|
||||
private InMemorySubjectRepository $subjectRepository;
|
||||
private TenantContext $tenantContext;
|
||||
private Clock $clock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = new InMemoryTeacherReplacementRepository();
|
||||
$this->classRepository = new InMemoryClassRepository();
|
||||
$this->subjectRepository = new InMemorySubjectRepository();
|
||||
$this->tenantContext = new TenantContext();
|
||||
$this->clock = new class implements Clock {
|
||||
public function now(): DateTimeImmutable
|
||||
{
|
||||
return new DateTimeImmutable('2026-03-15 10:00:00');
|
||||
}
|
||||
};
|
||||
|
||||
$this->seedClassesAndSubjects();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itProvidesReplacedClassesForAuthenticatedTeacher(): void
|
||||
{
|
||||
$provider = $this->createProvider(self::REPLACEMENT_TEACHER_ID);
|
||||
$this->setTenant();
|
||||
|
||||
$replacement = $this->createReplacement();
|
||||
$this->repository->save($replacement);
|
||||
|
||||
$result = $provider->provide(new GetCollection());
|
||||
|
||||
self::assertCount(1, $result);
|
||||
self::assertContainsOnlyInstancesOf(ReplacedClassResource::class, $result);
|
||||
self::assertSame((string) $replacement->id, $result[0]->replacementId);
|
||||
self::assertSame(self::REPLACED_TEACHER_ID, $result[0]->replacedTeacherId);
|
||||
self::assertSame(self::CLASS_ID, $result[0]->classId);
|
||||
self::assertSame(self::SUBJECT_ID, $result[0]->subjectId);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itReturnsEmptyWhenNoReplacements(): void
|
||||
{
|
||||
$provider = $this->createProvider(self::REPLACEMENT_TEACHER_ID);
|
||||
$this->setTenant();
|
||||
|
||||
$result = $provider->provide(new GetCollection());
|
||||
|
||||
self::assertSame([], $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itThrowsWhenUserNotAuthenticated(): void
|
||||
{
|
||||
$security = $this->createMock(Security::class);
|
||||
$security->method('getUser')->willReturn(null);
|
||||
|
||||
$handler = new GetReplacedClassesForTeacherHandler(
|
||||
$this->repository,
|
||||
$this->classRepository,
|
||||
$this->subjectRepository,
|
||||
$this->clock,
|
||||
);
|
||||
|
||||
$provider = new ReplacedClassesProvider($handler, $this->tenantContext, $security);
|
||||
$this->setTenant();
|
||||
|
||||
$this->expectException(UnauthorizedHttpException::class);
|
||||
$this->expectExceptionMessage('Authentification requise.');
|
||||
$provider->provide(new GetCollection());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itThrowsWhenTenantNotSet(): void
|
||||
{
|
||||
$provider = $this->createProvider(self::REPLACEMENT_TEACHER_ID);
|
||||
|
||||
$this->expectException(UnauthorizedHttpException::class);
|
||||
$this->expectExceptionMessage('Tenant non défini.');
|
||||
$provider->provide(new GetCollection());
|
||||
}
|
||||
|
||||
private function setTenant(): void
|
||||
{
|
||||
$this->tenantContext->setCurrentTenant(new TenantConfig(
|
||||
tenantId: InfraTenantId::fromString(self::TENANT_UUID),
|
||||
subdomain: 'test',
|
||||
databaseUrl: 'sqlite:///:memory:',
|
||||
));
|
||||
}
|
||||
|
||||
private function createProvider(string $userId): ReplacedClassesProvider
|
||||
{
|
||||
$securityUser = new SecurityUser(
|
||||
UserId::fromString($userId),
|
||||
'teacher@test.com',
|
||||
'hashed',
|
||||
TenantId::fromString(self::TENANT_UUID),
|
||||
['ROLE_PROF'],
|
||||
);
|
||||
|
||||
$security = $this->createMock(Security::class);
|
||||
$security->method('getUser')->willReturn($securityUser);
|
||||
|
||||
$handler = new GetReplacedClassesForTeacherHandler(
|
||||
$this->repository,
|
||||
$this->classRepository,
|
||||
$this->subjectRepository,
|
||||
$this->clock,
|
||||
);
|
||||
|
||||
return new ReplacedClassesProvider($handler, $this->tenantContext, $security);
|
||||
}
|
||||
|
||||
private function seedClassesAndSubjects(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_UUID);
|
||||
$schoolId = SchoolId::fromString(self::SCHOOL_ID);
|
||||
$academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);
|
||||
$now = new DateTimeImmutable('2026-01-01');
|
||||
|
||||
$class = SchoolClass::reconstitute(
|
||||
ClassId::fromString(self::CLASS_ID), $tenantId, $schoolId, $academicYearId,
|
||||
new ClassName('6ème A'), null, null,
|
||||
ClassStatus::ACTIVE,
|
||||
null, $now, $now, null,
|
||||
);
|
||||
$this->classRepository->save($class);
|
||||
|
||||
$subject = Subject::reconstitute(
|
||||
SubjectId::fromString(self::SUBJECT_ID), $tenantId, $schoolId,
|
||||
new SubjectName('Mathématiques'), new SubjectCode('MATH'), null,
|
||||
SubjectStatus::ACTIVE, null, $now, $now, null,
|
||||
);
|
||||
$this->subjectRepository->save($subject);
|
||||
}
|
||||
|
||||
private function createReplacement(): TeacherReplacement
|
||||
{
|
||||
return TeacherReplacement::designer(
|
||||
tenantId: TenantId::fromString(self::TENANT_UUID),
|
||||
replacedTeacherId: UserId::fromString(self::REPLACED_TEACHER_ID),
|
||||
replacementTeacherId: UserId::fromString(self::REPLACEMENT_TEACHER_ID),
|
||||
startDate: new DateTimeImmutable('2026-03-01'),
|
||||
endDate: new DateTimeImmutable('2026-03-31'),
|
||||
classes: [
|
||||
new ClassSubjectPair(
|
||||
ClassId::fromString(self::CLASS_ID),
|
||||
SubjectId::fromString(self::SUBJECT_ID),
|
||||
),
|
||||
],
|
||||
reason: null,
|
||||
createdBy: UserId::fromString(self::CREATED_BY_ID),
|
||||
now: new DateTimeImmutable('2026-02-15 10:00:00'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Scolarite\Infrastructure\Api\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use App\Administration\Domain\Model\SchoolClass\ClassId;
|
||||
use App\Administration\Domain\Model\Subject\SubjectId;
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Scolarite\Domain\Model\TeacherReplacement\ClassSubjectPair;
|
||||
use App\Scolarite\Domain\Model\TeacherReplacement\TeacherReplacement;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\TeacherReplacementItemProvider;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\TeacherReplacementResource;
|
||||
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryTeacherReplacementRepository;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use App\Shared\Infrastructure\Tenant\TenantConfig;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use App\Shared\Infrastructure\Tenant\TenantId as InfraTenantId;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecision;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
final class TeacherReplacementItemProviderTest extends TestCase
|
||||
{
|
||||
private const string TENANT_UUID = '550e8400-e29b-41d4-a716-446655440001';
|
||||
private const string REPLACED_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
|
||||
private const string REPLACEMENT_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440011';
|
||||
private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
|
||||
private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
|
||||
private const string CREATED_BY_ID = '550e8400-e29b-41d4-a716-446655440099';
|
||||
|
||||
private InMemoryTeacherReplacementRepository $repository;
|
||||
private TenantContext $tenantContext;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = new InMemoryTeacherReplacementRepository();
|
||||
$this->tenantContext = new TenantContext();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itProvidesSingleReplacementById(): void
|
||||
{
|
||||
$provider = $this->createProvider(granted: true);
|
||||
$this->setTenant();
|
||||
|
||||
$replacement = $this->createReplacement();
|
||||
$this->repository->save($replacement);
|
||||
|
||||
$result = $provider->provide(
|
||||
new Delete(),
|
||||
['id' => (string) $replacement->id],
|
||||
);
|
||||
|
||||
self::assertInstanceOf(TeacherReplacementResource::class, $result);
|
||||
self::assertSame((string) $replacement->id, $result->id);
|
||||
self::assertSame(self::REPLACED_TEACHER_ID, $result->replacedTeacherId);
|
||||
self::assertSame(self::REPLACEMENT_TEACHER_ID, $result->replacementTeacherId);
|
||||
self::assertSame('active', $result->status);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itThrowsNotFoundWhenReplacementDoesNotExist(): void
|
||||
{
|
||||
$provider = $this->createProvider(granted: true);
|
||||
$this->setTenant();
|
||||
|
||||
$this->expectException(NotFoundHttpException::class);
|
||||
$this->expectExceptionMessage('Remplacement non trouvé.');
|
||||
|
||||
$provider->provide(
|
||||
new Delete(),
|
||||
['id' => '550e8400-e29b-41d4-a716-446655440099'],
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itRejectsUnauthorizedAccess(): void
|
||||
{
|
||||
$provider = $this->createProvider(granted: false);
|
||||
$this->setTenant();
|
||||
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
|
||||
$provider->provide(
|
||||
new Delete(),
|
||||
['id' => '550e8400-e29b-41d4-a716-446655440099'],
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itRejectsRequestWithoutTenant(): void
|
||||
{
|
||||
$provider = $this->createProvider(granted: true);
|
||||
|
||||
$this->expectException(UnauthorizedHttpException::class);
|
||||
|
||||
$provider->provide(
|
||||
new Delete(),
|
||||
['id' => '550e8400-e29b-41d4-a716-446655440099'],
|
||||
);
|
||||
}
|
||||
|
||||
private function setTenant(): void
|
||||
{
|
||||
$this->tenantContext->setCurrentTenant(new TenantConfig(
|
||||
tenantId: InfraTenantId::fromString(self::TENANT_UUID),
|
||||
subdomain: 'test',
|
||||
databaseUrl: 'sqlite:///:memory:',
|
||||
));
|
||||
}
|
||||
|
||||
private function createProvider(bool $granted): TeacherReplacementItemProvider
|
||||
{
|
||||
$authChecker = new class($granted) implements AuthorizationCheckerInterface {
|
||||
public function __construct(private readonly bool $granted)
|
||||
{
|
||||
}
|
||||
|
||||
public function isGranted(mixed $attribute, mixed $subject = null, ?AccessDecision $accessDecision = null): bool
|
||||
{
|
||||
return $this->granted;
|
||||
}
|
||||
};
|
||||
|
||||
return new TeacherReplacementItemProvider($this->repository, $this->tenantContext, $authChecker);
|
||||
}
|
||||
|
||||
private function createReplacement(): TeacherReplacement
|
||||
{
|
||||
return TeacherReplacement::designer(
|
||||
tenantId: TenantId::fromString(self::TENANT_UUID),
|
||||
replacedTeacherId: UserId::fromString(self::REPLACED_TEACHER_ID),
|
||||
replacementTeacherId: UserId::fromString(self::REPLACEMENT_TEACHER_ID),
|
||||
startDate: new DateTimeImmutable('2026-03-01'),
|
||||
endDate: new DateTimeImmutable('2026-03-31'),
|
||||
classes: [
|
||||
new ClassSubjectPair(
|
||||
ClassId::fromString(self::CLASS_ID),
|
||||
SubjectId::fromString(self::SUBJECT_ID),
|
||||
),
|
||||
],
|
||||
reason: null,
|
||||
createdBy: UserId::fromString(self::CREATED_BY_ID),
|
||||
now: new DateTimeImmutable('2026-02-15 10:00:00'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Scolarite\Infrastructure\Api\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
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\GetActiveReplacements\GetActiveReplacementsHandler;
|
||||
use App\Scolarite\Domain\Model\TeacherReplacement\ClassSubjectPair;
|
||||
use App\Scolarite\Domain\Model\TeacherReplacement\TeacherReplacement;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\TeacherReplacementsCollectionProvider;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\TeacherReplacementResource;
|
||||
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryTeacherReplacementRepository;
|
||||
use App\Shared\Domain\Clock;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use App\Shared\Infrastructure\Tenant\TenantConfig;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use App\Shared\Infrastructure\Tenant\TenantId as InfraTenantId;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecision;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
final class TeacherReplacementsCollectionProviderTest extends TestCase
|
||||
{
|
||||
private const string TENANT_UUID = '550e8400-e29b-41d4-a716-446655440001';
|
||||
private const string REPLACED_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
|
||||
private const string REPLACEMENT_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440011';
|
||||
private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
|
||||
private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
|
||||
private const string CREATED_BY_ID = '550e8400-e29b-41d4-a716-446655440099';
|
||||
|
||||
private InMemoryTeacherReplacementRepository $repository;
|
||||
private TenantContext $tenantContext;
|
||||
private Clock $clock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = new InMemoryTeacherReplacementRepository();
|
||||
$this->tenantContext = new TenantContext();
|
||||
$this->clock = new class implements Clock {
|
||||
public function now(): DateTimeImmutable
|
||||
{
|
||||
return new DateTimeImmutable('2026-03-15 10:00:00');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itProvidesCollectionOfActiveReplacements(): void
|
||||
{
|
||||
$provider = $this->createProvider(granted: true);
|
||||
$this->setTenant();
|
||||
|
||||
$replacement = $this->createReplacement();
|
||||
$this->repository->save($replacement);
|
||||
|
||||
$result = $provider->provide(new GetCollection());
|
||||
|
||||
self::assertCount(1, $result);
|
||||
self::assertContainsOnlyInstancesOf(TeacherReplacementResource::class, $result);
|
||||
self::assertSame((string) $replacement->id, $result[0]->id);
|
||||
self::assertSame(self::REPLACED_TEACHER_ID, $result[0]->replacedTeacherId);
|
||||
self::assertSame(self::REPLACEMENT_TEACHER_ID, $result[0]->replacementTeacherId);
|
||||
self::assertSame('active', $result[0]->status);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itReturnsEmptyWhenNoActiveReplacements(): void
|
||||
{
|
||||
$provider = $this->createProvider(granted: true);
|
||||
$this->setTenant();
|
||||
|
||||
$result = $provider->provide(new GetCollection());
|
||||
|
||||
self::assertSame([], $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itRejectsUnauthorizedAccess(): void
|
||||
{
|
||||
$provider = $this->createProvider(granted: false);
|
||||
$this->setTenant();
|
||||
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
$provider->provide(new GetCollection());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itRejectsRequestWithoutTenant(): void
|
||||
{
|
||||
$provider = $this->createProvider(granted: true);
|
||||
|
||||
$this->expectException(UnauthorizedHttpException::class);
|
||||
$provider->provide(new GetCollection());
|
||||
}
|
||||
|
||||
private function setTenant(): void
|
||||
{
|
||||
$this->tenantContext->setCurrentTenant(new TenantConfig(
|
||||
tenantId: InfraTenantId::fromString(self::TENANT_UUID),
|
||||
subdomain: 'test',
|
||||
databaseUrl: 'sqlite:///:memory:',
|
||||
));
|
||||
}
|
||||
|
||||
private function createProvider(bool $granted): TeacherReplacementsCollectionProvider
|
||||
{
|
||||
$authChecker = new class($granted) implements AuthorizationCheckerInterface {
|
||||
public function __construct(private readonly bool $granted)
|
||||
{
|
||||
}
|
||||
|
||||
public function isGranted(mixed $attribute, mixed $subject = null, ?AccessDecision $accessDecision = null): bool
|
||||
{
|
||||
return $this->granted;
|
||||
}
|
||||
};
|
||||
|
||||
$handler = new GetActiveReplacementsHandler($this->repository, $this->clock);
|
||||
|
||||
return new TeacherReplacementsCollectionProvider($handler, $this->tenantContext, $authChecker);
|
||||
}
|
||||
|
||||
private function createReplacement(): TeacherReplacement
|
||||
{
|
||||
return TeacherReplacement::designer(
|
||||
tenantId: TenantId::fromString(self::TENANT_UUID),
|
||||
replacedTeacherId: UserId::fromString(self::REPLACED_TEACHER_ID),
|
||||
replacementTeacherId: UserId::fromString(self::REPLACEMENT_TEACHER_ID),
|
||||
startDate: new DateTimeImmutable('2026-03-01'),
|
||||
endDate: new DateTimeImmutable('2026-03-31'),
|
||||
classes: [
|
||||
new ClassSubjectPair(
|
||||
ClassId::fromString(self::CLASS_ID),
|
||||
SubjectId::fromString(self::SUBJECT_ID),
|
||||
),
|
||||
],
|
||||
reason: 'Congé maladie',
|
||||
createdBy: UserId::fromString(self::CREATED_BY_ID),
|
||||
now: new DateTimeImmutable('2026-02-15 10:00:00'),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user