info('Starte Synchronisation von Ferienwohnungs-Buchungen...'); $force = $this->option('force'); // Statistiken $stats = [ 'processed' => 0, 'created' => 0, 'updated' => 0, 'skipped' => 0, 'errors' => 0, ]; // Hole alle Buchungen mit TravelUser und invoice_number $query = TravelUserBookingFewo::with(['travel_user']) ->whereNotNull('travel_user_id') ->whereNotNull('invoice_number') ->where('invoice_number', '!=', '') // Nur wenn invoice_number eine reine Nummer ist (keine Storno etc.) ->whereRaw('invoice_number REGEXP "^[0-9]+$"') ->whereHas('travel_user', function ($q) { $q->whereNotNull('email') ->where('email', '!=', ''); }) // Nur Buchungen, bei denen die Reise bereits beendet ist (to_date in der Vergangenheit) ->whereNotNull('to_date') ->where('to_date', '<', now()); if (!$force) { // Nur Buchungen der letzten 30 Tage (basierend auf Rückreisedatum) wenn nicht --force $query->where('to_date', '>=', now()->subDays(30)); } $bookings = $query->get(); $this->info("Verarbeite {$bookings->count()} Buchungen..."); $bar = $this->output->createProgressBar($bookings->count()); $bar->start(); foreach ($bookings as $booking) { try { $stats['processed']++; $travelUser = $booking->travel_user; // Validiere E-Mail if (!$travelUser || !$travelUser->email || !filter_var($travelUser->email, FILTER_VALIDATE_EMAIL)) { $stats['skipped']++; $bar->advance(); continue; } // Filtere Alias/Proxy E-Mail-Adressen von Buchungsplattformen $blockedDomains = [ '@guest.booking.com', '@messages.homeaway.com', '@fewo.check24.de', '@booking.com', '@homeaway.com', '@check24.de', '@partner.booking.com', ]; $emailLower = strtolower($travelUser->email); $isBlockedEmail = false; foreach ($blockedDomains as $domain) { if (str_ends_with($emailLower, strtolower($domain))) { $isBlockedEmail = true; break; } } if ($isBlockedEmail) { $stats['skipped']++; $bar->advance(); continue; } // Prüfe ob invoice_number wirklich eine reine Zahl ist if (!preg_match('/^[0-9]+$/', $booking->invoice_number)) { $stats['skipped']++; $bar->advance(); continue; } // Generiere Hash für Duplikat-Erkennung $syncHash = NewsletterContact::generateSyncHash( $travelUser->email, NewsletterContact::SOURCE_BOOKING_FERIENWOHNUNGEN ); // Suche oder erstelle Kontakt $contact = NewsletterContact::withTrashed() ->where('email', strtolower(trim($travelUser->email))) ->first(); $isNew = false; if (!$contact) { // Neuer Kontakt $contact = new NewsletterContact(); $isNew = true; $stats['created']++; } else { // Wenn gelöscht, wiederherstellen if ($contact->trashed()) { $contact->restore(); } $stats['updated']++; } // Aktualisiere Kontaktdaten $contact->email = strtolower(trim($travelUser->email)); $contact->firstname = $travelUser->first_name ?: $contact->firstname; $contact->lastname = $travelUser->last_name ?: $contact->lastname; // Setze Gruppe Ferienwohnungen $contact->group_ferienwohnungen = true; // Source nur bei neuem Kontakt setzen (wenn noch nicht aus Kulturreisen) if ($isNew) { $contact->source = NewsletterContact::SOURCE_BOOKING_FERIENWOHNUNGEN; $contact->subscribed_at = $booking->booking_date ? \Carbon\Carbon::parse($booking->booking_date) : $booking->created_at; } // Referenz zum TravelUser $contact->travel_user_id = $travelUser->id; // Aktualisiere Buchungsstatistiken // Nur Buchungen mit invoice_number (reine Nummer) zählen $userBookings = TravelUserBookingFewo::where('travel_user_id', $travelUser->id) ->whereNotNull('invoice_number') ->where('invoice_number', '!=', '') ->whereRaw('invoice_number REGEXP "^[0-9]+$"') ->count(); $contact->total_bookings_ferienwohnungen = $userBookings; // Letztes Buchungsdatum $lastBooking = TravelUserBookingFewo::where('travel_user_id', $travelUser->id) ->whereNotNull('invoice_number') ->where('invoice_number', '!=', '') ->whereRaw('invoice_number REGEXP "^[0-9]+$"') ->orderBy('booking_date', 'DESC') ->first(); if ($lastBooking && $lastBooking->booking_date) { $lastBookingDate = \Carbon\Carbon::parse($lastBooking->booking_date); if (!$contact->last_booking_at || $lastBookingDate->gt($contact->last_booking_at)) { $contact->last_booking_at = $lastBookingDate; } } // Letztes Reiseenddatum (to_date) - nur abgeschlossene Reisen $lastTravelEndBooking = TravelUserBookingFewo::where('travel_user_id', $travelUser->id) ->whereNotNull('invoice_number') ->where('invoice_number', '!=', '') ->whereRaw('invoice_number REGEXP "^[0-9]+$"') ->whereNotNull('to_date') ->where('to_date', '<', now()) ->orderBy('to_date', 'DESC') ->first(); if ($lastTravelEndBooking && $lastTravelEndBooking->to_date) { $lastTravelEndDate = \Carbon\Carbon::parse($lastTravelEndBooking->to_date); if (!$contact->last_travel_end_date || $lastTravelEndDate->gt($contact->last_travel_end_date)) { $contact->last_travel_end_date = $lastTravelEndDate; } } // Status if ($isNew || $contact->status === NewsletterContact::STATUS_INACTIVE) { $contact->status = NewsletterContact::STATUS_ACTIVE; } $contact->sync_hash = $syncHash; $contact->last_synced_at = now(); $contact->save(); // Log erstellen if ($isNew) { $contact->logs()->create([ 'action' => 'booking_added', 'description' => 'Kontakt durch Ferienwohnungs-Buchung erstellt', 'metadata' => [ 'booking_id' => $booking->id, 'invoice_number' => $booking->invoice_number, ], ]); } } catch (\Exception $e) { $stats['errors']++; $this->error("Fehler bei Buchung {$booking->id}: " . $e->getMessage()); } $bar->advance(); } $bar->finish(); $this->newLine(2); // Statistiken ausgeben $this->info('Synchronisation abgeschlossen!'); $this->table( ['Statistik', 'Anzahl'], [ ['Verarbeitet', $stats['processed']], ['Neu erstellt', $stats['created']], ['Aktualisiert', $stats['updated']], ['Übersprungen', $stats['skipped']], ['Fehler', $stats['errors']], ] ); return 0; } }