option('dry-run'); $batch = (string) Str::uuid(); $limit = $this->option('limit') !== null ? max(1, (int) $this->option('limit')) : null; $groups = $this->groups(); $stats = ['adressen' => 0, 'zusammengefuehrt' => 0, 'firmen_weg' => 0, 'meldungen' => 0, 'both' => 0, 'uebersprungen' => []]; $plans = []; $skips = []; foreach ($groups->lazy() as $group) { if ($limit !== null && $stats['adressen'] >= $limit) { break; } $result = $service->plan($group); if ($result instanceof CompanyMergeSkip) { // Eine Adresse, unter der nichts (mehr) zusammenzuführen ist, // zählt nicht gegen das Limit — sonst füllt sie beim zweiten // Lauf die ersten 50 Plätze. if ($result->reason !== CompanyMergeSkip::NothingToMerge) { $skips[] = $result; } $stats['uebersprungen'][$result->reason] = ($stats['uebersprungen'][$result->reason] ?? 0) + 1; continue; } $stats['adressen']++; $stats['firmen_weg'] += count($result->losers); $stats['meldungen'] += $result->pressReleases; $stats['both'] += $result->targetPortal->value === 'both' && $result->survivor->portal->value !== 'both' ? 1 : 0; if ($dryRun) { if (count($plans) < 30) { $plans[] = $result; } continue; } $service->merge($result, $batch, flushCaches: false); $stats['zusammengefuehrt']++; } if (! $dryRun && $stats['zusammengefuehrt'] > 0) { $service->flushCaches(); } if ($this->option('json')) { $this->line((string) json_encode([ 'dry_run' => $dryRun, 'batch' => $dryRun ? null : $batch, 'statistik' => $stats, 'plaene' => array_map(fn (CompanyMergePlan $plan): array => $this->describe($plan), $plans), 'uebersprungen' => array_map(fn (CompanyMergeSkip $skip): array => ['email' => $skip->email, 'grund' => $skip->reason, 'details' => $skip->details], $skips), ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); return self::SUCCESS; } $this->info(sprintf( '%s%d Adressen, %d Firmen gehen im Überlebenden auf, %d Meldungen wandern, %d Firmen werden portalübergreifend (both).', $dryRun ? '[Dry-Run] ' : '', $stats['adressen'], $stats['firmen_weg'], $stats['meldungen'], $stats['both'], )); if (! $dryRun) { $this->line(sprintf(' Batch %s — Rückabwicklung: companies:unmerge --batch=%s', $batch, $batch)); } foreach ($stats['uebersprungen'] as $reason => $count) { $this->line(sprintf(' übersprungen (%s): %d', $reason, $count)); } if ($plans !== []) { $this->newLine(); $this->table( ['Adresse', 'bleibt', 'Portal', 'geht auf', 'Meldungen'], array_map(fn (CompanyMergePlan $plan): array => [ $plan->email, sprintf('#%d %s', $plan->survivor->getKey(), Str::limit($plan->survivor->name, 40)), $plan->targetPortal->value, implode(', ', array_map(fn (Company $loser): string => sprintf('#%d %s', $loser->getKey(), Str::limit($loser->name, 30)), $plan->losers)), $plan->pressReleases, ], $plans), ); } if ($skips !== []) { $this->newLine(); $this->table( ['übersprungen', 'Grund', 'Details'], array_map(fn (CompanyMergeSkip $skip): array => [$skip->email, $skip->reason, implode(', ', $skip->details)], array_slice($skips, 0, 50)), ); } return self::SUCCESS; } /** * Zusammenführbare Adressen: berechnete Klasse, sofern keine * Handentscheidung dagegen steht — oder Handentscheidung dafür. * * @return Builder */ private function groups(): Builder { $mergeable = array_map( fn (CompanyAddressClass $class): string => $class->value, array_filter(CompanyAddressClass::cases(), fn (CompanyAddressClass $class): bool => $class->isMergeable()), ); return CompanyEmailGroup::query() ->when($this->option('email'), fn (Builder $query, string $email) => $query->where('email', Company::emailKey($email))) ->where(function (Builder $query) use ($mergeable): void { $query->whereIn('manual_classification', $mergeable) ->orWhere(fn (Builder $computed) => $computed->whereNull('manual_classification')->whereIn('classification', $mergeable)); }) ->orderBy('id'); } /** * @return array */ private function describe(CompanyMergePlan $plan): array { return [ 'email' => $plan->email, 'bleibt' => ['id' => $plan->survivor->getKey(), 'name' => $plan->survivor->name, 'portal' => $plan->survivor->portal->value, 'slug' => $plan->survivor->slug], 'portal_danach' => $plan->targetPortal->value, 'geht_auf' => array_map(fn (Company $loser): array => ['id' => $loser->getKey(), 'name' => $loser->name, 'portal' => $loser->portal->value, 'slug' => $loser->slug], $plan->losers), 'ausgenommen_410' => array_map(fn (Company $company): int => $company->getKey(), $plan->excluded), 'meldungen' => $plan->pressReleases, ]; } }