feat: Provisionner automatiquement un nouvel établissement
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

Lorsqu'un super-admin crée un établissement via l'interface, le système
doit automatiquement créer la base tenant, exécuter les migrations,
créer le premier utilisateur admin et envoyer l'invitation — le tout
de manière asynchrone pour ne pas bloquer la réponse HTTP.

Ce mécanisme rend chaque établissement opérationnel dès sa création
sans intervention manuelle sur l'infrastructure.
This commit is contained in:
2026-04-08 13:55:41 +02:00
parent bec211ebf0
commit 3575d095a1
106 changed files with 9586 additions and 380 deletions

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Application\Query\GetEvaluationDifficulty;
final readonly class EvaluationDifficultyDto
{
public function __construct(
public string $evaluationId,
public string $title,
public string $classId,
public string $className,
public string $subjectId,
public string $subjectName,
public string $date,
public ?float $average,
public int $gradedCount,
public ?float $subjectAverage,
public ?float $percentile,
) {
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Application\Query\GetEvaluationDifficulty;
use App\Scolarite\Application\Port\TeacherStatisticsReader;
use App\Scolarite\Domain\Service\TeacherStatisticsCalculator;
use function array_sum;
use function count;
use function round;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler(bus: 'query.bus')]
final readonly class GetEvaluationDifficultyHandler
{
public function __construct(
private TeacherStatisticsReader $reader,
private TeacherStatisticsCalculator $calculator,
) {
}
/** @return list<EvaluationDifficultyDto> */
public function __invoke(GetEvaluationDifficultyQuery $query): array
{
$evaluations = $this->reader->teacherEvaluationDifficulties(
$query->teacherId,
$query->tenantId,
);
/** @var array<string, list<float>> $otherAveragesCache */
$otherAveragesCache = [];
$results = [];
foreach ($evaluations as $eval) {
/** @var string $subjectId */
$subjectId = $eval['subjectId'];
if (!isset($otherAveragesCache[$subjectId])) {
$otherAveragesCache[$subjectId] = $this->reader->subjectAveragesForOtherTeachers(
$query->teacherId,
$subjectId,
$query->tenantId,
);
}
$otherAvgs = $otherAveragesCache[$subjectId];
$subjectAverage = $otherAvgs !== [] ? round(array_sum($otherAvgs) / count($otherAvgs), 2) : null;
$percentile = $eval['average'] !== null && $otherAvgs !== []
? $this->calculator->calculatePercentile($eval['average'], $otherAvgs)
: null;
$results[] = new EvaluationDifficultyDto(
evaluationId: $eval['evaluationId'],
title: $eval['title'],
classId: $eval['classId'],
className: $eval['className'],
subjectId: $subjectId,
subjectName: $eval['subjectName'],
date: $eval['date'],
average: $eval['average'],
gradedCount: $eval['gradedCount'],
subjectAverage: $subjectAverage,
percentile: $percentile,
);
}
return $results;
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Scolarite\Application\Query\GetEvaluationDifficulty;
final readonly class GetEvaluationDifficultyQuery
{
public function __construct(
public string $teacherId,
public string $tenantId,
) {
}
}