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.
213 lines
6.7 KiB
PHP
213 lines
6.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Scolarite\Infrastructure\Persistence\Doctrine;
|
|
|
|
use App\Administration\Domain\Model\Subject\SubjectId;
|
|
use App\Administration\Domain\Model\User\UserId;
|
|
use App\Scolarite\Domain\Repository\StudentAverageRepository;
|
|
use App\Shared\Domain\Tenant\TenantId;
|
|
use Doctrine\DBAL\Connection;
|
|
use Override;
|
|
|
|
final readonly class DoctrineStudentAverageRepository implements StudentAverageRepository
|
|
{
|
|
public function __construct(
|
|
private Connection $connection,
|
|
) {
|
|
}
|
|
|
|
#[Override]
|
|
public function saveSubjectAverage(
|
|
TenantId $tenantId,
|
|
UserId $studentId,
|
|
SubjectId $subjectId,
|
|
string $periodId,
|
|
float $average,
|
|
int $gradeCount,
|
|
): void {
|
|
$this->connection->executeStatement(
|
|
'INSERT INTO student_averages (id, tenant_id, student_id, subject_id, period_id, average, grade_count, updated_at)
|
|
VALUES (gen_random_uuid(), :tenant_id, :student_id, :subject_id, :period_id, :average, :grade_count, NOW())
|
|
ON CONFLICT (student_id, subject_id, period_id) DO UPDATE SET
|
|
average = EXCLUDED.average,
|
|
grade_count = EXCLUDED.grade_count,
|
|
updated_at = NOW()',
|
|
[
|
|
'tenant_id' => (string) $tenantId,
|
|
'student_id' => (string) $studentId,
|
|
'subject_id' => (string) $subjectId,
|
|
'period_id' => $periodId,
|
|
'average' => $average,
|
|
'grade_count' => $gradeCount,
|
|
],
|
|
);
|
|
}
|
|
|
|
#[Override]
|
|
public function saveGeneralAverage(
|
|
TenantId $tenantId,
|
|
UserId $studentId,
|
|
string $periodId,
|
|
float $average,
|
|
): void {
|
|
$this->connection->executeStatement(
|
|
'INSERT INTO student_general_averages (id, tenant_id, student_id, period_id, average, updated_at)
|
|
VALUES (gen_random_uuid(), :tenant_id, :student_id, :period_id, :average, NOW())
|
|
ON CONFLICT (student_id, period_id) DO UPDATE SET
|
|
average = EXCLUDED.average,
|
|
updated_at = NOW()',
|
|
[
|
|
'tenant_id' => (string) $tenantId,
|
|
'student_id' => (string) $studentId,
|
|
'period_id' => $periodId,
|
|
'average' => $average,
|
|
],
|
|
);
|
|
}
|
|
|
|
#[Override]
|
|
public function findSubjectAveragesForStudent(
|
|
UserId $studentId,
|
|
string $periodId,
|
|
TenantId $tenantId,
|
|
): array {
|
|
$rows = $this->connection->fetchAllAssociative(
|
|
'SELECT average FROM student_averages
|
|
WHERE student_id = :student_id
|
|
AND period_id = :period_id
|
|
AND tenant_id = :tenant_id',
|
|
[
|
|
'student_id' => (string) $studentId,
|
|
'period_id' => $periodId,
|
|
'tenant_id' => (string) $tenantId,
|
|
],
|
|
);
|
|
|
|
return array_map(
|
|
/** @param array<string, mixed> $row */
|
|
static function (array $row): float {
|
|
/** @var string|float $avg */
|
|
$avg = $row['average'];
|
|
|
|
return (float) $avg;
|
|
},
|
|
$rows,
|
|
);
|
|
}
|
|
|
|
#[Override]
|
|
public function findDetailedSubjectAveragesForStudent(
|
|
UserId $studentId,
|
|
string $periodId,
|
|
TenantId $tenantId,
|
|
): array {
|
|
$rows = $this->connection->fetchAllAssociative(
|
|
'SELECT sa.subject_id, sa.average, sa.grade_count, s.name as subject_name
|
|
FROM student_averages sa
|
|
LEFT JOIN subjects s ON s.id = sa.subject_id
|
|
WHERE sa.student_id = :student_id
|
|
AND sa.period_id = :period_id
|
|
AND sa.tenant_id = :tenant_id
|
|
ORDER BY s.name ASC',
|
|
[
|
|
'student_id' => (string) $studentId,
|
|
'period_id' => $periodId,
|
|
'tenant_id' => (string) $tenantId,
|
|
],
|
|
);
|
|
|
|
return array_map(
|
|
/** @param array<string, mixed> $row */
|
|
static function (array $row): array {
|
|
/** @var string $subjectId */
|
|
$subjectId = $row['subject_id'];
|
|
/** @var string|null $subjectName */
|
|
$subjectName = $row['subject_name'];
|
|
/** @var string|float $average */
|
|
$average = $row['average'];
|
|
/** @var string|int $gradeCount */
|
|
$gradeCount = $row['grade_count'];
|
|
|
|
return [
|
|
'subjectId' => $subjectId,
|
|
'subjectName' => $subjectName,
|
|
'average' => (float) $average,
|
|
'gradeCount' => (int) $gradeCount,
|
|
];
|
|
},
|
|
$rows,
|
|
);
|
|
}
|
|
|
|
#[Override]
|
|
public function findGeneralAverageForStudent(
|
|
UserId $studentId,
|
|
string $periodId,
|
|
TenantId $tenantId,
|
|
): ?float {
|
|
$row = $this->connection->fetchAssociative(
|
|
'SELECT average FROM student_general_averages
|
|
WHERE student_id = :student_id
|
|
AND period_id = :period_id
|
|
AND tenant_id = :tenant_id',
|
|
[
|
|
'student_id' => (string) $studentId,
|
|
'period_id' => $periodId,
|
|
'tenant_id' => (string) $tenantId,
|
|
],
|
|
);
|
|
|
|
if ($row === false) {
|
|
return null;
|
|
}
|
|
|
|
/** @var string|float $average */
|
|
$average = $row['average'];
|
|
|
|
return (float) $average;
|
|
}
|
|
|
|
#[Override]
|
|
public function deleteSubjectAverage(
|
|
UserId $studentId,
|
|
SubjectId $subjectId,
|
|
string $periodId,
|
|
TenantId $tenantId,
|
|
): void {
|
|
$this->connection->executeStatement(
|
|
'DELETE FROM student_averages
|
|
WHERE student_id = :student_id
|
|
AND subject_id = :subject_id
|
|
AND period_id = :period_id
|
|
AND tenant_id = :tenant_id',
|
|
[
|
|
'student_id' => (string) $studentId,
|
|
'subject_id' => (string) $subjectId,
|
|
'period_id' => $periodId,
|
|
'tenant_id' => (string) $tenantId,
|
|
],
|
|
);
|
|
}
|
|
|
|
#[Override]
|
|
public function deleteGeneralAverage(
|
|
UserId $studentId,
|
|
string $periodId,
|
|
TenantId $tenantId,
|
|
): void {
|
|
$this->connection->executeStatement(
|
|
'DELETE FROM student_general_averages
|
|
WHERE student_id = :student_id
|
|
AND period_id = :period_id
|
|
AND tenant_id = :tenant_id',
|
|
[
|
|
'student_id' => (string) $studentId,
|
|
'period_id' => $periodId,
|
|
'tenant_id' => (string) $tenantId,
|
|
],
|
|
);
|
|
}
|
|
}
|