feat: Configurer les jours fériés et vacances du calendrier scolaire
Les administrateurs d'établissement avaient besoin de gérer le calendrier scolaire (FR80) pour que l'EDT et les devoirs respectent automatiquement les jours non travaillés. Sans cette configuration centralisée, chaque module devait gérer indépendamment les contraintes de dates. Le calendrier s'appuie sur l'API data.education.gouv.fr pour importer les vacances officielles par zone (A/B/C) et calcule les 11 jours fériés français (dont les fêtes mobiles liées à Pâques). Les enseignants sont notifiés par email lors de l'ajout d'une journée pédagogique. Un query IsSchoolDay et une validation des dates d'échéance de devoirs permettent aux autres modules de s'intégrer sans couplage direct.
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Functional\Administration\Application;
|
||||
|
||||
use App\Administration\Application\Query\ValidateHomeworkDueDate\ValidateHomeworkDueDateHandler;
|
||||
use App\Administration\Application\Query\ValidateHomeworkDueDate\ValidateHomeworkDueDateQuery;
|
||||
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntry;
|
||||
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryId;
|
||||
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryType;
|
||||
use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendar;
|
||||
use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendarRepository;
|
||||
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* Tests fonctionnels pour ValidateHomeworkDueDate.
|
||||
*
|
||||
* Vérifie le comportement bout-en-bout avec calendrier persisté en BDD.
|
||||
* Complémente les tests unitaires qui utilisent un repository in-memory.
|
||||
*
|
||||
* @see Story 2.11 - AC3 (jours fériés), AC4 (périodes vacances)
|
||||
*/
|
||||
final class ValidateHomeworkDueDateFunctionalTest extends KernelTestCase
|
||||
{
|
||||
private const string TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
||||
private const string ACADEMIC_YEAR_ID = '11111111-1111-1111-1111-111111111111';
|
||||
|
||||
private ValidateHomeworkDueDateHandler $handler;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->handler = static::getContainer()->get(ValidateHomeworkDueDateHandler::class);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
/** @var Connection $connection */
|
||||
$connection = static::getContainer()->get(Connection::class);
|
||||
$connection->executeStatement(
|
||||
'DELETE FROM school_calendar_entries WHERE tenant_id = :tenant_id AND academic_year_id = :academic_year_id',
|
||||
['tenant_id' => self::TENANT_ID, 'academic_year_id' => self::ACADEMIC_YEAR_ID],
|
||||
);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// AC3 (P0) — Blocage jours fériés
|
||||
// =========================================================================
|
||||
|
||||
#[Test]
|
||||
public function itRejectsHolidayAsHomeworkDueDate(): void
|
||||
{
|
||||
$this->persistCalendar([
|
||||
new CalendarEntry(
|
||||
id: CalendarEntryId::generate(),
|
||||
type: CalendarEntryType::HOLIDAY,
|
||||
startDate: new DateTimeImmutable('2024-12-25'),
|
||||
endDate: new DateTimeImmutable('2024-12-25'),
|
||||
label: 'Noël',
|
||||
),
|
||||
]);
|
||||
|
||||
// 2024-12-25 is a Wednesday (weekday) but a holiday
|
||||
$result = ($this->handler)(new ValidateHomeworkDueDateQuery(
|
||||
tenantId: self::TENANT_ID,
|
||||
academicYearId: self::ACADEMIC_YEAR_ID,
|
||||
dueDate: '2024-12-25',
|
||||
));
|
||||
|
||||
self::assertFalse($result->valid);
|
||||
self::assertNotNull($result->reason);
|
||||
self::assertStringContainsString('férié', $result->reason);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// AC3/AC4 (P0/P1) — Blocage vacances
|
||||
// =========================================================================
|
||||
|
||||
#[Test]
|
||||
public function itRejectsVacationDayAsHomeworkDueDate(): void
|
||||
{
|
||||
$this->persistCalendar([
|
||||
new CalendarEntry(
|
||||
id: CalendarEntryId::generate(),
|
||||
type: CalendarEntryType::VACATION,
|
||||
startDate: new DateTimeImmutable('2025-02-15'),
|
||||
endDate: new DateTimeImmutable('2025-03-02'),
|
||||
label: 'Vacances d\'hiver',
|
||||
),
|
||||
]);
|
||||
|
||||
// 2025-02-20 is a Thursday during the vacation
|
||||
$result = ($this->handler)(new ValidateHomeworkDueDateQuery(
|
||||
tenantId: self::TENANT_ID,
|
||||
academicYearId: self::ACADEMIC_YEAR_ID,
|
||||
dueDate: '2025-02-20',
|
||||
));
|
||||
|
||||
self::assertFalse($result->valid);
|
||||
self::assertNotNull($result->reason);
|
||||
self::assertStringContainsString('vacances', $result->reason);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// AC4 (P1) — Warning retour vacances
|
||||
// =========================================================================
|
||||
|
||||
#[Test]
|
||||
public function itAcceptsReturnDayWithWarning(): void
|
||||
{
|
||||
$this->persistCalendar([
|
||||
new CalendarEntry(
|
||||
id: CalendarEntryId::generate(),
|
||||
type: CalendarEntryType::VACATION,
|
||||
startDate: new DateTimeImmutable('2025-02-15'),
|
||||
endDate: new DateTimeImmutable('2025-03-02'),
|
||||
label: 'Vacances d\'hiver',
|
||||
),
|
||||
]);
|
||||
|
||||
// 2025-03-03 is a Monday, the day after vacation ends (2025-03-02)
|
||||
$result = ($this->handler)(new ValidateHomeworkDueDateQuery(
|
||||
tenantId: self::TENANT_ID,
|
||||
academicYearId: self::ACADEMIC_YEAR_ID,
|
||||
dueDate: '2025-03-03',
|
||||
));
|
||||
|
||||
self::assertTrue($result->valid);
|
||||
self::assertNotEmpty($result->warnings);
|
||||
self::assertStringContainsString('retour de vacances', $result->warnings[0]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Cas nominal — jour ouvré normal
|
||||
// =========================================================================
|
||||
|
||||
#[Test]
|
||||
public function itAcceptsNormalWeekday(): void
|
||||
{
|
||||
$this->persistCalendar();
|
||||
|
||||
// 2025-03-10 is a Monday with no calendar entry
|
||||
$result = ($this->handler)(new ValidateHomeworkDueDateQuery(
|
||||
tenantId: self::TENANT_ID,
|
||||
academicYearId: self::ACADEMIC_YEAR_ID,
|
||||
dueDate: '2025-03-10',
|
||||
));
|
||||
|
||||
self::assertTrue($result->valid);
|
||||
self::assertNull($result->reason);
|
||||
self::assertEmpty($result->warnings);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helpers
|
||||
// =========================================================================
|
||||
|
||||
private function persistCalendar(array $entries = []): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);
|
||||
|
||||
$calendar = SchoolCalendar::initialiser($tenantId, $academicYearId);
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$calendar->ajouterEntree($entry);
|
||||
}
|
||||
|
||||
/** @var SchoolCalendarRepository $repository */
|
||||
$repository = static::getContainer()->get(SchoolCalendarRepository::class);
|
||||
$repository->save($calendar);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user