瀏覽代碼

add online_registration availibility and status routes and services

Olivier Massot 2 年之前
父節點
當前提交
57a90f7df4

+ 1 - 0
config/opentalent/products.yaml

@@ -228,6 +228,7 @@ opentalent:
             - EducationStudentWish
             - OnlineRegistrationSettings
             - RegistrationStatus
+            - RegistrationAvailability
           roles:
             - ROLE_ONLINEREGISTRATION_ADMINISTRATION
 

+ 59 - 0
src/ApiResources/OnlineRegistration/RegistrationAvailability.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\ApiResources\OnlineRegistration;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use App\ApiResources\ApiResourcesInterface;
+use App\State\Provider\OnlineRegistration\RegistrationAvailabilityProvider;
+
+#[ApiResource(
+    operations: [
+        new Get(
+            uriTemplate: '/online_registration/availability/{accessId}',
+            requirements: ['accessId' => '\\d+'],
+            defaults: ['accessId' => 0],
+            provider: RegistrationAvailabilityProvider::class
+        )
+    ]
+)]
+class RegistrationAvailability implements ApiResourcesInterface
+{
+    #[ApiProperty(identifier: true)]
+    private int $accessId;
+
+    private bool $available;
+
+    private ?string $message = null;
+
+    public function getAccessId(): int
+    {
+        return $this->accessId;
+    }
+
+    public function setAccessId(int $accessId): void
+    {
+        $this->accessId = $accessId;
+    }
+
+    public function isAvailable(): bool
+    {
+        return $this->available;
+    }
+
+    public function setAvailable(bool $available): void
+    {
+        $this->available = $available;
+    }
+
+    public function getMessage(): ?string
+    {
+        return $this->message;
+    }
+
+    public function setMessage(?string $message): void
+    {
+        $this->message = $message;
+    }
+}

+ 1 - 1
src/ApiResources/OnlineRegistration/RegistrationStatus.php

@@ -12,7 +12,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
 #[ApiResource(
     operations: [
         new Get(
-            uriTemplate: '/online_registration/registration_status/{accessId}',
+            uriTemplate: '/online_registration/status/{accessId}',
             requirements: ['accessId' => '\\d+'],
             defaults: ['accessId' => 0],
             provider: RegistrationStatusProvider::class

+ 26 - 0
src/Entity/Access/Access.php

@@ -195,6 +195,12 @@ class Access implements UserInterface, PasswordAuthenticatedUserInterface
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false, onDelete: "SET NULL")]
     private AccessFamily $accessFamily;
 
+    #[ORM\Column]
+    private bool $newAccess = false;
+
+    #[ORM\Column]
+    private bool $ielEnabled = false;
+
     #[ORM\OneToMany(mappedBy: 'educationalProjectPayer', targetEntity: EducationalProjectPayer::class, cascade: ['persist'], orphanRemoval: true)]
     private Collection $billingEducationalProjectPayers;
 
@@ -1036,6 +1042,26 @@ class Access implements UserInterface, PasswordAuthenticatedUserInterface
         return $this;
     }
 
+    public function isNewAccess(): bool
+    {
+        return $this->newAccess;
+    }
+
+    public function setNewAccess(bool $newAccess): void
+    {
+        $this->newAccess = $newAccess;
+    }
+
+    public function isIelEnabled(): bool
+    {
+        return $this->ielEnabled;
+    }
+
+    public function setIelEnabled(bool $ielEnabled): void
+    {
+        $this->ielEnabled = $ielEnabled;
+    }
+
     /**
      * @return Collection<int, EducationalProjectPayer>
      */

+ 27 - 14
src/Entity/AccessWish/AccessFamilyWish.php

@@ -9,7 +9,7 @@ use Doctrine\ORM\Mapping as ORM;
 use Doctrine\Common\Collections\Collection;
 
 /**
- * Classe ... qui ...
+ * Famille à laquelle est rattaché un AccessWish
  */
 //#[Auditable]
 #[ORM\Entity]
@@ -20,9 +20,6 @@ class AccessFamilyWish
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
-    #[ORM\Column]
-    private bool $registrationCompleted = false;
-
     #[ORM\OneToMany(mappedBy: 'accessFamilyWish', targetEntity: AccessWish::class, cascade: ['remove'])]
     private Collection $accessWishes;
 
@@ -33,6 +30,12 @@ class AccessFamilyWish
     #[ORM\Column(type: 'datetime', nullable: true)]
     private \DateTimeInterface $updateDate;
 
+    #[ORM\Column]
+    private bool $registrationCompleted = false;
+
+    #[ORM\Column]
+    private bool $closeRegistration = false;
+
     public function __construct()
     {
         $this->accessWishes = new ArrayCollection();
@@ -43,16 +46,6 @@ class AccessFamilyWish
         return $this->id;
     }
 
-    public function isRegistrationCompleted(): bool
-    {
-        return $this->registrationCompleted;
-    }
-
-    public function setRegistrationCompleted(bool $registrationCompleted): void
-    {
-        $this->registrationCompleted = $registrationCompleted;
-    }
-
     /**
      * @return Collection<int, AccessWish>
      */
