$rows * @return array{imported: int, already_present: int, ineligible: int, unknown_user: int, ineligible_users: list} */ public function import(string $portal, iterable $rows, bool $dryRun = false): array { $result = [ 'imported' => 0, 'already_present' => 0, 'ineligible' => 0, 'unknown_user' => 0, 'ineligible_users' => [], ]; foreach ($rows as $row) { $key = trim((string) $row['api_key']); if ($key === '') { continue; } $user = $this->resolveUser($portal, (int) $row['legacy_id']); if (! $user) { $result['unknown_user']++; continue; } $hash = hash('sha256', $key); if (PersonalAccessToken::query()->where('token', $hash)->exists()) { $result['already_present']++; continue; } if (! $this->eligibility->canCreateToken($user)) { $result['ineligible']++; $result['ineligible_users'][] = (string) $user->email; continue; } if (! $dryRun) { $user->tokens()->create([ 'name' => self::TOKEN_NAME, 'token' => $hash, 'abilities' => self::ABILITIES, ]); } $result['imported']++; } return $result; } /** * Löst den Token-Besitzer über die legacy_import_map auf. Die Spalten * users.legacy_portal/legacy_id reichen nicht: Bei E-Mail-Merges (gleicher * Kunde in beiden Altportalen) trägt der User nur das zuletzt importierte * Portal — die Import-Map kennt dagegen beide Zuordnungen, sodass auch der * Schlüssel des "anderen" Portals seinen Besitzer findet. */ private function resolveUser(string $portal, int $legacyId): ?User { $mappedUserId = LegacyImportMap::query() ->where('legacy_portal', $portal) ->where('legacy_table', 'sf_guard_user') ->where('legacy_id', $legacyId) ->value('target_id'); if ($mappedUserId !== null) { return User::query()->find($mappedUserId); } return User::query() ->where('legacy_portal', $portal) ->where('legacy_id', $legacyId) ->first(); } }