feat(demo): add tenant demo data generator
Add a relaunchable demo seed flow so a tenant can be populated quickly on a VPS or demo environment without manual setup.
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Infrastructure\Console;
|
||||
|
||||
use App\Administration\Domain\Model\AcademicYear\PeriodType;
|
||||
use App\Administration\Domain\Model\SchoolCalendar\SchoolZone;
|
||||
use App\Administration\Infrastructure\Service\DemoDataGenerationResult;
|
||||
use App\Administration\Infrastructure\Service\DemoDataGenerator;
|
||||
use App\Shared\Infrastructure\Tenant\TenantConfig;
|
||||
use App\Shared\Infrastructure\Tenant\TenantNotFoundException;
|
||||
use App\Shared\Infrastructure\Tenant\TenantRegistry;
|
||||
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function getenv;
|
||||
use function implode;
|
||||
|
||||
use Override;
|
||||
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Throwable;
|
||||
|
||||
use function trim;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:dev:generate-demo-data',
|
||||
description: 'Génère un jeu de données complet pour un tenant de démonstration',
|
||||
)]
|
||||
final class GenerateDemoDataCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly TenantRegistry $tenantRegistry,
|
||||
private readonly DemoDataGenerator $demoDataGenerator,
|
||||
#[Autowire('%kernel.project_dir%')]
|
||||
private readonly string $projectDir,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addOption('tenant', null, InputOption::VALUE_OPTIONAL, 'Sous-domaine du tenant cible')
|
||||
->addOption('password', null, InputOption::VALUE_OPTIONAL, 'Mot de passe partagé pour tous les comptes de démo', 'DemoPassword123!')
|
||||
->addOption('school', null, InputOption::VALUE_OPTIONAL, 'Nom de l’établissement affiché sur les comptes générés')
|
||||
->addOption('zone', null, InputOption::VALUE_OPTIONAL, 'Zone scolaire (A, B ou C)', 'B')
|
||||
->addOption('period-type', null, InputOption::VALUE_OPTIONAL, 'Découpage des périodes (trimester ou semester)', PeriodType::TRIMESTER->value)
|
||||
->addOption('internal-run', null, InputOption::VALUE_NONE, 'Option interne pour exécuter la génération dans la base tenant');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
/** @var string|null $tenantOption */
|
||||
$tenantOption = $input->getOption('tenant');
|
||||
/** @var string $password */
|
||||
$password = $input->getOption('password');
|
||||
/** @var string|null $schoolOption */
|
||||
$schoolOption = $input->getOption('school');
|
||||
/** @var string $zoneOption */
|
||||
$zoneOption = $input->getOption('zone');
|
||||
/** @var string $periodTypeOption */
|
||||
$periodTypeOption = $input->getOption('period-type');
|
||||
$internalRun = $input->getOption('internal-run');
|
||||
|
||||
$tenantConfig = $this->resolveTenantConfig($tenantOption, $io);
|
||||
if ($tenantConfig === null) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$zone = SchoolZone::tryFrom(strtoupper(trim($zoneOption)));
|
||||
if ($zone === null) {
|
||||
$io->error('Zone invalide. Valeurs attendues: A, B, C.');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$periodType = PeriodType::tryFrom(strtolower(trim($periodTypeOption)));
|
||||
if ($periodType === null) {
|
||||
$io->error('Type de période invalide. Valeurs attendues: trimester, semester.');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$schoolName = trim((string) $schoolOption);
|
||||
if ($schoolName === '') {
|
||||
$schoolName = $this->demoDataGenerator->defaultSchoolNameForSubdomain($tenantConfig->subdomain);
|
||||
}
|
||||
|
||||
if ($internalRun) {
|
||||
return $this->runInTenantProcess($tenantConfig, $password, $schoolName, $zone, $periodType, $io);
|
||||
}
|
||||
|
||||
return $this->relaunchAgainstTenantDatabase($tenantConfig, $password, $schoolName, $zone, $periodType, $io);
|
||||
}
|
||||
|
||||
private function relaunchAgainstTenantDatabase(
|
||||
TenantConfig $tenantConfig,
|
||||
string $password,
|
||||
string $schoolName,
|
||||
SchoolZone $zone,
|
||||
PeriodType $periodType,
|
||||
SymfonyStyle $io,
|
||||
): int {
|
||||
$io->section(sprintf(
|
||||
'Génération du jeu de démo pour le tenant "%s" sur sa base dédiée',
|
||||
$tenantConfig->subdomain,
|
||||
));
|
||||
|
||||
$process = new Process(
|
||||
command: [
|
||||
'php',
|
||||
'bin/console',
|
||||
'app:dev:generate-demo-data',
|
||||
'--tenant=' . $tenantConfig->subdomain,
|
||||
'--password=' . $password,
|
||||
'--school=' . $schoolName,
|
||||
'--zone=' . $zone->value,
|
||||
'--period-type=' . $periodType->value,
|
||||
'--internal-run',
|
||||
],
|
||||
cwd: $this->projectDir,
|
||||
env: [
|
||||
...getenv(),
|
||||
'DATABASE_URL' => $tenantConfig->databaseUrl,
|
||||
],
|
||||
timeout: 300,
|
||||
);
|
||||
|
||||
$process->run(static function (string $type, string $buffer) use ($io): void {
|
||||
$io->write($buffer);
|
||||
});
|
||||
|
||||
if ($process->isSuccessful()) {
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io->error(sprintf(
|
||||
'La génération a échoué pour le tenant "%s".',
|
||||
$tenantConfig->subdomain,
|
||||
));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
private function runInTenantProcess(
|
||||
TenantConfig $tenantConfig,
|
||||
string $password,
|
||||
string $schoolName,
|
||||
SchoolZone $zone,
|
||||
PeriodType $periodType,
|
||||
SymfonyStyle $io,
|
||||
): int {
|
||||
try {
|
||||
$result = $this->demoDataGenerator->generate(
|
||||
tenantConfig: $tenantConfig,
|
||||
password: $password,
|
||||
schoolName: $schoolName,
|
||||
zone: $zone,
|
||||
periodType: $periodType,
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
$io->error([
|
||||
sprintf('Impossible de générer les données de démo pour "%s".', $tenantConfig->subdomain),
|
||||
$e->getMessage(),
|
||||
]);
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->renderResult($result, $io);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function renderResult(DemoDataGenerationResult $result, SymfonyStyle $io): void
|
||||
{
|
||||
$io->success(sprintf(
|
||||
'Jeu de démo prêt pour le tenant "%s" (%s).',
|
||||
$result->tenantSubdomain,
|
||||
$result->academicYearLabel,
|
||||
));
|
||||
|
||||
$io->table(
|
||||
['Élément', 'Résultat'],
|
||||
[
|
||||
['Établissement', $result->schoolName],
|
||||
['Utilisateurs créés', (string) $result->createdUsers],
|
||||
['Matières créées', (string) $result->createdSubjects],
|
||||
['Classes créées', (string) $result->createdClasses],
|
||||
['Affectations élèves', (string) $result->createdClassAssignments],
|
||||
['Affectations enseignants', (string) $result->createdTeacherAssignments],
|
||||
['Liens parent-enfant', (string) $result->createdGuardianLinks],
|
||||
['Créneaux d’emploi du temps', (string) $result->createdScheduleSlots],
|
||||
['Périodes créées', $result->periodConfigurationCreated ? 'oui' : 'non'],
|
||||
['Calendrier créé', $result->schoolCalendarCreated ? 'oui' : 'non'],
|
||||
['Mode de notation créé', $result->gradingConfigurationCreated ? 'oui' : 'non'],
|
||||
],
|
||||
);
|
||||
|
||||
$io->writeln(sprintf('Mot de passe commun: <info>%s</info>', $result->sharedPassword));
|
||||
|
||||
$io->table(
|
||||
['Rôle', 'Nom', 'Email'],
|
||||
array_map(
|
||||
static fn (array $account): array => [
|
||||
$account['role'],
|
||||
$account['name'],
|
||||
$account['email'],
|
||||
],
|
||||
$result->accounts,
|
||||
),
|
||||
);
|
||||
|
||||
if ($result->warnings !== []) {
|
||||
$io->warning($result->warnings);
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveTenantConfig(?string $tenantOption, SymfonyStyle $io): ?TenantConfig
|
||||
{
|
||||
$tenant = trim((string) $tenantOption);
|
||||
|
||||
if ($tenant !== '') {
|
||||
try {
|
||||
return $this->tenantRegistry->getBySubdomain($tenant);
|
||||
} catch (TenantNotFoundException) {
|
||||
$io->error(sprintf(
|
||||
'Tenant "%s" introuvable. Disponibles: %s',
|
||||
$tenant,
|
||||
implode(', ', $this->availableSubdomains()),
|
||||
));
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$configs = $this->tenantRegistry->getAllConfigs();
|
||||
if (count($configs) === 1) {
|
||||
return $configs[0];
|
||||
}
|
||||
|
||||
$io->error(sprintf(
|
||||
'Plusieurs tenants sont configurés. Précise --tenant parmi: %s',
|
||||
implode(', ', $this->availableSubdomains()),
|
||||
));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function availableSubdomains(): array
|
||||
{
|
||||
return array_values(array_map(
|
||||
static fn (TenantConfig $config): string => $config->subdomain,
|
||||
$this->tenantRegistry->getAllConfigs(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Infrastructure\Service;
|
||||
|
||||
final class DemoDataGenerationResult
|
||||
{
|
||||
public int $createdUsers = 0;
|
||||
public int $createdSubjects = 0;
|
||||
public int $createdClasses = 0;
|
||||
public int $createdClassAssignments = 0;
|
||||
public int $createdTeacherAssignments = 0;
|
||||
public int $createdGuardianLinks = 0;
|
||||
public int $createdScheduleSlots = 0;
|
||||
public bool $periodConfigurationCreated = false;
|
||||
public bool $schoolCalendarCreated = false;
|
||||
public bool $gradingConfigurationCreated = false;
|
||||
|
||||
/** @var list<array{role: string, name: string, email: string}> */
|
||||
public array $accounts = [];
|
||||
|
||||
/** @var list<string> */
|
||||
public array $warnings = [];
|
||||
|
||||
public function __construct(
|
||||
public readonly string $tenantSubdomain,
|
||||
public readonly string $schoolName,
|
||||
public readonly string $academicYearLabel,
|
||||
public readonly string $sharedPassword,
|
||||
) {
|
||||
}
|
||||
|
||||
public function addAccount(string $role, string $name, string $email): void
|
||||
{
|
||||
$this->accounts[] = [
|
||||
'role' => $role,
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
];
|
||||
}
|
||||
|
||||
public function addWarning(string $warning): void
|
||||
{
|
||||
$this->warnings[] = $warning;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,957 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Administration\Infrastructure\Service;
|
||||
|
||||
use App\Administration\Application\Port\OfficialCalendarProvider;
|
||||
use App\Administration\Application\Port\PasswordHasher;
|
||||
use App\Administration\Domain\Model\AcademicYear\DefaultPeriods;
|
||||
use App\Administration\Domain\Model\AcademicYear\PeriodType;
|
||||
use App\Administration\Domain\Model\ClassAssignment\ClassAssignment;
|
||||
use App\Administration\Domain\Model\GradingConfiguration\SchoolGradingConfiguration;
|
||||
use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendar;
|
||||
use App\Administration\Domain\Model\SchoolCalendar\SchoolZone;
|
||||
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
|
||||
use App\Administration\Domain\Model\SchoolClass\ClassName;
|
||||
use App\Administration\Domain\Model\SchoolClass\SchoolClass;
|
||||
use App\Administration\Domain\Model\SchoolClass\SchoolId;
|
||||
use App\Administration\Domain\Model\SchoolClass\SchoolLevel;
|
||||
use App\Administration\Domain\Model\StudentGuardian\RelationshipType;
|
||||
use App\Administration\Domain\Model\StudentGuardian\StudentGuardian;
|
||||
use App\Administration\Domain\Model\Subject\Subject;
|
||||
use App\Administration\Domain\Model\Subject\SubjectCode;
|
||||
use App\Administration\Domain\Model\Subject\SubjectColor;
|
||||
use App\Administration\Domain\Model\Subject\SubjectName;
|
||||
use App\Administration\Domain\Model\TeacherAssignment\TeacherAssignment;
|
||||
use App\Administration\Domain\Model\User\Email;
|
||||
use App\Administration\Domain\Model\User\Role;
|
||||
use App\Administration\Domain\Model\User\StatutCompte;
|
||||
use App\Administration\Domain\Model\User\User;
|
||||
use App\Administration\Domain\Model\User\UserId;
|
||||
use App\Administration\Domain\Repository\ClassAssignmentRepository;
|
||||
use App\Administration\Domain\Repository\ClassRepository;
|
||||
use App\Administration\Domain\Repository\GradingConfigurationRepository;
|
||||
use App\Administration\Domain\Repository\PeriodConfigurationRepository;
|
||||
use App\Administration\Domain\Repository\StudentGuardianRepository;
|
||||
use App\Administration\Domain\Repository\SubjectRepository;
|
||||
use App\Administration\Domain\Repository\TeacherAssignmentRepository;
|
||||
use App\Administration\Domain\Repository\UserRepository;
|
||||
use App\Administration\Infrastructure\School\SchoolIdResolver;
|
||||
use App\Scolarite\Domain\Model\Schedule\DayOfWeek;
|
||||
use App\Scolarite\Domain\Model\Schedule\ScheduleSlot;
|
||||
use App\Scolarite\Domain\Model\Schedule\TimeSlot;
|
||||
use App\Scolarite\Domain\Repository\ScheduleSlotRepository;
|
||||
use App\Shared\Domain\Clock;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use App\Shared\Infrastructure\Tenant\TenantConfig;
|
||||
use App\Shared\Infrastructure\Tenant\TenantContext;
|
||||
use DateTimeImmutable;
|
||||
use RuntimeException;
|
||||
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
use function ucwords;
|
||||
|
||||
final readonly class DemoDataGenerator
|
||||
{
|
||||
public function __construct(
|
||||
private UserRepository $userRepository,
|
||||
private ClassRepository $classRepository,
|
||||
private SubjectRepository $subjectRepository,
|
||||
private ClassAssignmentRepository $classAssignmentRepository,
|
||||
private TeacherAssignmentRepository $teacherAssignmentRepository,
|
||||
private StudentGuardianRepository $studentGuardianRepository,
|
||||
private ScheduleSlotRepository $scheduleSlotRepository,
|
||||
private PeriodConfigurationRepository $periodConfigurationRepository,
|
||||
private \App\Administration\Domain\Model\SchoolCalendar\SchoolCalendarRepository $schoolCalendarRepository,
|
||||
private GradingConfigurationRepository $gradingConfigurationRepository,
|
||||
private PasswordHasher $passwordHasher,
|
||||
private Clock $clock,
|
||||
private TenantContext $tenantContext,
|
||||
private CurrentAcademicYearResolver $currentAcademicYearResolver,
|
||||
private SchoolIdResolver $schoolIdResolver,
|
||||
private OfficialCalendarProvider $officialCalendarProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
public function generate(
|
||||
TenantConfig $tenantConfig,
|
||||
string $password,
|
||||
string $schoolName,
|
||||
SchoolZone $zone,
|
||||
PeriodType $periodType,
|
||||
): DemoDataGenerationResult {
|
||||
$this->tenantContext->setCurrentTenant($tenantConfig);
|
||||
|
||||
try {
|
||||
$tenantId = $tenantConfig->tenantId;
|
||||
$academicYearId = $this->resolveAcademicYearId();
|
||||
$academicYearStartYear = $this->resolveAcademicYearStartYear();
|
||||
$academicYearLabel = sprintf('%d-%d', $academicYearStartYear, $academicYearStartYear + 1);
|
||||
$schoolId = SchoolId::fromString($this->schoolIdResolver->resolveForTenant((string) $tenantId));
|
||||
$now = $this->clock->now();
|
||||
$hashedPassword = $this->passwordHasher->hash($password);
|
||||
|
||||
$result = new DemoDataGenerationResult(
|
||||
tenantSubdomain: $tenantConfig->subdomain,
|
||||
schoolName: $schoolName,
|
||||
academicYearLabel: $academicYearLabel,
|
||||
sharedPassword: $password,
|
||||
);
|
||||
|
||||
$this->ensurePeriodConfiguration($tenantId, $academicYearId, $academicYearStartYear, $periodType, $result);
|
||||
$this->ensureSchoolCalendar($tenantId, $academicYearId, $academicYearLabel, $zone, $now, $result);
|
||||
$this->ensureGradingConfiguration($tenantId, $schoolId, $academicYearId, $now, $result);
|
||||
|
||||
$users = $this->seedUsers($tenantConfig, $schoolName, $hashedPassword, $now, $result);
|
||||
$subjects = $this->seedSubjects($tenantId, $schoolId, $now, $result);
|
||||
$classes = $this->seedClasses($tenantId, $schoolId, $academicYearId, $now, $result);
|
||||
|
||||
$this->seedClassAssignments($tenantId, $academicYearId, $users, $classes, $now, $result);
|
||||
$this->seedTeacherAssignments($tenantId, $academicYearId, $users, $classes, $subjects, $now, $result);
|
||||
$this->seedStudentGuardianLinks($tenantId, $users, $now, $result);
|
||||
$this->seedScheduleSlots($tenantId, $classes, $subjects, $users, $academicYearStartYear, $now, $result);
|
||||
|
||||
return $result;
|
||||
} finally {
|
||||
$this->tenantContext->clear();
|
||||
}
|
||||
}
|
||||
|
||||
private function ensurePeriodConfiguration(
|
||||
TenantId $tenantId,
|
||||
AcademicYearId $academicYearId,
|
||||
int $academicYearStartYear,
|
||||
PeriodType $periodType,
|
||||
DemoDataGenerationResult $result,
|
||||
): void {
|
||||
if ($this->periodConfigurationRepository->findByAcademicYear($tenantId, $academicYearId) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$configuration = DefaultPeriods::forType($periodType, $academicYearStartYear);
|
||||
$this->periodConfigurationRepository->save($tenantId, $academicYearId, $configuration);
|
||||
$result->periodConfigurationCreated = true;
|
||||
}
|
||||
|
||||
private function ensureSchoolCalendar(
|
||||
TenantId $tenantId,
|
||||
AcademicYearId $academicYearId,
|
||||
string $academicYearLabel,
|
||||
SchoolZone $zone,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
): void {
|
||||
if ($this->schoolCalendarRepository->findByTenantAndYear($tenantId, $academicYearId) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$calendar = SchoolCalendar::initialiser($tenantId, $academicYearId);
|
||||
$calendar->configurer(
|
||||
$zone,
|
||||
$this->officialCalendarProvider->toutesEntreesOfficielles($zone, $academicYearLabel),
|
||||
$now,
|
||||
);
|
||||
|
||||
$this->schoolCalendarRepository->save($calendar);
|
||||
$result->schoolCalendarCreated = true;
|
||||
}
|
||||
|
||||
private function ensureGradingConfiguration(
|
||||
TenantId $tenantId,
|
||||
SchoolId $schoolId,
|
||||
AcademicYearId $academicYearId,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
): void {
|
||||
if ($this->gradingConfigurationRepository->findBySchoolAndYear($tenantId, $schoolId, $academicYearId) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$configuration = SchoolGradingConfiguration::configurer(
|
||||
tenantId: $tenantId,
|
||||
schoolId: $schoolId,
|
||||
academicYearId: $academicYearId,
|
||||
mode: SchoolGradingConfiguration::DEFAULT_MODE,
|
||||
hasExistingGrades: false,
|
||||
configuredAt: $now,
|
||||
);
|
||||
|
||||
$this->gradingConfigurationRepository->save($configuration);
|
||||
$result->gradingConfigurationCreated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, User>
|
||||
*/
|
||||
private function seedUsers(
|
||||
TenantConfig $tenantConfig,
|
||||
string $schoolName,
|
||||
string $hashedPassword,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
): array {
|
||||
$users = [];
|
||||
|
||||
foreach ($this->staffBlueprints() as $blueprint) {
|
||||
$users[$blueprint['key']] = $this->ensureActiveUser(
|
||||
tenantConfig: $tenantConfig,
|
||||
schoolName: $schoolName,
|
||||
hashedPassword: $hashedPassword,
|
||||
role: $blueprint['role'],
|
||||
emailSlug: $blueprint['emailSlug'],
|
||||
firstName: $blueprint['firstName'],
|
||||
lastName: $blueprint['lastName'],
|
||||
now: $now,
|
||||
result: $result,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->teacherBlueprints() as $blueprint) {
|
||||
$users[$blueprint['key']] = $this->ensureActiveUser(
|
||||
tenantConfig: $tenantConfig,
|
||||
schoolName: $schoolName,
|
||||
hashedPassword: $hashedPassword,
|
||||
role: Role::PROF,
|
||||
emailSlug: $blueprint['emailSlug'],
|
||||
firstName: $blueprint['firstName'],
|
||||
lastName: $blueprint['lastName'],
|
||||
now: $now,
|
||||
result: $result,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->parentBlueprints() as $blueprint) {
|
||||
$users[$blueprint['key']] = $this->ensureActiveUser(
|
||||
tenantConfig: $tenantConfig,
|
||||
schoolName: $schoolName,
|
||||
hashedPassword: $hashedPassword,
|
||||
role: Role::PARENT,
|
||||
emailSlug: $blueprint['emailSlug'],
|
||||
firstName: $blueprint['firstName'],
|
||||
lastName: $blueprint['lastName'],
|
||||
now: $now,
|
||||
result: $result,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->studentBlueprints() as $blueprint) {
|
||||
$users[$blueprint['key']] = $this->ensureActiveUser(
|
||||
tenantConfig: $tenantConfig,
|
||||
schoolName: $schoolName,
|
||||
hashedPassword: $hashedPassword,
|
||||
role: Role::ELEVE,
|
||||
emailSlug: $blueprint['emailSlug'],
|
||||
firstName: $blueprint['firstName'],
|
||||
lastName: $blueprint['lastName'],
|
||||
now: $now,
|
||||
result: $result,
|
||||
dateOfBirth: $blueprint['dateOfBirth'],
|
||||
studentNumber: $blueprint['studentNumber'],
|
||||
);
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Subject>
|
||||
*/
|
||||
private function seedSubjects(
|
||||
TenantId $tenantId,
|
||||
SchoolId $schoolId,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
): array {
|
||||
$subjects = [];
|
||||
|
||||
foreach ($this->subjectBlueprints() as $blueprint) {
|
||||
$code = new SubjectCode($blueprint['code']);
|
||||
$subject = $this->subjectRepository->findByCode($code, $tenantId, $schoolId);
|
||||
|
||||
if ($subject === null) {
|
||||
$subject = Subject::creer(
|
||||
tenantId: $tenantId,
|
||||
schoolId: $schoolId,
|
||||
name: new SubjectName($blueprint['name']),
|
||||
code: $code,
|
||||
color: new SubjectColor($blueprint['color']),
|
||||
createdAt: $now,
|
||||
);
|
||||
$subject->decrire($blueprint['description'], $now);
|
||||
$this->subjectRepository->save($subject);
|
||||
++$result->createdSubjects;
|
||||
}
|
||||
|
||||
$subjects[$blueprint['code']] = $subject;
|
||||
}
|
||||
|
||||
return $subjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, SchoolClass>
|
||||
*/
|
||||
private function seedClasses(
|
||||
TenantId $tenantId,
|
||||
SchoolId $schoolId,
|
||||
AcademicYearId $academicYearId,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
): array {
|
||||
$classes = [];
|
||||
|
||||
foreach ($this->classBlueprints() as $blueprint) {
|
||||
$name = new ClassName($blueprint['name']);
|
||||
$class = $this->classRepository->findByName($name, $tenantId, $academicYearId);
|
||||
|
||||
if ($class === null) {
|
||||
$class = SchoolClass::creer(
|
||||
tenantId: $tenantId,
|
||||
schoolId: $schoolId,
|
||||
academicYearId: $academicYearId,
|
||||
name: $name,
|
||||
level: $blueprint['level'],
|
||||
capacity: $blueprint['capacity'],
|
||||
createdAt: $now,
|
||||
);
|
||||
$class->decrire($blueprint['description'], $now);
|
||||
$this->classRepository->save($class);
|
||||
++$result->createdClasses;
|
||||
}
|
||||
|
||||
$classes[$blueprint['name']] = $class;
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, User> $users
|
||||
* @param array<string, SchoolClass> $classes
|
||||
*/
|
||||
private function seedClassAssignments(
|
||||
TenantId $tenantId,
|
||||
AcademicYearId $academicYearId,
|
||||
array $users,
|
||||
array $classes,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
): void {
|
||||
foreach ($this->studentBlueprints() as $blueprint) {
|
||||
$student = $users[$blueprint['key']];
|
||||
$class = $classes[$blueprint['className']];
|
||||
$assignment = $this->classAssignmentRepository->findByStudent($student->id, $academicYearId, $tenantId);
|
||||
|
||||
if ($assignment === null) {
|
||||
$assignment = ClassAssignment::affecter(
|
||||
tenantId: $tenantId,
|
||||
studentId: $student->id,
|
||||
classId: $class->id,
|
||||
academicYearId: $academicYearId,
|
||||
assignedAt: $now,
|
||||
);
|
||||
$this->classAssignmentRepository->save($assignment);
|
||||
++$result->createdClassAssignments;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$assignment->classId->equals($class->id)) {
|
||||
$assignment->changerClasse($class->id, $now);
|
||||
$this->classAssignmentRepository->save($assignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, User> $users
|
||||
* @param array<string, SchoolClass> $classes
|
||||
* @param array<string, Subject> $subjects
|
||||
*/
|
||||
private function seedTeacherAssignments(
|
||||
TenantId $tenantId,
|
||||
AcademicYearId $academicYearId,
|
||||
array $users,
|
||||
array $classes,
|
||||
array $subjects,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
): void {
|
||||
$teacherKeysBySubject = $this->teacherKeysBySubject();
|
||||
|
||||
foreach ($classes as $className => $class) {
|
||||
foreach ($subjects as $subjectCode => $subject) {
|
||||
$teacher = $users[$teacherKeysBySubject[$subjectCode]];
|
||||
$assignment = $this->teacherAssignmentRepository->findByTeacherClassSubject(
|
||||
teacherId: $teacher->id,
|
||||
classId: $class->id,
|
||||
subjectId: $subject->id,
|
||||
academicYearId: $academicYearId,
|
||||
tenantId: $tenantId,
|
||||
);
|
||||
|
||||
if ($assignment !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$removedAssignment = $this->teacherAssignmentRepository->findRemovedByTeacherClassSubject(
|
||||
teacherId: $teacher->id,
|
||||
classId: $class->id,
|
||||
subjectId: $subject->id,
|
||||
academicYearId: $academicYearId,
|
||||
tenantId: $tenantId,
|
||||
);
|
||||
|
||||
if ($removedAssignment !== null) {
|
||||
$removedAssignment->reactiver($now);
|
||||
$this->teacherAssignmentRepository->save($removedAssignment);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$createdAssignment = TeacherAssignment::creer(
|
||||
tenantId: $tenantId,
|
||||
teacherId: $teacher->id,
|
||||
classId: $class->id,
|
||||
subjectId: $subject->id,
|
||||
academicYearId: $academicYearId,
|
||||
createdAt: $now,
|
||||
);
|
||||
$this->teacherAssignmentRepository->save($createdAssignment);
|
||||
++$result->createdTeacherAssignments;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, User> $users
|
||||
*/
|
||||
private function seedStudentGuardianLinks(
|
||||
TenantId $tenantId,
|
||||
array $users,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
): void {
|
||||
$createdBy = $users['admin']->id;
|
||||
|
||||
foreach ($this->guardianLinkBlueprints() as $blueprint) {
|
||||
$student = $users[$blueprint['studentKey']];
|
||||
$guardian = $users[$blueprint['guardianKey']];
|
||||
$existingLink = $this->studentGuardianRepository->findByStudentAndGuardian(
|
||||
studentId: $student->id,
|
||||
guardianId: $guardian->id,
|
||||
tenantId: $tenantId,
|
||||
);
|
||||
|
||||
if ($existingLink !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$link = StudentGuardian::lier(
|
||||
studentId: $student->id,
|
||||
guardianId: $guardian->id,
|
||||
relationshipType: $blueprint['relationshipType'],
|
||||
tenantId: $tenantId,
|
||||
createdAt: $now,
|
||||
createdBy: $createdBy,
|
||||
);
|
||||
$this->studentGuardianRepository->save($link);
|
||||
++$result->createdGuardianLinks;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, SchoolClass> $classes
|
||||
* @param array<string, Subject> $subjects
|
||||
* @param array<string, User> $users
|
||||
*/
|
||||
private function seedScheduleSlots(
|
||||
TenantId $tenantId,
|
||||
array $classes,
|
||||
array $subjects,
|
||||
array $users,
|
||||
int $academicYearStartYear,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
): void {
|
||||
$teacherKeysBySubject = $this->teacherKeysBySubject();
|
||||
$recurrenceStart = new DateTimeImmutable(sprintf('%d-09-01', $academicYearStartYear));
|
||||
$recurrenceEnd = new DateTimeImmutable(sprintf('%d-06-30', $academicYearStartYear + 1));
|
||||
|
||||
foreach ($this->scheduleBlueprints() as $blueprint) {
|
||||
foreach ($blueprint['assignments'] as $assignmentBlueprint) {
|
||||
$class = $classes[$assignmentBlueprint['className']];
|
||||
$subject = $subjects[$assignmentBlueprint['subjectCode']];
|
||||
$teacher = $users[$teacherKeysBySubject[$assignmentBlueprint['subjectCode']]];
|
||||
|
||||
if ($this->scheduleSlotExists(
|
||||
tenantId: $tenantId,
|
||||
classId: $class->id,
|
||||
subjectId: $subject->id,
|
||||
teacherId: $teacher->id,
|
||||
dayOfWeek: $blueprint['dayOfWeek'],
|
||||
startTime: $blueprint['startTime'],
|
||||
endTime: $blueprint['endTime'],
|
||||
room: $assignmentBlueprint['room'],
|
||||
recurrenceStart: $recurrenceStart,
|
||||
recurrenceEnd: $recurrenceEnd,
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$slot = ScheduleSlot::creer(
|
||||
tenantId: $tenantId,
|
||||
classId: $class->id,
|
||||
subjectId: $subject->id,
|
||||
teacherId: $teacher->id,
|
||||
dayOfWeek: $blueprint['dayOfWeek'],
|
||||
timeSlot: new TimeSlot($blueprint['startTime'], $blueprint['endTime']),
|
||||
room: $assignmentBlueprint['room'],
|
||||
isRecurring: true,
|
||||
now: $now,
|
||||
recurrenceStart: $recurrenceStart,
|
||||
recurrenceEnd: $recurrenceEnd,
|
||||
);
|
||||
$this->scheduleSlotRepository->save($slot);
|
||||
++$result->createdScheduleSlots;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function scheduleSlotExists(
|
||||
TenantId $tenantId,
|
||||
\App\Administration\Domain\Model\SchoolClass\ClassId $classId,
|
||||
\App\Administration\Domain\Model\Subject\SubjectId $subjectId,
|
||||
UserId $teacherId,
|
||||
DayOfWeek $dayOfWeek,
|
||||
string $startTime,
|
||||
string $endTime,
|
||||
?string $room,
|
||||
DateTimeImmutable $recurrenceStart,
|
||||
DateTimeImmutable $recurrenceEnd,
|
||||
): bool {
|
||||
foreach ($this->scheduleSlotRepository->findRecurringByClass($classId, $tenantId) as $existingSlot) {
|
||||
if (!$existingSlot->subjectId->equals($subjectId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$existingSlot->teacherId->equals($teacherId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existingSlot->dayOfWeek !== $dayOfWeek) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existingSlot->timeSlot->startTime !== $startTime || $existingSlot->timeSlot->endTime !== $endTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existingSlot->room !== $room) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existingSlot->recurrenceStart?->format('Y-m-d') !== $recurrenceStart->format('Y-m-d')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existingSlot->recurrenceEnd?->format('Y-m-d') !== $recurrenceEnd->format('Y-m-d')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function ensureActiveUser(
|
||||
TenantConfig $tenantConfig,
|
||||
string $schoolName,
|
||||
string $hashedPassword,
|
||||
Role $role,
|
||||
string $emailSlug,
|
||||
string $firstName,
|
||||
string $lastName,
|
||||
DateTimeImmutable $now,
|
||||
DemoDataGenerationResult $result,
|
||||
?DateTimeImmutable $dateOfBirth = null,
|
||||
?string $studentNumber = null,
|
||||
): User {
|
||||
$email = new Email($this->emailFor($emailSlug, $tenantConfig->subdomain));
|
||||
$user = $this->userRepository->findByEmail($email, $tenantConfig->tenantId);
|
||||
|
||||
if ($user === null) {
|
||||
$user = User::reconstitute(
|
||||
id: UserId::generate(),
|
||||
email: $email,
|
||||
roles: [$role],
|
||||
tenantId: $tenantConfig->tenantId,
|
||||
schoolName: $schoolName,
|
||||
statut: StatutCompte::ACTIF,
|
||||
dateNaissance: $dateOfBirth,
|
||||
createdAt: $now,
|
||||
hashedPassword: $hashedPassword,
|
||||
activatedAt: $now,
|
||||
consentementParental: null,
|
||||
firstName: $firstName,
|
||||
lastName: $lastName,
|
||||
studentNumber: $studentNumber,
|
||||
);
|
||||
$this->userRepository->save($user);
|
||||
++$result->createdUsers;
|
||||
} else {
|
||||
$userWasChanged = false;
|
||||
|
||||
if ($user->firstName !== $firstName || $user->lastName !== $lastName) {
|
||||
$user->mettreAJourInfos($firstName, $lastName);
|
||||
$userWasChanged = true;
|
||||
}
|
||||
|
||||
if (!$user->aLeRole($role)) {
|
||||
$user->attribuerRole($role, $now);
|
||||
$userWasChanged = true;
|
||||
}
|
||||
|
||||
if ($user->statut === StatutCompte::SUSPENDU) {
|
||||
$user->debloquer($now);
|
||||
$userWasChanged = true;
|
||||
}
|
||||
|
||||
if ($user->statut === StatutCompte::ACTIF) {
|
||||
$user->changerMotDePasse($hashedPassword, $now);
|
||||
$userWasChanged = true;
|
||||
} else {
|
||||
$result->addWarning(sprintf(
|
||||
'Le compte "%s" existe déjà avec le statut "%s". Le mot de passe commun n\'a pas pu être réinitialisé.',
|
||||
(string) $email,
|
||||
$user->statut->value,
|
||||
));
|
||||
}
|
||||
|
||||
if ($userWasChanged) {
|
||||
$this->userRepository->save($user);
|
||||
}
|
||||
}
|
||||
|
||||
$result->addAccount(
|
||||
role: $role->label(),
|
||||
name: trim($firstName . ' ' . $lastName),
|
||||
email: (string) $email,
|
||||
);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function resolveAcademicYearId(): AcademicYearId
|
||||
{
|
||||
$resolved = $this->currentAcademicYearResolver->resolve('current');
|
||||
if ($resolved === null) {
|
||||
throw new RuntimeException('Impossible de résoudre l\'année scolaire courante.');
|
||||
}
|
||||
|
||||
return AcademicYearId::fromString($resolved);
|
||||
}
|
||||
|
||||
private function resolveAcademicYearStartYear(): int
|
||||
{
|
||||
$startYear = $this->currentAcademicYearResolver->resolveStartYear('current');
|
||||
if ($startYear === null) {
|
||||
throw new RuntimeException('Impossible de résoudre le millésime de l\'année scolaire courante.');
|
||||
}
|
||||
|
||||
return $startYear;
|
||||
}
|
||||
|
||||
private function emailFor(string $emailSlug, string $subdomain): string
|
||||
{
|
||||
return sprintf('%s.%s@classeo.test', strtolower($emailSlug), strtolower($subdomain));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{key: string, role: Role, emailSlug: string, firstName: string, lastName: string}>
|
||||
*/
|
||||
private function staffBlueprints(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'key' => 'admin',
|
||||
'role' => Role::ADMIN,
|
||||
'emailSlug' => 'admin.direction',
|
||||
'firstName' => 'Camille',
|
||||
'lastName' => 'Martin',
|
||||
],
|
||||
[
|
||||
'key' => 'vie-scolaire',
|
||||
'role' => Role::VIE_SCOLAIRE,
|
||||
'emailSlug' => 'vie.scolaire',
|
||||
'firstName' => 'Nora',
|
||||
'lastName' => 'Petit',
|
||||
],
|
||||
[
|
||||
'key' => 'secretariat',
|
||||
'role' => Role::SECRETARIAT,
|
||||
'emailSlug' => 'secretariat',
|
||||
'firstName' => 'Lucie',
|
||||
'lastName' => 'Bernard',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{key: string, subjectCode: string, emailSlug: string, firstName: string, lastName: string}>
|
||||
*/
|
||||
private function teacherBlueprints(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'key' => 'teacher-math',
|
||||
'subjectCode' => 'MATH',
|
||||
'emailSlug' => 'prof.amina.benali',
|
||||
'firstName' => 'Amina',
|
||||
'lastName' => 'Benali',
|
||||
],
|
||||
[
|
||||
'key' => 'teacher-fr',
|
||||
'subjectCode' => 'FR',
|
||||
'emailSlug' => 'prof.julie.caron',
|
||||
'firstName' => 'Julie',
|
||||
'lastName' => 'Caron',
|
||||
],
|
||||
[
|
||||
'key' => 'teacher-hg',
|
||||
'subjectCode' => 'HG',
|
||||
'emailSlug' => 'prof.marc.garcia',
|
||||
'firstName' => 'Marc',
|
||||
'lastName' => 'Garcia',
|
||||
],
|
||||
[
|
||||
'key' => 'teacher-svt',
|
||||
'subjectCode' => 'SVT',
|
||||
'emailSlug' => 'prof.sophie.lambert',
|
||||
'firstName' => 'Sophie',
|
||||
'lastName' => 'Lambert',
|
||||
],
|
||||
[
|
||||
'key' => 'teacher-ang',
|
||||
'subjectCode' => 'ANG',
|
||||
'emailSlug' => 'prof.david.nguyen',
|
||||
'firstName' => 'David',
|
||||
'lastName' => 'Nguyen',
|
||||
],
|
||||
[
|
||||
'key' => 'teacher-eps',
|
||||
'subjectCode' => 'EPS',
|
||||
'emailSlug' => 'prof.clara.petit',
|
||||
'firstName' => 'Clara',
|
||||
'lastName' => 'Petit',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{key: string, emailSlug: string, firstName: string, lastName: string}>
|
||||
*/
|
||||
private function parentBlueprints(): array
|
||||
{
|
||||
return [
|
||||
['key' => 'parent-nadia-martin', 'emailSlug' => 'parent.nadia.martin', 'firstName' => 'Nadia', 'lastName' => 'Martin'],
|
||||
['key' => 'parent-karim-martin', 'emailSlug' => 'parent.karim.martin', 'firstName' => 'Karim', 'lastName' => 'Martin'],
|
||||
['key' => 'parent-ines-lopez', 'emailSlug' => 'parent.ines.lopez', 'firstName' => 'Ines', 'lastName' => 'Lopez'],
|
||||
['key' => 'parent-raul-lopez', 'emailSlug' => 'parent.raul.lopez', 'firstName' => 'Raul', 'lastName' => 'Lopez'],
|
||||
['key' => 'parent-claire-bernard', 'emailSlug' => 'parent.claire.bernard', 'firstName' => 'Claire', 'lastName' => 'Bernard'],
|
||||
['key' => 'parent-mehdi-petit', 'emailSlug' => 'parent.mehdi.petit', 'firstName' => 'Mehdi', 'lastName' => 'Petit'],
|
||||
['key' => 'parent-laura-moreau', 'emailSlug' => 'parent.laura.moreau', 'firstName' => 'Laura', 'lastName' => 'Moreau'],
|
||||
['key' => 'parent-olivier-moreau', 'emailSlug' => 'parent.olivier.moreau', 'firstName' => 'Olivier', 'lastName' => 'Moreau'],
|
||||
['key' => 'parent-celine-dupont', 'emailSlug' => 'parent.celine.dupont', 'firstName' => 'Celine', 'lastName' => 'Dupont'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{
|
||||
* key: string,
|
||||
* emailSlug: string,
|
||||
* firstName: string,
|
||||
* lastName: string,
|
||||
* className: string,
|
||||
* dateOfBirth: DateTimeImmutable,
|
||||
* studentNumber: string
|
||||
* }>
|
||||
*/
|
||||
private function studentBlueprints(): array
|
||||
{
|
||||
return [
|
||||
['key' => 'student-lina-martin', 'emailSlug' => 'eleve.lina.martin', 'firstName' => 'Lina', 'lastName' => 'Martin', 'className' => '6A', 'dateOfBirth' => new DateTimeImmutable('2013-02-14'), 'studentNumber' => 'DEMO-001'],
|
||||
['key' => 'student-chloe-lopez', 'emailSlug' => 'eleve.chloe.lopez', 'firstName' => 'Chloe', 'lastName' => 'Lopez', 'className' => '6A', 'dateOfBirth' => new DateTimeImmutable('2013-05-02'), 'studentNumber' => 'DEMO-002'],
|
||||
['key' => 'student-hugo-lopez', 'emailSlug' => 'eleve.hugo.lopez', 'firstName' => 'Hugo', 'lastName' => 'Lopez', 'className' => '6A', 'dateOfBirth' => new DateTimeImmutable('2013-07-22'), 'studentNumber' => 'DEMO-003'],
|
||||
['key' => 'student-zoe-moreau', 'emailSlug' => 'eleve.zoe.moreau', 'firstName' => 'Zoe', 'lastName' => 'Moreau', 'className' => '6A', 'dateOfBirth' => new DateTimeImmutable('2013-11-09'), 'studentNumber' => 'DEMO-004'],
|
||||
['key' => 'student-jade-bernard', 'emailSlug' => 'eleve.jade.bernard', 'firstName' => 'Jade', 'lastName' => 'Bernard', 'className' => '5A', 'dateOfBirth' => new DateTimeImmutable('2012-01-18'), 'studentNumber' => 'DEMO-005'],
|
||||
['key' => 'student-ethan-bernard', 'emailSlug' => 'eleve.ethan.bernard', 'firstName' => 'Ethan', 'lastName' => 'Bernard', 'className' => '5A', 'dateOfBirth' => new DateTimeImmutable('2012-03-27'), 'studentNumber' => 'DEMO-006'],
|
||||
['key' => 'student-nino-moreau', 'emailSlug' => 'eleve.nino.moreau', 'firstName' => 'Nino', 'lastName' => 'Moreau', 'className' => '5A', 'dateOfBirth' => new DateTimeImmutable('2012-09-04'), 'studentNumber' => 'DEMO-007'],
|
||||
['key' => 'student-sarah-dupont', 'emailSlug' => 'eleve.sarah.dupont', 'firstName' => 'Sarah', 'lastName' => 'Dupont', 'className' => '5A', 'dateOfBirth' => new DateTimeImmutable('2012-12-30'), 'studentNumber' => 'DEMO-008'],
|
||||
['key' => 'student-noah-martin', 'emailSlug' => 'eleve.noah.martin', 'firstName' => 'Noah', 'lastName' => 'Martin', 'className' => '4A', 'dateOfBirth' => new DateTimeImmutable('2011-02-11'), 'studentNumber' => 'DEMO-009'],
|
||||
['key' => 'student-adam-petit', 'emailSlug' => 'eleve.adam.petit', 'firstName' => 'Adam', 'lastName' => 'Petit', 'className' => '4A', 'dateOfBirth' => new DateTimeImmutable('2011-06-19'), 'studentNumber' => 'DEMO-010'],
|
||||
['key' => 'student-yasmine-petit', 'emailSlug' => 'eleve.yasmine.petit', 'firstName' => 'Yasmine', 'lastName' => 'Petit', 'className' => '4A', 'dateOfBirth' => new DateTimeImmutable('2011-08-25'), 'studentNumber' => 'DEMO-011'],
|
||||
['key' => 'student-mael-dupont', 'emailSlug' => 'eleve.mael.dupont', 'firstName' => 'Mael', 'lastName' => 'Dupont', 'className' => '4A', 'dateOfBirth' => new DateTimeImmutable('2011-10-03'), 'studentNumber' => 'DEMO-012'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{
|
||||
* code: non-empty-string,
|
||||
* name: non-empty-string,
|
||||
* color: non-empty-string,
|
||||
* description: non-empty-string
|
||||
* }>
|
||||
*/
|
||||
private function subjectBlueprints(): array
|
||||
{
|
||||
return [
|
||||
['code' => 'MATH', 'name' => 'Mathématiques', 'color' => '#2563EB', 'description' => 'Cours de mathématiques pour la démonstration.'],
|
||||
['code' => 'FR', 'name' => 'Français', 'color' => '#DC2626', 'description' => 'Lecture, grammaire et expression écrite.'],
|
||||
['code' => 'HG', 'name' => 'Histoire-Géographie', 'color' => '#D97706', 'description' => 'Repères historiques et géographiques.'],
|
||||
['code' => 'SVT', 'name' => 'SVT', 'color' => '#16A34A', 'description' => 'Sciences de la vie et de la Terre.'],
|
||||
['code' => 'ANG', 'name' => 'Anglais', 'color' => '#7C3AED', 'description' => 'Cours d’anglais pour tous les niveaux.'],
|
||||
['code' => 'EPS', 'name' => 'EPS', 'color' => '#0891B2', 'description' => 'Éducation physique et sportive.'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{name: string, level: SchoolLevel, capacity: int, description: string}>
|
||||
*/
|
||||
private function classBlueprints(): array
|
||||
{
|
||||
return [
|
||||
['name' => '6A', 'level' => SchoolLevel::SIXIEME, 'capacity' => 28, 'description' => 'Classe de sixième utilisée pour les démonstrations.'],
|
||||
['name' => '5A', 'level' => SchoolLevel::CINQUIEME, 'capacity' => 28, 'description' => 'Classe de cinquième utilisée pour les démonstrations.'],
|
||||
['name' => '4A', 'level' => SchoolLevel::QUATRIEME, 'capacity' => 28, 'description' => 'Classe de quatrième utilisée pour les démonstrations.'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{studentKey: string, guardianKey: string, relationshipType: RelationshipType}>
|
||||
*/
|
||||
private function guardianLinkBlueprints(): array
|
||||
{
|
||||
return [
|
||||
['studentKey' => 'student-lina-martin', 'guardianKey' => 'parent-nadia-martin', 'relationshipType' => RelationshipType::MOTHER],
|
||||
['studentKey' => 'student-lina-martin', 'guardianKey' => 'parent-karim-martin', 'relationshipType' => RelationshipType::FATHER],
|
||||
['studentKey' => 'student-noah-martin', 'guardianKey' => 'parent-nadia-martin', 'relationshipType' => RelationshipType::MOTHER],
|
||||
['studentKey' => 'student-noah-martin', 'guardianKey' => 'parent-karim-martin', 'relationshipType' => RelationshipType::FATHER],
|
||||
['studentKey' => 'student-chloe-lopez', 'guardianKey' => 'parent-ines-lopez', 'relationshipType' => RelationshipType::MOTHER],
|
||||
['studentKey' => 'student-chloe-lopez', 'guardianKey' => 'parent-raul-lopez', 'relationshipType' => RelationshipType::FATHER],
|
||||
['studentKey' => 'student-hugo-lopez', 'guardianKey' => 'parent-ines-lopez', 'relationshipType' => RelationshipType::MOTHER],
|
||||
['studentKey' => 'student-hugo-lopez', 'guardianKey' => 'parent-raul-lopez', 'relationshipType' => RelationshipType::FATHER],
|
||||
['studentKey' => 'student-jade-bernard', 'guardianKey' => 'parent-claire-bernard', 'relationshipType' => RelationshipType::MOTHER],
|
||||
['studentKey' => 'student-ethan-bernard', 'guardianKey' => 'parent-claire-bernard', 'relationshipType' => RelationshipType::MOTHER],
|
||||
['studentKey' => 'student-adam-petit', 'guardianKey' => 'parent-mehdi-petit', 'relationshipType' => RelationshipType::FATHER],
|
||||
['studentKey' => 'student-yasmine-petit', 'guardianKey' => 'parent-mehdi-petit', 'relationshipType' => RelationshipType::FATHER],
|
||||
['studentKey' => 'student-zoe-moreau', 'guardianKey' => 'parent-laura-moreau', 'relationshipType' => RelationshipType::MOTHER],
|
||||
['studentKey' => 'student-zoe-moreau', 'guardianKey' => 'parent-olivier-moreau', 'relationshipType' => RelationshipType::FATHER],
|
||||
['studentKey' => 'student-nino-moreau', 'guardianKey' => 'parent-laura-moreau', 'relationshipType' => RelationshipType::MOTHER],
|
||||
['studentKey' => 'student-nino-moreau', 'guardianKey' => 'parent-olivier-moreau', 'relationshipType' => RelationshipType::FATHER],
|
||||
['studentKey' => 'student-sarah-dupont', 'guardianKey' => 'parent-celine-dupont', 'relationshipType' => RelationshipType::TUTOR_F],
|
||||
['studentKey' => 'student-mael-dupont', 'guardianKey' => 'parent-celine-dupont', 'relationshipType' => RelationshipType::TUTOR_F],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{
|
||||
* dayOfWeek: DayOfWeek,
|
||||
* startTime: string,
|
||||
* endTime: string,
|
||||
* assignments: list<array{className: string, subjectCode: string, room: string}>
|
||||
* }>
|
||||
*/
|
||||
private function scheduleBlueprints(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'dayOfWeek' => DayOfWeek::MONDAY,
|
||||
'startTime' => '08:00',
|
||||
'endTime' => '09:00',
|
||||
'assignments' => [
|
||||
['className' => '6A', 'subjectCode' => 'MATH', 'room' => 'B12'],
|
||||
['className' => '5A', 'subjectCode' => 'FR', 'room' => 'B14'],
|
||||
['className' => '4A', 'subjectCode' => 'HG', 'room' => 'B16'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'dayOfWeek' => DayOfWeek::MONDAY,
|
||||
'startTime' => '09:00',
|
||||
'endTime' => '10:00',
|
||||
'assignments' => [
|
||||
['className' => '6A', 'subjectCode' => 'SVT', 'room' => 'LAB1'],
|
||||
['className' => '5A', 'subjectCode' => 'ANG', 'room' => 'B15'],
|
||||
['className' => '4A', 'subjectCode' => 'EPS', 'room' => 'GYM1'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'dayOfWeek' => DayOfWeek::TUESDAY,
|
||||
'startTime' => '08:00',
|
||||
'endTime' => '09:00',
|
||||
'assignments' => [
|
||||
['className' => '6A', 'subjectCode' => 'FR', 'room' => 'B13'],
|
||||
['className' => '5A', 'subjectCode' => 'HG', 'room' => 'B16'],
|
||||
['className' => '4A', 'subjectCode' => 'MATH', 'room' => 'B12'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'dayOfWeek' => DayOfWeek::TUESDAY,
|
||||
'startTime' => '09:00',
|
||||
'endTime' => '10:00',
|
||||
'assignments' => [
|
||||
['className' => '6A', 'subjectCode' => 'ANG', 'room' => 'B15'],
|
||||
['className' => '5A', 'subjectCode' => 'EPS', 'room' => 'GYM1'],
|
||||
['className' => '4A', 'subjectCode' => 'SVT', 'room' => 'LAB1'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'dayOfWeek' => DayOfWeek::THURSDAY,
|
||||
'startTime' => '13:00',
|
||||
'endTime' => '14:00',
|
||||
'assignments' => [
|
||||
['className' => '6A', 'subjectCode' => 'HG', 'room' => 'B16'],
|
||||
['className' => '5A', 'subjectCode' => 'MATH', 'room' => 'B12'],
|
||||
['className' => '4A', 'subjectCode' => 'FR', 'room' => 'B14'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'dayOfWeek' => DayOfWeek::FRIDAY,
|
||||
'startTime' => '14:00',
|
||||
'endTime' => '15:00',
|
||||
'assignments' => [
|
||||
['className' => '6A', 'subjectCode' => 'EPS', 'room' => 'GYM1'],
|
||||
['className' => '5A', 'subjectCode' => 'SVT', 'room' => 'LAB1'],
|
||||
['className' => '4A', 'subjectCode' => 'ANG', 'room' => 'B15'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function teacherKeysBySubject(): array
|
||||
{
|
||||
$mapping = [];
|
||||
|
||||
foreach ($this->teacherBlueprints() as $blueprint) {
|
||||
$mapping[$blueprint['subjectCode']] = $blueprint['key'];
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
public function defaultSchoolNameForSubdomain(string $subdomain): string
|
||||
{
|
||||
return sprintf(
|
||||
'Établissement de démo %s',
|
||||
ucwords(str_replace('-', ' ', trim($subdomain))),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user