@@ -92,4 +85,24 @@ class AccessFamilyWish
     {
         $this->updateDate = $updateDate;
     }
+
+    public function isRegistrationCompleted(): bool
+    {
+        return $this->registrationCompleted;
+    }
+
+    public function setRegistrationCompleted(bool $registrationCompleted): void
+    {
+        $this->registrationCompleted = $registrationCompleted;
+    }
+
+    public function isCloseRegistration(): bool
+    {
+        return $this->closeRegistration;
+    }
+
+    public function setCloseRegistration(bool $closeRegistration): void
+    {
+        $this->closeRegistration = $closeRegistration;
+    }
 }

+ 16 - 1
src/Entity/AccessWish/AccessWish.php

@@ -10,13 +10,14 @@ use App\Entity\Core\File;
 use App\Entity\Core\Tagg;
 use App\Entity\Organization\Organization;
 //use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
+use App\Enum\OnlineRegistration\ValidationStateEnum;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\ORM\Mapping as ORM;
 use Doctrine\Common\Collections\Collection;
 use \DateTime;
 
 /**
- * Classe ... qui ...
+ * Demande d'inscription d'un Access via l'IEL
  */
 //#[Auditable]
 #[ApiResource(operations: [])]
@@ -61,6 +62,10 @@ class AccessWish
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
     private Collection $tags;
 
+    #[ORM\Column(length: 50)]
+    #[Assert\Choice(callback: [ValidationStateEnum::class, 'toArray'])]
+    private string $validationState;
+
     /**
      * Date de création dde l'entité
      * @var DateTime
@@ -281,4 +286,14 @@ class AccessWish
     {
         $this->updateDate = $updateDate;
     }
+
+    public function getValidationState(): string
+    {
+        return $this->validationState;
+    }
+
+    public function setValidationState(string $validationState): void
+    {
+        $this->validationState = $validationState;
+    }
 }

+ 90 - 0
src/Entity/OnlineRegistration/OnlineRegistrationOpeningPeriod.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace App\Entity\OnlineRegistration;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Période d'ouverture de l'IEL pour une organisation
+ */
+//#[ApiResource(operations: [])]
+//#[ORM\Entity]
+class OnlineRegistrationOpeningPeriod
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private ?int $id = null;
+
+    #[ORM\ManyToOne(inversedBy: 'onlineRegistrationOpeningPeriods')]
+    #[ORM\JoinColumn(nullable: false)]
+    private ?OnlineRegistrationSettings $onlineRegistrationSettings = null;
+
+    /**
+     * TODO: c'est quoi?
+     * @var OnlineRegistrationSettings|null
+     */
+    #[ORM\ManyToOne(inversedBy: 'onlineRegistrationOpeningPeriods')]
+    #[ORM\JoinColumn(nullable: false)]
+    private ?OnlineRegistrationSettings $onlineRegistrationSettingsNewEnrolments = null;
+
+    /**
+     * Date de début de la période d'IEL
+     * @var DateTime
+     */
+    #[ORM\Column(type: 'datetime', nullable: false)]
+    private \DateTimeInterface $startDate;
+
+    /**
+     * Date de fin de la période d'IEL
+     * @var DateTime
+     */
+    #[ORM\Column(type: 'datetime', nullable: false)]
+    private \DateTimeInterface $endDate;
+
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    public function getOnlineRegistrationSettings(): ?OnlineRegistrationSettings
+    {
+        return $this->onlineRegistrationSettings;
+    }
+
+    public function setOnlineRegistrationSettings(?OnlineRegistrationSettings $onlineRegistrationSettings): void
+    {
+        $this->onlineRegistrationSettings = $onlineRegistrationSettings;
+    }
+
+    public function getOnlineRegistrationSettingsNewEnrolments(): ?OnlineRegistrationSettings
+    {
+        return $this->onlineRegistrationSettingsNewEnrolments;
+    }
+
+    public function setOnlineRegistrationSettingsNewEnrolments(?OnlineRegistrationSettings $onlineRegistrationSettingsNewEnrolments): void
+    {
+        $this->onlineRegistrationSettingsNewEnrolments = $onlineRegistrationSettingsNewEnrolments;
+    }
+
+    public function getStartDate(): \DateTimeInterface
+    {
+        return $this->startDate;
+    }
+
+    public function setStartDate(\DateTimeInterface $startDate): void
+    {
+        $this->startDate = $startDate;
+    }
+
+    public function getEndDate(): \DateTimeInterface
+    {
+        return $this->endDate;
+    }
+
+    public function setEndDate(\DateTimeInterface $endDate): void
+    {
+        $this->endDate = $endDate;
+    }
+}

+ 140 - 0
src/Entity/OnlineRegistration/OnlineRegistrationSettings.php

