multiValueParser = new MultiValueParser(); } public function __invoke(ImportTeachersCommand $command): void { $batchId = ImportBatchId::fromString($command->batchId); $tenantId = TenantId::fromString($command->tenantId); $academicYearId = AcademicYearId::fromString($command->academicYearId); $schoolId = SchoolId::fromString($this->schoolIdResolver->resolveForTenant($command->tenantId)); $now = $this->clock->now(); $batch = $this->teacherImportBatchRepository->get($batchId); $batch->demarrer($now); $this->teacherImportBatchRepository->save($batch); $lignes = $batch->lignes(); $importedCount = 0; $errorCount = 0; $processedCount = 0; try { /** @var array $subjectCache */ $subjectCache = []; /** @var list $existingSubjectCodes */ $existingSubjectCodes = []; /** @var list $newlyCreatedCodes */ $newlyCreatedCodes = []; foreach ($this->subjectRepository->findAllActiveByTenant($tenantId) as $subject) { $subjectCache[(string) $subject->name] = $subject->id; $existingSubjectCodes[] = (string) $subject->code; } /** @var array */ $classCache = []; foreach ($lignes as $row) { try { $firstName = trim($row->mappedData[TeacherImportField::FIRST_NAME->value] ?? ''); $lastName = trim($row->mappedData[TeacherImportField::LAST_NAME->value] ?? ''); $emailRaw = trim($row->mappedData[TeacherImportField::EMAIL->value] ?? ''); $subjectsRaw = $row->mappedData[TeacherImportField::SUBJECTS->value] ?? ''; $classesRaw = $row->mappedData[TeacherImportField::CLASSES->value] ?? ''; $emailVO = new Email($emailRaw); $existingUser = $this->userRepository->findByEmail($emailVO, $tenantId); if ($existingUser !== null && !$command->updateExisting) { throw new DomainException(sprintf('L\'email "%s" est déjà utilisé.', $emailRaw)); } $this->connection->beginTransaction(); try { $subjects = $this->multiValueParser->parse($subjectsRaw); $classes = $this->multiValueParser->parse($classesRaw); $resolvedSubjectIds = $this->resolveSubjectIds( $subjects, $tenantId, $schoolId, $command->createMissingSubjects, $now, $subjectCache, $existingSubjectCodes, $newlyCreatedCodes, ); $resolvedClassIds = $this->resolveClassIds( $classes, $tenantId, $academicYearId, $classCache, ); if ($existingUser !== null) { $existingUser->mettreAJourInfos($firstName, $lastName); $this->userRepository->save($existingUser); $this->addMissingAssignments( $existingUser, $resolvedSubjectIds, $resolvedClassIds, $tenantId, $academicYearId, $now, ); $this->connection->commit(); } else { $user = User::inviter( email: $emailVO, role: Role::PROF, tenantId: $tenantId, schoolName: $command->schoolName, firstName: $firstName, lastName: $lastName, invitedAt: $now, ); $this->userRepository->save($user); foreach ($resolvedSubjectIds as $subjectId) { foreach ($resolvedClassIds as $classId) { $assignment = TeacherAssignment::creer( tenantId: $tenantId, teacherId: $user->id, classId: $classId, subjectId: $subjectId, academicYearId: $academicYearId, createdAt: $now, ); $this->teacherAssignmentRepository->save($assignment); } } $this->connection->commit(); foreach ($user->pullDomainEvents() as $event) { $this->eventBus->dispatch($event); } } } catch (Throwable $e) { $this->connection->rollBack(); throw $e; } ++$importedCount; } catch (DomainException $e) { $this->logger->warning('Import enseignant ligne {line} échouée : {message}', [ 'line' => $row->lineNumber, 'message' => $e->getMessage(), 'batch_id' => $command->batchId, ]); ++$errorCount; } ++$processedCount; if ($processedCount % 50 === 0) { $batch->mettreAJourProgression($importedCount, $errorCount); $this->teacherImportBatchRepository->save($batch); } } $batch->terminer($importedCount, $errorCount, $this->clock->now()); $this->teacherImportBatchRepository->save($batch); } catch (Throwable $e) { $batch->echouer($errorCount, $this->clock->now()); $this->teacherImportBatchRepository->save($batch); throw $e; } } /** * Ajoute les affectations manquantes pour un enseignant existant. * * @param list $subjectIds * @param list $classIds */ private function addMissingAssignments( User $teacher, array $subjectIds, array $classIds, TenantId $tenantId, AcademicYearId $academicYearId, DateTimeImmutable $now, ): void { foreach ($subjectIds as $subjectId) { foreach ($classIds as $classId) { $existing = $this->teacherAssignmentRepository->findByTeacherClassSubject( $teacher->id, $classId, $subjectId, $academicYearId, $tenantId, ); if ($existing !== null) { continue; } $assignment = TeacherAssignment::creer( tenantId: $tenantId, teacherId: $teacher->id, classId: $classId, subjectId: $subjectId, academicYearId: $academicYearId, createdAt: $now, ); $this->teacherAssignmentRepository->save($assignment); } } } /** * @param list $subjectNames * @param array $cache * @param list $existingCodes * @param list $newlyCreatedCodes * * @return list */ private function resolveSubjectIds( array $subjectNames, TenantId $tenantId, SchoolId $schoolId, bool $createMissing, DateTimeImmutable $now, array &$cache, array &$existingCodes, array &$newlyCreatedCodes, ): array { $ids = []; foreach ($subjectNames as $name) { if (isset($cache[$name])) { $ids[] = $cache[$name]; continue; } if ($createMissing) { $subjectId = $this->createSubject($name, $tenantId, $schoolId, $now, $existingCodes, $newlyCreatedCodes); $cache[$name] = $subjectId; $ids[] = $subjectId; } } return $ids; } /** * @param list $classNames * @param array $cache * * @return list */ private function resolveClassIds( array $classNames, TenantId $tenantId, AcademicYearId $academicYearId, array &$cache, ): array { $ids = []; foreach ($classNames as $name) { if (isset($cache[$name])) { $ids[] = $cache[$name]; continue; } $classNameVO = new ClassName($name); $class = $this->classRepository->findByName($classNameVO, $tenantId, $academicYearId); if ($class !== null) { $cache[$name] = $class->id; $ids[] = $class->id; } } return $ids; } /** * @param list $existingCodes * @param list $newlyCreatedCodes */ private function createSubject( string $name, TenantId $tenantId, SchoolId $schoolId, DateTimeImmutable $now, array &$existingCodes, array &$newlyCreatedCodes, ): SubjectId { if (trim($name) === '') { throw new DomainException('Le nom de la matière ne peut pas être vide.'); } $code = $this->generateUniqueSubjectCode($name, $existingCodes, $newlyCreatedCodes); if ($code === '') { throw new DomainException(sprintf('Impossible de générer un code pour la matière "%s".', $name)); } $subject = Subject::creer( tenantId: $tenantId, schoolId: $schoolId, name: new SubjectName($name), code: new SubjectCode($code), color: null, createdAt: $now, ); $this->subjectRepository->save($subject); $newlyCreatedCodes[] = $code; return $subject->id; } /** * @param list $existingCodes * @param list $newlyCreatedCodes */ private function generateUniqueSubjectCode(string $name, array $existingCodes, array $newlyCreatedCodes): string { $base = strtoupper(substr(trim($name), 0, 4)); if (mb_strlen($base) < 2) { $base .= 'XX'; } $allCodes = [...$existingCodes, ...$newlyCreatedCodes]; if (!in_array($base, $allCodes, true)) { return $base; } for ($i = 2; $i <= 99; ++$i) { $candidate = $base . $i; if (!in_array($candidate, $allCodes, true)) { return $candidate; } } return $base; } }