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,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Processor;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Administration\Domain\Exception\TenantMismatchException;
|
||||
use App\Administration\Domain\Exception\UserNotFoundException;
|
||||
use App\Administration\Infrastructure\Security\SecurityUser;
|
||||
use App\Scolarite\Application\Command\DesignateReplacement\DesignateReplacementCommand;
|
||||
use App\Scolarite\Application\Command\DesignateReplacement\DesignateReplacementHandler;
|
||||
use App\Scolarite\Domain\Exception\DatesRemplacementInvalidesException;
|
||||
use App\Scolarite\Domain\Exception\RemplacementSameTeacherException;
|
||||
use App\Scolarite\Domain\Exception\UtilisateurNonEnseignantException;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\TeacherReplacementResource;
|
||||
use App\Scolarite\Infrastructure\Security\TeacherReplacementVoter;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use Override;
|
||||
use Ramsey\Uuid\Exception\InvalidUuidStringException;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
/**
|
||||
* @implements ProcessorInterface<TeacherReplacementResource, TeacherReplacementResource>
|
||||
*/
|
||||
final readonly class CreateTeacherReplacementProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private DesignateReplacementHandler $handler,
|
||||
private TenantContext $tenantContext,
|
||||
private MessageBusInterface $eventBus,
|
||||
private AuthorizationCheckerInterface $authorizationChecker,
|
||||
private Security $security,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TeacherReplacementResource $data
|
||||
*/
|
||||
#[Override]
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): TeacherReplacementResource
|
||||
{
|
||||
if (!$this->authorizationChecker->isGranted(TeacherReplacementVoter::CREATE)) {
|
||||
throw new AccessDeniedHttpException('Vous n\'êtes pas autorisé à créer un remplacement.');
|
||||
}
|
||||
|
||||
if (!$this->tenantContext->hasTenant()) {
|
||||
throw new UnauthorizedHttpException('Bearer', 'Tenant non défini.');
|
||||
}
|
||||
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$user instanceof SecurityUser) {
|
||||
throw new UnauthorizedHttpException('Bearer', 'Authentification requise.');
|
||||
}
|
||||
|
||||
try {
|
||||
$command = new DesignateReplacementCommand(
|
||||
tenantId: (string) $this->tenantContext->getCurrentTenantId(),
|
||||
replacedTeacherId: $data->replacedTeacherId ?? '',
|
||||
replacementTeacherId: $data->replacementTeacherId ?? '',
|
||||
startDate: $data->startDate ?? '',
|
||||
endDate: $data->endDate ?? '',
|
||||
classes: $data->classes ?? [],
|
||||
reason: $data->reason,
|
||||
createdBy: $user->userId(),
|
||||
);
|
||||
|
||||
$replacement = ($this->handler)($command);
|
||||
|
||||
foreach ($replacement->pullDomainEvents() as $event) {
|
||||
$this->eventBus->dispatch($event);
|
||||
}
|
||||
|
||||
return TeacherReplacementResource::fromDomain($replacement);
|
||||
} catch (RemplacementSameTeacherException|DatesRemplacementInvalidesException|TenantMismatchException|UtilisateurNonEnseignantException $e) {
|
||||
throw new BadRequestHttpException($e->getMessage());
|
||||
} catch (UserNotFoundException $e) {
|
||||
throw new NotFoundHttpException($e->getMessage());
|
||||
} catch (InvalidUuidStringException $e) {
|
||||
throw new BadRequestHttpException('UUID invalide : ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Processor;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Scolarite\Application\Command\EndReplacement\EndReplacementCommand;
|
||||
use App\Scolarite\Application\Command\EndReplacement\EndReplacementHandler;
|
||||
use App\Scolarite\Domain\Exception\RemplacementDejaTermineException;
|
||||
use App\Scolarite\Domain\Exception\RemplacementNotFoundException;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\TeacherReplacementResource;
|
||||
use App\Scolarite\Infrastructure\Security\TeacherReplacementVoter;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use Override;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
/**
|
||||
* @implements ProcessorInterface<TeacherReplacementResource, null>
|
||||
*/
|
||||
final readonly class EndTeacherReplacementProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EndReplacementHandler $handler,
|
||||
private TenantContext $tenantContext,
|
||||
private AuthorizationCheckerInterface $authorizationChecker,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): null
|
||||
{
|
||||
if (!$this->authorizationChecker->isGranted(TeacherReplacementVoter::DELETE)) {
|
||||
throw new AccessDeniedHttpException('Vous n\'êtes pas autorisé à terminer un remplacement.');
|
||||
}
|
||||
|
||||
if (!$this->tenantContext->hasTenant()) {
|
||||
throw new UnauthorizedHttpException('Bearer', 'Tenant non défini.');
|
||||
}
|
||||
|
||||
/** @var string $id */
|
||||
$id = $uriVariables['id'];
|
||||
|
||||
try {
|
||||
($this->handler)(new EndReplacementCommand(
|
||||
replacementId: $id,
|
||||
tenantId: (string) $this->tenantContext->getCurrentTenantId(),
|
||||
));
|
||||
} catch (RemplacementNotFoundException $e) {
|
||||
throw new NotFoundHttpException($e->getMessage());
|
||||
} catch (RemplacementDejaTermineException $e) {
|
||||
throw new BadRequestHttpException($e->getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Administration\Infrastructure\Security\SecurityUser;
|
||||
use App\Scolarite\Application\Query\GetReplacedClassesForTeacher\GetReplacedClassesForTeacherHandler;
|
||||
use App\Scolarite\Application\Query\GetReplacedClassesForTeacher\GetReplacedClassesForTeacherQuery;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\ReplacedClassResource;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use Override;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
|
||||
/**
|
||||
* Fournit les classes du remplaçant connecté (GET /api/me/replaced-classes).
|
||||
*
|
||||
* @implements ProviderInterface<ReplacedClassResource>
|
||||
*/
|
||||
final readonly class ReplacedClassesProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private GetReplacedClassesForTeacherHandler $handler,
|
||||
private TenantContext $tenantContext,
|
||||
private Security $security,
|
||||
) {
|
||||
}
|
||||
|
||||
/** @return array<ReplacedClassResource> */
|
||||
#[Override]
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$user instanceof SecurityUser) {
|
||||
throw new UnauthorizedHttpException('Bearer', 'Authentification requise.');
|
||||
}
|
||||
|
||||
if (!$this->tenantContext->hasTenant()) {
|
||||
throw new UnauthorizedHttpException('Bearer', 'Tenant non défini.');
|
||||
}
|
||||
|
||||
$query = new GetReplacedClassesForTeacherQuery(
|
||||
replacementTeacherId: $user->userId(),
|
||||
tenantId: (string) $this->tenantContext->getCurrentTenantId(),
|
||||
);
|
||||
|
||||
return array_map(
|
||||
ReplacedClassResource::fromDto(...),
|
||||
($this->handler)($query),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Scolarite\Domain\Model\TeacherReplacement\TeacherReplacementId;
|
||||
use App\Scolarite\Domain\Repository\TeacherReplacementRepository;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\TeacherReplacementResource;
|
||||
use App\Scolarite\Infrastructure\Security\TeacherReplacementVoter;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use Override;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
/**
|
||||
* @implements ProviderInterface<TeacherReplacementResource>
|
||||
*/
|
||||
final readonly class TeacherReplacementItemProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private TeacherReplacementRepository $replacementRepository,
|
||||
private TenantContext $tenantContext,
|
||||
private AuthorizationCheckerInterface $authorizationChecker,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): TeacherReplacementResource
|
||||
{
|
||||
if (!$this->authorizationChecker->isGranted(TeacherReplacementVoter::DELETE)) {
|
||||
throw new AccessDeniedHttpException('Vous n\'êtes pas autorisé à terminer les remplacements.');
|
||||
}
|
||||
|
||||
if (!$this->tenantContext->hasTenant()) {
|
||||
throw new UnauthorizedHttpException('Bearer', 'Tenant non défini.');
|
||||
}
|
||||
|
||||
/** @var string $id */
|
||||
$id = $uriVariables['id'];
|
||||
$tenantId = TenantId::fromString((string) $this->tenantContext->getCurrentTenantId());
|
||||
|
||||
$replacement = $this->replacementRepository->findById(
|
||||
TeacherReplacementId::fromString($id),
|
||||
$tenantId,
|
||||
);
|
||||
|
||||
if ($replacement === null) {
|
||||
throw new NotFoundHttpException('Remplacement non trouvé.');
|
||||
}
|
||||
|
||||
return TeacherReplacementResource::fromDomain($replacement);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Scolarite\Application\Query\GetActiveReplacements\GetActiveReplacementsHandler;
|
||||
use App\Scolarite\Application\Query\GetActiveReplacements\GetActiveReplacementsQuery;
|
||||
use App\Scolarite\Application\Query\GetActiveReplacements\ReplacementDto;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\TeacherReplacementResource;
|
||||
use App\Scolarite\Infrastructure\Security\TeacherReplacementVoter;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
|
||||
use function array_map;
|
||||
|
||||
use Override;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
/**
|
||||
* @implements ProviderInterface<TeacherReplacementResource>
|
||||
*/
|
||||
final readonly class TeacherReplacementsCollectionProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private GetActiveReplacementsHandler $handler,
|
||||
private TenantContext $tenantContext,
|
||||
private AuthorizationCheckerInterface $authorizationChecker,
|
||||
) {
|
||||
}
|
||||
|
||||
/** @return array<TeacherReplacementResource> */
|
||||
#[Override]
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
||||
{
|
||||
if (!$this->authorizationChecker->isGranted(TeacherReplacementVoter::VIEW)) {
|
||||
throw new AccessDeniedHttpException('Vous n\'êtes pas autorisé à voir les remplacements.');
|
||||
}
|
||||
|
||||
if (!$this->tenantContext->hasTenant()) {
|
||||
throw new UnauthorizedHttpException('Bearer', 'Tenant non défini.');
|
||||
}
|
||||
|
||||
$query = new GetActiveReplacementsQuery(
|
||||
tenantId: (string) $this->tenantContext->getCurrentTenantId(),
|
||||
);
|
||||
|
||||
$dtos = ($this->handler)($query);
|
||||
|
||||
return array_map(
|
||||
static fn (ReplacementDto $dto) => TeacherReplacementResource::fromDto($dto),
|
||||
$dtos,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Scolarite\Application\Query\GetReplacedClassesForTeacher\ReplacedClassDto;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\ReplacedClassesProvider;
|
||||
|
||||
/**
|
||||
* Classes/matières pour lesquelles l'enseignant connecté est remplaçant.
|
||||
*/
|
||||
#[ApiResource(
|
||||
shortName: 'ReplacedClass',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/me/replaced-classes',
|
||||
provider: ReplacedClassesProvider::class,
|
||||
name: 'get_replaced_classes',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class ReplacedClassResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public string $id;
|
||||
|
||||
public string $replacementId;
|
||||
|
||||
public string $replacedTeacherId;
|
||||
|
||||
public string $classId;
|
||||
|
||||
public string $subjectId;
|
||||
|
||||
public string $className;
|
||||
|
||||
public string $subjectName;
|
||||
|
||||
public string $startDate;
|
||||
|
||||
public string $endDate;
|
||||
|
||||
public static function fromDto(ReplacedClassDto $dto): self
|
||||
{
|
||||
$resource = new self();
|
||||
$resource->id = $dto->replacementId . '_' . $dto->classId . '_' . $dto->subjectId;
|
||||
$resource->replacementId = $dto->replacementId;
|
||||
$resource->replacedTeacherId = $dto->replacedTeacherId;
|
||||
$resource->classId = $dto->classId;
|
||||
$resource->subjectId = $dto->subjectId;
|
||||
$resource->className = $dto->className;
|
||||
$resource->subjectName = $dto->subjectName;
|
||||
$resource->startDate = $dto->startDate->format('Y-m-d');
|
||||
$resource->endDate = $dto->endDate->format('Y-m-d');
|
||||
|
||||
return $resource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Scolarite\Application\Query\GetActiveReplacements\ReplacementDto;
|
||||
use App\Scolarite\Domain\Model\TeacherReplacement\TeacherReplacement;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\CreateTeacherReplacementProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\EndTeacherReplacementProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\TeacherReplacementItemProvider;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\TeacherReplacementsCollectionProvider;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* API Resource pour la gestion des remplacements enseignants.
|
||||
*
|
||||
* @see Story 2.9 - Désignation Remplaçants Temporaires
|
||||
* @see FR9 - Désigner remplaçant temporaire
|
||||
*/
|
||||
#[ApiResource(
|
||||
shortName: 'TeacherReplacement',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/teacher-replacements',
|
||||
provider: TeacherReplacementsCollectionProvider::class,
|
||||
name: 'get_teacher_replacements',
|
||||
),
|
||||
new Post(
|
||||
uriTemplate: '/teacher-replacements',
|
||||
processor: CreateTeacherReplacementProcessor::class,
|
||||
validationContext: ['groups' => ['Default', 'create']],
|
||||
name: 'create_teacher_replacement',
|
||||
),
|
||||
new Delete(
|
||||
uriTemplate: '/teacher-replacements/{id}',
|
||||
provider: TeacherReplacementItemProvider::class,
|
||||
processor: EndTeacherReplacementProcessor::class,
|
||||
name: 'end_teacher_replacement',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class TeacherReplacementResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $id = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'L\'identifiant de l\'enseignant remplacé est requis.', groups: ['create'])]
|
||||
public ?string $replacedTeacherId = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'L\'identifiant du remplaçant est requis.', groups: ['create'])]
|
||||
public ?string $replacementTeacherId = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'La date de début est requise.', groups: ['create'])]
|
||||
public ?string $startDate = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'La date de fin est requise.', groups: ['create'])]
|
||||
public ?string $endDate = null;
|
||||
|
||||
/** @var array<array{classId: string, subjectId: string}>|null */
|
||||
#[Assert\NotBlank(message: 'Au moins une classe/matière est requise.', groups: ['create'])]
|
||||
#[Assert\All(
|
||||
constraints: [
|
||||
new Assert\Collection(
|
||||
fields: [
|
||||
'classId' => [
|
||||
new Assert\NotBlank(message: 'L\'identifiant de la classe est requis.', groups: ['create']),
|
||||
new Assert\Uuid(message: 'L\'identifiant de la classe doit être un UUID valide.', groups: ['create']),
|
||||
],
|
||||
'subjectId' => [
|
||||
new Assert\NotBlank(message: 'L\'identifiant de la matière est requis.', groups: ['create']),
|
||||
new Assert\Uuid(message: 'L\'identifiant de la matière doit être un UUID valide.', groups: ['create']),
|
||||
],
|
||||
],
|
||||
allowExtraFields: false,
|
||||
allowMissingFields: false,
|
||||
groups: ['create'],
|
||||
),
|
||||
],
|
||||
groups: ['create'],
|
||||
)]
|
||||
public ?array $classes = null;
|
||||
|
||||
public ?string $reason = null;
|
||||
|
||||
public ?string $status = null;
|
||||
|
||||
public ?DateTimeImmutable $createdAt = null;
|
||||
|
||||
public ?DateTimeImmutable $endedAt = null;
|
||||
|
||||
public static function fromDomain(TeacherReplacement $replacement): self
|
||||
{
|
||||
$resource = new self();
|
||||
$resource->id = (string) $replacement->id;
|
||||
$resource->replacedTeacherId = (string) $replacement->replacedTeacherId;
|
||||
$resource->replacementTeacherId = (string) $replacement->replacementTeacherId;
|
||||
$resource->startDate = $replacement->startDate->format('Y-m-d');
|
||||
$resource->endDate = $replacement->endDate->format('Y-m-d');
|
||||
$resource->classes = array_map(
|
||||
static fn ($pair) => [
|
||||
'classId' => (string) $pair->classId,
|
||||
'subjectId' => (string) $pair->subjectId,
|
||||
],
|
||||
$replacement->classes,
|
||||
);
|
||||
$resource->reason = $replacement->reason;
|
||||
$resource->status = $replacement->status->value;
|
||||
$resource->createdAt = $replacement->createdAt;
|
||||
$resource->endedAt = $replacement->endedAt;
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
public static function fromDto(ReplacementDto $dto): self
|
||||
{
|
||||
$resource = new self();
|
||||
$resource->id = $dto->id;
|
||||
$resource->replacedTeacherId = $dto->replacedTeacherId;
|
||||
$resource->replacementTeacherId = $dto->replacementTeacherId;
|
||||
$resource->startDate = $dto->startDate->format('Y-m-d');
|
||||
$resource->endDate = $dto->endDate->format('Y-m-d');
|
||||
$resource->classes = $dto->classes;
|
||||
$resource->reason = $dto->reason;
|
||||
$resource->status = $dto->status;
|
||||
|
||||
return $resource;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user