@@ -0,0 +1,140 @@
+<?php
+
+namespace App\Entity\OnlineRegistration;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Organization\Organization;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+use JetBrains\PhpStorm\Pure;
+
+/**
+ * Configuration de l'IEL pour une organization
+ */
+//#[ApiResource(operations: [])]
+//#[ORM\Entity]
+class OnlineRegistrationSettings
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private ?int $id = null;
+
+    #[ORM\OneToOne(inversedBy: 'organization', targetEntity: Organization::class)]
+    private Organization $organization;
+
+    #[ORM\OneToMany(mappedBy: 'onlineRegistrationSettings', targetEntity: OnlineRegistrationOpeningPeriod::class, orphanRemoval: true)]
+    private Collection $openingPeriods;
+
+    #[ORM\OneToMany(mappedBy: 'onlineRegistrationSettingsNewEnrolments', targetEntity: OnlineRegistrationOpeningPeriod::class, orphanRemoval: true)]
+    private Collection $openingPeriodsNewEnrolments;
+
+    /**
+     * TODO: compléter la doc
+     * @var bool
+     */
+    #[ORM\Column]
+    private bool $addNewStudents = false;
+
+    /**
+     * TODO: compléter la doc
+     * @var bool
+     */
+    #[ORM\Column]
+    private bool $addNewEducations = false;
+
+    #[Pure]
+    public function __construct()
+    {
+        $this->openingPeriods = new ArrayCollection();
+        $this->openingPeriodsNewEnrolments = new ArrayCollection();
+    }
+
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    public function getOrganization(): Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(Organization $organization): void
+    {
+        $this->organization = $organization;
+    }
+
+    public function getOnlineRegistrationOpeningPeriods(): Collection
+    {
+        return $this->openingPeriods;
+    }
+
+    public function addOnlineRegistrationOpeningPeriod(OnlineRegistrationOpeningPeriod $openingPeriod): self
+    {
+        if (!$this->openingPeriods->contains($openingPeriod)) {
+            $this->openingPeriods[] = $openingPeriod;
+            $openingPeriod->setOnlineRegistrationSettings($this);
+        }
+        return $this;
+    }
+
+    public function removeOnlineRegistrationOpeningPeriod(OnlineRegistrationOpeningPeriod $openingPeriod): self
+    {
+        if ($this->openingPeriods->removeElement($openingPeriod)) {
+            // set the owning side to null (unless already changed)
+            if ($openingPeriod->getOnlineRegistrationSettings() === $this) {
+                $openingPeriod->setOnlineRegistrationSettings(null);
+            }
+        }
+        return $this;
+    }
+
+    public function getOnlineRegistrationOpeningPeriodsNewEnrolments(): Collection
+    {
+        return $this->openingPeriodsNewEnrolments;
+    }
+
+    public function addOnlineRegistrationOpeningPeriodNewEnrolments(OnlineRegistrationOpeningPeriod $openingPeriod): self
+    {
+        if (!$this->openingPeriodsNewEnrolments->contains($openingPeriod)) {
+            $this->openingPeriodsNewEnrolments[] = $openingPeriod;
+            $openingPeriod->setOnlineRegistrationSettingsNewEnrolments($this);
+        }
+        return $this;
+    }
+
+    public function removeOnlineRegistrationOpeningPeriodNewEnrolments(OnlineRegistrationOpeningPeriod $openingPeriod): self
+    {
+        if ($this->openingPeriodsNewEnrolments->removeElement($openingPeriod)) {
+            // set the owning side to null (unless already changed)
+            if ($openingPeriod->getOnlineRegistrationSettingsNewEnrolments() === $this) {
+                $openingPeriod->setOnlineRegistrationSettingsNewEnrolments(null);
+            }
+        }
+        return $this;
+    }
+
+    // TODO: confirmer que l'action est bien `can`
+    public function canAddNewStudents(): bool
+    {
+        return $this->addNewStudents;
+    }
+
+    public function setAddNewStudents(bool $addNewStudents): void
+    {
+        $this->addNewStudents = $addNewStudents;
+    }
+
+    // TODO: confirmer que l'action est bien `can`
+    public function canAddNewEducations(): bool
+    {
+        return $this->addNewEducations;
+    }
+
+    public function setAddNewEducations(bool $addNewEducations): void
+    {
+        $this->addNewEducations = $addNewEducations;
+    }
+}

+ 14 - 1
src/Entity/Organization/Organization.php

@@ -38,7 +38,7 @@ use App\Entity\Product\Equipment;
 use App\Entity\Product\Intangible;
 use App\Repository\Organization\OrganizationRepository;
 use App\State\Processor\Organization\OrganizationProcessor;
-
+use \App\Entity\Organization\OnlineRegistrationSettings;
 //use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
@@ -314,6 +314,9 @@ class Organization
     #[ORM\OneToMany(mappedBy: 'organization', targetEntity: Donor::class, orphanRemoval: true)]
     private Collection $donors;
 
+//    #[ORM\OneToOne()]
+//    private OnlineRegistrationSettings $onlineRegistrationSettings;
+
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'organizations', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_organization')]
     #[ORM\JoinColumn(name: 'organization_id', referencedColumnName: 'id')]
