feat: Bloquer la création de devoirs non conformes en mode hard
Les établissements utilisant le mode "Hard" des règles de devoirs empêchent désormais les enseignants de créer des devoirs hors règles. Contrairement au mode "Soft" (avertissement avec possibilité de passer outre), le mode "Hard" est un blocage strict : même acknowledgeWarning ne permet pas de contourner. L'API retourne 422 (au lieu de 409 pour le soft) avec des dates conformes suggérées calculées via le calendrier scolaire (weekends, fériés, vacances exclus). Le frontend affiche un modal de blocage avec les raisons, des dates cliquables, et une validation client inline qui empêche la soumission de dates non conformes.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Application\Service;
|
||||
|
||||
use App\Administration\Domain\Model\HomeworkRules\HomeworkRules;
|
||||
use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendar;
|
||||
|
||||
use function count;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* Suggère les prochaines dates d'échéance conformes aux règles de devoirs.
|
||||
*
|
||||
* Utilisé en mode hard pour proposer des alternatives à l'enseignant
|
||||
* quand la date choisie est refusée.
|
||||
*/
|
||||
final readonly class ValidDueDateSuggester
|
||||
{
|
||||
private const int MAX_SEARCH_DAYS = 60;
|
||||
|
||||
public function __construct(
|
||||
private HomeworkRulesValidator $validator,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateTimeImmutable[] Dates conformes triées chronologiquement
|
||||
*/
|
||||
public function suggerer(
|
||||
DateTimeImmutable $requestedDate,
|
||||
HomeworkRules $rules,
|
||||
DateTimeImmutable $creationDate,
|
||||
int $maxSuggestions = 3,
|
||||
?SchoolCalendar $calendar = null,
|
||||
): array {
|
||||
if (!$rules->estActif()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$suggestions = [];
|
||||
$checkDate = $requestedDate;
|
||||
|
||||
for ($i = 0; $i < self::MAX_SEARCH_DAYS && count($suggestions) < $maxSuggestions; ++$i) {
|
||||
$checkDate = $checkDate->modify('+1 day');
|
||||
|
||||
// Vérifier via le calendrier scolaire (weekends + fériés + vacances)
|
||||
if ($calendar !== null) {
|
||||
if (!$calendar->estJourOuvre($checkDate)) {
|
||||
continue;
|
||||
}
|
||||
} elseif ((int) $checkDate->format('N') >= 6) {
|
||||
// Fallback : sauter les weekends si pas de calendrier
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $this->validator->valider($rules, $checkDate, $creationDate);
|
||||
|
||||
if ($result->estValide()) {
|
||||
$suggestions[] = $checkDate;
|
||||
}
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,11 @@ final readonly class CreateHomeworkHandler
|
||||
$rulesResult = $this->rulesChecker->verifier($tenantId, $dueDate, $now);
|
||||
|
||||
if ($rulesResult->estBloquant()) {
|
||||
throw new ReglesDevoirsNonRespecteesException($rulesResult->toArray());
|
||||
throw new ReglesDevoirsNonRespecteesException(
|
||||
$rulesResult->toArray(),
|
||||
bloquant: true,
|
||||
suggestedDates: $rulesResult->suggestedDates,
|
||||
);
|
||||
}
|
||||
|
||||
if ($rulesResult->estAvertissement() && !$command->acknowledgeWarning) {
|
||||
|
||||
@@ -17,10 +17,12 @@ final readonly class HomeworkRulesCheckResult
|
||||
{
|
||||
/**
|
||||
* @param RuleWarning[] $warnings
|
||||
* @param string[] $suggestedDates Dates conformes alternatives (format Y-m-d)
|
||||
*/
|
||||
public function __construct(
|
||||
public array $warnings,
|
||||
public bool $bloquant,
|
||||
public array $suggestedDates = [],
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Scolarite\Domain\Exception;
|
||||
|
||||
use function array_column;
|
||||
|
||||
use DomainException;
|
||||
|
||||
use function implode;
|
||||
|
||||
/**
|
||||
* Levée quand un devoir enfreint les règles configurées
|
||||
* et que l'enseignant n'a pas encore confirmé.
|
||||
@@ -14,10 +18,19 @@ final class ReglesDevoirsNonRespecteesException extends DomainException
|
||||
{
|
||||
/**
|
||||
* @param array<array{ruleType: string, message: string, params: array<string, mixed>}> $warnings
|
||||
* @param string[] $suggestedDates Dates conformes alternatives (format Y-m-d)
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $warnings,
|
||||
public readonly bool $bloquant = false,
|
||||
public readonly array $suggestedDates = [],
|
||||
) {
|
||||
parent::__construct('Le devoir ne respecte pas les règles configurées.');
|
||||
$raisons = implode(' ', array_column($warnings, 'message'));
|
||||
|
||||
parent::__construct(
|
||||
$bloquant
|
||||
? 'Impossible de créer ce devoir : ' . $raisons
|
||||
: 'Le devoir ne respecte pas les règles configurées.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,16 @@ final readonly class CreateHomeworkProcessor implements ProcessorInterface
|
||||
|
||||
return HomeworkResource::fromDomain($homework);
|
||||
} catch (ReglesDevoirsNonRespecteesException $e) {
|
||||
if ($e->bloquant) {
|
||||
return new JsonResponse([
|
||||
'type' => 'homework_rules_blocked',
|
||||
'message' => $e->getMessage(),
|
||||
'warnings' => $e->warnings,
|
||||
'suggestedDates' => $e->suggestedDates,
|
||||
'exceptionRequestPath' => '/dashboard/teacher/homework/request-exception',
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'type' => 'homework_rules_warning',
|
||||
'message' => $e->getMessage(),
|
||||
|
||||
@@ -7,9 +7,11 @@ namespace App\Scolarite\Infrastructure\Service;
|
||||
use App\Administration\Application\Service\HomeworkRulesValidationResult;
|
||||
use App\Administration\Application\Service\HomeworkRulesValidator;
|
||||
use App\Administration\Application\Service\RuleViolation;
|
||||
use App\Administration\Application\Service\ValidDueDateSuggester;
|
||||
use App\Administration\Domain\Model\HomeworkRules\HomeworkRule;
|
||||
use App\Administration\Domain\Model\HomeworkRules\HomeworkRules;
|
||||
use App\Administration\Domain\Repository\HomeworkRulesRepository;
|
||||
use App\Scolarite\Application\Port\CurrentCalendarProvider;
|
||||
use App\Scolarite\Application\Port\HomeworkRulesChecker;
|
||||
use App\Scolarite\Application\Port\HomeworkRulesCheckResult;
|
||||
use App\Scolarite\Application\Port\RuleWarning;
|
||||
@@ -28,6 +30,8 @@ final readonly class AdministrationHomeworkRulesChecker implements HomeworkRules
|
||||
public function __construct(
|
||||
private HomeworkRulesRepository $rulesRepository,
|
||||
private HomeworkRulesValidator $validator,
|
||||
private ValidDueDateSuggester $suggester,
|
||||
private CurrentCalendarProvider $calendarProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -45,11 +49,25 @@ final readonly class AdministrationHomeworkRulesChecker implements HomeworkRules
|
||||
|
||||
$result = $this->validator->valider($rules, $dueDate, $creationDate);
|
||||
|
||||
return $this->toCheckResult($result, $rules);
|
||||
return $this->toCheckResult($result, $rules, $dueDate, $creationDate);
|
||||
}
|
||||
|
||||
private function toCheckResult(HomeworkRulesValidationResult $result, HomeworkRules $rules): HomeworkRulesCheckResult
|
||||
{
|
||||
private function toCheckResult(
|
||||
HomeworkRulesValidationResult $result,
|
||||
HomeworkRules $rules,
|
||||
DateTimeImmutable $dueDate,
|
||||
DateTimeImmutable $creationDate,
|
||||
): HomeworkRulesCheckResult {
|
||||
$suggestedDates = [];
|
||||
|
||||
if ($result->estBloquant()) {
|
||||
$calendar = $this->calendarProvider->forCurrentYear($rules->tenantId);
|
||||
$suggestedDates = array_map(
|
||||
static fn (DateTimeImmutable $d): string => $d->format('Y-m-d'),
|
||||
$this->suggester->suggerer($dueDate, $rules, $creationDate, calendar: $calendar),
|
||||
);
|
||||
}
|
||||
|
||||
return new HomeworkRulesCheckResult(
|
||||
warnings: array_map(
|
||||
fn (RuleViolation $v): RuleWarning => new RuleWarning(
|
||||
@@ -60,6 +78,7 @@ final readonly class AdministrationHomeworkRulesChecker implements HomeworkRules
|
||||
$result->violations,
|
||||
),
|
||||
bloquant: $result->estBloquant(),
|
||||
suggestedDates: $suggestedDates,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user