23-01-2026
This commit is contained in:
parent
8fd1f4d451
commit
389d5d1820
59 changed files with 9642 additions and 883 deletions
391
app/Http/Controllers/NewsletterController.php
Normal file
391
app/Http/Controllers/NewsletterController.php
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\NewsletterContact;
|
||||
use App\Models\NewsletterLog;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Yajra\DataTables\Facades\DataTables;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use App\Exports\NewsletterExport;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class NewsletterController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(['admin', '2fa']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste aller Newsletter-Kontakte
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$data = [
|
||||
'statistics' => $this->getStatistics(),
|
||||
];
|
||||
return view('newsletter.index', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* DataTables Daten für die Liste
|
||||
*/
|
||||
public function getDatatable(Request $request)
|
||||
{
|
||||
$query = NewsletterContact::query()
|
||||
->select([
|
||||
'id',
|
||||
'email',
|
||||
'firstname',
|
||||
'lastname',
|
||||
'group_kulturreisen',
|
||||
'group_ferienwohnungen',
|
||||
'status',
|
||||
'source',
|
||||
'total_bookings_kulturreisen',
|
||||
'total_bookings_ferienwohnungen',
|
||||
'last_booking_at',
|
||||
'last_travel_end_date',
|
||||
'created_at',
|
||||
]);
|
||||
|
||||
// Filter nach Gruppe
|
||||
if ($request->has('group') && $request->group != '') {
|
||||
if ($request->group == 'kulturreisen') {
|
||||
$query->where('group_kulturreisen', true);
|
||||
} elseif ($request->group == 'ferienwohnungen') {
|
||||
$query->where('group_ferienwohnungen', true);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter nach Status
|
||||
if ($request->has('status') && $request->status != '') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Filter nach Source
|
||||
if ($request->has('source') && $request->source != '') {
|
||||
$query->where('source', $request->source);
|
||||
}
|
||||
|
||||
// Filter nach Datum der letzten Buchung (von)
|
||||
if ($request->has('travel_from') && $request->travel_from != '') {
|
||||
$query->whereDate('last_travel_end_date', '>=', Carbon::parse($request->travel_from)->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
// Filter nach Datum der letzten Buchung (bis)
|
||||
if ($request->has('travel_to') && $request->travel_to != '') {
|
||||
$query->whereDate('last_travel_end_date', '<=', Carbon::parse($request->travel_to)->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
return DataTables::of($query)
|
||||
->addColumn('full_name', function ($contact) {
|
||||
return $contact->full_name ?: '-';
|
||||
})
|
||||
->addColumn('groups', function ($contact) {
|
||||
$html = '';
|
||||
if ($contact->group_kulturreisen) {
|
||||
$html .= '<span class="badge badge-info">Kulturreisen</span> ';
|
||||
}
|
||||
if ($contact->group_ferienwohnungen) {
|
||||
$html .= '<span class="badge badge-primary">Ferienwohnungen</span>';
|
||||
}
|
||||
return $html ?: '-';
|
||||
})
|
||||
->addColumn('status_badge', function ($contact) {
|
||||
return '<span class="badge badge-' . $contact->status_color . '">' . $contact->status_label . '</span>';
|
||||
})
|
||||
->addColumn('total_bookings', function ($contact) {
|
||||
$html = '';
|
||||
if ($contact->total_bookings_kulturreisen > 0) {
|
||||
$html .= '<span class="badge badge-secondary">K: ' . $contact->total_bookings_kulturreisen . '</span> ';
|
||||
}
|
||||
if ($contact->total_bookings_ferienwohnungen > 0) {
|
||||
$html .= '<span class="badge badge-secondary">F: ' . $contact->total_bookings_ferienwohnungen . '</span>';
|
||||
}
|
||||
return $html ?: '0';
|
||||
})
|
||||
->addColumn('last_booking', function ($contact) {
|
||||
return $contact->last_booking_at ? $contact->last_booking_at->format('d.m.Y') : '-';
|
||||
})
|
||||
->addColumn('last_travel', function ($contact) {
|
||||
return $contact->last_travel_end_date ? $contact->last_travel_end_date->format('d.m.Y') : '-';
|
||||
})
|
||||
->addColumn('source_label', function ($contact) {
|
||||
return $contact->source_label;
|
||||
})
|
||||
->addColumn('created', function ($contact) {
|
||||
return $contact->created_at->format('d.m.Y');
|
||||
})
|
||||
->addColumn('actions', function ($contact) {
|
||||
$html = '<div class="btn-group">';
|
||||
$html .= '<a href="' . route('newsletter.detail', $contact->id) . '" class="btn btn-sm btn-info"><i class="fa fa-eye"></i></a>';
|
||||
$html .= '<a href="' . route('newsletter.edit', $contact->id) . '" class="btn btn-sm btn-primary"><i class="fa fa-edit"></i></a>';
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
})
|
||||
->rawColumns(['groups', 'status_badge', 'total_bookings', 'actions'])
|
||||
->make(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailansicht eines Kontakts
|
||||
*/
|
||||
public function detail($id)
|
||||
{
|
||||
$contact = NewsletterContact::with(['customer', 'travel_user', 'logs.user'])
|
||||
->findOrFail($id);
|
||||
|
||||
$data = [
|
||||
'contact' => $contact,
|
||||
];
|
||||
|
||||
return view('newsletter.detail', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formular zum Bearbeiten eines Kontakts
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
if ($id === 'new') {
|
||||
$contact = new NewsletterContact();
|
||||
$contact->status = NewsletterContact::STATUS_ACTIVE;
|
||||
$contact->source = NewsletterContact::SOURCE_MANUAL;
|
||||
} else {
|
||||
$contact = NewsletterContact::findOrFail($id);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'contact' => $contact,
|
||||
'id' => $id,
|
||||
];
|
||||
|
||||
return view('newsletter.edit', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichern eines Kontakts
|
||||
*/
|
||||
public function store($id, Request $request)
|
||||
{
|
||||
$rules = [
|
||||
'email' => 'required|email',
|
||||
'status' => 'required|in:' . implode(',', [
|
||||
NewsletterContact::STATUS_ACTIVE,
|
||||
NewsletterContact::STATUS_INACTIVE,
|
||||
NewsletterContact::STATUS_UNSUBSCRIBED,
|
||||
NewsletterContact::STATUS_BOUNCED,
|
||||
]),
|
||||
];
|
||||
|
||||
$validator = Validator::make($request->all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return back()
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
if ($id === 'new') {
|
||||
$contact = new NewsletterContact();
|
||||
$isNew = true;
|
||||
} else {
|
||||
$contact = NewsletterContact::findOrFail($id);
|
||||
$isNew = false;
|
||||
}
|
||||
|
||||
// Speichere alte Werte für Log
|
||||
$oldStatus = $contact->status;
|
||||
$oldGroups = [
|
||||
'kulturreisen' => $contact->group_kulturreisen,
|
||||
'ferienwohnungen' => $contact->group_ferienwohnungen,
|
||||
];
|
||||
|
||||
$contact->email = strtolower(trim($request->email));
|
||||
$contact->firstname = $request->firstname;
|
||||
$contact->lastname = $request->lastname;
|
||||
$contact->status = $request->status;
|
||||
$contact->source = $request->source ?? NewsletterContact::SOURCE_MANUAL;
|
||||
$contact->group_kulturreisen = $request->has('group_kulturreisen');
|
||||
$contact->group_ferienwohnungen = $request->has('group_ferienwohnungen');
|
||||
$contact->notes = $request->notes;
|
||||
|
||||
if ($isNew) {
|
||||
$contact->subscribed_at = now();
|
||||
}
|
||||
|
||||
$contact->save();
|
||||
|
||||
// Log erstellen
|
||||
if ($isNew) {
|
||||
$contact->logs()->create([
|
||||
'action' => 'subscribed',
|
||||
'description' => 'Kontakt manuell erstellt',
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
} else {
|
||||
// Status geändert?
|
||||
if ($oldStatus !== $contact->status) {
|
||||
$contact->logs()->create([
|
||||
'action' => 'status_changed',
|
||||
'description' => 'Status geändert von ' . NewsletterContact::$statusLabels[$oldStatus] . ' zu ' . $contact->status_label,
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Gruppen geändert?
|
||||
if (
|
||||
$oldGroups['kulturreisen'] !== $contact->group_kulturreisen ||
|
||||
$oldGroups['ferienwohnungen'] !== $contact->group_ferienwohnungen
|
||||
) {
|
||||
$contact->logs()->create([
|
||||
'action' => 'group_changed',
|
||||
'description' => 'Gruppenzugehörigkeit geändert',
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
\Session()->flash('alert-success', $isNew ? 'Kontakt erstellt' : 'Kontakt aktualisiert');
|
||||
|
||||
return redirect()->route('newsletter.detail', $contact->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kontakt löschen (soft delete)
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
$contact = NewsletterContact::findOrFail($id);
|
||||
|
||||
$contact->logs()->create([
|
||||
'action' => 'unsubscribed',
|
||||
'description' => 'Kontakt gelöscht',
|
||||
'user_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
$contact->delete();
|
||||
|
||||
\Session()->flash('alert-success', 'Kontakt gelöscht');
|
||||
|
||||
return redirect()->route('newsletter.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Kontakt abmelden
|
||||
*/
|
||||
public function unsubscribe($id, Request $request)
|
||||
{
|
||||
$contact = NewsletterContact::findOrFail($id);
|
||||
$contact->unsubscribe($request->reason);
|
||||
|
||||
\Session()->flash('alert-success', 'Kontakt abgemeldet');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Kontakt wieder aktivieren
|
||||
*/
|
||||
public function resubscribe($id)
|
||||
{
|
||||
$contact = NewsletterContact::findOrFail($id);
|
||||
$contact->resubscribe();
|
||||
|
||||
\Session()->flash('alert-success', 'Kontakt wieder aktiviert');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronisation starten
|
||||
*/
|
||||
public function sync(Request $request)
|
||||
{
|
||||
$type = $request->get('type', 'all');
|
||||
$force = $request->has('force');
|
||||
|
||||
$output = [];
|
||||
|
||||
if ($type === 'all' || $type === 'kulturreisen') {
|
||||
Artisan::call('newsletter:sync-kulturreisen', $force ? ['--force' => true] : []);
|
||||
$output['kulturreisen'] = Artisan::output();
|
||||
}
|
||||
|
||||
if ($type === 'all' || $type === 'ferienwohnungen') {
|
||||
Artisan::call('newsletter:sync-ferienwohnungen', $force ? ['--force' => true] : []);
|
||||
$output['ferienwohnungen'] = Artisan::output();
|
||||
}
|
||||
|
||||
\Session()->flash('alert-success', 'Synchronisation abgeschlossen');
|
||||
|
||||
return back()->with('sync_output', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export von Kontakten
|
||||
*/
|
||||
public function export(Request $request)
|
||||
{
|
||||
$query = NewsletterContact::query();
|
||||
|
||||
// Filter nach Gruppe
|
||||
if ($request->has('group') && $request->group != '') {
|
||||
if ($request->group == 'kulturreisen') {
|
||||
$query->where('group_kulturreisen', true);
|
||||
} elseif ($request->group == 'ferienwohnungen') {
|
||||
$query->where('group_ferienwohnungen', true);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter nach Status
|
||||
if ($request->has('status') && $request->status != '') {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Filter nach Source
|
||||
if ($request->has('source') && $request->source != '') {
|
||||
$query->where('source', $request->source);
|
||||
}
|
||||
|
||||
// Filter nach Datum der letzten Reise (von)
|
||||
if ($request->has('travel_from') && $request->travel_from != '') {
|
||||
$query->whereDate('last_travel_end_date', '>=', Carbon::parse($request->travel_from)->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
// Filter nach Datum der letzten Reise (bis)
|
||||
if ($request->has('travel_to') && $request->travel_to != '') {
|
||||
$query->whereDate('last_travel_end_date', '<=', Carbon::parse($request->travel_to)->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
$contacts = $query->get();
|
||||
|
||||
// Dateiname mit Datum und Filter-Infos
|
||||
$group = $request->get('group', 'all');
|
||||
$status = $request->get('status', 'all');
|
||||
$filename = 'newsletter_' . $group . '_' . $status . '_' . date('Y-m-d') . '.csv';
|
||||
|
||||
return Excel::download(new NewsletterExport($contacts), $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiken für Dashboard
|
||||
*/
|
||||
private function getStatistics()
|
||||
{
|
||||
return [
|
||||
'total' => NewsletterContact::count(),
|
||||
'active' => NewsletterContact::where('status', NewsletterContact::STATUS_ACTIVE)->count(),
|
||||
'kulturreisen' => NewsletterContact::where('group_kulturreisen', true)->count(),
|
||||
'ferienwohnungen' => NewsletterContact::where('group_ferienwohnungen', true)->count(),
|
||||
'with_bookings' => NewsletterContact::withBookings()->count(),
|
||||
'multiple_bookers' => NewsletterContact::multipleBookers()->count(),
|
||||
'unsubscribed' => NewsletterContact::where('status', NewsletterContact::STATUS_UNSUBSCRIBED)->count(),
|
||||
'last_sync' => NewsletterContact::max('last_synced_at'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue