Files
Classeo/backend/src/Scolarite/Infrastructure/Api/Processor/UpdateScheduleSlotProcessor.php
Mathias STRASSER 39f8650b92 fix: Filtrer enseignants et matières par affectation lors de la création de créneaux
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.
2026-03-07 21:14:04 +01:00

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());
}
}
}