12-05-2026 Frontend dev
This commit is contained in:
parent
405df0a122
commit
5b8bdf4182
779 changed files with 480564 additions and 6241 deletions
408
app/Console/Commands/VerifyLegacyImport.php
Normal file
408
app/Console/Commands/VerifyLegacyImport.php
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\Portal;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class VerifyLegacyImport extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'legacy:verify
|
||||
{--portal=all : Portal (presseecho|businessportal24|all)}
|
||||
{--skip-legacy : Legacy-DB-Checks überspringen}
|
||||
{--no-report : Keinen JSON-Report schreiben}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Verifiziert den Legacy-Import und schreibt einen maschinenlesbaren Report.';
|
||||
|
||||
private const PORTAL_CONNECTIONS = [
|
||||
'presseecho' => 'mysql_presseecho',
|
||||
'businessportal24' => 'mysql_businessportal',
|
||||
];
|
||||
|
||||
private const ENTITY_MAP = [
|
||||
'users' => [
|
||||
'legacy_table' => 'sf_guard_user',
|
||||
'target_table' => 'users',
|
||||
'target_column' => 'legacy_id',
|
||||
],
|
||||
'companies' => [
|
||||
'legacy_table' => 'company',
|
||||
'target_table' => 'companies',
|
||||
'target_column' => 'legacy_id',
|
||||
],
|
||||
'contacts' => [
|
||||
'legacy_table' => 'contact',
|
||||
'target_table' => 'contacts',
|
||||
'target_column' => 'legacy_id',
|
||||
],
|
||||
'categories' => [
|
||||
'legacy_table' => 'category',
|
||||
'target_table' => 'categories',
|
||||
'target_column' => 'legacy_id',
|
||||
],
|
||||
'press_releases' => [
|
||||
'legacy_table' => 'press_release',
|
||||
'target_table' => 'press_releases',
|
||||
'target_column' => 'legacy_id',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$portalOption = (string) $this->option('portal');
|
||||
$skipLegacy = (bool) $this->option('skip-legacy');
|
||||
|
||||
$portals = $portalOption === 'all'
|
||||
? array_keys(self::PORTAL_CONNECTIONS)
|
||||
: [$portalOption];
|
||||
|
||||
if (array_diff($portals, array_keys(self::PORTAL_CONNECTIONS)) !== []) {
|
||||
$this->error("Unbekanntes Portal: {$portalOption}");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$report = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'portal' => $portalOption,
|
||||
'skip_legacy' => $skipLegacy,
|
||||
'checks' => [],
|
||||
'summary' => [
|
||||
'failures' => 0,
|
||||
'warnings' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
$this->info('Legacy-Import-Verifikation');
|
||||
$this->newLine();
|
||||
|
||||
if (! $skipLegacy) {
|
||||
$this->verifyLegacyCounts($report, $portals);
|
||||
$this->verifyLegacyInvoices($report, $portals);
|
||||
} else {
|
||||
$this->warn('Legacy-DB-Checks werden übersprungen.');
|
||||
}
|
||||
|
||||
$this->verifyImportMapTargets($report, $portals);
|
||||
$this->verifyTargetForeignKeys($report);
|
||||
$this->verifyUserMerges($report);
|
||||
$this->verifyGrandfatheredSubscriptions($report);
|
||||
|
||||
$this->renderSummary($report);
|
||||
|
||||
if (! (bool) $this->option('no-report')) {
|
||||
$path = 'migration/verify-'.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 $report['summary']['failures'] > 0 ? self::FAILURE : self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
* @param list<string> $portals
|
||||
*/
|
||||
private function verifyLegacyCounts(array &$report, array $portals): void
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
foreach ($portals as $portal) {
|
||||
foreach (self::ENTITY_MAP as $entity => $config) {
|
||||
$legacyCount = $this->legacyCount($portal, $config['legacy_table']);
|
||||
$targetCount = DB::table($config['target_table'])
|
||||
->where('legacy_portal', $portal)
|
||||
->whereNotNull($config['target_column'])
|
||||
->count();
|
||||
$mappedCount = DB::table('legacy_import_map')
|
||||
->where('legacy_portal', $portal)
|
||||
->where('legacy_table', $config['legacy_table'])
|
||||
->where('target_table', $config['target_table'])
|
||||
->count();
|
||||
|
||||
$rows[] = [
|
||||
'portal' => $portal,
|
||||
'entity' => $entity,
|
||||
'legacy_count' => $legacyCount,
|
||||
'target_count' => $targetCount,
|
||||
'mapped_count' => $mappedCount,
|
||||
'delta' => $legacyCount - $targetCount,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->addCheck($report, 'legacy_counts', 'ok', 'Legacy- und Ziel-Counts erfasst.', $rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
* @param list<string> $portals
|
||||
*/
|
||||
private function verifyLegacyInvoices(array &$report, array $portals): void
|
||||
{
|
||||
$rows = [];
|
||||
$failures = 0;
|
||||
|
||||
foreach ($portals as $portal) {
|
||||
$legacy = DB::connection(self::PORTAL_CONNECTIONS[$portal])
|
||||
->table('invoice')
|
||||
->selectRaw('COUNT(*) as count, COALESCE(SUM(amount), 0) as amount')
|
||||
->first();
|
||||
|
||||
$target = DB::table('legacy_invoices')
|
||||
->where('legacy_portal', $portal)
|
||||
->selectRaw('COUNT(*) as count, COALESCE(SUM(amount_cents), 0) as amount_cents')
|
||||
->first();
|
||||
|
||||
$legacyAmountCents = (int) round((float) $legacy->amount * 100);
|
||||
$targetAmountCents = (int) $target->amount_cents;
|
||||
$countDelta = (int) $legacy->count - (int) $target->count;
|
||||
$amountDelta = $legacyAmountCents - $targetAmountCents;
|
||||
|
||||
if ($countDelta !== 0 || $amountDelta !== 0) {
|
||||
$failures++;
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'portal' => $portal,
|
||||
'legacy_count' => (int) $legacy->count,
|
||||
'target_count' => (int) $target->count,
|
||||
'count_delta' => $countDelta,
|
||||
'legacy_amount_cents' => $legacyAmountCents,
|
||||
'target_amount_cents' => $targetAmountCents,
|
||||
'amount_delta_cents' => $amountDelta,
|
||||
];
|
||||
}
|
||||
|
||||
$this->addCheck(
|
||||
$report,
|
||||
'legacy_invoices',
|
||||
$failures === 0 ? 'ok' : 'fail',
|
||||
$failures === 0 ? 'Legacy-Rechnungen stimmen mit dem Archiv überein.' : 'Legacy-Rechnungsarchiv weicht von der Quelle ab.',
|
||||
$rows,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
* @param list<string> $portals
|
||||
*/
|
||||
private function verifyImportMapTargets(array &$report, array $portals): void
|
||||
{
|
||||
$rows = [];
|
||||
$failures = 0;
|
||||
|
||||
foreach (self::ENTITY_MAP as $entity => $config) {
|
||||
foreach ($portals as $portal) {
|
||||
$missingTargets = DB::table('legacy_import_map as map')
|
||||
->leftJoin($config['target_table'].' as target', 'target.id', '=', 'map.target_id')
|
||||
->where('map.legacy_portal', $portal)
|
||||
->where('map.legacy_table', $config['legacy_table'])
|
||||
->where('map.target_table', $config['target_table'])
|
||||
->whereNull('target.id')
|
||||
->count();
|
||||
|
||||
if ($missingTargets > 0) {
|
||||
$failures++;
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'portal' => $portal,
|
||||
'entity' => $entity,
|
||||
'missing_targets' => $missingTargets,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->addCheck(
|
||||
$report,
|
||||
'import_map_targets',
|
||||
$failures === 0 ? 'ok' : 'fail',
|
||||
$failures === 0 ? 'Alle Import-Map-Ziele existieren.' : 'Import-Map enthält verwaiste Ziel-IDs.',
|
||||
$rows,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
*/
|
||||
private function verifyTargetForeignKeys(array &$report): void
|
||||
{
|
||||
$checks = [
|
||||
'companies.owner_user_id' => DB::table('companies as c')
|
||||
->leftJoin('users as u', 'u.id', '=', 'c.owner_user_id')
|
||||
->whereNotNull('c.owner_user_id')
|
||||
->whereNull('u.id'),
|
||||
'contacts.company_id' => DB::table('contacts as c')
|
||||
->leftJoin('companies as co', 'co.id', '=', 'c.company_id')
|
||||
->whereNull('co.id'),
|
||||
'press_releases.user_id' => DB::table('press_releases as pr')
|
||||
->leftJoin('users as u', 'u.id', '=', 'pr.user_id')
|
||||
->whereNull('u.id'),
|
||||
'press_releases.company_id' => DB::table('press_releases as pr')
|
||||
->leftJoin('companies as c', 'c.id', '=', 'pr.company_id')
|
||||
->whereNotNull('pr.company_id')
|
||||
->whereNull('c.id'),
|
||||
'press_releases.category_id' => DB::table('press_releases as pr')
|
||||
->leftJoin('categories as c', 'c.id', '=', 'pr.category_id')
|
||||
->whereNull('c.id'),
|
||||
'company_user.company_id' => DB::table('company_user as pivot')
|
||||
->leftJoin('companies as c', 'c.id', '=', 'pivot.company_id')
|
||||
->whereNull('c.id'),
|
||||
'company_user.user_id' => DB::table('company_user as pivot')
|
||||
->leftJoin('users as u', 'u.id', '=', 'pivot.user_id')
|
||||
->whereNull('u.id'),
|
||||
'contact_user.contact_id' => DB::table('contact_user as pivot')
|
||||
->leftJoin('contacts as c', 'c.id', '=', 'pivot.contact_id')
|
||||
->whereNull('c.id'),
|
||||
'contact_user.user_id' => DB::table('contact_user as pivot')
|
||||
->leftJoin('users as u', 'u.id', '=', 'pivot.user_id')
|
||||
->whereNull('u.id'),
|
||||
'press_release_contact.press_release_id' => DB::table('press_release_contact as pivot')
|
||||
->leftJoin('press_releases as pr', 'pr.id', '=', 'pivot.press_release_id')
|
||||
->whereNull('pr.id'),
|
||||
'press_release_contact.contact_id' => DB::table('press_release_contact as pivot')
|
||||
->leftJoin('contacts as c', 'c.id', '=', 'pivot.contact_id')
|
||||
->whereNull('c.id'),
|
||||
'legacy_invoices.user_id' => DB::table('legacy_invoices as li')
|
||||
->leftJoin('users as u', 'u.id', '=', 'li.user_id')
|
||||
->whereNotNull('li.user_id')
|
||||
->whereNull('u.id'),
|
||||
];
|
||||
|
||||
$rows = [];
|
||||
$failures = 0;
|
||||
|
||||
foreach ($checks as $name => $query) {
|
||||
$count = $query->count();
|
||||
|
||||
if ($count > 0) {
|
||||
$failures++;
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'relation' => $name,
|
||||
'dangling_count' => $count,
|
||||
];
|
||||
}
|
||||
|
||||
$this->addCheck(
|
||||
$report,
|
||||
'target_foreign_keys',
|
||||
$failures === 0 ? 'ok' : 'fail',
|
||||
$failures === 0 ? 'Keine verwaisten Ziel-Foreign-Keys gefunden.' : 'Zieltabellen enthalten verwaiste Foreign Keys.',
|
||||
$rows,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
*/
|
||||
private function verifyUserMerges(array &$report): void
|
||||
{
|
||||
$bothUsers = DB::table('users')
|
||||
->where('portal', Portal::Both->value)
|
||||
->count();
|
||||
|
||||
$importMapsForBothUsers = DB::table('legacy_import_map as map')
|
||||
->join('users as users', 'users.id', '=', 'map.target_id')
|
||||
->where('map.legacy_table', 'sf_guard_user')
|
||||
->where('map.target_table', 'users')
|
||||
->where('users.portal', Portal::Both->value)
|
||||
->count();
|
||||
|
||||
$this->addCheck($report, 'user_merges', 'ok', 'Portalübergreifende User-Merges erfasst.', [
|
||||
'users_with_portal_both' => $bothUsers,
|
||||
'legacy_maps_to_portal_both_users' => $importMapsForBothUsers,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
*/
|
||||
private function verifyGrandfatheredSubscriptions(array &$report): void
|
||||
{
|
||||
$rows = DB::table('user_payment_options')
|
||||
->selectRaw('status, COUNT(*) as count, MIN(grandfathered_until) as earliest_grandfathered_until, MAX(grandfathered_until) as latest_grandfathered_until')
|
||||
->where('status', 'grandfathered')
|
||||
->groupBy('status')
|
||||
->get()
|
||||
->map(fn (object $row): array => [
|
||||
'status' => $row->status,
|
||||
'count' => (int) $row->count,
|
||||
'earliest_grandfathered_until' => $row->earliest_grandfathered_until,
|
||||
'latest_grandfathered_until' => $row->latest_grandfathered_until,
|
||||
])
|
||||
->all();
|
||||
|
||||
$this->addCheck($report, 'grandfathered_subscriptions', 'ok', 'Grandfathered Subscriptions erfasst.', $rows);
|
||||
}
|
||||
|
||||
private function legacyCount(string $portal, string $table): int
|
||||
{
|
||||
return DB::connection(self::PORTAL_CONNECTIONS[$portal])
|
||||
->table($table)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
* @param array<mixed> $details
|
||||
*/
|
||||
private function addCheck(array &$report, string $name, string $status, string $message, array $details): void
|
||||
{
|
||||
$report['checks'][$name] = [
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
'details' => $details,
|
||||
];
|
||||
|
||||
if ($status === 'fail') {
|
||||
$report['summary']['failures']++;
|
||||
}
|
||||
|
||||
if ($status === 'warning') {
|
||||
$report['summary']['warnings']++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
*/
|
||||
private function renderSummary(array $report): void
|
||||
{
|
||||
foreach ($report['checks'] as $name => $check) {
|
||||
$status = match ($check['status']) {
|
||||
'ok' => '<info>OK</info>',
|
||||
'warning' => '<comment>WARN</comment>',
|
||||
default => '<error>FAIL</error>',
|
||||
};
|
||||
|
||||
$this->line("{$status} {$name}: {$check['message']}");
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->table(
|
||||
['Status', 'Anzahl'],
|
||||
[
|
||||
['Fehler', $report['summary']['failures']],
|
||||
['Warnungen', $report['summary']['warnings']],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue