Commit d64d1c6c authored by Anthony Gautreau's avatar Anthony Gautreau
Browse files

Pegase : un noeud SEMESTRE du catalogue n'est jamais un cours, ses ECTS vont à...

Pegase : un noeud SEMESTRE du catalogue n'est jamais un cours, ses ECTS vont à son cours s'il n'en retient qu'un
parent f74fbb23
Loading
Loading
Loading
Loading
Loading
+19 −0
Changes for api/src/Pegase/Selection/CoursCatalogue.php: 19 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -29,4 +29,23 @@ final readonly class CoursCatalogue
        public ?string $regroupement,
    ) {
    }

    /**
     * @param float $ects ECTS repris d'un nœud traversé dont ce cours est le seul retenu
     *
     * @return self la même occurrence avec ces ECTS
     */
    public function avecEcts(float $ects): self
    {
        return new self(
            id: $this->id,
            code: $this->code,
            libelle: $this->libelle,
            type: $this->type,
            ects: $ects,
            semestre: $this->semestre,
            composante: $this->composante,
            regroupement: $this->regroupement,
        );
    }
}
+44 −19
Changes for api/src/Pegase/Selection/FormationCatalogue.php: 44 added lines, 19 removed lines.
Original line number Diff line number Diff line
@@ -12,8 +12,10 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire;
 * Sélection par une formation catalogue : les cours sont les objets placés sous un nœud de regroupement,
 * directement ou dans ses listes. Un objet propre au catalogue (non mutualisé), sans ECTS, qui contient
 * une liste est un conteneur (« Licence Psychologie S1 ») : les cours sont alors dans ses listes. Un objet
 * mutualisé est toujours un cours, même s'il regroupe des options (« UE libres »). Les sous-parties d'un
 * cours, les nœuds de structuration placés avant le regroupement et les codes exclus sont ignorés.
 * mutualisé est toujours un cours, même s'il regroupe des options (« UE libres »). Un nœud SEMESTRE n'est
 * jamais un cours : ses cours sont dans ses enfants. Un nœud traversé qui porte des ECTS les donne à son
 * cours s'il n'en retient qu'un et que ce cours n'en a pas. Les sous-parties d'un cours, les nœuds de
 * structuration placés avant le regroupement et les codes exclus sont ignorés.
 *
 * Le semestre est déduit du libellé du nœud SEMESTRE le plus haut (« Semestre 3 », « S2 »,
 * « 2ème sem »...) : impair pour le premier semestre, pair pour le second.
@@ -84,8 +86,7 @@ final class FormationCatalogue implements SelectionCoursInterface
            throw new PegaseException($message);
        }

        $cours = [];
        $this->parcourir($this->client->odf('maquette/'.$formation['id'])['racine'], [], $cours);
        $cours = $this->parcourir($this->client->odf('maquette/'.$formation['id'])['racine'], []);

        return new SelectionPeriode($cours, [$formation['id']]);
    }
