'DE', 'Austria' => 'AT', 'Switzerland' => 'CH', 'Liechtenstein' => 'LI', 'Luxembourg' => 'LU', 'Belgium' => 'BE', 'Netherlands' => 'NL', 'France' => 'FR', 'Italy' => 'IT', 'Spain' => 'ES', 'Portugal' => 'PT', 'Poland' => 'PL', 'Czech Republic' => 'CZ', 'Denmark' => 'DK', 'Sweden' => 'SE', 'Norway' => 'NO', 'Finland' => 'FI', 'Ireland' => 'IE', 'Greece' => 'GR', 'Hungary' => 'HU', 'Cyprus' => 'CY', 'United Kingdom' => 'GB', 'United States' => 'US', 'India' => 'IN', ]; public function __construct(private readonly LegacyAddressParser $parser) {} /** * @param array> $countryNamesByPortal Portal → [legacy country_id → englischer Name] * @return array{imported: int, skipped_existing: int, unparseable: list, unknown_country: list, missing_invoice: list} */ public function import(array $countryNamesByPortal, bool $dryRun = false): array { $report = [ 'imported' => 0, 'skipped_existing' => 0, 'unparseable' => [], 'unknown_country' => [], 'missing_invoice' => [], ]; $optionsByUser = UserPaymentOption::query() ->where('status', UserPaymentOptionStatus::Grandfathered->value) ->with('user.billingAddress') ->get() ->filter(fn (UserPaymentOption $option): bool => $option->user !== null) ->groupBy('user_id'); foreach ($optionsByUser as $options) { $user = $options->first()->user; if ($user->billingAddress) { $report['skipped_existing']++; continue; } $invoice = $this->latestSourceInvoice($options); if (! $invoice) { $report['missing_invoice'][] = $user->email; continue; } $legacyAddress = $invoice->pdf_payload['billing_address'] ?? []; $portal = $invoice->legacy_portal->value; $countryName = $countryNamesByPortal[$portal][(int) ($legacyAddress['country_id'] ?? 0)] ?? null; $countryCode = self::COUNTRY_NAME_TO_ISO[$countryName] ?? null; if ($countryCode === null) { $report['unknown_country'][] = sprintf('%s (country_id %s)', $user->email, $legacyAddress['country_id'] ?? '—'); continue; } $parsed = $this->parser->parse($legacyAddress['address'] ?? null, $countryName); if ($parsed === null) { $report['unparseable'][] = sprintf('%s ("%s")', $user->email, str_replace(["\r\n", "\n"], ' / ', trim((string) ($legacyAddress['address'] ?? '')))); continue; } if (! $dryRun) { BillingAddress::query()->create([ 'user_id' => $user->id, 'name' => (string) ($legacyAddress['name'] ?? $user->name), 'company' => $parsed['company'], 'address1' => $parsed['address1'], 'address2' => $parsed['address2'], 'postal_code' => $parsed['postal_code'], 'city' => $parsed['city'], 'country_code' => $countryCode, ]); } $report['imported']++; } return $report; } /** * Jüngste Quell-Rechnung über alle Vereinbarungen des Users — hat er * mehrere Pakete (z. B. je Spoke eines), zählt der neueste Adress-Stand. * * @param Collection $options */ private function latestSourceInvoice(Collection $options): ?LegacyInvoice { return $options ->map(function (UserPaymentOption $option): ?LegacyInvoice { $conditions = $option->legacy_conditions ?? []; $number = $conditions['source_invoice_number'] ?? null; $portal = $conditions['legacy_portal'] ?? null; if (! $number || ! $portal) { return null; } return LegacyInvoice::query() ->where('legacy_portal', $portal) ->where('number', $number) ->first(); }) ->filter() ->sortByDesc(fn (LegacyInvoice $invoice) => [$invoice->invoice_date, $invoice->id]) ->first(); } }