@@ -1693,6 +1696,16 @@ class Organization
         return $this;
     }
 
+//    public function getOnlineRegistrationSettings(): \App\Entity\Organization\OnlineRegistrationSettings
+//    {
+//        return $this->onlineRegistrationSettings;
+//    }
+//
+//    public function setOnlineRegistrationSettings(\App\Entity\Organization\OnlineRegistrationSettings $onlineRegistrationSettings): void
+//    {
+//        $this->onlineRegistrationSettings = $onlineRegistrationSettings;
+//    }
+
     /**
      * @return Collection<int, Tagg>
      */

+ 21 - 0
src/Enum/OnlineRegistration/ValidationStateEnum.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Enum\OnlineRegistration;
+
+use MyCLabs\Enum\Enum;
+
+/**
+ * Niveau de validation d'une inscription en ligne
+ *
+ * TODO: à valider
+ *
+ * @method static OPEN()
+ * @method static OPEN_FOR_EDUCATIONS()
+ * @method static CLOSE()
+ */
+class ValidationStateEnum extends Enum
+{
+    const OPEN = 'OPEN';
+    const OPEN_FOR_EDUCATIONS = 'OPEN_FOR_EDUCATIONS';
+    const CLOSE = 'CLOSE';
+}

+ 54 - 0
src/Service/Access/Utils.php

@@ -7,6 +7,7 @@ use App\Entity\Access\Access;
 use App\Entity\Organization\Organization;
 use App\Repository\Access\AccessRepository;
 use App\Service\ServiceIterator\OptionalsRolesIterator;
+use App\Service\Utils\DatesUtils;
 use App\Test\Service\Access\UtilsTest;
 use Doctrine\Common\Collections\Criteria;
 use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
@@ -79,4 +80,57 @@ class Utils
             'organization' => $organization
         ]);
     }
