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,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;
|
||||
}
|
||||
Reference in New Issue
Block a user