Enth\u00e4lt gemischt: Laravel-10-Upgrade + Phase 1 (Contacts-Modul, Duplicats-Commands, Soft-Delete+Merge-Fields) + Phase 2 Code-Umstellungen (inquiry_id, $table='contacts'/'inquiries') + Offers-Modul (Migrationen, Models, offer_id in Booking, offer-Disk in filesystems.php). Phase 2 + Offers werden im folgenden Commit nach dev/backups/phase2-offers-2026-04-17/ verschoben, damit der Workspace auf Phase-1-only (= Test-System-Stand) reduziert ist und direkt auf Live deploybar wird. Tarball-Backup zus\u00e4tzlich unter: ../backups-safety/workspace-pre-phase1-rollback-2026-04-17.tar.gz Made-with: Cursor
227 lines
7.8 KiB
PHP
227 lines
7.8 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Booking;
|
|
use App\Models\Customer;
|
|
use App\Models\NewsletterContact;
|
|
use App\Models\Status;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class SyncNewsletterKulturreisen extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'newsletter:sync-kulturreisen {--force : Force full sync}';
|
|
|
|
/**
|
|
* The console description of the command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Synchronisiert Kulturreisen-Buchungen mit Newsletter-Kontakten';
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function handle()
|
|
{
|
|
$this->info('Starte Synchronisation von Kulturreisen-Buchungen...');
|
|
|
|
$force = $this->option('force');
|
|
|
|
// Statistiken
|
|
$stats = [
|
|
'processed' => 0,
|
|
'created' => 0,
|
|
'updated' => 0,
|
|
'skipped' => 0,
|
|
'errors' => 0,
|
|
];
|
|
|
|
// Hole alle Buchungen mit Kunden
|
|
$query = Booking::with(['customer', 'lead.status'])
|
|
->whereNotNull('customer_id')
|
|
->whereHas('customer', function ($q) {
|
|
$q->whereNotNull('email')
|
|
->where('email', '!=', '');
|
|
})
|
|
// Nur Buchungen, bei denen die Reise bereits beendet ist (end_date in der Vergangenheit)
|
|
->whereNotNull('end_date')
|
|
->where('end_date', '<', now());
|
|
|
|
if (!$force) {
|
|
// Nur Buchungen der letzten 30 Tage (basierend auf Rückreisedatum) wenn nicht --force
|
|
$query->where('end_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']++;
|
|
|
|
$customer = $booking->customer;
|
|
|
|
// Validiere E-Mail
|
|
if (!$customer || !$customer->email || !filter_var($customer->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($customer->email);
|
|
$isBlockedEmail = false;
|
|
foreach ($blockedDomains as $domain) {
|
|
if (str_ends_with($emailLower, strtolower($domain))) {
|
|
$isBlockedEmail = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($isBlockedEmail) {
|
|
$stats['skipped']++;
|
|
$bar->advance();
|
|
continue;
|
|
}
|
|
|
|
// Generiere Hash für Duplikat-Erkennung
|
|
$syncHash = NewsletterContact::generateSyncHash(
|
|
$customer->email,
|
|
NewsletterContact::SOURCE_BOOKING_KULTURREISEN
|
|
);
|
|
|
|
// Suche oder erstelle Kontakt
|
|
$contact = NewsletterContact::withTrashed()
|
|
->where('email', strtolower(trim($customer->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($customer->email));
|
|
$contact->firstname = $customer->firstname ?: $contact->firstname;
|
|
$contact->lastname = $customer->name ?: $contact->lastname;
|
|
|
|
// Setze Gruppe Kulturreisen
|
|
$contact->group_kulturreisen = true;
|
|
|
|
// Source nur bei neuem Kontakt setzen
|
|
if ($isNew) {
|
|
$contact->source = NewsletterContact::SOURCE_BOOKING_KULTURREISEN;
|
|
$contact->subscribed_at = $booking->booking_date ?: $booking->created_at;
|
|
}
|
|
|
|
// Referenz zum Customer
|
|
$contact->customer_id = $customer->id;
|
|
|
|
// Aktualisiere Buchungsstatistiken
|
|
$customerBookings = Booking::where('customer_id', $customer->id)->count();
|
|
$contact->total_bookings_kulturreisen = $customerBookings;
|
|
|
|
// Letztes Buchungsdatum
|
|
$lastBooking = Booking::where('customer_id', $customer->id)
|
|
->orderBy('booking_date', 'DESC')
|
|
->first();
|
|
|
|
if ($lastBooking && $lastBooking->booking_date) {
|
|
$contact->last_booking_at = $lastBooking->booking_date;
|
|
}
|
|
|
|
// Letztes Reiseenddatum (end_date) - nur abgeschlossene Reisen
|
|
$lastTravelEndBooking = Booking::where('customer_id', $customer->id)
|
|
->whereNotNull('end_date')
|
|
->where('end_date', '<', now())
|
|
->orderBy('end_date', 'DESC')
|
|
->first();
|
|
|
|
if ($lastTravelEndBooking && $lastTravelEndBooking->end_date) {
|
|
if (!$contact->last_travel_end_date || $lastTravelEndBooking->end_date->gt($contact->last_travel_end_date)) {
|
|
$contact->last_travel_end_date = $lastTravelEndBooking->end_date;
|
|
}
|
|
}
|
|
|
|
// 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 Kulturreisen-Buchung erstellt',
|
|
'metadata' => [
|
|
'booking_id' => $booking->id,
|
|
// Metadaten-Key bleibt `lead_id`; Wert kommt aus booking.inquiry_id.
|
|
'lead_id' => $booking->inquiry_id,
|
|
],
|
|
]);
|
|
}
|
|
} 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;
|
|
}
|
|
}
|