Quand une classe n'avait aucune affectation enseignant-matière, les selects de la modale de création de créneau affichaient tous les enseignants et toutes les matières au lieu d'une liste vide. Cela permettait de soumettre des combinaisons invalides, produisant un message d'erreur avec des UUID incompréhensibles. Les dropdowns n'affichent plus que les enseignants/matières effectivement affectés à la classe sélectionnée. Le message d'erreur backend est reformulé sans UUID pour le cas où la validation frontend serait contournée.
120 lines
4.6 KiB
PHP
120 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Scolarite\Infrastructure\Api\Processor;
|
|
|
|
use ApiPlatform\Metadata\Operation;
|
|
use ApiPlatform\State\ProcessorInterface;
|
|
use App\Scolarite\Application\Command\UpdateScheduleSlot\UpdateScheduleSlotCommand;
|
|
use App\Scolarite\Application\Command\UpdateScheduleSlot\UpdateScheduleSlotHandler;
|
|
use App\Scolarite\Domain\Exception\CreneauHoraireInvalideException;
|
|
use App\Scolarite\Domain\Exception\EnseignantNonAffecteException;
|
|
use App\Scolarite\Domain\Exception\ScheduleSlotNotFoundException;
|
|
use App\Scolarite\Domain\Service\ScheduleConflict;
|
|
use App\Scolarite\Infrastructure\Api\Resource\ScheduleSlotResource;
|
|
use App\Scolarite\Infrastructure\Security\ScheduleSlotVoter;
|
|
use App\Shared\Infrastructure\Tenant\TenantContext;
|
|
|
|
use function array_map;
|
|
|
|
use Override;
|
|
use Ramsey\Uuid\Exception\InvalidUuidStringException;
|
|
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\HttpKernel\Exception\UnprocessableEntityHttpException;
|
|
use Symfony\Component\Messenger\MessageBusInterface;
|
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
|
use ValueError;
|
|
|
|
/**
|
|
* Processor API Platform pour modifier un créneau d'emploi du temps.
|
|
*
|
|
* @implements ProcessorInterface<ScheduleSlotResource, ScheduleSlotResource>
|
|
*/
|
|
final readonly class UpdateScheduleSlotProcessor implements ProcessorInterface
|
|
{
|
|
public function __construct(
|
|
private UpdateScheduleSlotHandler $handler,
|
|
private TenantContext $tenantContext,
|
|
private MessageBusInterface $eventBus,
|
|
private AuthorizationCheckerInterface $authorizationChecker,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @param ScheduleSlotResource $data
|
|
*/
|
|
#[Override]
|
|
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ScheduleSlotResource
|
|
{
|
|
if (!$this->authorizationChecker->isGranted(ScheduleSlotVoter::EDIT)) {
|
|
throw new AccessDeniedHttpException("Vous n'êtes pas autorisé à modifier l'emploi du temps.");
|
|
}
|
|
|
|
if (!$this->tenantContext->hasTenant()) {
|
|
throw new UnauthorizedHttpException('Bearer', 'Tenant non défini.');
|
|
}
|
|
|
|
/** @var string|null $slotId */
|
|
$slotId = $uriVariables['id'] ?? null;
|
|
if ($slotId === null) {
|
|
throw new NotFoundHttpException('Créneau non trouvé.');
|
|
}
|
|
|
|
$tenantId = (string) $this->tenantContext->getCurrentTenantId();
|
|
|
|
try {
|
|
$command = new UpdateScheduleSlotCommand(
|
|
tenantId: $tenantId,
|
|
slotId: $slotId,
|
|
classId: $data->classId ?? '',
|
|
subjectId: $data->subjectId ?? '',
|
|
teacherId: $data->teacherId ?? '',
|
|
dayOfWeek: $data->dayOfWeek ?? 1,
|
|
startTime: $data->startTime ?? '',
|
|
endTime: $data->endTime ?? '',
|
|
room: $data->room,
|
|
forceConflicts: $data->forceConflicts ?? false,
|
|
);
|
|
|
|
$result = ($this->handler)($command);
|
|
$slot = $result['slot'];
|
|
/** @var array<ScheduleConflict> $conflicts */
|
|
$conflicts = $result['conflicts'];
|
|
|
|
if ($conflicts === [] || $command->forceConflicts) {
|
|
foreach ($slot->pullDomainEvents() as $event) {
|
|
$this->eventBus->dispatch($event);
|
|
}
|
|
}
|
|
|
|
$resource = ScheduleSlotResource::fromDomain($slot);
|
|
|
|
if ($conflicts !== []) {
|
|
$resource->conflicts = array_map(
|
|
static fn (ScheduleConflict $c) => [
|
|
'type' => $c->type,
|
|
'description' => $c->description,
|
|
'slotId' => (string) $c->conflictingSlot->id,
|
|
],
|
|
$conflicts,
|
|
);
|
|
}
|
|
|
|
return $resource;
|
|
} catch (EnseignantNonAffecteException) {
|
|
throw new UnprocessableEntityHttpException(
|
|
"L'enseignant sélectionné n'est pas affecté à cette classe pour cette matière. "
|
|
. 'Veuillez vérifier les affectations enseignant-classe-matière.',
|
|
);
|
|
} catch (ScheduleSlotNotFoundException|InvalidUuidStringException) {
|
|
throw new NotFoundHttpException('Créneau non trouvé.');
|
|
} catch (CreneauHoraireInvalideException|ValueError $e) {
|
|
throw new BadRequestHttpException($e->getMessage());
|
|
}
|
|
}
|
|
}
|