dryRun = (bool) $this->option('dry-run'); if ($this->dryRun) { $this->warn('DRY-RUN Modus — keine Daten werden verändert'); } else { $this->warn('ACHTUNG: Diese Operation verändert Produktionsdaten.'); if (!$this->option('force') && !$this->confirm('Fortfahren?')) { $this->info('Abgebrochen.'); return self::SUCCESS; } } $this->newLine(); DB::transaction(function () { $this->processLevel( 'HIGH', fn () => $this->findByEmail() ); $this->processLevel( 'MEDIUM', fn () => $this->findByNameBirthdate() ); $this->processLevel( 'LOW', fn () => $this->findByNameZip() ); }); $this->newLine(); $this->info(sprintf( '%s %d Duplikate zusammengeführt | %d leads aktualisiert | %d bookings aktualisiert', $this->dryRun ? '[DRY-RUN]' : '', $this->mergedCount, $this->updatedLeads, $this->updatedBookings )); if ($this->dryRun) { $this->newLine(); $this->line('Zum Ausführen: php artisan contacts:merge-duplicates'); } return self::SUCCESS; } // ───────────────────────────────────────────────────────────────────────── // Duplikat-Gruppen ermitteln // ───────────────────────────────────────────────────────────────────────── private function findByEmail(): array { return DB::table('contacts') ->select('email', DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')) ->whereNotNull('email') ->where('email', '!=', '') ->whereNull('merged_into_id') ->groupBy('email') ->having(DB::raw('COUNT(*)'), '>', 1) ->pluck('ids') ->map(fn ($ids) => explode(',', $ids)) ->all(); } private function findByNameBirthdate(): array { return DB::table('contacts') ->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')) ->whereNotNull('name') ->whereNotNull('firstname') ->whereNotNull('birthdate') ->whereNull('merged_into_id') ->groupBy('name', 'firstname', 'birthdate') ->having(DB::raw('COUNT(*)'), '>', 1) ->pluck('ids') ->map(fn ($ids) => explode(',', $ids)) ->all(); } private function findByNameZip(): array { return DB::table('contacts') ->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')) ->whereNotNull('name') ->whereNotNull('firstname') ->whereNotNull('zip') ->where('zip', '!=', '') ->whereNull('merged_into_id') ->groupBy('name', 'firstname', 'zip') ->having(DB::raw('COUNT(*)'), '>', 1) ->pluck('ids') ->map(fn ($ids) => explode(',', $ids)) ->all(); } // ───────────────────────────────────────────────────────────────────────── // Verarbeitung // ───────────────────────────────────────────────────────────────────────── private function processLevel(string $level, callable $finder): void { if ($filter = $this->option('confidence')) { if (strtoupper($filter) !== $level) { return; } } $groups = $finder(); if (empty($groups)) { $this->line("[{$level}] Keine Duplikate."); return; } $this->line(sprintf('[%s] %d Gruppe(n) gefunden', $level, count($groups))); foreach ($groups as $ids) { $masterId = (int) $ids[0]; // erster = neuester $duplicateIds = array_map('intval', array_slice($ids, 1)); $this->line(sprintf( ' Master: #%d ← Duplikate: %s', $masterId, implode(', ', array_map(fn ($id) => '#' . $id, $duplicateIds)) )); foreach ($duplicateIds as $dupeId) { $this->mergeInto($masterId, $dupeId); } } } private function mergeInto(int $masterId, int $dupeId): void { // 1. Leads umhängen $leadCount = DB::table('inquiries')->where('customer_id', $dupeId)->count(); if ($leadCount > 0) { $this->line(" lead.customer_id: {$leadCount} Zeile(n) → #{$masterId}"); if (!$this->dryRun) { DB::table('inquiries') ->where('customer_id', $dupeId) ->update(['customer_id' => $masterId]); } $this->updatedLeads += $leadCount; } // 2. Bookings umhängen $bookingCount = DB::table('booking')->where('customer_id', $dupeId)->count(); if ($bookingCount > 0) { $this->line(" booking.customer_id: {$bookingCount} Zeile(n) → #{$masterId}"); if (!$this->dryRun) { DB::table('booking') ->where('customer_id', $dupeId) ->update(['customer_id' => $masterId]); } $this->updatedBookings += $bookingCount; } // 3. customer_mails umhängen $mailCount = DB::table('customer_mails')->where('customer_id', $dupeId)->count(); if ($mailCount > 0) { $this->line(" customer_mails.customer_id: {$mailCount} Zeile(n) → #{$masterId}"); if (!$this->dryRun) { DB::table('customer_mails') ->where('customer_id', $dupeId) ->update(['customer_id' => $masterId]); } } // 4. lead_mails umhängen $leadMailCount = DB::table('lead_mails')->where('customer_id', $dupeId)->count(); if ($leadMailCount > 0) { $this->line(" lead_mails.customer_id: {$leadMailCount} Zeile(n) → #{$masterId}"); if (!$this->dryRun) { DB::table('lead_mails') ->where('customer_id', $dupeId) ->update(['customer_id' => $masterId]); } } // 5. Duplikat als zusammengeführt markieren if (!$this->dryRun) { DB::table('contacts') ->where('id', $dupeId) ->update([ 'merged_into_id' => $masterId, 'merged_at' => now(), ]); } $this->mergedCount++; } }