feat: Calculer automatiquement les moyennes après chaque saisie de notes
Some checks failed
CI / Backend Tests (push) Has been cancelled
CI / Frontend Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Naming Conventions (push) Has been cancelled
CI / Build Check (push) Has been cancelled

Les enseignants ont besoin de moyennes à jour immédiatement après la
publication ou modification des notes, sans attendre un batch nocturne.

Le système recalcule via Domain Events synchrones : statistiques
d'évaluation (min/max/moyenne/médiane), moyennes matières pondérées
(normalisation /20), et moyenne générale par élève. Les résultats sont
stockés dans des tables dénormalisées avec cache Redis (TTL 5 min).

Trois endpoints API exposent les données avec contrôle d'accès par rôle.
Une commande console permet le backfill des données historiques au
déploiement.
This commit is contained in:
2026-03-30 06:22:03 +02:00
parent b70d5ec2ad
commit b7dc27f2a5
786 changed files with 118783 additions and 316 deletions

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Repository;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Model\Grade\AppreciationTemplate;
use App\Scolarite\Domain\Model\Grade\AppreciationTemplateId;
use App\Shared\Domain\Tenant\TenantId;
interface AppreciationTemplateRepository
{
public function save(AppreciationTemplate $template): void;
public function findById(AppreciationTemplateId $id, TenantId $tenantId): ?AppreciationTemplate;
/** @return array<AppreciationTemplate> */
public function findByTeacher(UserId $teacherId, TenantId $tenantId): array;
public function delete(AppreciationTemplateId $id, TenantId $tenantId): void;
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Repository;
use App\Scolarite\Domain\Model\Competency\CompetencyEvaluation;
use App\Scolarite\Domain\Model\Competency\CompetencyEvaluationId;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
interface CompetencyEvaluationRepository
{
public function save(CompetencyEvaluation $competencyEvaluation): void;
public function findById(CompetencyEvaluationId $id): ?CompetencyEvaluation;
/** @return array<CompetencyEvaluation> */
public function findByEvaluation(EvaluationId $evaluationId): array;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Repository;
use App\Scolarite\Domain\Model\Competency\CompetencyFramework;
use App\Scolarite\Domain\Model\Competency\CompetencyFrameworkId;
use App\Shared\Domain\Tenant\TenantId;
interface CompetencyFrameworkRepository
{
public function save(CompetencyFramework $framework): void;
public function findById(CompetencyFrameworkId $id, TenantId $tenantId): ?CompetencyFramework;
public function findDefault(TenantId $tenantId): ?CompetencyFramework;
/** @return array<CompetencyFramework> */
public function findByTenant(TenantId $tenantId): array;
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Repository;
use App\Scolarite\Domain\Model\Competency\Competency;
use App\Scolarite\Domain\Model\Competency\CompetencyFrameworkId;
use App\Scolarite\Domain\Model\Competency\CompetencyId;
use App\Shared\Domain\Tenant\TenantId;
interface CompetencyRepository
{
public function save(Competency $competency): void;
public function findById(CompetencyId $id, TenantId $tenantId): ?Competency;
/** @return array<Competency> */
public function findByFramework(CompetencyFrameworkId $frameworkId): array;
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Repository;
use App\Scolarite\Domain\Model\Competency\CustomCompetencyLevel;
use App\Shared\Domain\Tenant\TenantId;
interface CustomCompetencyLevelRepository
{
public function save(CustomCompetencyLevel $level): void;
/** @return array<CustomCompetencyLevel> */
public function findByTenant(TenantId $tenantId): array;
public function hasByTenant(TenantId $tenantId): bool;
}

View File

@@ -5,11 +5,13 @@ declare(strict_types=1);
namespace App\Scolarite\Domain\Repository;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Exception\EvaluationNotFoundException;
use App\Scolarite\Domain\Model\Evaluation\Evaluation;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
interface EvaluationRepository
{
@@ -28,4 +30,16 @@ interface EvaluationRepository
/** @return array<Evaluation> */
public function findByClass(ClassId $classId, TenantId $tenantId): array;
/** @return array<Evaluation> Toutes les évaluations publiées (notes visibles) */
public function findAllWithPublishedGrades(TenantId $tenantId): array;
/** @return array<Evaluation> Évaluations dont les notes sont publiées */
public function findWithPublishedGradesBySubjectAndClassInDateRange(
SubjectId $subjectId,
ClassId $classId,
DateTimeImmutable $startDate,
DateTimeImmutable $endDate,
TenantId $tenantId,
): array;
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Repository;
use App\Scolarite\Domain\Model\Evaluation\ClassStatistics;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
interface EvaluationStatisticsRepository
{
public function save(EvaluationId $evaluationId, ClassStatistics $statistics): void;
public function findByEvaluation(EvaluationId $evaluationId): ?ClassStatistics;
public function delete(EvaluationId $evaluationId): void;
}

View File

@@ -14,6 +14,8 @@ interface GradeRepository
{
public function save(Grade $grade): void;
public function saveAppreciation(Grade $grade): void;
/** @throws GradeNotFoundException */
public function get(GradeId $id, TenantId $tenantId): Grade;
@@ -22,5 +24,12 @@ interface GradeRepository
/** @return array<Grade> */
public function findByEvaluation(EvaluationId $evaluationId, TenantId $tenantId): array;
/**
* @param array<EvaluationId> $evaluationIds
*
* @return array<string, list<Grade>> Clé = evaluationId (string)
*/
public function findByEvaluations(array $evaluationIds, TenantId $tenantId): array;
public function hasGradesForEvaluation(EvaluationId $evaluationId, TenantId $tenantId): bool;
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Repository;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Shared\Domain\Tenant\TenantId;
interface StudentAverageRepository
{
public function saveSubjectAverage(
TenantId $tenantId,
UserId $studentId,
SubjectId $subjectId,
string $periodId,
float $average,
int $gradeCount,
): void;
public function saveGeneralAverage(
TenantId $tenantId,
UserId $studentId,
string $periodId,
float $average,
): void;
/** @return list<float> Moyennes matières d'un élève pour une période */
public function findSubjectAveragesForStudent(
UserId $studentId,
string $periodId,
TenantId $tenantId,
): array;
/** @return list<array{subjectId: string, subjectName: string|null, average: float, gradeCount: int}> */
public function findDetailedSubjectAveragesForStudent(
UserId $studentId,
string $periodId,
TenantId $tenantId,
): array;
public function findGeneralAverageForStudent(
UserId $studentId,
string $periodId,
TenantId $tenantId,
): ?float;
public function deleteSubjectAverage(
UserId $studentId,
SubjectId $subjectId,
string $periodId,
TenantId $tenantId,
): void;
public function deleteGeneralAverage(
UserId $studentId,
string $periodId,
TenantId $tenantId,
): void;
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Domain\Repository;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Model\Competency\CompetencyEvaluationId;
use App\Scolarite\Domain\Model\Competency\CompetencyId;
use App\Scolarite\Domain\Model\Competency\StudentCompetencyResult;
use App\Shared\Domain\Tenant\TenantId;
interface StudentCompetencyResultRepository
{
public function save(StudentCompetencyResult $result): void;
public function delete(StudentCompetencyResult $result): void;
/** @return array<StudentCompetencyResult> */
public function findByCompetencyEvaluation(
CompetencyEvaluationId $competencyEvaluationId,
TenantId $tenantId,
): array;
public function findByCompetencyEvaluationAndStudent(
CompetencyEvaluationId $competencyEvaluationId,
UserId $studentId,
TenantId $tenantId,
): ?StudentCompetencyResult;
/** @return array<StudentCompetencyResult> */
public function findByStudentAndCompetency(
UserId $studentId,
CompetencyId $competencyId,
TenantId $tenantId,
): array;
}