+
+    /**
+     * Retourne les noms des fonctions actives d'un access au sein d'une organisation
+     *
+     * @param Access $access
+     * @return array<string>
+     * @throws \Exception
+     */
+    public function getActiveFunctions(Access $access): array {
+        $functions = [];
+
+        foreach ($access->getOrganizationFunction() as $function) {
+            if (
+                $function->getEndDate() !== null &&
+                DatesUtils::new() >= $function->getEndDate()->add(new \DateInterval('PT23H59M'))
+            ) {
+                // On est en dehors de la période de validité de la fonction
+                continue;
+            }
+
+            $functions[] = $function->getFunctionType()->getMission();
+        }
+
+        return $functions;
+    }
+
+    /**
+     * L'access possède-t-il la fonction donnée au sein de son organisation
+     *
+     * @param Access $access
+     * @param string $functionName
+     * @return bool
+     * @throws \Exception
+     */
+    public function hasActiveFunction(Access $access, string $functionName): bool {
+        $functions = [];
+
+        foreach ($access->getOrganizationFunction() as $function) {
+            if (
+                $function->getEndDate() !== null &&
+                DatesUtils::new() >= $function->getEndDate()->add(new \DateInterval('PT23H59M'))
+            ) {
+                // On est en dehors de la période de validité de la fonction
+                continue;
+            }
+
+            if ($function->getFunctionType()->getMission() === $functionName) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }

+ 34 - 0
src/Service/OnlineRegistration/OnlineRegistrationService.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Service\OnlineRegistration;
+
+use App\Entity\Access\Access;
+use App\Entity\AccessWish\AccessWish;
+use App\Service\Utils\DatesUtils;
+
+/**
+ * --- SERVICE INUTILISE POUR LE MOMENT ---
+ */
+class OnlineRegistrationService
+{
+
+    /**
+     * Retourne le premier AccessWish correspondant à l'année en cours, ou null si aucun n'est trouvé
+     *
+     * @param Access $access
+     * @return AccessWish | null
+     */
+    public function getCurrentAccessWish(Access $access): AccessWish | null {
+        $currentYear =  DatesUtils::new()->format('Y');
+
+        foreach ($access->getAccessWishes() as $accessWish)
+        {
+            /** @var AccessWish $accessWish */
+            if ($accessWish->getCreateDate()->format('Y') === $currentYear) {
+                return $accessWish;
+            }
+        }
+
+        return null;
+    }
+}

+ 285 - 0
src/Service/OnlineRegistration/RegistrationStartingService.php

@@ -0,0 +1,285 @@
+<?php
+
+namespace App\Service\OnlineRegistration;
+
+use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+use App\Enum\OnlineRegistration\ValidationStateEnum;
+use App\Service\Access\Utils as AccessUtils;
+use App\Service\Utils\ArrayUtils;
+use App\Service\Utils\DatesUtils;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+
+/**
+ * --- SERVICE INUTILISE POUR LE MOMENT ---
+ * Service de lancement de l'inscription en ligne
+ */
+class RegistrationStartingService
+{
+    const EDUCATION_REREGISTER_RIGHT = "canReregisterEducations";
+    const EDUCATION_ADD_RIGHT = "canAddEducations";
+    const STUDENT_ADD_RIGHT = "canAddStudents";
+    const ONLY_EDUCATION_ADD_RIGHT = "lockAllExeptAddNewEducation";
+
+
+    public function __construct(
+        private OnlineRegistrationService $onlineRegistrationService,
+        private AccessUtils $accessUtils
+    ) {}
+
+    /**
+     * Teste si un Access peut accéder à l'IEL
+     * Sinon, lève une RuntimeException
+     *
+     * @param Access $access
+     * @return bool
+     */
+    public function canStartOnlineRegistration(Access $access): bool {
+
+        if ($this->accessAndFamilyWishAlreadyValidateCanAddEducation($access)) {
+            return true;
+        }
+
+        $functions = $this->accessUtils->getActiveFunctions($access);
+
+        $hasNoFunction = $functions === ["OTHER_FUNCTION"] && !$access->getAdminAccess();
+
+        $isStudent = in_array('STUDENT', $functions);
+
+        if (!$isStudent && $access->getAccessFamily()) {
+            // On regarde si un membre de la famille est un élève actif
+            $isStudent = ArrayUtils::any(
+                $access->getAccessFamily()->getAccesses()->toArray(),
+                function ($access) { return $this->accessUtils->hasActiveFunction($access, 'STUDENT'); }
+            );
+        }
+
+        $reregistration = $isStudent && !$access->isNewAccess();
+
+        // La structure dois posséder des periodes d'ouvertures
+        if (!$this->hasOpeningHours($access->getOrganization(), $reregistration)) {
+            throw new \RuntimeException('no_opening_hours_users');
+        }
+
+        // Aujourd'hui doit faire partie des périodes d'ouvertures
+        if(!$this->isInOpeningPeriod($access->getOrganization(), $reregistration)) {
+            throw new \RuntimeException('not_inside_period');
+        }
+
+        // Si l'Access a déjà répondu une fois cette année à l'IEL (ou un membre de la famille)
+        if($this->checkIelValidationDateTimeFamily($access)) {
+            throw new \RuntimeException('iel_allready_done');
+        }
+        else if (!$hasNoFunction && !$access->isIelEnabled()) {
+            throw new \RuntimeException('no_access_granted');
+        }
+
+        return true;
+    }
+
+    /**
+     * TODO: compléter la doc et trouver un nom plus court
+     *
+     * @param Access $access
+     * @return bool
+     * @throws \Exception
+     */
+    private function accessAndFamilyWishAlreadyValidateCanAddEducation(Access $access): bool {
+        $currentAccessWish = $this->onlineRegistrationService->getCurrentAccessWish($access);
+
+        if (!$currentAccessWish) {
+            return false;
+        }
+
+        $accessWishAlreadyValidateCanAddEducation =
+            $this->accessWishAlreadyValidateCanAddEducation($access) ||
+            (
+                $access->getAccessFamily() &&
+                !$access->getAccessFamily()->getAccesses()->isEmpty() &&
+                ArrayUtils::any(
+                    $access->getAccessFamily()->getAccesses()->toArray(),
+                    function ($familyAccess) { return $this->accessWishAlreadyValidateCanAddEducation($familyAccess); }
+                )
+            );
+
+        if ($accessWishAlreadyValidateCanAddEducation) {
+            // La structure dois posséder des periodes d'ouvertures
+            if (!$this->hasOpeningHours($access->getOrganization(),false)){
+                throw new \Exception('no_opening_hours_users');
+            }
+
+            // Aujourd'hui doit faire partit des périodes d'ouvertures
+            if (!$this->isInsidePeriod($access->getOrganization(),false)){
+                throw new \Exception('not_inside_period');
+            }
+        }
+
+        return $accessWishAlreadyValidateCanAddEducation;
+    }
+
+
+    /**
+     * TODO: compléter la doc et trouver un nom plus court
+     *
+     * @param Access $access
+     * @return bool
+     */
+    private function accessWishAlreadyValidateCanAddEducation(Access $access): bool
+    {
+        $currentAccessWish = $this->onlineRegistrationService->getCurrentAccessWish($access);
+
+        if (!$currentAccessWish) {
+            return false;
+        }
+
+        $accessFamilyWish = $currentAccessWish->getAccessFamilyWish();
+
+        if ($accessFamilyWish?->isCloseRegistration()) {
+            return false;
+        }
+
+        if ($currentAccessWish->getValidationState() !== ValidationStateEnum::OPEN_FOR_EDUCATIONS) {
+            return false;
+        }
+
+        $studentsAndEducationsAuthorization = $this->buildStudentAuthorizations($access);
+
+        return $studentsAndEducationsAuthorization[self::EDUCATION_ADD_RIGHT] === true;
+    }
+
+    /**
+     * Détermine les autorisations d'un étudiant
+     *
+     * @param Access $access
+     * @return array<string, bool>
+     */
+    private function buildStudentAuthorizations(Access $access): array {
+
+        $rights = [
+            self::EDUCATION_REREGISTER_RIGHT => false,
+            self::EDUCATION_ADD_RIGHT => true,
+            self::STUDENT_ADD_RIGHT => true,
+            self::ONLY_EDUCATION_ADD_RIGHT => false
+        ];
+
+        $isNewStudent = $access->isNewAccess();
+        if ($isNewStudent) {
+            return $rights;
+        }
+
+        $organization = $access->getOrganization();
+        if ($organization === null) {
+            throw new \RuntimeException('Missing organization');
+        }
+
+        $organizationSettings = $organization->getOnlineRegistrationSettings();
+        $addNewStudentsSetting = $organizationSettings->canAddNewStudents();
+        $addNewEducationsSetting = $organizationSettings->canAddNewEducations();
+
+        $reRegistrationIsOpen = $this->isRegistrationOpen($organization, true);
+        // TODO: je comprend pas bien cette condition?
+        $rights[self::EDUCATION_REREGISTER_RIGHT] = $reRegistrationIsOpen || (!$addNewStudentsSetting && !$addNewEducationsSetting);
+
+        $newRegistrationIsOpen = $this->isRegistrationOpen($organization, false);
+        $rights[self::EDUCATION_ADD_RIGHT] =  $newRegistrationIsOpen || $addNewEducationsSetting;
+        $rights[self::STUDENT_ADD_RIGHT] =  $newRegistrationIsOpen || $addNewStudentsSetting;
+
+        $currentAccessWish = $this->onlineRegistrationService->getCurrentAccessWish($access);
+
+        $rights[self::STUDENT_ADD_RIGHT] = $currentAccessWish &&
+            $currentAccessWish->getValidationState() === ValidationStateEnum::OPEN_FOR_EDUCATIONS ||
+            (
+                $currentAccessWish->getAccessFamilyWish() &&
+                ArrayUtils::any(
+                    $currentAccessWish->getAccessFamilyWish()->getAccessWishes()->toArray(),
+                    function ($wish) { return $wish->getValidationState() === ValidationStateEnum::OPEN_FOR_EDUCATIONS; }
+                )
+            );
+
+        return $rights;
+    }
+
+    /**
+     * Retourne true si l'IEL est ouvert pour l'organisation donnée
+     *
+     * @param Organization $organization
+     * @param bool $reregistration
+     * @return bool
+     */
+    private function isRegistrationOpen(Organization $organization, bool $reregistration = true) {
+        return $this->hasOpeningHours($organization, $reregistration) &&
+               $this->isInOpeningPeriod($organization, $reregistration);
+    }
+
+    /**
+     * Retourne les périodes d'ouverture de l'IEL d'une organization, selon qu'il s'agisse de réinscription ou non
+     *
+     * @param Organization $organization
+     * @param bool $reregistration
+     * @return Collection
+     */
+    private function getOrganizationPeriods(Organization $organization, bool $reregistration = true): Collection {
+        $organizationSettings = $organization->getOnlineRegistrationSettings();
+        return $reregistration ?
+            $organizationSettings->getOpeningPeriods() :
+            $organizationSettings->getOpeningPeriodsNewEnrolments();
+    }
+
+    /**
+     * TODO: completer doc
+     *
+     * @param Organization $organization
+     * @param bool $reregistration
+     * @return bool
+     */
+    private function hasOpeningHours(Organization $organization, bool $reregistration = true): bool {
+        $openingPeriods = $this->getOrganizationPeriods($organization, $reregistration);
+        return count($openingPeriods) > 0;
+    }
+
+    /**
+     * Vérifie que nous sommes actuellement dans au moins une des périodes d'ouverture de l'IEL de l'organisation
+     *
+     * @param Organization $organization
+     * @param $reregistration
+     * @return bool
+     */
+    public function isInOpeningPeriod(Organization $organization, bool $reregistration = true): bool {
+        return ArrayUtils::any(
+            $this->getOrganizationPeriods($organization, $reregistration)->toArray(),
+            function ($period) { return DatesUtils::isNowInInterval($period->getStartDate(), $period->getEndDate()); }
+        );
+    }
+
+    /**
+     * TODO: completer doc et revoir le nom
+     *
+     * @param Access $access
+     * @return bool
+     */
+    private function checkIelValidationDateTimeFamily(Access $access): bool {
+        $today = DatesUtils::new();
+
+        if(
+            $access->getIelValidationDatetime() &&
+            ($access->getIelValidationDatetime()->format('Y') === $today->format('Y'))
+        ) {
+            return true;
+        }
+
+        if (
+            $access->getAccessFamily() &&
+            !$access->getAccessFamily()->getAccesses()->isEmpty()
+        ) {
+            return ArrayUtils::any(
+                $access->getAccessFamily()->getAccesses()->toArray(),
+                function ($access) use ($today) {
+                    return $access->getIelValidationDatetime() && $access->getIelValidationDatetime()->format('Y') === $today->format('Y');
+                }
+            );
+        }
+
+        return false;
+    }
+}

+ 7 - 22
src/Service/OnlineRegistration/RegistrationStatusService.php

@@ -9,6 +9,7 @@ use App\Enum\OnlineRegistration\RegistrationStatusEnum;
 use App\Enum\OnlineRegistration\WishRegistrationEnum;
 
 /**
+ * --- SERVICE INUTILISE POUR LE MOMENT ---
  * Fournit le statut de l'enregistrement en ligne d'un Access
  */
 class RegistrationStatusService
@@ -19,6 +20,10 @@ class RegistrationStatusService
      */
     const DISPLAYING_TIME = 30;
 
+    public function __construct(
+        private OnlineRegistrationService $onlineRegistrationService
+    ) {}
+
     /**
      * Détermine er retourne le statut de l'enregistrement en ligne d'un Access.
      *
@@ -27,7 +32,7 @@ class RegistrationStatusService
      */
     public function getStatus(Access $access): string | null {
 
-        $currentAccessWish = $this->getCurrentAccessWish($access);
+        $currentAccessWish = $this->onlineRegistrationService->getCurrentAccessWish($access);
         if ($currentAccessWish === null) {
             // TODO: est-ce qu'il ne faudrait pas plutôt lever une erreur ici plutôt que de retourner null?
             return null;
@@ -87,26 +92,6 @@ class RegistrationStatusService
         return DatesUtils::daysSince($lastUpdate);
     }
 
-    /**
-     * Retourne le premier AccessWish correspondant à l'année en cours, ou null si aucun n'est trouvé
-     *
-     * @param Access $access
-     * @return AccessWish | null
-     */
-    protected function getCurrentAccessWish(Access $access): AccessWish | null {
-        $currentYear =  DatesUtils::new()->format('Y');
-
-        foreach ($access->getAccessWishes() as $accessWish)
-        {
-            /** @var AccessWish $accessWish */
-            if ($accessWish->getCreateDate()->format('Y') === $currentYear) {
-                return $accessWish;
-            }
-        }
-
-        return null;
-    }
-
     /**
      * Retourne le décompte des souhaits de l'utilisateur par RegistrationStatus, ou null si aucun souhaits.
      *
@@ -118,7 +103,7 @@ class RegistrationStatusService
         $wishes = $currentAccessWish->getEducationStudentWishes();
         $reregistrationWishes = $currentAccessWish->getEducationStudentReregistrationsWishes();
 
-        if ($reregistrationWishes->count() === 0 && $wishes->count() === 0) {
+        if ($reregistrationWishes->isEmpty() && $wishes->isEmpty()) {
             // TODO: est-ce qu'il ne faudrait pas plutôt lever une erreur ici plutôt que de retourner null?
             return null;
         }

+ 6 - 4
src/Service/Rest/ApiRequestService.php

@@ -27,13 +27,14 @@ class ApiRequestService implements ApiRequestInterface
      * @param string $path
      * @param array<mixed> $parameters
      * @param array<mixed> $options
+     * @param boolean $throw @see https://symfony.com/doc/current/http_client.html#handling-exceptions
      * @return array<mixed>
      * @throws HttpException
      * @throws \JsonException
      */
-    public function getJsonContent(string $path, array $parameters = [], array $options = []): array
+    public function getJsonContent(string $path, array $parameters = [], array $options = [], bool $throw = true): array
     {
-        return json_decode($this->getContent($path, $parameters, $options), true, 512, JSON_THROW_ON_ERROR);
+        return json_decode($this->getContent($path, $parameters, $options, $throw), true, 512, JSON_THROW_ON_ERROR);
     }
 
     /**
@@ -42,13 +43,14 @@ class ApiRequestService implements ApiRequestInterface
      * @param string $path
      * @param array<mixed> $parameters
      * @param array<mixed> $options
+     * @param boolean $throw   @see https://symfony.com/doc/current/http_client.html#handling-exceptions
      * @return string
      * @throws HttpException
      */
-    public function getContent(string $path, array $parameters = [], array $options = []): string
+    public function getContent(string $path, array $parameters = [], array $options = [], bool $throw = true): string
     {
         try {
-            return $this->get($path, $parameters, $options)->getContent();
+            return $this->get($path, $parameters, $options)->getContent($throw);
         } catch (ClientExceptionInterface | TransportExceptionInterface | RedirectionExceptionInterface | ServerExceptionInterface $e) {
             throw new HttpException($e->getCode(), 'Request error : ' . $e->getMessage(), $e);
         }

+ 32 - 0
src/Service/Utils/ArrayUtils.php

@@ -5,6 +5,38 @@ namespace App\Service\Utils;
 
 class ArrayUtils
 {
+    /**
+     * Vérifie que tous les éléments d'une array valident le callback
+     *
+     * @param array $array
+     * @param callable $callback
+     * @return bool
+     */
+    public static function all(array $array, callable $callback) {
+        foreach ($arr as $item){
+            if (call_user_func($callback, $item)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Vérifie qu'au moins un élément d'une array valide le callback
+     *
+     * @param array $array
+     * @param callable $callback
+     * @return bool
+     */
+    public static function any(array $array, callable $callback) {
+        foreach ($arr as $item){
+            if (!call_user_func($callback, $item)){
+                return false;
+            }
+        }
+        return true;
+    }
+
     /**
      * Returns an array containing changes between the first array the keys/values from an array
      * which are absent or different from $initialData

+ 50 - 0
src/State/Provider/OnlineRegistration/RegistrationAvailabilityProvider.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\State\Provider\OnlineRegistration;
+
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Operation;
+use ApiPlatform\State\ProviderInterface;
+use App\ApiResources\OnlineRegistration\RegistrationAvailability;
+use App\Entity\Access\Access;
+use App\Service\ApiLegacy\ApiLegacyRequestService;
+use Symfony\Bundle\SecurityBundle\Security;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+class RegistrationAvailabilityProvider implements ProviderInterface
+{
+    public function __construct(
+        private Security $security,
+        private ApiLegacyRequestService $apiLegacyRequestService
+    ) {}
+
+    /**
+     * @param Operation $operation
+     * @param mixed[] $uriVariables
+     * @param mixed[] $context
+     * @return RegistrationAvailability|null
+     */
+    public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?RegistrationAvailability
+    {
+        if($operation instanceof GetCollection) {
+            throw new \RuntimeException('not supported', 500);
+        }
+
+        /** @var Access $currentAccess */
+        $currentAccess = $this->security->getUser();
+        if ($uriVariables['accessId'] !== $currentAccess->getId()) {
+            throw new \RuntimeException('forbidden', 503);
+        }
+
+        $response = $this->apiLegacyRequestService->get("/api/online_registration/access_verification");
+
+        $content = json_decode($response->getContent(false), true, 512, JSON_THROW_ON_ERROR);
+
+        $availability = new RegistrationAvailability();
+        $availability->setAccessId($currentAccess->getId());
+        $availability->setAvailable($response->getStatusCode() === 200);
+        $availability->setMessage($content['message']);
+
+        return $availability;
+    }
+}

+ 25 - 8
src/State/Provider/OnlineRegistration/RegistrationStatusProvider.php

@@ -7,8 +7,9 @@ use ApiPlatform\Metadata\Operation;
 use ApiPlatform\State\ProviderInterface;
 use App\ApiResources\OnlineRegistration\RegistrationStatus;
 use App\Entity\Access\Access;
+use App\Enum\OnlineRegistration\RegistrationStatusEnum;
 use App\Repository\Access\AccessRepository;
-use App\Service\OnlineRegistration\RegistrationStatusService;
+use App\Service\ApiLegacy\ApiLegacyRequestService;
 use Symfony\Bundle\SecurityBundle\Security;
 use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
 use RuntimeException;
@@ -17,8 +18,7 @@ class RegistrationStatusProvider implements ProviderInterface
 {
     public function __construct(
         private Security $security,
-        private RegistrationStatusService $registrationStatusService,
-        private AccessRepository $accessRepository
+        private ApiLegacyRequestService $apiLegacyRequestService
     ) {}
 
     /**
@@ -32,16 +32,33 @@ class RegistrationStatusProvider implements ProviderInterface
             throw new RuntimeException('not supported', 500);
         }
 
-        // TODO: voir à refactoriser le "getUser" dans un data provider de base (d'autres ont besoin de l'access et de l'orga en cours)
+        /** @var Access $currentAccess */
         $currentAccess = $this->security->getUser();
+        if ($uriVariables['accessId'] !== $currentAccess->getId()) {
+            throw new RuntimeException('forbidden', 503);
+        }
+
+        // --- L'appel au service remplacera l'appel à l'API v1 à l'avenir --
+        // $registrationStatusValue = $this->registrationStatusService->getStatus($currentAccess);
+
+        $response = $this->apiLegacyRequestService->getJsonContent("/api/online_registration/registration_status");
+        if (!isset($response['status'])) {
+            throw new RuntimeException('An error occured', 500);
+        }
 
-        $access = $this->accessRepository->find($uriVariables['accessId']) ?? $currentAccess;
+        $statusMap = [
+            "your_application_is_awaiting_processing" => RegistrationStatusEnum::NEGOTIABLE()->getValue(),
+            "your_registration_file_has_been_validated" => RegistrationStatusEnum::ACCEPTED()->getValue(),
+            "you_have_been_placed_on_the_waiting_list" => RegistrationStatusEnum::PENDING()->getValue(),
+            "your_application_has_been_refused" => RegistrationStatusEnum::DENIED()->getValue(),
+            null => null
+        ];
 
-        $registrationStatusValue = $this->registrationStatusService->getStatus($access);
+        $status  = $statusMap[$response['status']];
 
         $registrationStatus = new RegistrationStatus();
-        $registrationStatus->setAccessId($access->getId());
-        $registrationStatus->setStatus($registrationStatusValue);
+        $registrationStatus->setAccessId($currentAccess->getId());
+        $registrationStatus->setStatus($status);
 
         return $registrationStatus;
     }