@@ -95,50 +96,66 @@ final class FormationCatalogue implements SelectionCoursInterface
     *
     * @param array<mixed>       $objet    objet maquette
     * @param list<array<mixed>> $ancetres objets de la racine au parent de $objet
     * @param list<CoursCatalogue> $cours    cours retenus, complétés au fil du parcours
     *
     * @return list<CoursCatalogue> cours retenus sous $objet
     */
    private function parcourir(array $objet, array $ancetres, array &$cours): void
    private function parcourir(array $objet, array $ancetres): array
    {
        $chemin = [...$ancetres, $objet];
        if ($this->typeRegroupement === ($objet['type'] ?? null)) {
            $this->retenir($objet, $chemin, $objet, $cours);

            return;
            return $this->retenir($objet, $chemin, $objet);
        }
        $cours = [];
        foreach ($objet['enfants'] ?? [] as $enfant) {
            $this->parcourir($enfant['objetMaquette'], $chemin, $cours);
            array_push($cours, ...$this->parcourir($enfant['objetMaquette'], $chemin));
        }

        return $cours;
    }

    /**
     * Retient les enfants d'un regroupement, d'une de ses listes ou d'un conteneur.
     * Retient les enfants d'un regroupement ou d'un nœud traversé.
     *
     * @param array<mixed>       $parent       objet dont on lit les enfants
     * @param list<array<mixed>> $chemin       objets de la racine à $parent
     * @param array<mixed>       $regroupement nœud de regroupement au-dessus de $parent
     * @param list<CoursCatalogue> $cours        cours retenus
     *
     * @return list<CoursCatalogue> cours retenus sous $parent
     */
    private function retenir(array $parent, array $chemin, array $regroupement, array &$cours): void
    private function retenir(array $parent, array $chemin, array $regroupement): array
    {
        $cours = [];
        foreach ($parent['enfants'] ?? [] as $enfant) {
            $objet = $enfant['objetMaquette'];
            if (isset($this->exclus[$objet['code'] ?? ''])) {
                continue;
            }
            if (self::CLASSE_LISTE === ($objet['classe'] ?? null) || $this->estConteneur($objet)) {
                $this->retenir($objet, [...$chemin, $objet], $regroupement, $cours);
            } else {
            if (!$this->estTraverse($objet)) {
                $cours[] = $this->cours($objet, $chemin, $regroupement);
                continue;
            }
            $trouves = $this->retenir($objet, [...$chemin, $objet], $regroupement);
            $ects = $this->ects($objet);
            if (null !== $ects && 1 === \count($trouves) && null === $trouves[0]->ects) {
                $trouves = [$trouves[0]->avecEcts($ects)];
            }
            array_push($cours, ...$trouves);
        }

        return $cours;
    }

    /**
     * Liste, nœud SEMESTRE ou conteneur propre au catalogue : ses cours sont dans ses enfants.
     *
     * @param array<mixed> $objet
     */
    private function estConteneur(array $objet): bool
    private function estTraverse(array $objet): bool
    {
        if (null !== ($objet['ects'] ?? null) || true === ($objet['mutualise'] ?? false)) {
        if (self::CLASSE_LISTE === ($objet['classe'] ?? null) || self::SEMESTRE === ($objet['type'] ?? null)) {
            return true;
        }
        if (null !== $this->ects($objet) || true === ($objet['mutualise'] ?? false)) {
            return false;
        }
        foreach ($objet['enfants'] ?? [] as $enfant) {
@@ -150,6 +167,14 @@ final class FormationCatalogue implements SelectionCoursInterface
        return false;
    }

    /**
     * @param array<mixed> $objet
     */
    private function ects(array $objet): ?float
    {
        return isset($objet['ects']) ? (float) $objet['ects'] : null;
    }

    /**
     * @param array<mixed>       $objet        cours
     * @param list<array<mixed>> $chemin       objets de la racine au parent du cours
@@ -165,7 +190,7 @@ final class FormationCatalogue implements SelectionCoursInterface
            code: $objet['code'],
            libelle: $objet['libelle'] ?? $objet['code'],
            type: ($objet['type'] ?? '') ?: null,
            ects: isset($objet['ects']) ? (float) $objet['ects'] : null,
            ects: $this->ects($objet),
            semestre: null === $semestre ? null : $this->semestre($semestre['libelle'] ?? ''),
            composante: $composante['libelle'] ?? null,
            regroupement: $regroupement['libelle'] ?? null,
+7 −0
Changes for api/tests/Pegase/FormationCatalogueTest.php: 7 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -57,6 +57,10 @@ class FormationCatalogueTest extends TestCase
                            $this->objet('C7', 'EC', [$this->liste([$this->objet('C7A', 'EC')])], mutualise: true),
                            // Liste dans la liste
                            $this->liste([$this->objet('C8', 'EC')]),
                            // Semestre placé sous le regroupement, avec ECTS : ses enfants sont les cours, le seul reprend ses ECTS
                            $this->objet('SEM6', 'SEMESTRE', [$this->objet('C9', 'EC')], libelle: '6 Mobilité', ects: 30, mutualise: true),
                            // Plusieurs cours : la part de chacun n'est pas connue
                            $this->objet('SEM7', 'SEMESTRE', [$this->liste([$this->objet('C10', 'EC'), $this->objet('C11', 'EC')])], ects: 30, mutualise: true),
                        ]),
                    ]),
                ])], libelle: 'Semestre 5'),
@@ -76,6 +80,9 @@ class FormationCatalogueTest extends TestCase
            ['C6', 1, null, 'RDE5', 6.0, 'UE'],
            ['C7', 1, null, 'RDE5', null, 'EC'],
            ['C8', 1, null, 'RDE5', null, 'EC'],
            ['C9', 1, null, 'RDE5', 30.0, 'EC'],
            ['C10', 1, null, 'RDE5', null, 'EC'],
            ['C11', 1, null, 'RDE5', null, 'EC'],
        ], array_map(fn (CoursCatalogue $c) => [$c->code, $c->semestre, $c->composante, $c->regroupement, $c->ects, $c->type], $selection->cours));
    }