feat: Permettre aux enseignants de contourner les règles de devoirs avec justification
Akeneo permet de configurer des règles de devoirs en mode Hard qui bloquent totalement la création. Or certains cas légitimes (sorties scolaires, événements exceptionnels) nécessitent de passer outre ces règles. Sans mécanisme d'exception, l'enseignant est bloqué et doit contacter manuellement la direction. Cette implémentation ajoute un flux complet d'exception : l'enseignant justifie sa demande (min 20 caractères), le devoir est créé immédiatement, et la direction est notifiée par email. Le handler vérifie côté serveur que les règles sont réellement bloquantes avant d'accepter l'exception, empêchant toute fabrication de fausses exceptions via l'API. La direction dispose d'un rapport filtrable par période, enseignant et type de règle.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Application\Command\CreateHomeworkWithException;
|
||||
|
||||
final readonly class CreateHomeworkWithExceptionCommand
|
||||
{
|
||||
/**
|
||||
* @param string[] $ruleTypes Types de règles contournées (ex: ['minimum_delay'])
|
||||
*/
|
||||
public function __construct(
|
||||
public string $tenantId,
|
||||
public string $classId,
|
||||
public string $subjectId,
|
||||
public string $teacherId,
|
||||
public string $title,
|
||||
public ?string $description,
|
||||
public string $dueDate,
|
||||
public string $justification,
|
||||
public array $ruleTypes,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Application\Command\CreateHomeworkWithException;
|
||||
|
||||
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\Port\CurrentCalendarProvider;
|
||||
use App\Scolarite\Application\Port\EnseignantAffectationChecker;
|
||||
use App\Scolarite\Application\Port\HomeworkRulesChecker;
|
||||
use App\Scolarite\Domain\Exception\EnseignantNonAffecteException;
|
||||
use App\Scolarite\Domain\Exception\ExceptionNonNecessaireException;
|
||||
use App\Scolarite\Domain\Model\Homework\Homework;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkRuleException;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRepository;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRuleExceptionRepository;
|
||||
use App\Scolarite\Domain\Service\DueDateValidator;
|
||||
use App\Shared\Domain\Clock;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
use Throwable;
|
||||
|
||||
#[AsMessageHandler(bus: 'command.bus')]
|
||||
final readonly class CreateHomeworkWithExceptionHandler
|
||||
{
|
||||
public function __construct(
|
||||
private HomeworkRepository $homeworkRepository,
|
||||
private HomeworkRuleExceptionRepository $exceptionRepository,
|
||||
private EnseignantAffectationChecker $affectationChecker,
|
||||
private CurrentCalendarProvider $calendarProvider,
|
||||
private DueDateValidator $dueDateValidator,
|
||||
private HomeworkRulesChecker $rulesChecker,
|
||||
private Clock $clock,
|
||||
private Connection $connection,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{homework: Homework, exception: HomeworkRuleException}
|
||||
*/
|
||||
public function __invoke(CreateHomeworkWithExceptionCommand $command): array
|
||||
{
|
||||
$tenantId = TenantId::fromString($command->tenantId);
|
||||
$classId = ClassId::fromString($command->classId);
|
||||
$subjectId = SubjectId::fromString($command->subjectId);
|
||||
$teacherId = UserId::fromString($command->teacherId);
|
||||
$now = $this->clock->now();
|
||||
|
||||
if (!$this->affectationChecker->estAffecte($teacherId, $classId, $subjectId, $tenantId)) {
|
||||
throw EnseignantNonAffecteException::pourClasseEtMatiere($teacherId, $classId, $subjectId);
|
||||
}
|
||||
|
||||
$calendar = $this->calendarProvider->forCurrentYear($tenantId);
|
||||
$dueDate = new DateTimeImmutable($command->dueDate);
|
||||
$this->dueDateValidator->valider($dueDate, $now, $calendar);
|
||||
|
||||
$rulesResult = $this->rulesChecker->verifier($tenantId, $dueDate, $now);
|
||||
|
||||
if (!$rulesResult->estBloquant()) {
|
||||
throw ExceptionNonNecessaireException::carReglesNonBloquantes();
|
||||
}
|
||||
|
||||
$homework = Homework::creer(
|
||||
tenantId: $tenantId,
|
||||
classId: $classId,
|
||||
subjectId: $subjectId,
|
||||
teacherId: $teacherId,
|
||||
title: $command->title,
|
||||
description: $command->description,
|
||||
dueDate: $dueDate,
|
||||
now: $now,
|
||||
);
|
||||
|
||||
$exception = HomeworkRuleException::demander(
|
||||
tenantId: $tenantId,
|
||||
homeworkId: $homework->id,
|
||||
ruleTypes: $rulesResult->ruleTypes(),
|
||||
justification: $command->justification,
|
||||
createdBy: $teacherId,
|
||||
now: $now,
|
||||
);
|
||||
|
||||
$this->connection->beginTransaction();
|
||||
|
||||
try {
|
||||
$this->homeworkRepository->save($homework);
|
||||
$this->exceptionRepository->save($exception);
|
||||
$this->connection->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->connection->rollBack();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return ['homework' => $homework, 'exception' => $exception];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Application\Query\GetHomeworkExceptionsReport;
|
||||
|
||||
use App\Administration\Domain\Model\User\User;
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Administration\Domain\Repository\UserRepository;
|
||||
use App\Scolarite\Domain\Model\Homework\Homework;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkRuleException;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRepository;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRuleExceptionRepository;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
|
||||
use function array_filter;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
use function str_contains;
|
||||
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
|
||||
#[AsMessageHandler(bus: 'query.bus')]
|
||||
final readonly class GetHomeworkExceptionsReportHandler
|
||||
{
|
||||
public function __construct(
|
||||
private HomeworkRuleExceptionRepository $exceptionRepository,
|
||||
private HomeworkRepository $homeworkRepository,
|
||||
private UserRepository $userRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
/** @return array<HomeworkExceptionDto> */
|
||||
public function __invoke(GetHomeworkExceptionsReportQuery $query): array
|
||||
{
|
||||
$tenantId = TenantId::fromString($query->tenantId);
|
||||
$exceptions = $this->exceptionRepository->findByTenant($tenantId);
|
||||
|
||||
$startDate = $query->startDate !== null ? new DateTimeImmutable($query->startDate) : null;
|
||||
$endDate = $query->endDate !== null ? new DateTimeImmutable($query->endDate . ' 23:59:59') : null;
|
||||
|
||||
$filtered = array_filter(
|
||||
$exceptions,
|
||||
fn (HomeworkRuleException $e): bool => $this->matchesFilters(
|
||||
$e,
|
||||
$startDate,
|
||||
$endDate,
|
||||
$query->teacherId,
|
||||
$query->ruleType,
|
||||
),
|
||||
);
|
||||
|
||||
if ($filtered === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$homeworkCache = $this->preloadHomeworks($filtered, $tenantId);
|
||||
$teacherCache = $this->preloadTeachers($filtered);
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($filtered as $exception) {
|
||||
$hwId = (string) $exception->homeworkId;
|
||||
$teacherId = (string) $exception->createdBy;
|
||||
|
||||
$homework = $homeworkCache[$hwId] ?? null;
|
||||
$teacher = $teacherCache[$teacherId] ?? null;
|
||||
|
||||
$results[] = new HomeworkExceptionDto(
|
||||
id: (string) $exception->id,
|
||||
homeworkId: $hwId,
|
||||
homeworkTitle: $homework !== null ? $homework->title : '(supprimé)',
|
||||
ruleType: $exception->ruleType,
|
||||
justification: $exception->justification,
|
||||
teacherId: $teacherId,
|
||||
teacherName: $teacher !== null ? $teacher->firstName . ' ' . $teacher->lastName : 'Inconnu',
|
||||
createdAt: $exception->createdAt->format(DateTimeImmutable::ATOM),
|
||||
);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<HomeworkRuleException> $exceptions
|
||||
*
|
||||
* @return array<string, Homework|null>
|
||||
*/
|
||||
private function preloadHomeworks(array $exceptions, TenantId $tenantId): array
|
||||
{
|
||||
$cache = [];
|
||||
|
||||
foreach ($exceptions as $exception) {
|
||||
$hwId = (string) $exception->homeworkId;
|
||||
|
||||
if (!isset($cache[$hwId])) {
|
||||
$cache[$hwId] = $this->homeworkRepository->findById($exception->homeworkId, $tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<HomeworkRuleException> $exceptions
|
||||
*
|
||||
* @return array<string, User|null>
|
||||
*/
|
||||
private function preloadTeachers(array $exceptions): array
|
||||
{
|
||||
$cache = [];
|
||||
|
||||
foreach ($exceptions as $exception) {
|
||||
$teacherId = (string) $exception->createdBy;
|
||||
|
||||
if (!isset($cache[$teacherId])) {
|
||||
$cache[$teacherId] = $this->userRepository->findById(UserId::fromString($teacherId));
|
||||
}
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
private function matchesFilters(
|
||||
HomeworkRuleException $exception,
|
||||
?DateTimeImmutable $startDate,
|
||||
?DateTimeImmutable $endDate,
|
||||
?string $teacherId,
|
||||
?string $ruleType,
|
||||
): bool {
|
||||
if ($startDate !== null && $exception->createdAt < $startDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($endDate !== null && $exception->createdAt > $endDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($teacherId !== null && (string) $exception->createdBy !== $teacherId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($ruleType !== null && !str_contains($exception->ruleType, $ruleType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Application\Query\GetHomeworkExceptionsReport;
|
||||
|
||||
final readonly class GetHomeworkExceptionsReportQuery
|
||||
{
|
||||
public function __construct(
|
||||
public string $tenantId,
|
||||
public ?string $startDate = null,
|
||||
public ?string $endDate = null,
|
||||
public ?string $teacherId = null,
|
||||
public ?string $ruleType = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Application\Query\GetHomeworkExceptionsReport;
|
||||
|
||||
final readonly class HomeworkExceptionDto
|
||||
{
|
||||
public function __construct(
|
||||
public string $id,
|
||||
public string $homeworkId,
|
||||
public string $homeworkTitle,
|
||||
public string $ruleType,
|
||||
public string $justification,
|
||||
public string $teacherId,
|
||||
public string $teacherName,
|
||||
public string $createdAt,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Domain\Event;
|
||||
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkId;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkRuleExceptionId;
|
||||
use App\Shared\Domain\DomainEvent;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use DateTimeImmutable;
|
||||
use Override;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
final readonly class ExceptionDevoirDemandee implements DomainEvent
|
||||
{
|
||||
/**
|
||||
* @param string[] $ruleTypes
|
||||
*/
|
||||
public function __construct(
|
||||
public HomeworkRuleExceptionId $exceptionId,
|
||||
public TenantId $tenantId,
|
||||
public HomeworkId $homeworkId,
|
||||
public array $ruleTypes,
|
||||
public string $justification,
|
||||
public UserId $createdBy,
|
||||
private DateTimeImmutable $occurredOn,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function occurredOn(): DateTimeImmutable
|
||||
{
|
||||
return $this->occurredOn;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function aggregateId(): UuidInterface
|
||||
{
|
||||
return $this->exceptionId->value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
final class ExceptionNonNecessaireException extends DomainException
|
||||
{
|
||||
public static function carReglesNonBloquantes(): self
|
||||
{
|
||||
return new self('Impossible de demander une exception : les règles ne bloquent pas ce devoir.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Domain\Exception;
|
||||
|
||||
use DomainException;
|
||||
|
||||
final class JustificationTropCourteException extends DomainException
|
||||
{
|
||||
public static function avecMinimum(int $minimum): self
|
||||
{
|
||||
return new self("La justification doit contenir au moins {$minimum} caractères.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Domain\Model\Homework;
|
||||
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Scolarite\Domain\Event\ExceptionDevoirDemandee;
|
||||
use App\Scolarite\Domain\Exception\JustificationTropCourteException;
|
||||
use App\Shared\Domain\AggregateRoot;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use DateTimeImmutable;
|
||||
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function mb_strlen;
|
||||
|
||||
final class HomeworkRuleException extends AggregateRoot
|
||||
{
|
||||
private const int JUSTIFICATION_MIN_LENGTH = 20;
|
||||
|
||||
private function __construct(
|
||||
public private(set) HomeworkRuleExceptionId $id,
|
||||
public private(set) TenantId $tenantId,
|
||||
public private(set) HomeworkId $homeworkId,
|
||||
public private(set) string $ruleType,
|
||||
public private(set) string $justification,
|
||||
public private(set) UserId $createdBy,
|
||||
public private(set) DateTimeImmutable $createdAt,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $ruleTypes Types de règles contournées
|
||||
*/
|
||||
public static function demander(
|
||||
TenantId $tenantId,
|
||||
HomeworkId $homeworkId,
|
||||
array $ruleTypes,
|
||||
string $justification,
|
||||
UserId $createdBy,
|
||||
DateTimeImmutable $now,
|
||||
): self {
|
||||
if (mb_strlen($justification) < self::JUSTIFICATION_MIN_LENGTH) {
|
||||
throw JustificationTropCourteException::avecMinimum(self::JUSTIFICATION_MIN_LENGTH);
|
||||
}
|
||||
|
||||
$ruleType = implode(',', $ruleTypes);
|
||||
|
||||
$exception = new self(
|
||||
id: HomeworkRuleExceptionId::generate(),
|
||||
tenantId: $tenantId,
|
||||
homeworkId: $homeworkId,
|
||||
ruleType: $ruleType,
|
||||
justification: $justification,
|
||||
createdBy: $createdBy,
|
||||
createdAt: $now,
|
||||
);
|
||||
|
||||
$exception->recordEvent(new ExceptionDevoirDemandee(
|
||||
exceptionId: $exception->id,
|
||||
tenantId: $tenantId,
|
||||
homeworkId: $homeworkId,
|
||||
ruleTypes: $ruleTypes,
|
||||
justification: $justification,
|
||||
createdBy: $createdBy,
|
||||
occurredOn: $now,
|
||||
));
|
||||
|
||||
return $exception;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function ruleTypes(): array
|
||||
{
|
||||
return explode(',', $this->ruleType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Pour usage Infrastructure uniquement
|
||||
*/
|
||||
public static function reconstitute(
|
||||
HomeworkRuleExceptionId $id,
|
||||
TenantId $tenantId,
|
||||
HomeworkId $homeworkId,
|
||||
string $ruleType,
|
||||
string $justification,
|
||||
UserId $createdBy,
|
||||
DateTimeImmutable $createdAt,
|
||||
): self {
|
||||
return new self(
|
||||
id: $id,
|
||||
tenantId: $tenantId,
|
||||
homeworkId: $homeworkId,
|
||||
ruleType: $ruleType,
|
||||
justification: $justification,
|
||||
createdBy: $createdBy,
|
||||
createdAt: $createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Domain\Model\Homework;
|
||||
|
||||
use App\Shared\Domain\EntityId;
|
||||
|
||||
final readonly class HomeworkRuleExceptionId extends EntityId
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Domain\Repository;
|
||||
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkId;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkRuleException;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
|
||||
interface HomeworkRuleExceptionRepository
|
||||
{
|
||||
public function save(HomeworkRuleException $exception): void;
|
||||
|
||||
/** @return array<HomeworkRuleException> */
|
||||
public function findByHomework(HomeworkId $homeworkId, TenantId $tenantId): array;
|
||||
|
||||
/** @return array<HomeworkRuleException> */
|
||||
public function findByTenant(TenantId $tenantId): array;
|
||||
|
||||
/**
|
||||
* Returns the set of homework IDs that have at least one exception.
|
||||
*
|
||||
* @return array<string> Homework IDs as strings
|
||||
*/
|
||||
public function homeworkIdsWithExceptions(TenantId $tenantId): array;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Processor;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Administration\Infrastructure\Security\SecurityUser;
|
||||
use App\Scolarite\Application\Command\CreateHomeworkWithException\CreateHomeworkWithExceptionCommand;
|
||||
use App\Scolarite\Application\Command\CreateHomeworkWithException\CreateHomeworkWithExceptionHandler;
|
||||
use App\Scolarite\Domain\Exception\DateEcheanceInvalideException;
|
||||
use App\Scolarite\Domain\Exception\EnseignantNonAffecteException;
|
||||
use App\Scolarite\Domain\Exception\ExceptionNonNecessaireException;
|
||||
use App\Scolarite\Domain\Exception\JustificationTropCourteException;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\HomeworkExceptionResource;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\HomeworkResource;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use Override;
|
||||
use Ramsey\Uuid\Exception\InvalidUuidStringException;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
/**
|
||||
* @implements ProcessorInterface<HomeworkExceptionResource, HomeworkResource>
|
||||
*/
|
||||
final readonly class CreateHomeworkWithExceptionProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private CreateHomeworkWithExceptionHandler $handler,
|
||||
private TenantContext $tenantContext,
|
||||
private MessageBusInterface $eventBus,
|
||||
private Security $security,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HomeworkExceptionResource $data
|
||||
*/
|
||||
#[Override]
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): HomeworkResource
|
||||
{
|
||||
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 CreateHomeworkWithExceptionCommand(
|
||||
tenantId: (string) $this->tenantContext->getCurrentTenantId(),
|
||||
classId: $data->classId ?? '',
|
||||
subjectId: $data->subjectId ?? '',
|
||||
teacherId: $user->userId(),
|
||||
title: $data->title ?? '',
|
||||
description: $data->description,
|
||||
dueDate: $data->dueDate ?? '',
|
||||
justification: $data->justification ?? '',
|
||||
ruleTypes: $data->ruleTypes ?? [],
|
||||
);
|
||||
|
||||
$result = ($this->handler)($command);
|
||||
|
||||
$homework = $result['homework'];
|
||||
$exception = $result['exception'];
|
||||
|
||||
foreach ($homework->pullDomainEvents() as $event) {
|
||||
$this->eventBus->dispatch($event);
|
||||
}
|
||||
|
||||
foreach ($exception->pullDomainEvents() as $event) {
|
||||
$this->eventBus->dispatch($event);
|
||||
}
|
||||
|
||||
return HomeworkResource::fromDomain(
|
||||
$homework,
|
||||
ruleException: $exception,
|
||||
);
|
||||
} catch (ExceptionNonNecessaireException|JustificationTropCourteException $e) {
|
||||
throw new BadRequestHttpException($e->getMessage());
|
||||
} catch (EnseignantNonAffecteException|DateEcheanceInvalideException $e) {
|
||||
throw new BadRequestHttpException($e->getMessage());
|
||||
} catch (InvalidUuidStringException $e) {
|
||||
throw new BadRequestHttpException('UUID invalide : ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use App\Administration\Domain\Repository\SubjectRepository;
|
||||
use App\Administration\Infrastructure\Security\SecurityUser;
|
||||
use App\Scolarite\Domain\Model\Homework\Homework;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRepository;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRuleExceptionRepository;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\HomeworkResource;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
|
||||
@@ -34,6 +35,7 @@ final readonly class HomeworkCollectionProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private HomeworkRepository $homeworkRepository,
|
||||
private HomeworkRuleExceptionRepository $exceptionRepository,
|
||||
private TenantContext $tenantContext,
|
||||
private Security $security,
|
||||
private ClassRepository $classRepository,
|
||||
@@ -77,10 +79,22 @@ final readonly class HomeworkCollectionProvider implements ProviderInterface
|
||||
));
|
||||
}
|
||||
|
||||
$allExceptions = $this->exceptionRepository->findByTenant($tenantId);
|
||||
$exceptionsByHomework = [];
|
||||
|
||||
foreach ($allExceptions as $exception) {
|
||||
$hwId = (string) $exception->homeworkId;
|
||||
|
||||
if (!isset($exceptionsByHomework[$hwId])) {
|
||||
$exceptionsByHomework[$hwId] = $exception;
|
||||
}
|
||||
}
|
||||
|
||||
return array_map(fn (Homework $homework) => HomeworkResource::fromDomain(
|
||||
$homework,
|
||||
$this->resolveClassName($homework),
|
||||
$this->resolveSubjectName($homework),
|
||||
$exceptionsByHomework[(string) $homework->id] ?? null,
|
||||
), $homeworks);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Administration\Domain\Model\User\Role;
|
||||
use App\Administration\Infrastructure\Security\SecurityUser;
|
||||
use App\Scolarite\Application\Query\GetHomeworkExceptionsReport\GetHomeworkExceptionsReportHandler;
|
||||
use App\Scolarite\Application\Query\GetHomeworkExceptionsReport\GetHomeworkExceptionsReportQuery;
|
||||
use App\Scolarite\Application\Query\GetHomeworkExceptionsReport\HomeworkExceptionDto;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\HomeworkExceptionResource;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
|
||||
use function array_map;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
|
||||
use Override;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
|
||||
/**
|
||||
* @implements ProviderInterface<HomeworkExceptionResource>
|
||||
*/
|
||||
final readonly class HomeworkExceptionsReportProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private GetHomeworkExceptionsReportHandler $handler,
|
||||
private TenantContext $tenantContext,
|
||||
private RequestStack $requestStack,
|
||||
private Security $security,
|
||||
) {
|
||||
}
|
||||
|
||||
/** @return array<HomeworkExceptionResource> */
|
||||
#[Override]
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
||||
{
|
||||
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.');
|
||||
}
|
||||
|
||||
if (!in_array(Role::ADMIN->value, $user->getRoles(), true)
|
||||
&& !in_array(Role::SUPER_ADMIN->value, $user->getRoles(), true)) {
|
||||
throw new AccessDeniedHttpException('Accès réservé à la direction.');
|
||||
}
|
||||
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
$startDate = $request?->query->get('startDate');
|
||||
$endDate = $request?->query->get('endDate');
|
||||
$teacherId = $request?->query->get('teacherId');
|
||||
$ruleType = $request?->query->get('ruleType');
|
||||
|
||||
$query = new GetHomeworkExceptionsReportQuery(
|
||||
tenantId: (string) $this->tenantContext->getCurrentTenantId(),
|
||||
startDate: is_string($startDate) ? $startDate : null,
|
||||
endDate: is_string($endDate) ? $endDate : null,
|
||||
teacherId: is_string($teacherId) ? $teacherId : null,
|
||||
ruleType: is_string($ruleType) ? $ruleType : null,
|
||||
);
|
||||
|
||||
$dtos = ($this->handler)($query);
|
||||
|
||||
return array_map(
|
||||
static function (HomeworkExceptionDto $dto): HomeworkExceptionResource {
|
||||
$resource = new HomeworkExceptionResource();
|
||||
$resource->id = $dto->id;
|
||||
$resource->homeworkId = $dto->homeworkId;
|
||||
$resource->homeworkTitle = $dto->homeworkTitle;
|
||||
$resource->ruleType = $dto->ruleType;
|
||||
$resource->justification = $dto->justification;
|
||||
$resource->teacherId = $dto->teacherId;
|
||||
$resource->teacherName = $dto->teacherName;
|
||||
$resource->createdAt = $dto->createdAt;
|
||||
|
||||
return $resource;
|
||||
},
|
||||
$dtos,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use App\Administration\Domain\Repository\SubjectRepository;
|
||||
use App\Administration\Infrastructure\Security\SecurityUser;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkId;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRepository;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRuleExceptionRepository;
|
||||
use App\Scolarite\Infrastructure\Api\Resource\HomeworkResource;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use Override;
|
||||
@@ -26,6 +27,7 @@ final readonly class HomeworkItemProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private HomeworkRepository $homeworkRepository,
|
||||
private HomeworkRuleExceptionRepository $exceptionRepository,
|
||||
private TenantContext $tenantContext,
|
||||
private Security $security,
|
||||
private ClassRepository $classRepository,
|
||||
@@ -64,11 +66,14 @@ final readonly class HomeworkItemProvider implements ProviderInterface
|
||||
|
||||
$class = $this->classRepository->findById($homework->classId);
|
||||
$subject = $this->subjectRepository->findById($homework->subjectId);
|
||||
$tenantId = $this->tenantContext->getCurrentTenantId();
|
||||
$exceptions = $this->exceptionRepository->findByHomework($homework->id, $tenantId);
|
||||
|
||||
return HomeworkResource::fromDomain(
|
||||
$homework,
|
||||
$class !== null ? (string) $class->name : null,
|
||||
$subject !== null ? (string) $subject->name : null,
|
||||
$exceptions[0] ?? null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Api\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\CreateHomeworkWithExceptionProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Provider\HomeworkExceptionsReportProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'HomeworkException',
|
||||
operations: [
|
||||
new Post(
|
||||
uriTemplate: '/homework/with-exception',
|
||||
processor: CreateHomeworkWithExceptionProcessor::class,
|
||||
validationContext: ['groups' => ['Default', 'create']],
|
||||
name: 'create_homework_with_exception',
|
||||
),
|
||||
new GetCollection(
|
||||
uriTemplate: '/admin/homework-exceptions',
|
||||
provider: HomeworkExceptionsReportProvider::class,
|
||||
name: 'get_homework_exceptions_report',
|
||||
),
|
||||
],
|
||||
)]
|
||||
final class HomeworkExceptionResource
|
||||
{
|
||||
#[ApiProperty(identifier: true)]
|
||||
public ?string $id = null;
|
||||
|
||||
// --- Input fields for POST (create with exception) ---
|
||||
|
||||
#[Assert\NotBlank(message: 'La classe est requise.', groups: ['create'])]
|
||||
#[Assert\Uuid(message: 'L\'identifiant de la classe doit être un UUID valide.', groups: ['create'])]
|
||||
public ?string $classId = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'La matière est requise.', groups: ['create'])]
|
||||
#[Assert\Uuid(message: 'L\'identifiant de la matière doit être un UUID valide.', groups: ['create'])]
|
||||
public ?string $subjectId = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'Le titre est requis.', groups: ['create'])]
|
||||
#[Assert\Length(max: 255, maxMessage: 'Le titre ne peut pas dépasser 255 caractères.')]
|
||||
public ?string $title = null;
|
||||
|
||||
public ?string $description = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'La date d\'échéance est requise.', groups: ['create'])]
|
||||
public ?string $dueDate = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'La justification est requise.', groups: ['create'])]
|
||||
#[Assert\Length(min: 20, minMessage: 'La justification doit contenir au moins 20 caractères.', groups: ['create'])]
|
||||
public ?string $justification = null;
|
||||
|
||||
/** @var string[]|null */
|
||||
public ?array $ruleTypes = null;
|
||||
|
||||
// --- Output fields ---
|
||||
|
||||
public ?string $homeworkId = null;
|
||||
public ?string $homeworkTitle = null;
|
||||
public ?string $ruleType = null;
|
||||
public ?string $teacherId = null;
|
||||
public ?string $teacherName = null;
|
||||
public ?string $createdAt = null;
|
||||
public ?bool $hasRuleException = null;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Scolarite\Domain\Model\Homework\Homework;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkRuleException;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\CreateHomeworkProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\DeleteHomeworkProcessor;
|
||||
use App\Scolarite\Infrastructure\Api\Processor\UpdateHomeworkProcessor;
|
||||
@@ -92,10 +93,17 @@ final class HomeworkResource
|
||||
|
||||
public ?bool $hasRuleOverride = null;
|
||||
|
||||
public ?bool $hasRuleException = null;
|
||||
|
||||
public ?string $ruleExceptionJustification = null;
|
||||
|
||||
public ?string $ruleExceptionRuleType = null;
|
||||
|
||||
public static function fromDomain(
|
||||
Homework $homework,
|
||||
?string $className = null,
|
||||
?string $subjectName = null,
|
||||
?HomeworkRuleException $ruleException = null,
|
||||
): self {
|
||||
$resource = new self();
|
||||
$resource->id = (string) $homework->id;
|
||||
@@ -111,6 +119,9 @@ final class HomeworkResource
|
||||
$resource->createdAt = $homework->createdAt;
|
||||
$resource->updatedAt = $homework->updatedAt;
|
||||
$resource->hasRuleOverride = $homework->ruleOverride !== null;
|
||||
$resource->hasRuleException = $ruleException !== null;
|
||||
$resource->ruleExceptionJustification = $ruleException?->justification;
|
||||
$resource->ruleExceptionRuleType = $ruleException?->ruleType;
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Messaging;
|
||||
|
||||
use App\Administration\Domain\Model\User\Role;
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Administration\Domain\Repository\UserRepository;
|
||||
use App\Scolarite\Domain\Event\ExceptionDevoirDemandee;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRepository;
|
||||
|
||||
use function array_filter;
|
||||
use function count;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Throwable;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* Notifie la direction lorsqu'un enseignant demande une exception aux règles de devoirs.
|
||||
*/
|
||||
#[AsMessageHandler(bus: 'event.bus')]
|
||||
final readonly class OnExceptionDevoirDemandeeHandler
|
||||
{
|
||||
public function __construct(
|
||||
private HomeworkRepository $homeworkRepository,
|
||||
private UserRepository $userRepository,
|
||||
private MailerInterface $mailer,
|
||||
private Environment $twig,
|
||||
private LoggerInterface $logger,
|
||||
private string $fromEmail = 'noreply@classeo.fr',
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(ExceptionDevoirDemandee $event): void
|
||||
{
|
||||
$homework = $this->homeworkRepository->findById($event->homeworkId, $event->tenantId);
|
||||
|
||||
if ($homework === null) {
|
||||
$this->logger->warning('Exception devoir : homework introuvable', [
|
||||
'homework_id' => (string) $event->homeworkId,
|
||||
'exception_id' => (string) $event->exceptionId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$teacher = $this->userRepository->findById(UserId::fromString((string) $event->createdBy));
|
||||
|
||||
$allUsers = $this->userRepository->findAllByTenant($event->tenantId);
|
||||
$directors = array_filter(
|
||||
$allUsers,
|
||||
static fn ($user) => $user->aLeRole(Role::ADMIN),
|
||||
);
|
||||
|
||||
if (count($directors) === 0) {
|
||||
$this->logger->info('Exception devoir demandée — aucun directeur à notifier', [
|
||||
'tenant_id' => (string) $event->tenantId,
|
||||
'homework_id' => (string) $event->homeworkId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$teacherName = $teacher !== null ? $teacher->firstName . ' ' . $teacher->lastName : 'Enseignant inconnu';
|
||||
|
||||
$html = $this->twig->render('emails/homework_exception_notification.html.twig', [
|
||||
'teacherName' => $teacherName,
|
||||
'homeworkTitle' => $homework->title,
|
||||
'ruleTypes' => $event->ruleTypes,
|
||||
'justification' => $event->justification,
|
||||
'dueDate' => $homework->dueDate->format('d/m/Y'),
|
||||
]);
|
||||
|
||||
$sent = 0;
|
||||
|
||||
foreach ($directors as $director) {
|
||||
try {
|
||||
$email = (new Email())
|
||||
->from($this->fromEmail)
|
||||
->to((string) $director->email)
|
||||
->subject('Exception aux règles de devoirs — ' . $homework->title)
|
||||
->html($html);
|
||||
|
||||
$this->mailer->send($email);
|
||||
++$sent;
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->warning('Échec envoi notification exception devoir', [
|
||||
'director_email' => (string) $director->email,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->info('Notifications exception devoir envoyées', [
|
||||
'tenant_id' => (string) $event->tenantId,
|
||||
'homework_id' => (string) $event->homeworkId,
|
||||
'exception_id' => (string) $event->exceptionId,
|
||||
'emails_sent' => $sent,
|
||||
'directors_total' => count($directors),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Persistence\Doctrine;
|
||||
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkId;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkRuleException;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkRuleExceptionId;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRuleExceptionRepository;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
|
||||
use function array_map;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Override;
|
||||
|
||||
final readonly class DoctrineHomeworkRuleExceptionRepository implements HomeworkRuleExceptionRepository
|
||||
{
|
||||
public function __construct(
|
||||
private Connection $connection,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function save(HomeworkRuleException $exception): void
|
||||
{
|
||||
$this->connection->executeStatement(
|
||||
'INSERT INTO homework_rule_exceptions (id, tenant_id, homework_id, rule_type, justification, created_by, created_at)
|
||||
VALUES (:id, :tenant_id, :homework_id, :rule_type, :justification, :created_by, :created_at)
|
||||
ON CONFLICT (id) DO NOTHING',
|
||||
[
|
||||
'id' => (string) $exception->id,
|
||||
'tenant_id' => (string) $exception->tenantId,
|
||||
'homework_id' => (string) $exception->homeworkId,
|
||||
'rule_type' => $exception->ruleType,
|
||||
'justification' => $exception->justification,
|
||||
'created_by' => (string) $exception->createdBy,
|
||||
'created_at' => $exception->createdAt->format(DateTimeImmutable::ATOM),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function findByHomework(HomeworkId $homeworkId, TenantId $tenantId): array
|
||||
{
|
||||
$rows = $this->connection->fetchAllAssociative(
|
||||
'SELECT * FROM homework_rule_exceptions
|
||||
WHERE homework_id = :homework_id AND tenant_id = :tenant_id
|
||||
ORDER BY created_at DESC',
|
||||
[
|
||||
'homework_id' => (string) $homeworkId,
|
||||
'tenant_id' => (string) $tenantId,
|
||||
],
|
||||
);
|
||||
|
||||
return array_map($this->hydrate(...), $rows);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function findByTenant(TenantId $tenantId): array
|
||||
{
|
||||
$rows = $this->connection->fetchAllAssociative(
|
||||
'SELECT * FROM homework_rule_exceptions
|
||||
WHERE tenant_id = :tenant_id
|
||||
ORDER BY created_at DESC',
|
||||
['tenant_id' => (string) $tenantId],
|
||||
);
|
||||
|
||||
return array_map($this->hydrate(...), $rows);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function homeworkIdsWithExceptions(TenantId $tenantId): array
|
||||
{
|
||||
$rows = $this->connection->fetchAllAssociative(
|
||||
'SELECT DISTINCT homework_id FROM homework_rule_exceptions WHERE tenant_id = :tenant_id',
|
||||
['tenant_id' => (string) $tenantId],
|
||||
);
|
||||
|
||||
return array_map(
|
||||
/** @param array<string, mixed> $row */
|
||||
static function (array $row): string {
|
||||
/** @var string $homeworkId */
|
||||
$homeworkId = $row['homework_id'];
|
||||
|
||||
return $homeworkId;
|
||||
},
|
||||
$rows,
|
||||
);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $row */
|
||||
private function hydrate(array $row): HomeworkRuleException
|
||||
{
|
||||
/** @var string $id */
|
||||
$id = $row['id'];
|
||||
/** @var string $tenantId */
|
||||
$tenantId = $row['tenant_id'];
|
||||
/** @var string $homeworkId */
|
||||
$homeworkId = $row['homework_id'];
|
||||
/** @var string $ruleType */
|
||||
$ruleType = $row['rule_type'];
|
||||
/** @var string $justification */
|
||||
$justification = $row['justification'];
|
||||
/** @var string $createdBy */
|
||||
$createdBy = $row['created_by'];
|
||||
/** @var string $createdAt */
|
||||
$createdAt = $row['created_at'];
|
||||
|
||||
return HomeworkRuleException::reconstitute(
|
||||
id: HomeworkRuleExceptionId::fromString($id),
|
||||
tenantId: TenantId::fromString($tenantId),
|
||||
homeworkId: HomeworkId::fromString($homeworkId),
|
||||
ruleType: $ruleType,
|
||||
justification: $justification,
|
||||
createdBy: UserId::fromString($createdBy),
|
||||
createdAt: new DateTimeImmutable($createdAt),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Infrastructure\Persistence\InMemory;
|
||||
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkId;
|
||||
use App\Scolarite\Domain\Model\Homework\HomeworkRuleException;
|
||||
use App\Scolarite\Domain\Repository\HomeworkRuleExceptionRepository;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_unique;
|
||||
use function array_values;
|
||||
|
||||
use Override;
|
||||
|
||||
final class InMemoryHomeworkRuleExceptionRepository implements HomeworkRuleExceptionRepository
|
||||
{
|
||||
/** @var array<string, HomeworkRuleException> */
|
||||
private array $byId = [];
|
||||
|
||||
#[Override]
|
||||
public function save(HomeworkRuleException $exception): void
|
||||
{
|
||||
$this->byId[(string) $exception->id] = $exception;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function findByHomework(HomeworkId $homeworkId, TenantId $tenantId): array
|
||||
{
|
||||
return array_values(array_filter(
|
||||
$this->byId,
|
||||
static fn (HomeworkRuleException $e): bool => $e->homeworkId->equals($homeworkId)
|
||||
&& $e->tenantId->equals($tenantId),
|
||||
));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function findByTenant(TenantId $tenantId): array
|
||||
{
|
||||
return array_values(array_filter(
|
||||
$this->byId,
|
||||
static fn (HomeworkRuleException $e): bool => $e->tenantId->equals($tenantId),
|
||||
));
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function homeworkIdsWithExceptions(TenantId $tenantId): array
|
||||
{
|
||||
$tenantExceptions = $this->findByTenant($tenantId);
|
||||
|
||||
return array_values(array_unique(array_map(
|
||||
static fn (HomeworkRuleException $e): string => (string) $e->homeworkId,
|
||||
$tenantExceptions,
|
||||
)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user