companies:retire-agency-type: Firmen-Typ „Agentur" abschaffen

A4 des Umbaus Agentur-Attribution → User-Profil: Agentur-Firmen mit
PMs oder Pressekontakten werden zu type=company umtypisiert (sie sind
faktisch Newsrooms), leere Agentur-Firmen soft-gelöscht. Geschützt vor
Löschung (nur Umtypisierung): Mitgliedschaften Dritter in company_user
oder Verknüpfungen in user_payment_option_company. --dry-run +
JSON-Report; im Deploy-Runbook nach legacy:migrate-agency-names
eingereiht.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-08 09:33:39 +00:00
parent 85fddbfdf8
commit 59ac67a4c8
3 changed files with 252 additions and 4 deletions

View file

@ -0,0 +1,119 @@
<?php
namespace App\Console\Commands;
use App\Enums\CompanyType;
use App\Models\Company;
use App\Scopes\PortalScope;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Storage;
/**
* Schafft den Firmen-Typ „Agentur" ab (dev/agentur-attribution/
* entwicklungsplan.md, A4): Agentur-Firmen mit PMs oder Pressekontakten
* werden zu normalen Firmen umtypisiert (sie sind faktisch Newsrooms),
* leere Agentur-Firmen werden soft-gelöscht. Nie gelöscht wird, wenn
* Dritte per company_user Mitglied sind oder eine Zahlungsoptions-
* Verknüpfung (user_payment_option_company) existiert — dann wird nur
* umtypisiert. Läuft im Go-Live NACH legacy:migrate-agency-names.
*
* Verwendung:
* php artisan companies:retire-agency-type --dry-run
* php artisan companies:retire-agency-type
*/
class RetireAgencyCompanyType extends Command
{
protected $signature = 'companies:retire-agency-type
{--dry-run : Nur anzeigen und Report schreiben, keine Firmen ändern}';
protected $description = 'Typisiert Legacy-Agentur-Firmen zu normalen Firmen um bzw. soft-löscht leere Agentur-Firmen.';
public function handle(): int
{
$dryRun = (bool) $this->option('dry-run');
if ($dryRun) {
$this->warn('[DRY-RUN] Kein tatsächlicher Schreibvorgang.');
}
$agencies = Company::withoutGlobalScope(PortalScope::class)
->where('type', CompanyType::Agency)
->withExists([
// PMs inkl. soft-gelöschter und portalfremder Datensätze —
// eine Firma mit irgendeiner PM-Historie bleibt Newsroom.
'pressReleases as has_press_releases' => fn (Builder $query) => $query->withoutGlobalScopes(),
'contacts as has_contacts',
'userPaymentOptions as has_payment_option_links',
// Mitgliedschaften Dritter: jeder Pivot-Eintrag, der nicht der
// Owner selbst ist (bei Owner-losen Firmen jeder Eintrag).
'users as has_third_party_members' => fn (Builder $query) => $query->where(
fn (Builder $query) => $query
->whereColumn('company_user.user_id', '!=', 'companies.owner_user_id')
->orWhereNull('companies.owner_user_id'),
),
])
->get();
$report = [
'generated_at' => now()->toIso8601String(),
'dry_run' => $dryRun,
'summary' => [],
'retyped' => [],
'deleted' => [],
'protected' => [],
];
foreach ($agencies as $agency) {
$entry = [
'company_id' => $agency->id,
'portal' => $agency->portal?->value,
'name' => $agency->name,
];
$isNewsroom = $agency->has_press_releases || $agency->has_contacts;
$isProtected = $agency->has_third_party_members || $agency->has_payment_option_links;
if (! $isNewsroom && ! $isProtected) {
if (! $dryRun) {
$agency->delete();
}
$report['deleted'][] = $entry;
continue;
}
if (! $isNewsroom && $isProtected) {
$report['protected'][] = $entry + [
'third_party_members' => (bool) $agency->has_third_party_members,
'payment_option_links' => (bool) $agency->has_payment_option_links,
];
}
if (! $dryRun) {
$agency->update(['type' => CompanyType::Company]);
}
$report['retyped'][] = $entry;
}
$report['summary'] = [
'agency_companies' => $agencies->count(),
'retyped' => count($report['retyped']),
'deleted' => count($report['deleted']),
'protected' => count($report['protected']),
];
$this->table(
['Agentur-Firmen', 'Umtypisiert', 'Soft-gelöscht', 'Geschützt (nur umtypisiert)'],
[array_values($report['summary'])],
);
$path = 'migration/retire-agency-type-'.now()->format('Ymd-His').'.json';
Storage::disk('local')->put($path, json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$this->line("Report geschrieben: storage/app/private/{$path}");
return self::SUCCESS;
}
}