connection; $legacyPortal = $ctx->legacyPortalValue(); $portal = $ctx->portalEnum; DB::connection($conn) ->table('contact') ->orderBy('id') ->chunk(self::CHUNK_SIZE, function ($rows) use ($ctx, $result, $legacyPortal, $portal): void { foreach ($rows as $row) { try { $this->importRow($row, $ctx, $result, $legacyPortal, $portal); } catch (\Throwable $e) { $result->addError("Contact legacy_id={$row->id}: {$e->getMessage()}"); } } }); return $result; } private function importRow( object $row, ImportContext $ctx, ImportResult $result, string $legacyPortal, Portal $portal, ): void { $alreadyImported = LegacyImportMap::query() ->where('legacy_portal', $legacyPortal) ->where('legacy_table', 'contact') ->where('legacy_id', $row->id) ->exists(); if ($alreadyImported && ! $ctx->force) { $result->incrementSkipped(); return; } if ($ctx->dryRun) { $result->incrementImported(); return; } // Firma aus Import-Map auflösen $companyId = null; if ($row->company_id) { $companyMap = LegacyImportMap::query() ->where('legacy_portal', $legacyPortal) ->where('legacy_table', 'company') ->where('legacy_id', $row->company_id) ->first(); $companyId = $companyMap?->target_id; } if (! $companyId) { // Kontakt ohne zugeordnete Firma überspringen (FK-Constraint) $result->incrementSkipped(); return; } $salutationKey = $this->mapSalutation($row->salutation_id ?? 0); $contact = Contact::withoutTimestamps(function () use ($legacyPortal, $row, $companyId, $portal, $salutationKey): Contact { return Contact::withoutGlobalScopes()->updateOrCreate( ['legacy_portal' => $legacyPortal, 'legacy_id' => $row->id], [ 'company_id' => $companyId, 'portal' => $portal->value, 'salutation_key' => $salutationKey, 'title' => $this->cleanText($row->title, 80), 'first_name' => $this->cleanText($row->first_name, 80), 'last_name' => $this->cleanText($row->last_name, 80), 'responsibility' => $this->cleanText($row->responsibility, 255), 'phone' => $this->cleanText($row->phone, 255), 'fax' => $this->cleanText($row->fax, 255), 'email' => $this->cleanText($row->email, 190), 'created_at' => $row->created_at ?? now(), 'updated_at' => $row->updated_at ?? $row->created_at ?? now(), ] ); }); LegacyImportMap::query()->updateOrCreate( [ 'legacy_portal' => $legacyPortal, 'legacy_table' => 'contact', 'legacy_id' => $row->id, ], [ 'target_table' => 'contacts', 'target_id' => $contact->id, 'imported_at' => now(), ] ); if ($alreadyImported) { $result->incrementUpdated(); } else { $result->incrementImported(); } } private function mapSalutation(int $salutationId): ?string { return match ($salutationId) { 1 => 'mr', 2 => 'mrs', default => null, }; } private function cleanText(?string $value, int $maxLength): ?string { if (blank($value)) { return null; } $clean = html_entity_decode((string) $value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $clean = preg_replace('/[\x00-\x1F\x7F\xC2\xA0]/u', ' ', $clean) ?? $clean; $clean = trim((string) $clean); return blank($clean) ? null : mb_substr($clean, 0, $maxLength); } }