23-01-2026
This commit is contained in:
parent
8fd1f4d451
commit
389d5d1820
59 changed files with 9642 additions and 883 deletions
326
app/Models/NewsletterContact.php
Normal file
326
app/Models/NewsletterContact.php
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
/**
|
||||
* Class NewsletterContact
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $email
|
||||
* @property string|null $firstname
|
||||
* @property string|null $lastname
|
||||
* @property bool $group_kulturreisen
|
||||
* @property bool $group_ferienwohnungen
|
||||
* @property string $source
|
||||
* @property string $status
|
||||
* @property Carbon|null $subscribed_at
|
||||
* @property Carbon|null $unsubscribed_at
|
||||
* @property Carbon|null $last_booking_at
|
||||
* @property Carbon|null $last_travel_end_date
|
||||
* @property int $total_bookings_kulturreisen
|
||||
* @property int $total_bookings_ferienwohnungen
|
||||
* @property int|null $customer_id
|
||||
* @property int|null $travel_user_id
|
||||
* @property Carbon|null $last_synced_at
|
||||
* @property string|null $sync_hash
|
||||
* @property string|null $notes
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property-read Customer|null $customer
|
||||
* @property-read TravelUser|null $travel_user
|
||||
* @property-read Collection|NewsletterLog[] $logs
|
||||
* @package App\Models
|
||||
*/
|
||||
class NewsletterContact extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $connection = 'mysql';
|
||||
|
||||
protected $table = 'newsletter_contacts';
|
||||
|
||||
protected $casts = [
|
||||
'group_kulturreisen' => 'boolean',
|
||||
'group_ferienwohnungen' => 'boolean',
|
||||
'subscribed_at' => 'datetime',
|
||||
'unsubscribed_at' => 'datetime',
|
||||
'last_booking_at' => 'datetime',
|
||||
'last_travel_end_date' => 'datetime',
|
||||
'last_synced_at' => 'datetime',
|
||||
'total_bookings_kulturreisen' => 'int',
|
||||
'total_bookings_ferienwohnungen' => 'int',
|
||||
'customer_id' => 'int',
|
||||
'travel_user_id' => 'int',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'email',
|
||||
'firstname',
|
||||
'lastname',
|
||||
'group_kulturreisen',
|
||||
'group_ferienwohnungen',
|
||||
'source',
|
||||
'status',
|
||||
'subscribed_at',
|
||||
'unsubscribed_at',
|
||||
'last_booking_at',
|
||||
'last_travel_end_date',
|
||||
'total_bookings_kulturreisen',
|
||||
'total_bookings_ferienwohnungen',
|
||||
'customer_id',
|
||||
'travel_user_id',
|
||||
'last_synced_at',
|
||||
'sync_hash',
|
||||
'notes',
|
||||
];
|
||||
|
||||
// Konstanten für Source
|
||||
const SOURCE_BOOKING_KULTURREISEN = 'booking_kulturreisen';
|
||||
const SOURCE_BOOKING_FERIENWOHNUNGEN = 'booking_ferienwohnungen';
|
||||
const SOURCE_NEWSLETTER_SIGNUP = 'newsletter_signup';
|
||||
const SOURCE_MANUAL = 'manual';
|
||||
const SOURCE_IMPORT = 'import';
|
||||
|
||||
// Konstanten für Status
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_INACTIVE = 'inactive';
|
||||
const STATUS_UNSUBSCRIBED = 'unsubscribed';
|
||||
const STATUS_BOUNCED = 'bounced';
|
||||
|
||||
public static $sourceLabels = [
|
||||
self::SOURCE_BOOKING_KULTURREISEN => 'Buchung Kulturreisen',
|
||||
self::SOURCE_BOOKING_FERIENWOHNUNGEN => 'Buchung Ferienwohnungen',
|
||||
self::SOURCE_NEWSLETTER_SIGNUP => 'Newsletter-Anmeldung',
|
||||
self::SOURCE_MANUAL => 'Manuell',
|
||||
self::SOURCE_IMPORT => 'Import',
|
||||
];
|
||||
|
||||
public static $statusLabels = [
|
||||
self::STATUS_ACTIVE => 'Aktiv',
|
||||
self::STATUS_INACTIVE => 'Inaktiv',
|
||||
self::STATUS_UNSUBSCRIBED => 'Abgemeldet',
|
||||
self::STATUS_BOUNCED => 'Bounced',
|
||||
];
|
||||
|
||||
public static $statusColors = [
|
||||
self::STATUS_ACTIVE => 'success',
|
||||
self::STATUS_INACTIVE => 'secondary',
|
||||
self::STATUS_UNSUBSCRIBED => 'warning',
|
||||
self::STATUS_BOUNCED => 'danger',
|
||||
];
|
||||
|
||||
/**
|
||||
* Beziehung zum Customer (Kulturreisen)
|
||||
*/
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'customer_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Beziehung zum TravelUser (Ferienwohnungen)
|
||||
*/
|
||||
public function travel_user()
|
||||
{
|
||||
return $this->belongsTo(TravelUser::class, 'travel_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs zu diesem Kontakt
|
||||
*/
|
||||
public function logs()
|
||||
{
|
||||
return $this->hasMany(NewsletterLog::class, 'newsletter_contact_id')->orderBy('created_at', 'DESC');
|
||||
}
|
||||
|
||||
/**
|
||||
* Vollständiger Name
|
||||
*/
|
||||
public function getFullNameAttribute()
|
||||
{
|
||||
return trim($this->firstname . ' ' . $this->lastname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gruppenzugehörigkeit als Array
|
||||
*/
|
||||
public function getGroupsAttribute()
|
||||
{
|
||||
$groups = [];
|
||||
if ($this->group_kulturreisen) {
|
||||
$groups[] = 'Kulturreisen';
|
||||
}
|
||||
if ($this->group_ferienwohnungen) {
|
||||
$groups[] = 'Ferienwohnungen';
|
||||
}
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gruppenzugehörigkeit als String
|
||||
*/
|
||||
public function getGroupsStringAttribute()
|
||||
{
|
||||
return implode(', ', $this->groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status-Label
|
||||
*/
|
||||
public function getStatusLabelAttribute()
|
||||
{
|
||||
return self::$statusLabels[$this->status] ?? $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status-Color
|
||||
*/
|
||||
public function getStatusColorAttribute()
|
||||
{
|
||||
return self::$statusColors[$this->status] ?? 'secondary';
|
||||
}
|
||||
|
||||
/**
|
||||
* Source-Label
|
||||
*/
|
||||
public function getSourceLabelAttribute()
|
||||
{
|
||||
return self::$sourceLabels[$this->source] ?? $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status-Badge HTML
|
||||
*/
|
||||
public function getStatusBadgeAttribute()
|
||||
{
|
||||
return '<span class="badge badge-' . $this->status_color . '">' . $this->status_label . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gesamtzahl Buchungen
|
||||
*/
|
||||
public function getTotalBookingsAttribute()
|
||||
{
|
||||
return $this->total_bookings_kulturreisen + $this->total_bookings_ferienwohnungen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ist Kontakt aktiv?
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
return $this->status === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ist Kontakt abgemeldet?
|
||||
*/
|
||||
public function isUnsubscribed()
|
||||
{
|
||||
return $this->status === self::STATUS_UNSUBSCRIBED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hat Kontakt mindestens eine Buchung?
|
||||
*/
|
||||
public function hasBookings()
|
||||
{
|
||||
return $this->total_bookings > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kontakt abmelden
|
||||
*/
|
||||
public function unsubscribe($reason = null)
|
||||
{
|
||||
$this->status = self::STATUS_UNSUBSCRIBED;
|
||||
$this->unsubscribed_at = now();
|
||||
$this->save();
|
||||
|
||||
// Log erstellen
|
||||
$this->logs()->create([
|
||||
'action' => 'unsubscribed',
|
||||
'description' => $reason ?? 'Kontakt abgemeldet',
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kontakt wieder aktivieren
|
||||
*/
|
||||
public function resubscribe()
|
||||
{
|
||||
$this->status = self::STATUS_ACTIVE;
|
||||
$this->unsubscribed_at = null;
|
||||
$this->save();
|
||||
|
||||
// Log erstellen
|
||||
$this->logs()->create([
|
||||
'action' => 'subscribed',
|
||||
'description' => 'Kontakt wieder aktiviert',
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash für Duplikat-Erkennung generieren
|
||||
*/
|
||||
public static function generateSyncHash($email, $source)
|
||||
{
|
||||
return md5(strtolower(trim($email)) . '_' . $source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Nur aktive Kontakte
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('status', self::STATUS_ACTIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Nur Kulturreisen
|
||||
*/
|
||||
public function scopeKulturreisen($query)
|
||||
{
|
||||
return $query->where('group_kulturreisen', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Nur Ferienwohnungen
|
||||
*/
|
||||
public function scopeFerienwohnungen($query)
|
||||
{
|
||||
return $query->where('group_ferienwohnungen', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Mit Buchungen
|
||||
*/
|
||||
public function scopeWithBookings($query)
|
||||
{
|
||||
return $query->where(function ($q) {
|
||||
$q->where('total_bookings_kulturreisen', '>', 0)
|
||||
->orWhere('total_bookings_ferienwohnungen', '>', 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Mehrfachbucher
|
||||
*/
|
||||
public function scopeMultipleBookers($query)
|
||||
{
|
||||
return $query->where(function ($q) {
|
||||
$q->where('total_bookings_kulturreisen', '>', 1)
|
||||
->orWhere('total_bookings_ferienwohnungen', '>', 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue