diff --git a/.env b/.env index 8aae097..904c04d 100755 --- a/.env +++ b/.env @@ -16,6 +16,7 @@ APP_DOMAIN_TLD=test LOG_CHANNEL=stack SUCCESS_KEY=f6077389c9ce710e554763a5de02c8ec +EXCEPTION_MAIL=exception@adametz.media # Standard Database Connection DB_CONNECTION=mysql diff --git a/app/Console/Commands/ContactsFindDuplicates.php b/app/Console/Commands/ContactsFindDuplicates.php index 35ab2fb..cef982d 100644 --- a/app/Console/Commands/ContactsFindDuplicates.php +++ b/app/Console/Commands/ContactsFindDuplicates.php @@ -37,7 +37,7 @@ class ContactsFindDuplicates extends Command // ── HIGH: gleiche E-Mail ────────────────────────────────────────── if ($this->shouldCheck('HIGH')) { - $emailDupes = DB::table('contacts') + $emailDupes = DB::table('customer') ->select('email', DB::raw('COUNT(*) as cnt'), DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC) as ids')) ->whereNotNull('email') ->where('email', '!=', '') @@ -60,7 +60,7 @@ class ContactsFindDuplicates extends Command // ── MEDIUM: Name + Vorname + Geburtsdatum ──────────────────────── if ($this->shouldCheck('MEDIUM')) { - $nameBdDupes = DB::table('contacts') + $nameBdDupes = DB::table('customer') ->select( 'name', 'firstname', 'birthdate', DB::raw('COUNT(*) as cnt'), @@ -88,7 +88,7 @@ class ContactsFindDuplicates extends Command // ── LOW: Name + Vorname + PLZ ───────────────────────────────────── if ($this->shouldCheck('LOW')) { - $nameZipDupes = DB::table('contacts') + $nameZipDupes = DB::table('customer') ->select( 'name', 'firstname', 'zip', DB::raw('COUNT(*) as cnt'), diff --git a/app/Console/Commands/ContactsMergeDuplicates.php b/app/Console/Commands/ContactsMergeDuplicates.php index 9aeeca6..e51ca46 100644 --- a/app/Console/Commands/ContactsMergeDuplicates.php +++ b/app/Console/Commands/ContactsMergeDuplicates.php @@ -90,7 +90,7 @@ class ContactsMergeDuplicates extends Command private function findByEmail(): array { - return DB::table('contacts') + return DB::table('customer') ->select('email', DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')) ->whereNotNull('email') ->where('email', '!=', '') @@ -104,7 +104,7 @@ class ContactsMergeDuplicates extends Command private function findByNameBirthdate(): array { - return DB::table('contacts') + return DB::table('customer') ->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')) ->whereNotNull('name') ->whereNotNull('firstname') @@ -119,7 +119,7 @@ class ContactsMergeDuplicates extends Command private function findByNameZip(): array { - return DB::table('contacts') + return DB::table('customer') ->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')) ->whereNotNull('name') ->whereNotNull('firstname') @@ -173,11 +173,11 @@ class ContactsMergeDuplicates extends Command private function mergeInto(int $masterId, int $dupeId): void { // 1. Leads umhängen - $leadCount = DB::table('inquiries')->where('customer_id', $dupeId)->count(); + $leadCount = DB::table('lead')->where('customer_id', $dupeId)->count(); if ($leadCount > 0) { $this->line(" lead.customer_id: {$leadCount} Zeile(n) → #{$masterId}"); if (!$this->dryRun) { - DB::table('inquiries') + DB::table('lead') ->where('customer_id', $dupeId) ->update(['customer_id' => $masterId]); } @@ -220,7 +220,7 @@ class ContactsMergeDuplicates extends Command // 5. Duplikat als zusammengeführt markieren if (!$this->dryRun) { - DB::table('contacts') + DB::table('customer') ->where('id', $dupeId) ->update([ 'merged_into_id' => $masterId, diff --git a/app/Console/Commands/SyncNewsletterKulturreisen.php b/app/Console/Commands/SyncNewsletterKulturreisen.php index a6c5fbc..c895505 100644 --- a/app/Console/Commands/SyncNewsletterKulturreisen.php +++ b/app/Console/Commands/SyncNewsletterKulturreisen.php @@ -193,8 +193,7 @@ class SyncNewsletterKulturreisen extends Command '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, + 'lead_id' => $booking->lead_id, ], ]); } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 59c585d..ca9146f 100755 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,7 +2,16 @@ namespace App\Exceptions; +use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Auth\AuthenticationException; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Session\TokenMismatchException; +use Illuminate\Support\Facades\Mail; +use Illuminate\Validation\ValidationException; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Throwable; class Handler extends ExceptionHandler @@ -37,6 +46,65 @@ class Handler extends ExceptionHandler public function report(Throwable $exception) { parent::report($exception); + + $e = $this->mapException($exception); + + if ($this->shouldMailServerError($e)) { + $this->sendEmail($e); + } + } + + /** + * E-Mail nur bei echten Serverfeilern (5xx bzw. unbehandelte Exceptions), nicht bei lokalem Entwickeln. + */ + protected function shouldMailServerError(Throwable $e): bool + { + if (app()->environment('local', 'testing')) { + return false; + } + + if (! config('app.exception_mail')) { + return false; + } + + return $this->exceptionIndicatesServerError($e); + } + + /** + * Entspricht dem, was üblicherweise als HTTP500 ausgeliefert würde. + */ + protected function exceptionIndicatesServerError(Throwable $e): bool + { + if ( + $e instanceof AuthenticationException + || $e instanceof AuthorizationException + || $e instanceof ModelNotFoundException + || $e instanceof ValidationException + || $e instanceof TokenMismatchException + ) { + return false; + } + + if ($e instanceof HttpExceptionInterface) { + return $e->getStatusCode() >= 500; + } + + return true; + } + + protected function exceptionMailContextLine(): string + { + if (app()->runningInConsole()) { + $argv = $_SERVER['argv'] ?? []; + + return 'CLI: ' . (count($argv) ? implode(' ', $argv) : php_sapi_name()); + } + + if (app()->bound('request') && request()) { + return request()->fullUrl(); + } + + return 'n/a'; } /** @@ -52,4 +120,28 @@ class Handler extends ExceptionHandler { return parent::render($request, $exception); } + + public function sendEmail(Throwable $exception) + { + try { + $e = FlattenException::create($exception); + $handler = new HtmlErrorRenderer(true); + $css = $handler->getStylesheet(); + $content = $handler->getBody($e); + $to = config('app.exception_mail'); + $subject = config('app.name') . ' Exception: ' . $this->exceptionMailContextLine(); + + if ($to) { + Mail::send('emails.exception', compact('css', 'content'), function ($message) use ($to, $subject) { + $message->to($to)->subject($subject); + }); + } + } catch (Throwable $ex) { + file_put_contents( + storage_path('logs/laravel-' . date('Y-m-d') . '.log'), + '[' . date('Y-m-d H:i:s') . '] exception-handler-error: ' . $ex->getMessage() . "\n", + FILE_APPEND + ); + } + } } diff --git a/app/Http/Controllers/API/BookingController.php b/app/Http/Controllers/API/BookingController.php index 25b6c91..a020ad4 100755 --- a/app/Http/Controllers/API/BookingController.php +++ b/app/Http/Controllers/API/BookingController.php @@ -34,9 +34,7 @@ class BookingController extends Controller $ret = [ 'url_v1' => make_old_url('/index.php/booking/' . $booking->id . '/edit'), 'url_v3' => route('booking_detail', $booking->id), - // API-Feld bleibt `lead_id` aus Abwärtskompatibilität für API-Konsumenten; - // Wert kommt nach Modul 3 Phase 2 aus booking.inquiry_id. - 'lead_id' => $booking->inquiry_id + 'lead_id' => $booking->lead_id ]; return response()->json(['success' => "import", "ret" => $ret], $this->successStatus); diff --git a/app/Http/Controllers/Admin/ReportController.php b/app/Http/Controllers/Admin/ReportController.php index ab45f3d..7b46918 100755 --- a/app/Http/Controllers/Admin/ReportController.php +++ b/app/Http/Controllers/Admin/ReportController.php @@ -400,10 +400,10 @@ class ReportController extends Controller $price = 0; $isset = []; foreach ($all as $v){ - if(!in_array($v->booking->inquiry_id, $isset)){ + if(!in_array($v->booking->lead_id, $isset)){ $price += $v->booking->isCanceled() ? $v->booking->getPriceCanceledRaw() : $v->booking->getPriceRaw(); } - $isset[] = $v->booking->inquiry_id; + $isset[] = $v->booking->lead_id; } return Util::_number_format($price); @@ -416,8 +416,8 @@ class ReportController extends Controller $price = 0; $isset = []; foreach ($all as $v){ - $price += in_array($v->booking->inquiry_id, $isset) ? 0 : $v->booking->getPriceTotalRaw(); - $isset[] = $v->booking->inquiry_id; + $price += in_array($v->booking->lead_id, $isset) ? 0 : $v->booking->getPriceTotalRaw(); + $isset[] = $v->booking->lead_id; } return Util::_number_format($price); }) @@ -429,8 +429,8 @@ class ReportController extends Controller $proceeds = 0; $isset = []; foreach ($all as $v){ - $proceeds += in_array($v->booking->inquiry_id, $isset) ? 0 : $v->booking->proceeds(true); - $isset[] = $v->booking->inquiry_id; + $proceeds += in_array($v->booking->lead_id, $isset) ? 0 : $v->booking->proceeds(true); + $isset[] = $v->booking->lead_id; } return Util::_number_format($proceeds); /*$all = $query->get(); @@ -562,7 +562,7 @@ class ReportController extends Controller $ctemps[$export->booking->id][] = array( 'Zähler' => $new ? $export->getCounter() : "", 'MyJack Nr.' => $new ? $export->booking->merlin_order_number : "", - 'CRM Nr' => $new ? $export->booking->inquiry_id : "", + 'CRM Nr' => $new ? $export->booking->lead_id : "", 'Kunde' => $new ? $export->booking->customer->name : "", 'Reisedatum' => $new ? $export->booking->getStartDateFormat() : "", 'Organisation' => $new ? ($export->booking->isCanceled() ? $export->booking->price_canceled : $export->booking->price) : "", @@ -630,7 +630,7 @@ class ReportController extends Controller $ctemps[$export->booking->id][] = array( 'Zähler' => $new ? $export->getCounter() : "", 'MyJack Nr.' => $new ? $export->booking->merlin_order_number : "", - 'CRM Nr' => $new ? $export->booking->inquiry_id : "", + 'CRM Nr' => $new ? $export->booking->lead_id : "", 'Kunde' => $new ? $export->booking->customer->name : "", 'Reisedatum' => $new ? $export->booking->getStartDateFormat() : "", 'Reiseland' => $new && $export->booking->travel_country ? $export->booking->travel_country->name : "", diff --git a/app/Http/Controllers/Admin/ReportLeadsController.php b/app/Http/Controllers/Admin/ReportLeadsController.php index ddfe25a..2f2b888 100644 --- a/app/Http/Controllers/Admin/ReportLeadsController.php +++ b/app/Http/Controllers/Admin/ReportLeadsController.php @@ -49,7 +49,7 @@ class ReportLeadsController extends Controller { $query = Lead::with('customer')->with('sf_guard_user')->with('status') - ->select('inquiries.*'); + ->select('lead.*'); //->where('deleted_at', '=', null); /* @@ -132,7 +132,7 @@ class ReportLeadsController extends Controller // $q->select('sent_at')->where('sent_at', DB::raw("(select max('sent_at') customer_mails)")); //) })->orderBy( LeadMail::select('sent_at') - ->whereColumn('lead_id', 'inquiries.id') + ->whereColumn('lead_id', 'lead.id') ->orderBy('sent_at', 'DESC') ->limit(1) , $order); diff --git a/app/Http/Controllers/Admin/ReportProviderController.php b/app/Http/Controllers/Admin/ReportProviderController.php index d9dbf9c..297a96a 100644 --- a/app/Http/Controllers/Admin/ReportProviderController.php +++ b/app/Http/Controllers/Admin/ReportProviderController.php @@ -104,10 +104,10 @@ class ReportProviderController extends Controller $price = 0; $isset = []; foreach ($all as $v){ - if(!in_array($v->booking->inquiry_id, $isset)){ + if(!in_array($v->booking->lead_id, $isset)){ $price += $v->booking->isCanceled() ? $v->booking->getPriceCanceledRaw() : $v->booking->getPriceRaw(); } - $isset[] = $v->booking->inquiry_id; + $isset[] = $v->booking->lead_id; } return Util::_number_format($price); @@ -120,8 +120,8 @@ class ReportProviderController extends Controller $price = 0; $isset = []; foreach ($all as $v){ - $price += in_array($v->booking->inquiry_id, $isset) ? 0 : $v->booking->getPriceTotalRaw(); - $isset[] = $v->booking->inquiry_id; + $price += in_array($v->booking->lead_id, $isset) ? 0 : $v->booking->getPriceTotalRaw(); + $isset[] = $v->booking->lead_id; } return Util::_number_format($price); }) @@ -133,8 +133,8 @@ class ReportProviderController extends Controller $proceeds = 0; $isset = []; foreach ($all as $v){ - $proceeds += in_array($v->booking->inquiry_id, $isset) ? 0 : $v->booking->proceeds(true); - $isset[] = $v->booking->inquiry_id; + $proceeds += in_array($v->booking->lead_id, $isset) ? 0 : $v->booking->proceeds(true); + $isset[] = $v->booking->lead_id; } return Util::_number_format($proceeds); /*$all = $query->get(); @@ -272,7 +272,7 @@ class ReportProviderController extends Controller $ctemps[$export->booking->id][] = array( 'Zähler' => $new ? $export->getCounter() : "", 'MyJack Nr.' => $new ? $export->booking->merlin_order_number : "", - 'CRM Nr' => $new ? $export->booking->inquiry_id : "", + 'CRM Nr' => $new ? $export->booking->lead_id : "", 'Kunde' => $new ? $export->booking->customer->name : "", 'Reisedatum' => $new ? $export->booking->getStartDateFormat() : "", 'bis' => $new ? $export->booking->getEndDateFormat() : "", @@ -346,7 +346,7 @@ class ReportProviderController extends Controller $ctemps[$export->booking->id][] = array( 'Zähler' => $new ? $export->getCounter() : "", 'MyJack Nr.' => $new ? $export->booking->merlin_order_number : "", - 'CRM Nr' => $new ? $export->booking->inquiry_id : "", + 'CRM Nr' => $new ? $export->booking->lead_id : "", 'Kunde' => $new ? $export->booking->customer->name : "", 'Reisedatum' => $new ? $export->booking->getStartDateFormat() : "", 'bis' => $new ? $export->booking->getEndDateFormat() : "", diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php index effb97d..08ef241 100644 --- a/app/Http/Controllers/ContactController.php +++ b/app/Http/Controllers/ContactController.php @@ -157,11 +157,11 @@ class ContactController extends Controller } DB::transaction(function () use ($masterId, $dupeId) { - DB::table('inquiries')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]); + DB::table('lead')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]); DB::table('booking')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]); DB::table('customer_mails')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]); DB::table('lead_mails')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]); - DB::table('contacts')->where('id', $dupeId)->update([ + DB::table('customer')->where('id', $dupeId)->update([ 'merged_into_id' => $masterId, 'merged_at' => now(), ]); @@ -175,16 +175,16 @@ class ContactController extends Controller private function countDuplicateGroups(string $type): int { return match ($type) { - 'email' => DB::table('contacts')->selectRaw('COUNT(*) as cnt')->whereNotNull('email')->where('email', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('email')->havingRaw('COUNT(*) > 1')->get()->count(), - 'name_birthdate' => DB::table('contacts')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('birthdate')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'birthdate')->havingRaw('COUNT(*) > 1')->get()->count(), - 'name_zip' => DB::table('contacts')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('zip')->where('zip', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'zip')->havingRaw('COUNT(*) > 1')->get()->count(), + 'email' => DB::table('customer')->selectRaw('COUNT(*) as cnt')->whereNotNull('email')->where('email', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('email')->havingRaw('COUNT(*) > 1')->get()->count(), + 'name_birthdate' => DB::table('customer')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('birthdate')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'birthdate')->havingRaw('COUNT(*) > 1')->get()->count(), + 'name_zip' => DB::table('customer')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('zip')->where('zip', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'zip')->havingRaw('COUNT(*) > 1')->get()->count(), default => 0, }; } private function findByEmail(): array { - return DB::table('contacts') + return DB::table('customer') ->selectRaw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids') ->whereNotNull('email')->where('email', '!=', '') ->whereNull('merged_into_id')->whereNull('deleted_at') @@ -194,7 +194,7 @@ class ContactController extends Controller private function findByNameBirthdate(): array { - return DB::table('contacts') + return DB::table('customer') ->selectRaw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids') ->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('birthdate') ->whereNull('merged_into_id')->whereNull('deleted_at') @@ -204,7 +204,7 @@ class ContactController extends Controller private function findByNameZip(): array { - return DB::table('contacts') + return DB::table('customer') ->selectRaw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids') ->whereNotNull('name')->whereNotNull('firstname') ->whereNotNull('zip')->where('zip', '!=', '') @@ -228,8 +228,8 @@ class ContactController extends Controller // Reihenfolge wichtig: select() zuerst, dann withCount() — sonst überschreibt // select() die COUNT-Subqueries die withCount() per addSelect() eingetragen hat. $query = $showDeleted - ? Contact::onlyTrashed()->withoutGlobalScope('not_merged')->select('contacts.*')->withCount(['leads', 'bookings']) - : Contact::select('contacts.*')->withCount(['leads', 'bookings']); + ? Contact::onlyTrashed()->withoutGlobalScope('not_merged')->select('customer.*')->withCount(['leads', 'bookings']) + : Contact::select('customer.*')->withCount(['leads', 'bookings']); // Zusatzfilter aus der UI (werden als extra GET-Parameter gesendet) if ($request->filled('filter_has_leads')) { @@ -275,7 +275,7 @@ class ContactController extends Controller ->orderColumn('deleted_at', 'customer.deleted_at $1') ->filterColumn('id', function ($query, $keyword) { if ($keyword !== '') { - $query->where('contacts.id', 'LIKE', '%' . $keyword . '%'); + $query->where('customer.id', 'LIKE', '%' . $keyword . '%'); } }) ->filterColumn('name', function ($query, $keyword) { diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index fc149ed..f52fb12 100755 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -68,7 +68,7 @@ class CustomerController extends Controller public function getCustomers() { - $query = Customer::with('salutation')->select('contacts.*'); + $query = Customer::with('salutation')->select('customer.*'); return \DataTables::eloquent($query) ->addColumn('action_edit', function (Customer $customer) { diff --git a/app/Http/Controllers/LeadController.php b/app/Http/Controllers/LeadController.php index a4d99fb..60d7202 100755 --- a/app/Http/Controllers/LeadController.php +++ b/app/Http/Controllers/LeadController.php @@ -232,7 +232,7 @@ class LeadController extends Controller public function getLeads() { - $query = Lead::with('customer')->with('sf_guard_user')->with('status')->select('inquiries.*'); + $query = Lead::with('customer')->with('sf_guard_user')->with('status')->select('lead.*'); return \DataTables::eloquent($query) ->addColumn('action_edit', function (Lead $lead) { @@ -296,7 +296,7 @@ class LeadController extends Controller // $q->select('sent_at')->where('sent_at', DB::raw("(select max('sent_at') customer_mails)")); //) })->orderBy( LeadMail::select('sent_at') - ->whereColumn('lead_id', 'inquiries.id') + ->whereColumn('lead_id', 'lead.id') ->orderBy('sent_at', 'DESC') ->limit(1) , $order); diff --git a/app/Http/Controllers/RequestController.php b/app/Http/Controllers/RequestController.php index a02f566..a7f21e5 100755 --- a/app/Http/Controllers/RequestController.php +++ b/app/Http/Controllers/RequestController.php @@ -89,7 +89,7 @@ class RequestController extends Controller wirte old where has state to new has travel_documents $bs = Booking::whereHas('arrangements', function($q){ $q->where('state', '!=', NULL); - })->where('inquiry_id', '!=', NULL)->where('new_drafts', 0)->get(); + })->where('lead_id', '!=', NULL)->where('new_drafts', 0)->get(); foreach ($bs as $b){ $b->travel_documents = true; @@ -101,7 +101,7 @@ class RequestController extends Controller private function getSearchRequests() { - $query = Booking::with('lead')->with('customer')->with('customer_mails')->with('customer_mails')->select('booking.*')->where('inquiry_id', '!=', NULL); + $query = Booking::with('lead')->with('customer')->with('customer_mails')->with('customer_mails')->select('booking.*')->where('lead_id', '!=', NULL); if (Request::get('full_firstname_search') != "") { $query->whereHas('customer', function ($q) { @@ -241,7 +241,7 @@ class RequestController extends Controller } if (Request::get('full_lead_id_search') != "") { - $query->where('inquiry_id', 'LIKE', '%' . Request::get('full_lead_id_search') . '%'); + $query->where('lead_id', 'LIKE', '%' . Request::get('full_lead_id_search') . '%'); } if (Request::get('full_booking_id_search') != "") { $query->where('id', 'LIKE', '%' . Request::get('full_booking_id_search') . '%'); @@ -398,10 +398,10 @@ class RequestController extends Controller return '' . $booking->id . ''; }) ->addColumn('action_lead_edit', function (Booking $booking) { - return ''; + return ''; }) ->addColumn('lead_id', function (Booking $booking) { - return '' . $booking->inquiry_id . ''; + return '' . $booking->lead_id . ''; }) ->addColumn('travel_country_id', function (Booking $booking) { return '' . ($booking->travel_country_id ? $booking->travel_country->name : "-") . ''; @@ -523,7 +523,7 @@ class RequestController extends Controller } }) */ - ->orderColumn('lead_id', 'inquiry_id $1') + ->orderColumn('lead_id', 'lead_id $1') ->orderColumn('id', 'id $1') ->orderColumn('travel_country_id', 'travel_country_id $1') ->orderColumn('travelagenda_id', 'travelagenda_id $1') diff --git a/app/Models/Booking.php b/app/Models/Booking.php index f3bf11c..3d61b89 100644 --- a/app/Models/Booking.php +++ b/app/Models/Booking.php @@ -19,7 +19,7 @@ use Illuminate\Database\Eloquent\Collection; * @property int $id * @property Carbon $booking_date * @property int $customer_id - * @property int $inquiry_id + * @property int $lead_id * @property bool $new_drafts * @property int $sf_guard_user_id * @property int $branch_id @@ -212,8 +212,7 @@ class Booking extends Model protected $casts = [ 'customer_id' => 'int', - 'inquiry_id' => 'int', - 'offer_id' => 'int', + 'lead_id' => 'int', 'new_drafts' => 'bool', 'sf_guard_user_id' => 'int', 'branch_id' => 'int', @@ -257,8 +256,7 @@ class Booking extends Model protected $fillable = [ 'booking_date', 'customer_id', - 'inquiry_id', - 'offer_id', + 'lead_id', 'new_drafts', 'sf_guard_user_id', 'branch_id', @@ -395,29 +393,9 @@ class Booking extends Model return $this->belongsTo(Customer::class); } - /** - * Lead/Inquiry der Buchung. - * FK-Spalte `inquiry_id` (vormals `lead_id` — Modul 3 Phase 2 Rename). - * Methodenname bleibt `lead()` für Legacy-Kompatibilität; {@see self::inquiry()} - * ist der fachlich korrekte Alias und sollte in neuem Code verwendet werden. - */ public function lead() { - return $this->belongsTo(Lead::class, 'inquiry_id'); - } - - public function inquiry() - { - return $this->belongsTo(Lead::class, 'inquiry_id'); - } - - /** - * Angebot, aus dem diese Buchung entstanden ist (Modul 6, Ticket B8). - * Nullable — nicht jede Buchung hat einen Angebots-Vorlauf. - */ - public function offer() - { - return $this->belongsTo(\App\Models\Offer::class); + return $this->belongsTo(Lead::class); } public function sf_guard_user() diff --git a/app/Models/Contact.php b/app/Models/Contact.php index 2cd1868..6d941e8 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -18,7 +18,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * - merged_into_id + merged_at in $fillable * - mergedInto() / mergedContacts() Beziehungen * - * Tabellen-Name: 'contacts' (nach Phase 2 — RENAME TABLE customer → contacts). + * Tabellen-Name: 'customer' (wird in Phase 2 in 'contacts' umbenannt). * * @property int $id * @property int|null $salutation_id @@ -50,7 +50,7 @@ class Contact extends Model protected $connection = 'mysql'; - protected $table = 'contacts'; + protected $table = 'customer'; protected $casts = [ 'salutation_id' => 'int', diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 9853334..436b3ee 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -88,12 +88,7 @@ class Customer extends Model protected $connection = 'mysql'; - /** - * Modul 3 Phase 2: customer → contacts (RENAME TABLE). - * Der Model-Name bleibt aus Kompatibilität zum Legacy-Code bestehen; - * für die Neuimplementierung steht {@see Contact} bereit. - */ - protected $table = 'contacts'; + protected $table = 'customer'; protected $casts = [ 'salutation_id' => 'int', diff --git a/app/Models/Lead.php b/app/Models/Lead.php index f9441f5..227bd38 100644 --- a/app/Models/Lead.php +++ b/app/Models/Lead.php @@ -112,12 +112,7 @@ class Lead extends Model protected $connection = 'mysql'; - /** - * Modul 3 Phase 2: lead → inquiries (RENAME TABLE). - * Model-Name bleibt (um Breaking Changes in der gesamten Codebase zu vermeiden); - * fachlich ist das Modell jetzt eine "Inquiry" (Anfrage). - */ - protected $table = 'inquiries'; + protected $table = 'lead'; protected $casts = [ 'customer_id' => 'int', diff --git a/app/Models/Offer.php b/app/Models/Offer.php index 02b503c..dd276b9 100644 --- a/app/Models/Offer.php +++ b/app/Models/Offer.php @@ -1,132 +1,56 @@ 'int', + 'total' => 'float', + 'binary_data' => 'boolean' + ]; - protected $table = 'offers'; + protected $fillable = [ + 'lead_id', + 'total', + 'binary_data' + ]; - protected $fillable = [ - 'offer_number', - 'contact_id', - 'inquiry_id', - 'booking_id', - 'status', - 'current_version_id', - 'created_by', - ]; - - protected $casts = [ - 'contact_id' => 'int', - 'inquiry_id' => 'int', - 'booking_id' => 'int', - 'current_version_id' => 'int', - 'created_by' => 'int', - ]; - - public function contact(): BelongsTo - { - return $this->belongsTo(Contact::class); - } - - public function inquiry(): BelongsTo - { - // Nach Modul 3 Phase 2: `Lead`-Model bildet die `inquiries`-Tabelle ab - return $this->belongsTo(Lead::class, 'inquiry_id'); - } - - public function booking(): BelongsTo - { - return $this->belongsTo(Booking::class); - } - - public function currentVersion(): BelongsTo - { - return $this->belongsTo(OfferVersion::class, 'current_version_id'); - } - - public function versions(): HasMany - { - return $this->hasMany(OfferVersion::class)->orderBy('version_no'); - } - - public function accessTokens(): HasMany - { - return $this->hasMany(OfferAccessToken::class); - } - - public function creator(): BelongsTo - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function scopeStatus(Builder $q, string $status): Builder - { - return $q->where('status', $status); - } - - public function scopeOpen(Builder $q): Builder - { - return $q->whereIn('status', [self::STATUS_DRAFT, self::STATUS_SENT]); - } - - public function isEditable(): bool - { - return in_array($this->status, [self::STATUS_DRAFT, self::STATUS_SENT], true); - } + public function lead() + { + return $this->belongsTo(Lead::class); + } } diff --git a/app/Repositories/BookingPDFRepository.php b/app/Repositories/BookingPDFRepository.php index e5ba139..dc4d313 100644 --- a/app/Repositories/BookingPDFRepository.php +++ b/app/Repositories/BookingPDFRepository.php @@ -63,13 +63,13 @@ class BookingPDFRepository extends BaseRepository { $document = new stdClass(); $document->name = 'registration'; - $document->number = $this->model->inquiry_id; + $document->number = $this->model->lead_id; $document->title = 'BUCHUNGSAUFTRAG'; $document->voucher = null; $document->date = now(); $document->total = $this->model->getPriceRaw(); $dir = $this->getDirPath('pdf', 'booking', $document->date->format('Y')); - $filename = "Buchnungsauftrag-" . $this->model->inquiry_id . ".pdf"; + $filename = "Buchnungsauftrag-" . $this->model->lead_id . ".pdf"; $pdf_file = new CreatePDF('pdf.booking_registration'); $data = [ 'booking' => $this->model, @@ -85,7 +85,7 @@ class BookingPDFRepository extends BaseRepository { $document = new stdClass(); $document->name = 'confirmation'; - $document->number = $this->model->inquiry_id; + $document->number = $this->model->lead_id; $document->title = 'REISEBESTÄTIGUNG'; $document->voucher = null; $document->date = now(); @@ -104,7 +104,7 @@ class BookingPDFRepository extends BaseRepository $document->final_payment_date = date('Y-m-d'); } $dir = $this->getDirPath('pdf', 'booking', $document->date->format('Y')); - $filename = "Reisebestätigung-" . $this->model->inquiry_id . ".pdf"; + $filename = "Reisebestätigung-" . $this->model->lead_id . ".pdf"; $pdf_file = new CreatePDF('pdf.booking_confirmation'); $data = [ @@ -160,14 +160,14 @@ class BookingPDFRepository extends BaseRepository { $document = new stdClass(); $document->name = 'voucher'; - $document->number = $this->model->inquiry_id; + $document->number = $this->model->lead_id; $document->name = 'voucher'; $document->title = $agency ? 'VOUCHER Agentur' : 'VOUCHER'; $document->voucher = $agency ? 'agency' : 'client'; $document->date = now(); $dir = $this->getDirPath('pdf', 'voucher', $document->date->format('Y')); - $filename = ($agency ? 'VoucherAgentur' : 'Voucher') . "-" . $this->model->inquiry_id . ".pdf"; + $filename = ($agency ? 'VoucherAgentur' : 'Voucher') . "-" . $this->model->lead_id . ".pdf"; $pdf_file = new CreatePDF('pdf.booking_voucher'); $data = [ @@ -224,7 +224,7 @@ class BookingPDFRepository extends BaseRepository //init document $document = new stdClass(); $document->name = $identifier; - $document->number = $this->model->inquiry_id; + $document->number = $this->model->lead_id; $document->title = 'STORNOBESTÄTIGUNG'; $document->voucher = null; $document->date = Carbon::parse($data['storno_print']); @@ -253,7 +253,7 @@ class BookingPDFRepository extends BaseRepository $dir = $this->getDirPath('pdf', 'storno', $document->date->format('Y')); - $filename = "Reisestornierung -" . $this->model->inquiry_id . ".pdf"; + $filename = "Reisestornierung -" . $this->model->lead_id . ".pdf"; $pdf_file = new CreatePDF('pdf.booking_storno'); $data = [ @@ -288,9 +288,7 @@ class BookingPDFRepository extends BaseRepository $fill = [ 'booking_id' => $this->model->id, 'customer_id' => $this->model->customer_id, - // booking_documents.lead_id ist ein Shadow-Feld von booking.inquiry_id; - // die Spalte selbst wird von Phase 2 nicht umbenannt. - 'lead_id' => $this->model->inquiry_id, + 'lead_id' => $this->model->lead_id, 'identifier' => $identifier, 'filename' => $filename, 'dir' => $dir, diff --git a/app/Repositories/CustomerMailRepository.php b/app/Repositories/CustomerMailRepository.php index 6397f0c..c1b1226 100644 --- a/app/Repositories/CustomerMailRepository.php +++ b/app/Repositories/CustomerMailRepository.php @@ -135,8 +135,7 @@ class CustomerMailRepository extends BaseRepository { $customer_mail->fill([ 'booking_id' => $booking->id, 'customer_id' => $booking->customer_id, - // customer_mails.lead_id-Spalte bleibt unverändert; Wert kommt aus booking.inquiry_id - 'lead_id' => $booking->inquiry_id, + 'lead_id' => $booking->lead_id, 'is_answer' => $is_answer, 'reply_id' => $reply_id, 'email' => $mail_from, @@ -154,8 +153,7 @@ class CustomerMailRepository extends BaseRepository { $customer_mail = CustomerMail::create([ 'booking_id' => $booking->id, 'customer_id' => $booking->customer_id, - // customer_mails.lead_id-Spalte bleibt unverändert; Wert kommt aus booking.inquiry_id - 'lead_id' => $booking->inquiry_id, + 'lead_id' => $booking->lead_id, 'is_answer' => $is_answer, 'reply_id' => $reply_id, 'email' => $mail_from, @@ -302,7 +300,7 @@ class CustomerMailRepository extends BaseRepository { $value->id = $customer_mail->booking_id; $value->booking = $booking; $value->show = 'single'; - $value->lead_title_id = " - (".$value->booking->inquiry_id.")"; + $value->lead_title_id = " - (".$value->booking->lead_id.")"; $tmp = []; @@ -344,7 +342,7 @@ class CustomerMailRepository extends BaseRepository { $value->booking = $booking; $value->show = 'single'; $value->draft = true; - $value->lead_title_id = " - (".$value->booking->inquiry_id.")"; + $value->lead_title_id = " - (".$value->booking->lead_id.")"; }else{ //multi @@ -381,8 +379,8 @@ class CustomerMailRepository extends BaseRepository { $value->draft = false; $value->booking = $booking; $value->message = ""; - $value->subject = " - (".$value->booking->inquiry_id.")"; - $value->lead_title_id = " - (".$value->booking->inquiry_id.")"; + $value->subject = " - (".$value->booking->lead_id.")"; + $value->lead_title_id = " - (".$value->booking->lead_id.")"; $value->s_placeholder = "Betreff des Kunden"; $value->m_placeholder = "Nachricht des Kunden"; if(isset($data['customer_mail_id']) && $customer_mail = CustomerMail::find($data['customer_mail_id'])){ diff --git a/app/Repositories/LeadRepository.php b/app/Repositories/LeadRepository.php index ad0ac87..929426d 100644 --- a/app/Repositories/LeadRepository.php +++ b/app/Repositories/LeadRepository.php @@ -134,7 +134,7 @@ class LeadRepository extends BaseRepository { $data = [ 'booking_date' => date('Y-m-d'), //now 'customer_id' => $this->model->customer->id, - 'inquiry_id' => $this->model->id, + 'lead_id' => $this->model->id, 'new_drafts' => 1, 'sf_guard_user_id' => $this->model->sf_guard_user_id, 'branch_id' => 4, diff --git a/app/Services/BookingImport.php b/app/Services/BookingImport.php index 2e6988d..acff286 100644 --- a/app/Services/BookingImport.php +++ b/app/Services/BookingImport.php @@ -53,7 +53,7 @@ class BookingImport $data = [ 'booking_date' => $travel_booking->created->format('Y-m-d'), 'customer_id' => $customer->id, - 'inquiry_id' => $lead->id, + 'lead_id' => $lead->id, 'new_drafts' => $travel_booking->drafts === null ? 0 : 1, 'sf_guard_user_id' => 15, 'branch_id' => 4, diff --git a/bootstrap/cache/config.php b/bootstrap/cache/config.php index 66c2e87..3ecca50 100644 --- a/bootstrap/cache/config.php +++ b/bootstrap/cache/config.php @@ -1,6 +1,6 @@ - - array ( + + array( 'name' => 'STERN TOURS CRM', 'env' => 'local', 'debug' => true, @@ -15,8 +15,8 @@ 'key' => 'base64:cxq+xNckU1xLwp8V9Bfj9+nOK5iZL6urcZ1EBO8usXg=', 'cipher' => 'AES-256-CBC', 'success_key' => 'f6077389c9ce710e554763a5de02c8ec', - 'providers' => - array ( + 'providers' => + array( 0 => 'Illuminate\\Auth\\AuthServiceProvider', 1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', 2 => 'Illuminate\\Bus\\BusServiceProvider', @@ -54,8 +54,8 @@ 34 => 'Yajra\\DataTables\\DataTablesServiceProvider', 35 => 'Reliese\\Coders\\CodersServiceProvider', ), - 'aliases' => - array ( + 'aliases' => + array( 'App' => 'Illuminate\\Support\\Facades\\App', 'Arr' => 'Illuminate\\Support\\Arr', 'Artisan' => 'Illuminate\\Support\\Facades\\Artisan', @@ -103,46 +103,46 @@ 'PDF' => 'Barryvdh\\DomPDF\\Facade', ), ), - 'auth' => - array ( - 'defaults' => - array ( + 'auth' => + array( + 'defaults' => + array( 'guard' => 'web', 'passwords' => 'users', ), - 'guards' => - array ( - 'web' => - array ( + 'guards' => + array( + 'web' => + array( 'driver' => 'session', 'provider' => 'users', ), - 'api' => - array ( + 'api' => + array( 'driver' => 'passport', 'provider' => 'users', ), ), - 'providers' => - array ( - 'users' => - array ( + 'providers' => + array( + 'users' => + array( 'driver' => 'eloquent', 'model' => 'App\\User', ), ), - 'passwords' => - array ( - 'users' => - array ( + 'passwords' => + array( + 'users' => + array( 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, ), ), ), - 'booking' => - array ( + 'booking' => + array( 'identifier_general' => 'booking-pdf-g-', 'identifier_general_name' => 'booking-pdf-general-name', 'identifier_content' => 'booking-pdf-c-', @@ -153,105 +153,104 @@ 'coupon_default_value' => '50,00', 'coupon_valid_date_month' => 24, ), - 'broadcasting' => - array ( + 'broadcasting' => + array( 'default' => 'log', - 'connections' => - array ( - 'pusher' => - array ( + 'connections' => + array( + 'pusher' => + array( 'driver' => 'pusher', 'key' => '', 'secret' => '', 'app_id' => '', - 'options' => - array ( + 'options' => + array( 'cluster' => 'mt1', 'encrypted' => true, ), ), - 'redis' => - array ( + 'redis' => + array( 'driver' => 'redis', 'connection' => 'default', ), - 'log' => - array ( + 'log' => + array( 'driver' => 'log', ), - 'null' => - array ( + 'null' => + array( 'driver' => 'null', ), ), ), - 'cache' => - array ( + 'cache' => + array( 'default' => 'file', - 'stores' => - array ( - 'apc' => - array ( + 'stores' => + array( + 'apc' => + array( 'driver' => 'apc', ), - 'array' => - array ( + 'array' => + array( 'driver' => 'array', ), - 'database' => - array ( + 'database' => + array( 'driver' => 'database', 'table' => 'cache', 'connection' => NULL, ), - 'file' => - array ( + 'file' => + array( 'driver' => 'file', 'path' => '/workspace/mein.sterntours.de/storage/framework/cache/data', ), - 'memcached' => - array ( + 'memcached' => + array( 'driver' => 'memcached', 'persistent_id' => NULL, - 'sasl' => - array ( + 'sasl' => + array( 0 => NULL, 1 => NULL, ), - 'options' => - array ( - ), - 'servers' => - array ( - 0 => - array ( + 'options' => + array(), + 'servers' => + array( + 0 => + array( 'host' => '127.0.0.1', 'port' => 11211, 'weight' => 100, ), ), ), - 'redis' => - array ( + 'redis' => + array( 'driver' => 'redis', 'connection' => 'default', ), ), 'prefix' => 'stern_tours_crm_cache', ), - 'database' => - array ( + 'database' => + array( 'default' => 'mysql', - 'connections' => - array ( - 'sqlite' => - array ( + 'connections' => + array( + 'sqlite' => + array( 'driver' => 'sqlite', 'database' => 'stern_crm', 'prefix' => '', ), - 'mysql' => - array ( + 'mysql' => + array( 'driver' => 'mysql', 'host' => 'global-mysql', 'port' => '3306', @@ -265,8 +264,8 @@ 'strict' => true, 'engine' => NULL, ), - 'mysql_stern' => - array ( + 'mysql_stern' => + array( 'driver' => 'mysql', 'host' => 'global-mysql', 'port' => '3306', @@ -280,8 +279,8 @@ 'strict' => true, 'engine' => NULL, ), - 'pgsql' => - array ( + 'pgsql' => + array( 'driver' => 'pgsql', 'host' => 'global-mysql', 'port' => '3306', @@ -293,8 +292,8 @@ 'schema' => 'public', 'sslmode' => 'prefer', ), - 'sqlsrv' => - array ( + 'sqlsrv' => + array( 'driver' => 'sqlsrv', 'host' => 'global-mysql', 'port' => '3306', @@ -306,11 +305,11 @@ ), ), 'migrations' => 'migrations', - 'redis' => - array ( + 'redis' => + array( 'client' => 'predis', - 'default' => - array ( + 'default' => + array( 'host' => 'global-redis', 'password' => NULL, 'port' => '6379', @@ -318,71 +317,69 @@ ), ), ), - 'datatables' => - array ( - 'search' => - array ( + 'datatables' => + array( + 'search' => + array( 'smart' => true, 'multi_term' => true, 'case_insensitive' => true, 'use_wildcards' => false, ), 'index_column' => 'DT_Row_Index', - 'engines' => - array ( + 'engines' => + array( 'eloquent' => 'Yajra\\DataTables\\EloquentDataTable', 'query' => 'Yajra\\DataTables\\QueryDataTable', 'collection' => 'Yajra\\DataTables\\CollectionDataTable', 'resource' => 'Yajra\\DataTables\\ApiResourceDataTable', ), - 'builders' => - array ( - ), + 'builders' => + array(), 'nulls_last_sql' => '%s %s NULLS LAST', 'error' => NULL, - 'columns' => - array ( - 'excess' => - array ( + 'columns' => + array( + 'excess' => + array( 0 => 'rn', 1 => 'row_num', ), 'escape' => '*', - 'raw' => - array ( + 'raw' => + array( 0 => 'action', ), - 'blacklist' => - array ( + 'blacklist' => + array( 0 => 'password', 1 => 'remember_token', ), 'whitelist' => '*', ), - 'json' => - array ( - 'header' => - array ( - ), + 'json' => + array( + 'header' => + array(), 'options' => 0, ), - 'callback' => - array ( + 'callback' => + array( 0 => '$', 1 => '$.', 2 => 'function', ), ), - 'debugbar' => - array ( + 'debugbar' => + array( 'enabled' => NULL, 'hide_empty_tabs' => true, - 'except' => - array ( + 'except' => + array( 0 => 'telescope*', ), - 'storage' => - array ( + 'storage' => + array( 'enabled' => false, 'driver' => 'file', 'path' => '/workspace/mein.sterntours.de/storage/debugbar', @@ -401,8 +398,8 @@ 'error_handler' => false, 'error_level' => 32767, 'clockwork' => false, - 'collectors' => - array ( + 'collectors' => + array( 'phpinfo' => true, 'messages' => true, 'time' => true, @@ -426,87 +423,83 @@ 'cache' => false, 'models' => false, ), - 'options' => - array ( - 'auth' => - array ( + 'options' => + array( + 'auth' => + array( 'show_name' => true, ), - 'db' => - array ( + 'db' => + array( 'with_params' => true, 'backtrace' => true, 'timeline' => false, - 'explain' => - array ( + 'explain' => + array( 'enabled' => false, - 'types' => - array ( + 'types' => + array( 0 => 'SELECT', ), ), 'hints' => true, ), - 'mail' => - array ( + 'mail' => + array( 'full_log' => false, ), - 'views' => - array ( + 'views' => + array( 'data' => false, ), - 'route' => - array ( + 'route' => + array( 'label' => true, ), - 'logs' => - array ( + 'logs' => + array( 'file' => NULL, ), - 'cache' => - array ( + 'cache' => + array( 'values' => true, ), ), 'inject' => true, 'route_prefix' => '_debugbar', - 'route_middleware' => - array ( - ), + 'route_middleware' => + array(), 'route_domain' => NULL, 'theme' => 'auto', 'debug_backtrace_limit' => 50, ), - 'dompdf' => - array ( + 'dompdf' => + array( 'show_warnings' => false, 'public_path' => NULL, 'convert_entities' => true, - 'options' => - array ( + 'options' => + array( 'font_dir' => '/workspace/mein.sterntours.de/storage/fonts', 'font_cache' => '/workspace/mein.sterntours.de/storage/fonts', 'temp_dir' => '/tmp', 'chroot' => '/workspace/mein.sterntours.de', - 'allowed_protocols' => - array ( - 'file://' => - array ( - 'rules' => - array ( - ), + 'allowed_protocols' => + array( + 'file://' => + array( + 'rules' => + array(), ), - 'http://' => - array ( - 'rules' => - array ( - ), + 'http://' => + array( + 'rules' => + array(), ), - 'https://' => - array ( - 'rules' => - array ( - ), + 'https://' => + array( + 'rules' => + array(), ), ), 'log_output_file' => NULL, @@ -524,8 +517,8 @@ 'enable_html5_parser' => true, ), 'orientation' => 'portrait', - 'defines' => - array ( + 'defines' => + array( 'font_dir' => '/workspace/mein.sterntours.de/storage/fonts/', 'font_cache' => '/workspace/mein.sterntours.de/storage/fonts/', 'temp_dir' => '/tmp', @@ -543,14 +536,14 @@ 'enable_html5_parser' => false, ), ), - 'excel' => - array ( - 'exports' => - array ( + 'excel' => + array( + 'exports' => + array( 'chunk_size' => 1000, 'pre_calculate_formulas' => false, - 'csv' => - array ( + 'csv' => + array( 'delimiter' => ',', 'enclosure' => '"', 'line_ending' => ' @@ -560,15 +553,15 @@ 'excel_compatibility' => false, ), ), - 'imports' => - array ( + 'imports' => + array( 'read_only' => true, - 'heading_row' => - array ( + 'heading_row' => + array( 'formatter' => 'slug', ), - 'csv' => - array ( + 'csv' => + array( 'delimiter' => ',', 'enclosure' => '"', 'escape_character' => '\\', @@ -576,8 +569,8 @@ 'input_encoding' => 'UTF-8', ), ), - 'extension_detector' => - array ( + 'extension_detector' => + array( 'xlsx' => 'Xlsx', 'xlsm' => 'Xlsx', 'xltx' => 'Xlsx', @@ -595,114 +588,114 @@ 'tsv' => 'Csv', 'pdf' => 'Dompdf', ), - 'value_binder' => - array ( + 'value_binder' => + array( 'default' => 'Maatwebsite\\Excel\\DefaultValueBinder', ), - 'cache' => - array ( + 'cache' => + array( 'driver' => 'memory', - 'batch' => - array ( + 'batch' => + array( 'memory_limit' => 60000, ), - 'illuminate' => - array ( + 'illuminate' => + array( 'store' => NULL, ), 'default_ttl' => 10800, ), - 'transactions' => - array ( + 'transactions' => + array( 'handler' => 'db', ), - 'temporary_files' => - array ( + 'temporary_files' => + array( 'local_path' => '/tmp', 'remote_disk' => NULL, ), ), - 'fewo' => - array ( + 'fewo' => + array( 'identifier_content' => 'fewo-pdf-general', 'identifier_fewo' => 'fewo-pdf-', ), - 'filesystems' => - array ( + 'filesystems' => + array( 'default' => 'local', 'cloud' => 's3', - 'disks' => - array ( - 'local' => - array ( + 'disks' => + array( + 'local' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app', ), - 'public' => - array ( + 'public' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app/public', 'url' => 'https://mein.sterntours.test/storage', 'visibility' => 'public', ), - 'customer' => - array ( + 'customer' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app/customer', 'url' => 'https://mein.sterntours.test/storage/customer', 'visibility' => 'public', ), - 'travel_user' => - array ( + 'travel_user' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app/travel_user', 'url' => 'https://mein.sterntours.test/storage/travel_user', 'visibility' => 'public', ), - 'booking' => - array ( + 'booking' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app/booking', 'url' => 'https://mein.sterntours.test/storage/booking', 'visibility' => 'public', ), - 'general' => - array ( + 'general' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app/general', 'url' => 'https://mein.sterntours.test/storage/general', 'visibility' => 'public', ), - 'booking_fewo' => - array ( + 'booking_fewo' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app/booking_fewo', 'url' => 'https://mein.sterntours.test/storage/booking_fewo', 'visibility' => 'public', ), - 'lead' => - array ( + 'lead' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app/lead', 'url' => 'https://mein.sterntours.test/storage/lead', 'visibility' => 'public', ), - 'fewo_invoices' => - array ( + 'fewo_invoices' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app/fewo/invoices', 'url' => 'https://mein.sterntours.test/storage/fewo/invoices', 'visibility' => 'public', ), - 'fewo_infos' => - array ( + 'fewo_infos' => + array( 'driver' => 'local', 'root' => '/workspace/mein.sterntours.de/storage/app/fewo/infos', 'url' => 'https://mein.sterntours.test/storage/fewo/infos', 'visibility' => 'public', ), - 's3' => - array ( + 's3' => + array( 'driver' => 's3', 'key' => NULL, 'secret' => NULL, @@ -712,15 +705,15 @@ ), ), ), - 'fpdf' => - array ( + 'fpdf' => + array( 'orientation' => 'P', 'unit' => 'mm', 'size' => 'A4', 'useVaporHeaders' => false, ), - 'google2fa' => - array ( + 'google2fa' => + array( 'enabled' => true, 'lifetime' => 0, 'keep_alive' => true, @@ -732,8 +725,8 @@ 'forbid_old_passwords' => false, 'otp_secret_column' => 'secret_key', 'view' => 'auth.google2fa', - 'error_messages' => - array ( + 'error_messages' => + array( 'wrong_otp' => 'Das \'One Time Password\' ist falsch.', 'cannot_be_empty' => 'Das \'One Time Password\' kann nicht leer sein.', 'unknown' => 'Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.', @@ -741,22 +734,22 @@ 'throw_exceptions' => true, 'qrcode_image_backend' => 'svg', ), - 'hashing' => - array ( + 'hashing' => + array( 'driver' => 'bcrypt', - 'bcrypt' => - array ( + 'bcrypt' => + array( 'rounds' => 10, ), - 'argon' => - array ( + 'argon' => + array( 'memory' => 1024, 'threads' => 2, 'time' => 2, ), ), - 'ide-helper' => - array ( + 'ide-helper' => + array( 'filename' => '_ide_helper', 'models_filename' => '_ide_helper_models.php', 'meta_filename' => '.phpstorm.meta.php', @@ -767,37 +760,35 @@ 'write_model_relation_count_properties' => true, 'write_eloquent_model_mixins' => false, 'include_helpers' => false, - 'helper_files' => - array ( + 'helper_files' => + array( 0 => '/workspace/mein.sterntours.de/vendor/laravel/framework/src/Illuminate/Support/helpers.php', ), - 'model_locations' => - array ( + 'model_locations' => + array( 0 => 'app', 1 => 'packages', ), - 'ignored_models' => - array ( - ), - 'model_hooks' => - array ( - ), - 'extra' => - array ( - 'Eloquent' => - array ( + 'ignored_models' => + array(), + 'model_hooks' => + array(), + 'extra' => + array( + 'Eloquent' => + array( 0 => 'Illuminate\\Database\\Eloquent\\Builder', 1 => 'Illuminate\\Database\\Query\\Builder', ), - 'Session' => - array ( + 'Session' => + array( 0 => 'Illuminate\\Session\\Store', ), ), - 'magic' => - array ( - 'Log' => - array ( + 'magic' => + array( + 'Log' => + array( 'debug' => 'Monolog\\Logger::addDebug', 'info' => 'Monolog\\Logger::addInfo', 'notice' => 'Monolog\\Logger::addNotice', @@ -808,53 +799,48 @@ 'emergency' => 'Monolog\\Logger::addEmergency', ), ), - 'interfaces' => - array ( - ), + 'interfaces' => + array(), 'model_camel_case_properties' => false, - 'type_overrides' => - array ( + 'type_overrides' => + array( 'integer' => 'int', 'boolean' => 'bool', ), 'include_class_docblocks' => false, 'force_fqn' => false, 'use_generics_annotations' => true, - 'additional_relation_types' => - array ( - ), - 'additional_relation_return_types' => - array ( - ), - 'post_migrate' => - array ( - ), + 'additional_relation_types' => + array(), + 'additional_relation_return_types' => + array(), + 'post_migrate' => + array(), 'format' => 'php', - 'custom_db_types' => - array ( - ), + 'custom_db_types' => + array(), ), - 'image' => - array ( + 'image' => + array( 'driver' => 'gd', ), - 'lfm' => - array ( + 'lfm' => + array( 'use_package_routes' => true, 'allow_multi_user' => false, 'allow_share_folder' => false, 'user_folder_name' => 'IqContent\\LaravelFilemanager\\Handlers\\ConfigHandler', 'shared_folder_name' => 'shares', 'thumb_folder_name' => 'thumbs', - 'folder_categories' => - array ( - 'file' => - array ( + 'folder_categories' => + array( + 'file' => + array( 'folder_name' => 'files', 'startup_view' => 'grid', 'max_size' => 50000, - 'valid_mime' => - array ( + 'valid_mime' => + array( 0 => 'image/jpeg', 1 => 'image/pjpeg', 2 => 'image/png', @@ -870,13 +856,13 @@ 12 => 'application/excel', ), ), - 'image' => - array ( + 'image' => + array( 'folder_name' => 'photos', 'startup_view' => 'list', 'max_size' => 50000, - 'valid_mime' => - array ( + 'valid_mime' => + array( 0 => 'image/jpeg', 1 => 'image/pjpeg', 2 => 'image/png', @@ -898,8 +884,8 @@ 'should_change_file_mode' => true, 'over_write_on_duplicate' => false, 'should_create_thumbnails' => true, - 'raster_mimetypes' => - array ( + 'raster_mimetypes' => + array( 0 => 'image/jpeg', 1 => 'image/pjpeg', 2 => 'image/png', @@ -909,8 +895,8 @@ 'default_color' => '#ffc926', 'resize_aspectRatio' => false, 'resize_containment' => true, - 'file_type_array' => - array ( + 'file_type_array' => + array( 'pdf' => 'Adobe Acrobat', 'doc' => 'Microsoft Word', 'docx' => 'Microsoft Word', @@ -924,8 +910,8 @@ 'ppt' => 'Microsoft PowerPoint', 'pptx' => 'Microsoft PowerPoint', ), - 'file_icon_array' => - array ( + 'file_icon_array' => + array( 'pdf' => 'fa-file-pdf', 'doc' => 'fa-file-word', 'docx' => 'fa-file-word', @@ -944,17 +930,17 @@ 'dwg' => 'fa-file-image', 'youtube' => 'fab fa-youtube-square', ), - 'php_ini_overrides' => - array ( + 'php_ini_overrides' => + array( 'memory_limit' => '256M', ), ), - 'localization' => - array ( - 'supportedLocales' => - array ( - 'de' => - array ( + 'localization' => + array( + 'supportedLocales' => + array( + 'de' => + array( 'name' => 'German', 'script' => 'Latn', 'native' => 'Deutsch', @@ -962,61 +948,61 @@ ), ), ), - 'logging' => - array ( + 'logging' => + array( 'default' => 'stack', - 'channels' => - array ( - 'stack' => - array ( + 'channels' => + array( + 'stack' => + array( 'driver' => 'stack', - 'channels' => - array ( + 'channels' => + array( 0 => 'single', ), ), - 'single' => - array ( + 'single' => + array( 'driver' => 'single', 'path' => '/workspace/mein.sterntours.de/storage/logs/laravel.log', 'level' => 'debug', ), - 'daily' => - array ( + 'daily' => + array( 'driver' => 'daily', 'path' => '/workspace/mein.sterntours.de/storage/logs/laravel.log', 'level' => 'debug', 'days' => 7, ), - 'slack' => - array ( + 'slack' => + array( 'driver' => 'slack', 'url' => NULL, 'username' => 'Laravel Log', 'emoji' => ':boom:', 'level' => 'critical', ), - 'stderr' => - array ( + 'stderr' => + array( 'driver' => 'monolog', 'handler' => 'Monolog\\Handler\\StreamHandler', - 'with' => - array ( + 'with' => + array( 'stream' => 'php://stderr', ), ), - 'syslog' => - array ( + 'syslog' => + array( 'driver' => 'syslog', 'level' => 'debug', ), - 'errorlog' => - array ( + 'errorlog' => + array( 'driver' => 'errorlog', 'level' => 'debug', ), - 'browser' => - array ( + 'browser' => + array( 'driver' => 'single', 'path' => '/workspace/mein.sterntours.de/storage/logs/browser.log', 'level' => 'debug', @@ -1024,18 +1010,18 @@ ), ), ), - 'mail' => - array ( + 'mail' => + array( 'driver' => 'smtp', 'host' => 'global-mailpit', 'port' => '587', - 'from' => - array ( + 'from' => + array( 'address' => 'stern@sterntours.de', 'name' => 'DEV Reisebüro STERN TOURS', ), - 'mail_bbc' => - array ( + 'mail_bbc' => + array( 0 => 'kevin@adametz.media', ), 'mail_fewo_employee' => 'kevin@adametz.media', @@ -1043,25 +1029,24 @@ 'username' => 'stern@stern-tours.de', 'password' => '13C!NlecB!Phil4beAxKl', 'sendmail' => '/usr/sbin/sendmail -bs', - 'markdown' => - array ( + 'markdown' => + array( 'theme' => 'default', - 'paths' => - array ( + 'paths' => + array( 0 => '/workspace/mein.sterntours.de/resources/views/vendor/mail', ), ), ), - 'models' => - array ( - '*' => - array ( + 'models' => + array( + '*' => + array( 'path' => '/workspace/mein.sterntours.de/app/Models', 'namespace' => 'App\\Models', 'parent' => 'Illuminate\\Database\\Eloquent\\Model', - 'use' => - array ( - ), + 'use' => + array(), 'connection' => false, 'timestamps' => true, 'soft_deletes' => true, @@ -1070,391 +1055,390 @@ 'base_files' => false, 'snake_attributes' => true, 'qualified_tables' => false, - 'hidden' => - array ( + 'hidden' => + array( 0 => '*secret*', 1 => '*password', 2 => '*token', ), - 'guarded' => - array ( - ), - 'casts' => - array ( + 'guarded' => + array(), + 'casts' => + array( '*_json' => 'json', ), - 'except' => - array ( + 'except' => + array( 0 => 'migrations', ), ), ), - 'permissions' => - array ( - 'groups' => - array ( - 0 => - array ( - 'my-dat' => - array ( + 'permissions' => + array( + 'groups' => + array( + 0 => + array( + 'my-dat' => + array( 'name' => 'Ihre Daten', 'color' => 'client', ), ), - 1 => - array ( - 'crm' => - array ( + 1 => + array( + 'crm' => + array( 'name' => 'ADMIN CRM ', 'color' => 'admin', ), - 'crm-tp' => - array ( + 'crm-tp' => + array( 'name' => 'ADMIN CRM > Reiseprogramme', 'color' => 'admin', ), - 'crm-tp-pr' => - array ( + 'crm-tp-pr' => + array( 'name' => 'ADMIN CRM > Reiseprogramme > Programme', 'color' => 'admin', ), - 'crm-tp-dr' => - array ( + 'crm-tp-dr' => + array( 'name' => 'ADMIN CRM > Reiseprogramme > Vorlagen', 'color' => 'admin', ), - 'crm-tp-tc' => - array ( + 'crm-tp-tc' => + array( 'name' => 'ADMIN CRM > Reiseprogramme > Inhalte', 'color' => 'admin', ), - 'crm-bo' => - array ( + 'crm-bo' => + array( 'name' => 'ADMIN CRM > Buchungen', 'color' => 'admin', ), - 'crm-bo-re' => - array ( + 'crm-bo-re' => + array( 'name' => 'ADMIN CRM > Buchungen > Übersicht', 'color' => 'admin', ), - 'crm-bo-bo' => - array ( + 'crm-bo-bo' => + array( 'name' => 'ADMIN CRM > Buchungen > Buchungen', 'color' => 'admin', ), - 'crm-bo-le' => - array ( + 'crm-bo-le' => + array( 'name' => 'ADMIN CRM > Buchungen > Anfragen', 'color' => 'admin', ), - 'crm-bo-cu' => - array ( + 'crm-bo-cu' => + array( 'name' => 'ADMIN CRM > Buchungen > Kunden', 'color' => 'admin', ), - 'crm-cm' => - array ( + 'crm-cm' => + array( 'name' => 'ADMIN CRM > Kundenverwaltung', 'color' => 'admin', ), - 'crm-cm-cf' => - array ( + 'crm-cm-cf' => + array( 'name' => 'ADMIN CRM > Kundenverwaltung > Kunden (FeWo)', 'color' => 'admin', ), - 'crm-cm-bf' => - array ( + 'crm-cm-bf' => + array( 'name' => 'ADMIN CRM > Kundenverwaltung > Buchungen (FeWo)', 'color' => 'admin', ), - 'crm-mail' => - array ( + 'crm-mail' => + array( 'name' => 'ADMIN CRM > E-Mails', 'color' => 'admin', ), - 'crm-mail-le' => - array ( + 'crm-mail-le' => + array( 'name' => 'ADMIN CRM > E-Mails > Anfragen', 'color' => 'admin', ), - 'crm-mail-bo' => - array ( + 'crm-mail-bo' => + array( 'name' => 'ADMIN CRM > E-Mails > Buchungen', 'color' => 'admin', ), - 'crm-mail-bf' => - array ( + 'crm-mail-bf' => + array( 'name' => 'ADMIN CRM > E-Mails > Buchungen (Fewo)', 'color' => 'admin', ), - 'crm-iq-tl' => - array ( + 'crm-iq-tl' => + array( 'name' => 'ADMIN CRM > Reisebausteine', 'color' => 'admin', ), - 'crm-iq-tl-pro' => - array ( + 'crm-iq-tl-pro' => + array( 'name' => 'ADMIN CRM > Reisebausteine > Programm', 'color' => 'admin', ), - 'crm-iq-tl-gp' => - array ( + 'crm-iq-tl-gp' => + array( 'name' => 'ADMIN CRM > Reisebausteine > Gruppe', 'color' => 'admin', ), - 'crm-iq-tl-it' => - array ( + 'crm-iq-tl-it' => + array( 'name' => 'ADMIN CRM > Reisebausteine > Baustein', 'color' => 'admin', ), - 'crm-old-cm' => - array ( + 'crm-old-cm' => + array( 'name' => 'ADMIN CRM altes System > Kundenverwaltung', 'color' => 'info', ), - 'cms' => - array ( + 'cms' => + array( 'name' => 'ADMIN CMS', 'color' => 'secondary', ), - 'cms-iq-assets' => - array ( + 'cms-iq-assets' => + array( 'name' => 'ADMIN CMS > Medien', 'color' => 'secondary', ), - 'cms-tg' => - array ( + 'cms-tg' => + array( 'name' => 'ADMIN CMS > Reiseführer', 'color' => 'secondary', ), - 'cms-fewo' => - array ( + 'cms-fewo' => + array( 'name' => 'ADMIN CMS > FeWo', 'color' => 'secondary', ), - 'cms-book' => - array ( + 'cms-book' => + array( 'name' => 'ADMIN CMS > Buchungen', 'color' => 'secondary', ), - 'cms-fb' => - array ( + 'cms-fb' => + array( 'name' => 'ADMIN CMS > Feedback', 'color' => 'secondary', ), - 'cms-nw' => - array ( + 'cms-nw' => + array( 'name' => 'ADMIN CMS > News', 'color' => 'secondary', ), - 'cms-aq' => - array ( + 'cms-aq' => + array( 'name' => 'ADMIN CMS > Fragen & Antworten', 'color' => 'secondary', ), - 'cms-sb' => - array ( + 'cms-sb' => + array( 'name' => 'ADMIN CMS > Sidebar', 'color' => 'secondary', ), - 'cms-cn' => - array ( + 'cms-cn' => + array( 'name' => 'ADMIN CMS > Inhalte', 'color' => 'secondary', ), - 'cms-cn-in' => - array ( + 'cms-cn-in' => + array( 'name' => 'ADMIN CMS > Inhalte > Infos', 'color' => 'secondary', ), - 'cms-cn-al' => - array ( + 'cms-cn-al' => + array( 'name' => 'ADMIN CMS > Inhalte > Inhalte', 'color' => 'secondary', ), - 'cms-cn-au' => - array ( + 'cms-cn-au' => + array( 'name' => 'ADMIN CMS > Inhalte > Autor', 'color' => 'secondary', ), - 'cms-cn-co' => - array ( + 'cms-cn-co' => + array( 'name' => 'ADMIN CMS > Inhalte > Länder', 'color' => 'secondary', ), - 'cms-newsletter' => - array ( + 'cms-newsletter' => + array( 'name' => 'ADMIN CMS > Newsletter', 'color' => 'secondary', ), - 'crm-nav-api' => - array ( + 'crm-nav-api' => + array( 'name' => 'ADMIN CRM > Navigation API', 'color' => 'secondary', ), ), - 2 => - array ( - 'sua-bo-n-edit' => - array ( + 2 => + array( + 'sua-bo-n-edit' => + array( 'name' => 'SUPERADMIN > Buchungen > Notizen > bearbeiten', 'color' => 'secondary', ), - 'sua-fewo-n-edit' => - array ( + 'sua-fewo-n-edit' => + array( 'name' => 'SUPERADMIN > FeWo > Notizen > bearbeiten', 'color' => 'secondary', ), - 'sua-st' => - array ( + 'sua-st' => + array( 'name' => 'SUPERADMIN > Einstellungen', 'color' => 'superadmin', ), - 'sua-st-al' => - array ( + 'sua-st-al' => + array( 'name' => 'SUPERADMIN > Einstellungen > Airline', 'color' => 'superadmin', ), - 'sua-st-ap' => - array ( + 'sua-st-ap' => + array( 'name' => 'SUPERADMIN > Einstellungen > Airport', 'color' => 'superadmin', ), - 'sua-st-em' => - array ( + 'sua-st-em' => + array( 'name' => 'SUPERADMIN > Einstellungen > E-Mails', 'color' => 'superadmin', ), - 'sua-st-ke' => - array ( + 'sua-st-ke' => + array( 'name' => 'SUPERADMIN > Einstellungen > Keywords', 'color' => 'superadmin', ), - 'sua-st-sp' => - array ( + 'sua-st-sp' => + array( 'name' => 'SUPERADMIN > Einstellungen > Leistungsträger', 'color' => 'superadmin', ), - 'sua-st-tn' => - array ( + 'sua-st-tn' => + array( 'name' => 'SUPERADMIN > Einstellungen > Nationalitäten', 'color' => 'superadmin', ), - 'sua-st-co' => - array ( + 'sua-st-co' => + array( 'name' => 'SUPERADMIN > Einstellungen > Reiseländer', 'color' => 'superadmin', ), - 'sua-st-tp' => - array ( + 'sua-st-tp' => + array( 'name' => 'SUPERADMIN > Einstellungen > Reiseprogramme', 'color' => 'superadmin', ), - 'sua-st-tpl' => - array ( + 'sua-st-tpl' => + array( 'name' => 'SUPERADMIN > Einstellungen > Reiseorte', 'color' => 'superadmin', ), - 'sua-st-bs' => - array ( + 'sua-st-bs' => + array( 'name' => 'SUPERADMIN > Einstellungen > Reisestatus', 'color' => 'superadmin', ), - 'sua-st-tc' => - array ( + 'sua-st-tc' => + array( 'name' => 'SUPERADMIN > Einstellungen > Veranstalter', 'color' => 'superadmin', ), - 'sua-st-tca' => - array ( + 'sua-st-tca' => + array( 'name' => 'SUPERADMIN > Einstellungen > Reiseart', 'color' => 'superadmin', ), - 'sua-st-tap' => - array ( + 'sua-st-tap' => + array( 'name' => 'SUPERADMIN > Einstellungen > Zielflughafen', 'color' => 'superadmin', ), - 'sua-st-in' => - array ( + 'sua-st-in' => + array( 'name' => 'SUPERADMIN > Einstellungen > Versicherungen', 'color' => 'superadmin', ), - 'sua-st-ca' => - array ( + 'sua-st-ca' => + array( 'name' => 'SUPERADMIN > Einstellungen > Kategorien', 'color' => 'superadmin', ), - 'sua-st-tgn' => - array ( + 'sua-st-tgn' => + array( 'name' => 'SUPERADMIN > Einstellungen > Reisehinweise', 'color' => 'superadmin', ), - 'sua-re' => - array ( + 'sua-re' => + array( 'name' => 'SUPERADMIN > Export', 'color' => 'superadmin', ), - 'sua-re-bo' => - array ( + 'sua-re-bo' => + array( 'name' => 'SUPERADMIN > Export > Buchungen', 'color' => 'superadmin', ), - 'sua-re-pp' => - array ( + 'sua-re-pp' => + array( 'name' => 'SUPERADMIN > Export > Leistungsträger', 'color' => 'superadmin', ), - 'sua-re-fw' => - array ( + 'sua-re-fw' => + array( 'name' => 'SUPERADMIN > Export > Fewo', 'color' => 'superadmin', ), - 'sua-re-le' => - array ( + 'sua-re-le' => + array( 'name' => 'SUPERADMIN > Export > Anfragen', 'color' => 'superadmin', ), - 'sua-ur-rt' => - array ( + 'sua-ur-rt' => + array( 'name' => 'SUPERADMIN > User Rechte', 'color' => 'danger', ), ), ), - 'roles' => - array ( + 'roles' => + array( 0 => 'Kunde', 1 => 'Admin', 2 => 'SuperAdmin', ), ), - 'queue' => - array ( + 'queue' => + array( 'default' => 'sync', - 'connections' => - array ( - 'sync' => - array ( + 'connections' => + array( + 'sync' => + array( 'driver' => 'sync', ), - 'database' => - array ( + 'database' => + array( 'driver' => 'database', 'table' => 'jobs', 'queue' => 'default', 'retry_after' => 90, ), - 'beanstalkd' => - array ( + 'beanstalkd' => + array( 'driver' => 'beanstalkd', 'host' => 'localhost', 'queue' => 'default', 'retry_after' => 90, ), - 'sqs' => - array ( + 'sqs' => + array( 'driver' => 'sqs', 'key' => 'your-public-key', 'secret' => 'your-secret-key', @@ -1462,8 +1446,8 @@ 'queue' => 'your-queue-name', 'region' => 'us-east-1', ), - 'redis' => - array ( + 'redis' => + array( 'driver' => 'redis', 'connection' => 'default', 'queue' => 'default', @@ -1471,38 +1455,38 @@ 'block_for' => NULL, ), ), - 'failed' => - array ( + 'failed' => + array( 'database' => 'mysql', 'table' => 'failed_jobs', ), ), - 'services' => - array ( - 'mailgun' => - array ( + 'services' => + array( + 'mailgun' => + array( 'domain' => NULL, 'secret' => NULL, ), - 'ses' => - array ( + 'ses' => + array( 'key' => NULL, 'secret' => NULL, 'region' => 'us-east-1', ), - 'sparkpost' => - array ( + 'sparkpost' => + array( 'secret' => NULL, ), - 'stripe' => - array ( + 'stripe' => + array( 'model' => 'App\\User', 'key' => NULL, 'secret' => NULL, ), ), - 'session' => - array ( + 'session' => + array( 'driver' => 'file', 'lifetime' => '120', 'expire_on_close' => false, @@ -1511,8 +1495,8 @@ 'connection' => NULL, 'table' => 'sessions', 'store' => NULL, - 'lottery' => - array ( + 'lottery' => + array( 0 => 2, 1 => 100, ), @@ -1523,118 +1507,115 @@ 'http_only' => true, 'same_site' => NULL, ), - 'tinker' => - array ( - 'commands' => - array ( - ), - 'alias' => - array ( - ), - 'dont_alias' => - array ( - ), + 'tinker' => + array( + 'commands' => + array(), + 'alias' => + array(), + 'dont_alias' => + array(), 'trust_project' => 'always', ), - 'trustedproxy' => - array ( + 'trustedproxy' => + array( 'proxies' => NULL, 'headers' => 62, ), - 'view' => - array ( - 'paths' => - array ( + 'view' => + array( + 'paths' => + array( 0 => '/workspace/mein.sterntours.de/resources/views', ), 'compiled' => '/workspace/mein.sterntours.de/storage/framework/views', ), - 'cart' => - array ( + 'cart' => + array( 'tax' => 10, - 'database' => - array ( + 'database' => + array( 'connection' => NULL, 'table' => 'shoppingcart', ), 'destroy_on_logout' => false, - 'format' => - array ( + 'format' => + array( 'decimals' => 2, 'decimal_point' => '.', 'thousand_seperator' => '', ), 'discountOnFees' => false, ), - 'boost' => - array ( + 'boost' => + array( 'enabled' => true, 'browser_logs_watcher' => true, - 'executable_paths' => - array ( + 'executable_paths' => + array( 'php' => NULL, 'composer' => NULL, 'npm' => NULL, 'vendor_bin' => NULL, ), ), - 'mcp' => - array ( - 'redirect_domains' => - array ( + 'mcp' => + array( + 'redirect_domains' => + array( 0 => '*', ), ), - 'passport' => - array ( + 'passport' => + array( 'guard' => 'web', 'private_key' => NULL, 'public_key' => NULL, 'client_uuids' => false, - 'personal_access_client' => - array ( + 'personal_access_client' => + array( 'id' => NULL, 'secret' => NULL, ), ), - 'flare' => - array ( + 'flare' => + array( 'key' => NULL, - 'flare_middleware' => - array ( + 'flare_middleware' => + array( 0 => 'Spatie\\FlareClient\\FlareMiddleware\\RemoveRequestIp', 1 => 'Spatie\\FlareClient\\FlareMiddleware\\AddGitInformation', 2 => 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddNotifierName', 3 => 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddEnvironmentInformation', 4 => 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddExceptionInformation', 5 => 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddDumps', - 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddLogs' => - array ( + 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddLogs' => + array( 'maximum_number_of_collected_logs' => 200, ), - 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddQueries' => - array ( + 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddQueries' => + array( 'maximum_number_of_collected_queries' => 200, 'report_query_bindings' => true, ), - 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddJobs' => - array ( + 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddJobs' => + array( 'max_chained_job_reporting_depth' => 5, ), 6 => 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddContext', 7 => 'Spatie\\LaravelIgnition\\FlareMiddleware\\AddExceptionHandledStatus', - 'Spatie\\FlareClient\\FlareMiddleware\\CensorRequestBodyFields' => - array ( - 'censor_fields' => - array ( + 'Spatie\\FlareClient\\FlareMiddleware\\CensorRequestBodyFields' => + array( + 'censor_fields' => + array( 0 => 'password', 1 => 'password_confirmation', ), ), - 'Spatie\\FlareClient\\FlareMiddleware\\CensorRequestHeaders' => - array ( - 'headers' => - array ( + 'Spatie\\FlareClient\\FlareMiddleware\\CensorRequestHeaders' => + array( + 'headers' => + array( 0 => 'API-KEY', 1 => 'Authorization', 2 => 'Cookie', @@ -1646,14 +1627,14 @@ ), 'send_logs_as_events' => true, ), - 'ignition' => - array ( + 'ignition' => + array( 'editor' => 'phpstorm', 'theme' => 'auto', 'enable_share_button' => true, 'register_commands' => false, - 'solution_providers' => - array ( + 'solution_providers' => + array( 0 => 'Spatie\\Ignition\\Solutions\\SolutionProviders\\BadMethodCallSolutionProvider', 1 => 'Spatie\\Ignition\\Solutions\\SolutionProviders\\MergeConflictSolutionProvider', 2 => 'Spatie\\Ignition\\Solutions\\SolutionProviders\\UndefinedPropertySolutionProvider', @@ -1677,16 +1658,15 @@ 20 => 'Spatie\\LaravelIgnition\\Solutions\\SolutionProviders\\UnknownMysql8CollationSolutionProvider', 21 => 'Spatie\\LaravelIgnition\\Solutions\\SolutionProviders\\UnknownMariadbCollationSolutionProvider', ), - 'ignored_solution_providers' => - array ( - ), + 'ignored_solution_providers' => + array(), 'enable_runnable_solutions' => NULL, 'remote_sites_path' => '/workspace/mein.sterntours.de', 'local_sites_path' => '', 'housekeeping_endpoint_prefix' => '_ignition', 'settings_file_path' => '', - 'recorders' => - array ( + 'recorders' => + array( 0 => 'Spatie\\LaravelIgnition\\Recorders\\DumpRecorder\\DumpRecorder', 1 => 'Spatie\\LaravelIgnition\\Recorders\\JobRecorder\\JobRecorder', 2 => 'Spatie\\LaravelIgnition\\Recorders\\LogRecorder\\LogRecorder', @@ -1694,8 +1674,8 @@ ), 'open_ai_key' => NULL, 'with_stack_frame_arguments' => true, - 'argument_reducers' => - array ( + 'argument_reducers' => + array( 0 => 'Spatie\\Backtrace\\Arguments\\Reducers\\BaseTypeArgumentReducer', 1 => 'Spatie\\Backtrace\\Arguments\\Reducers\\ArrayArgumentReducer', 2 => 'Spatie\\Backtrace\\Arguments\\Reducers\\StdClassArgumentReducer', @@ -1709,8 +1689,8 @@ 10 => 'Spatie\\Backtrace\\Arguments\\Reducers\\StringableArgumentReducer', ), ), - 'sluggable' => - array ( + 'sluggable' => + array( 'source' => NULL, 'maxLength' => NULL, 'maxLengthKeepWords' => true, @@ -1722,8 +1702,7 @@ 'includeTrashed' => false, 'reserved' => NULL, 'onUpdate' => false, - 'slugEngineOptions' => - array ( - ), + 'slugEngineOptions' => + array(), ), ); diff --git a/config/app.php b/config/app.php index ab7a418..4cf57f2 100755 --- a/config/app.php +++ b/config/app.php @@ -40,6 +40,7 @@ return [ */ 'debug' => env('APP_DEBUG', false), + 'exception_mail' => env('EXCEPTION_MAIL', 'exception@adametz.media'), /* |-------------------------------------------------------------------------- diff --git a/config/filesystems.php b/config/filesystems.php index 97762c8..2318ca2 100755 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -72,12 +72,6 @@ return [ 'url' => env('APP_URL').'/storage/booking', 'visibility' => 'public', ], - 'offer' => [ - 'driver' => 'local', - 'root' => storage_path('app/offer'), - 'url' => env('APP_URL').'/storage/offer', - 'visibility' => 'public', - ], 'general' => [ 'driver' => 'local', 'root' => storage_path('app/general'), diff --git a/database/migrations/2025_04_15_200001_phase2_rename_customer_to_contacts.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_200001_phase2_rename_customer_to_contacts.php similarity index 100% rename from database/migrations/2025_04_15_200001_phase2_rename_customer_to_contacts.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_200001_phase2_rename_customer_to_contacts.php diff --git a/database/migrations/2025_04_15_200002_phase2_rename_lead_to_inquiries.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_200002_phase2_rename_lead_to_inquiries.php similarity index 100% rename from database/migrations/2025_04_15_200002_phase2_rename_lead_to_inquiries.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_200002_phase2_rename_lead_to_inquiries.php diff --git a/database/migrations/2025_04_15_200003_phase2_rename_booking_lead_id_to_inquiry_id.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_200003_phase2_rename_booking_lead_id_to_inquiry_id.php similarity index 100% rename from database/migrations/2025_04_15_200003_phase2_rename_booking_lead_id_to_inquiry_id.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_200003_phase2_rename_booking_lead_id_to_inquiry_id.php diff --git a/database/migrations/2025_04_15_300001_phase3_create_participants_unified_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_300001_phase3_create_participants_unified_table.php similarity index 100% rename from database/migrations/2025_04_15_300001_phase3_create_participants_unified_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_300001_phase3_create_participants_unified_table.php diff --git a/database/migrations/2025_04_15_300002_phase3_drop_old_participant_tables.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_300002_phase3_drop_old_participant_tables.php similarity index 100% rename from database/migrations/2025_04_15_300002_phase3_drop_old_participant_tables.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_300002_phase3_drop_old_participant_tables.php diff --git a/database/migrations/2025_04_15_400001_phase4_create_communications_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400001_phase4_create_communications_table.php similarity index 100% rename from database/migrations/2025_04_15_400001_phase4_create_communications_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400001_phase4_create_communications_table.php diff --git a/database/migrations/2025_04_15_400002_phase4_create_notices_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400002_phase4_create_notices_table.php similarity index 100% rename from database/migrations/2025_04_15_400002_phase4_create_notices_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400002_phase4_create_notices_table.php diff --git a/database/migrations/2025_04_15_400003_phase4_create_attachments_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400003_phase4_create_attachments_table.php similarity index 100% rename from database/migrations/2025_04_15_400003_phase4_create_attachments_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400003_phase4_create_attachments_table.php diff --git a/database/migrations/2025_04_15_400004_phase4_drop_old_communication_tables.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400004_phase4_drop_old_communication_tables.php similarity index 100% rename from database/migrations/2025_04_15_400004_phase4_drop_old_communication_tables.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400004_phase4_drop_old_communication_tables.php diff --git a/database/migrations/2025_04_15_400005_phase4_drop_old_notice_tables.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400005_phase4_drop_old_notice_tables.php similarity index 100% rename from database/migrations/2025_04_15_400005_phase4_drop_old_notice_tables.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400005_phase4_drop_old_notice_tables.php diff --git a/database/migrations/2025_04_15_400006_phase4_drop_old_attachment_tables.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400006_phase4_drop_old_attachment_tables.php similarity index 100% rename from database/migrations/2025_04_15_400006_phase4_drop_old_attachment_tables.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2025_04_15_400006_phase4_drop_old_attachment_tables.php diff --git a/database/migrations/2026_04_17_100001_create_offers_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100001_create_offers_table.php similarity index 100% rename from database/migrations/2026_04_17_100001_create_offers_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100001_create_offers_table.php diff --git a/database/migrations/2026_04_17_100002_create_offer_versions_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100002_create_offer_versions_table.php similarity index 100% rename from database/migrations/2026_04_17_100002_create_offer_versions_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100002_create_offer_versions_table.php diff --git a/database/migrations/2026_04_17_100003_create_offer_items_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100003_create_offer_items_table.php similarity index 100% rename from database/migrations/2026_04_17_100003_create_offer_items_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100003_create_offer_items_table.php diff --git a/database/migrations/2026_04_17_100004_create_offer_templates_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100004_create_offer_templates_table.php similarity index 100% rename from database/migrations/2026_04_17_100004_create_offer_templates_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100004_create_offer_templates_table.php diff --git a/database/migrations/2026_04_17_100005_create_offer_files_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100005_create_offer_files_table.php similarity index 100% rename from database/migrations/2026_04_17_100005_create_offer_files_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100005_create_offer_files_table.php diff --git a/database/migrations/2026_04_17_100006_create_offer_access_tokens_table.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100006_create_offer_access_tokens_table.php similarity index 100% rename from database/migrations/2026_04_17_100006_create_offer_access_tokens_table.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100006_create_offer_access_tokens_table.php diff --git a/database/migrations/2026_04_17_100007_add_offer_refs_to_offers_and_bookings.php b/dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100007_add_offer_refs_to_offers_and_bookings.php similarity index 100% rename from database/migrations/2026_04_17_100007_add_offer_refs_to_offers_and_bookings.php rename to dev/backups/phase2-offers-2026-04-17/FILES/migrations/2026_04_17_100007_add_offer_refs_to_offers_and_bookings.php diff --git a/dev/backups/phase2-offers-2026-04-17/FILES/models/Offer.php b/dev/backups/phase2-offers-2026-04-17/FILES/models/Offer.php new file mode 100644 index 0000000..02b503c --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/FILES/models/Offer.php @@ -0,0 +1,132 @@ + 'int', + 'inquiry_id' => 'int', + 'booking_id' => 'int', + 'current_version_id' => 'int', + 'created_by' => 'int', + ]; + + public function contact(): BelongsTo + { + return $this->belongsTo(Contact::class); + } + + public function inquiry(): BelongsTo + { + // Nach Modul 3 Phase 2: `Lead`-Model bildet die `inquiries`-Tabelle ab + return $this->belongsTo(Lead::class, 'inquiry_id'); + } + + public function booking(): BelongsTo + { + return $this->belongsTo(Booking::class); + } + + public function currentVersion(): BelongsTo + { + return $this->belongsTo(OfferVersion::class, 'current_version_id'); + } + + public function versions(): HasMany + { + return $this->hasMany(OfferVersion::class)->orderBy('version_no'); + } + + public function accessTokens(): HasMany + { + return $this->hasMany(OfferAccessToken::class); + } + + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + public function scopeStatus(Builder $q, string $status): Builder + { + return $q->where('status', $status); + } + + public function scopeOpen(Builder $q): Builder + { + return $q->whereIn('status', [self::STATUS_DRAFT, self::STATUS_SENT]); + } + + public function isEditable(): bool + { + return in_array($this->status, [self::STATUS_DRAFT, self::STATUS_SENT], true); + } +} diff --git a/app/Models/OfferAccessToken.php b/dev/backups/phase2-offers-2026-04-17/FILES/models/OfferAccessToken.php similarity index 100% rename from app/Models/OfferAccessToken.php rename to dev/backups/phase2-offers-2026-04-17/FILES/models/OfferAccessToken.php diff --git a/app/Models/OfferFile.php b/dev/backups/phase2-offers-2026-04-17/FILES/models/OfferFile.php similarity index 100% rename from app/Models/OfferFile.php rename to dev/backups/phase2-offers-2026-04-17/FILES/models/OfferFile.php diff --git a/app/Models/OfferItem.php b/dev/backups/phase2-offers-2026-04-17/FILES/models/OfferItem.php similarity index 100% rename from app/Models/OfferItem.php rename to dev/backups/phase2-offers-2026-04-17/FILES/models/OfferItem.php diff --git a/app/Models/OfferTemplate.php b/dev/backups/phase2-offers-2026-04-17/FILES/models/OfferTemplate.php similarity index 100% rename from app/Models/OfferTemplate.php rename to dev/backups/phase2-offers-2026-04-17/FILES/models/OfferTemplate.php diff --git a/app/Models/OfferVersion.php b/dev/backups/phase2-offers-2026-04-17/FILES/models/OfferVersion.php similarity index 100% rename from app/Models/OfferVersion.php rename to dev/backups/phase2-offers-2026-04-17/FILES/models/OfferVersion.php diff --git a/dev/backups/phase2-offers-2026-04-17/MANIFEST.md b/dev/backups/phase2-offers-2026-04-17/MANIFEST.md new file mode 100644 index 0000000..93962ee --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/MANIFEST.md @@ -0,0 +1,158 @@ +# Backup: Phase-2 + Offers (Rückbau für Phase-1-Live-Deploy) + +**Erstellt:** 2026-04-17 +**Zweck:** Der Workspace enthielt gemischt Phase 1 + Phase 2 (Code-Umstellung `inquiry_id`, `$table='contacts'/'inquiries'`) + Offers-Modul. Phase 1 muss zuerst auf Live, dann erst Phase 2 + Offers. Dieses Backup sichert alle **Phase-2- und Offers-Artefakte**, damit sie nach erfolgreichem Phase-1-Live-Deploy per `restore.sh` wieder in den Workspace eingespielt werden können. + +Parallel dazu existiert ein **Tarball-Backup** des gesamten Workspace-Zustands vor dem Rückbau unter `../../../../../backups-safety/workspace-pre-phase1-rollback-2026-04-17.tar.gz` (182 MB) und ein **Git-Commit** `e3dc1af` (lokal auf `master`, nicht gepusht) als doppeltes Sicherheitsnetz. + +--- + +## Was im Backup liegt + +### `FILES/migrations/` (18 Migrations-Dateien) + +**Phase 2 (3 Dateien):** +- `2025_04_15_200001_phase2_rename_customer_to_contacts.php` — RENAME TABLE customer → contacts +- `2025_04_15_200002_phase2_rename_lead_to_inquiries.php` — RENAME TABLE lead → inquiries +- `2025_04_15_200003_phase2_rename_booking_lead_id_to_inquiry_id.php` — Spalte `booking.lead_id` → `booking.inquiry_id` + +**Phase 3 (2 Dateien):** +- `2025_04_15_300001_phase3_create_participants_unified_table.php` +- `2025_04_15_300002_phase3_drop_old_participant_tables.php` + +**Phase 4 (6 Dateien):** +- `2025_04_15_400001_phase4_create_communications_table.php` +- `2025_04_15_400002_phase4_create_notices_table.php` +- `2025_04_15_400003_phase4_create_attachments_table.php` +- `2025_04_15_400004_phase4_drop_old_communication_tables.php` +- `2025_04_15_400005_phase4_drop_old_notice_tables.php` +- `2025_04_15_400006_phase4_drop_old_attachment_tables.php` + +**Offers (7 Dateien):** +- `2026_04_17_100001_create_offers_table.php` +- `2026_04_17_100002_create_offer_versions_table.php` +- `2026_04_17_100003_create_offer_items_table.php` +- `2026_04_17_100004_create_offer_templates_table.php` +- `2026_04_17_100005_create_offer_files_table.php` +- `2026_04_17_100006_create_offer_access_tokens_table.php` +- `2026_04_17_100007_add_offer_refs_to_offers_and_bookings.php` + +### `FILES/models/` (6 Offer-Models) + +- `Offer.php` — **neues** Offer-Model des Offers-Moduls (überschreibt das Legacy-Reliese-Model in `app/Models/Offer.php`; Legacy-Version ist in `HEAD^` bzw. Commit `389d5d1`) +- `OfferVersion.php` +- `OfferItem.php` +- `OfferTemplate.php` +- `OfferFile.php` +- `OfferAccessToken.php` + +### `PATCHES/` (Diffs für Phase-2/Offers-Rückbau) + +Jede Datei enthält den **vollständigen** oder **Phase-1-only**-Diff gegen `HEAD^` (= `389d5d1`, Basis-Live-Stand): + +- `Booking.php.full.diff` — kompletter Vorher-Diff (inkl. Phase-2 + Offers-Änderungen, die zurückgebaut wurden) +- `Booking.php.phase1-only.diff` — was nach Rückbau übrig blieb: **nur** `HasFactory` + `$dates`→`$casts datetime` (Laravel-10-Upgrade) +- `Customer.php.phase1-only.diff` — Phase-1-Arbeit (SoftDeletes, Merge-Fields, Relations, Global Scope); `$table = 'contacts'` zurück auf `'customer'` +- `Lead.php.phase1-only.diff` — `$table = 'inquiries'` zurück auf `'lead'` (+ Phase-1-$casts-Refactoring) +- `Contact.php.phase1-only.diff` — komplett neue Datei (Contacts-Modul); `$table = 'contacts'` zurück auf `'customer'` +- `filesystems.php.full.diff` — entfernte `offer`-Disk (komplett zurückgerollt; Datei jetzt identisch mit HEAD^) +- `Repositories.full.diff` / `Repositories.phase1-only.diff` — BookingPDFRepository (Laravel-10-Upgrade `Storage::disk()->path()`), LeadRepository, CustomerMailRepository +- `Controllers.phase1-only.diff` — RequestController (Phase-1-$casts), API/BookingController, Admin/Report*, LeadController, CustomerController, ContactController (komplett neu) +- `Services-Commands.phase1-only.diff` — BookingImport, SyncNewsletterKulturreisen (neu), Contacts*Duplicates (neu) +- `Views.phase1-only.diff` — (leer, reine Phase-2-Views wurden zurückgerollt) + +--- + +## Was im Workspace VERBLEIBT (Phase 1 + Laravel 10) + +**Deploy-ready auf Live** — alle Phase-1-Artefakte, die auf Test bereits laufen: + +### Neue Dateien (Phase 1) +- `database/migrations/2025_04_15_100001_phase1_add_merge_fields_to_customer_table.php` +- `database/migrations/2025_04_15_100002_phase1_add_soft_delete_to_customer_table.php` +- `app/Models/Contact.php` (mit `$table = 'customer'`) +- `app/Repositories/ContactRepository.php` +- `app/Http/Controllers/ContactController.php` +- `app/Console/Commands/ContactsFindDuplicates.php` +- `app/Console/Commands/ContactsMergeDuplicates.php` +- `resources/views/contact/*.blade.php` (5 Dateien) + +### Modifizierte Dateien (Phase 1 + Laravel 10 Upgrade) +- `app/Models/Customer.php` — SoftDeletes, Merge-Fields, Global Scope, Relations +- `app/Models/Booking.php` — HasFactory, `$dates`→`$casts datetime` +- `app/Models/Lead.php` — Phase-1-$casts-Refactoring +- Viele weitere Models mit Laravel-10-Upgrade-Änderungen +- `app/Repositories/BookingPDFRepository.php` — `Storage::disk()->path()` statt deprecated `getAdapter()->getPathPrefix()` +- `routes/web.php` — `/contacts`, `/contact/*` Routen +- `resources/views/layouts/includes/layout-sidenav.blade.php` — Contacts-Menüpunkt +- Weitere modifizierte Dateien, u.a. diverse Views, Tests (`tests/Feature/*`, `tests/Unit/*`), Konfiguration (`phpunit.xml`, `config/trustedproxy.php`), Composer/Package-Dateien +- `app/Services/MailDirService.php` (neu) +- `database/factories/BookingFactory.php`, `CustomerFactory.php`, `LeadFactory.php` (neu) + +**Alle diese Dateien gehen mit dem Phase-1-Live-Deploy mit.** + +--- + +## Restore-Anleitung + +### Automatisch (empfohlen) + +```bash +bash dev/backups/phase2-offers-2026-04-17/restore.sh +``` + +Das Script: +1. Prüft, ob Phase 2 auf Live eingespielt wurde (erfordert bewusste Bestätigung) +2. Spielt die 18 Migrations-Dateien zurück nach `database/migrations/` +3. Spielt die 6 Offer-Models zurück nach `app/Models/` (inkl. Überschreiben der Legacy-`Offer.php`) +4. Wendet die Phase-2/Offers-Änderungen wieder an (Booking.php inquiry_id + offer()-Relation, Customer/Lead/Contact $table, Repositories, Controllers, Services, Commands, Views, filesystems.php) +5. Zeigt einen abschließenden `git status` zur Verifikation + +### Manuell + +Falls das Script fehlschlägt oder man einzelne Teile prüfen möchte: + +**Migrations zurück:** +```bash +cp dev/backups/phase2-offers-2026-04-17/FILES/migrations/*.php database/migrations/ +``` + +**Models zurück (WARNUNG: überschreibt Legacy-Offer.php):** +```bash +cp dev/backups/phase2-offers-2026-04-17/FILES/models/*.php app/Models/ +``` + +**Code-Änderungen aus dem WIP-Sicherheits-Commit (`e3dc1af`) zurückholen:** +```bash +git checkout e3dc1af -- app/Models/Booking.php app/Models/Customer.php app/Models/Contact.php app/Models/Lead.php +git checkout e3dc1af -- app/Repositories/BookingPDFRepository.php app/Repositories/LeadRepository.php app/Repositories/CustomerMailRepository.php +git checkout e3dc1af -- app/Http/Controllers/RequestController.php app/Http/Controllers/API/BookingController.php +git checkout e3dc1af -- app/Http/Controllers/Admin/ReportController.php app/Http/Controllers/Admin/ReportProviderController.php app/Http/Controllers/Admin/ReportLeadsController.php +git checkout e3dc1af -- app/Http/Controllers/LeadController.php app/Http/Controllers/CustomerController.php app/Http/Controllers/ContactController.php +git checkout e3dc1af -- app/Services/BookingImport.php +git checkout e3dc1af -- app/Console/Commands/SyncNewsletterKulturreisen.php app/Console/Commands/ContactsFindDuplicates.php app/Console/Commands/ContactsMergeDuplicates.php +git checkout e3dc1af -- resources/views/customer/mail/modal-show-mail-inner.blade.php resources/views/pdf/components/booking_head.blade.php resources/views/pdf/components/booking_header.blade.php +git checkout e3dc1af -- config/filesystems.php +``` + +**Alternative — kompletter Restore aus dem Git-Commit:** +```bash +git checkout e3dc1af -- . +``` +(Aber dann sind auch die Phase-2-Migrationen in `database/migrations/` wieder da, was richtig ist.) + +--- + +## Notfall-Rollback (alles rückgängig, auch Phase-1-Rückbau) + +Falls der Phase-1-Rückbau komplett falsch war und man zum Ausgangszustand zurück will: + +```bash +# Option A: Git +git reset --hard e3dc1af # setzt Workspace auf den WIP-Sicherheits-Commit + +# Option B: Tarball (falls Git nicht funktioniert) +cd /workspace/mein.sterntours.de +rm -rf * .[a-z]* # Vorsicht! Löscht alles außer dem übergeordneten Dir +tar -xzf ../backups-safety/workspace-pre-phase1-rollback-2026-04-17.tar.gz +``` diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Booking.php.full.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Booking.php.full.diff new file mode 100644 index 0000000..2a28d0a --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/Booking.php.full.diff @@ -0,0 +1,110 @@ +diff --git a/app/Models/Booking.php b/app/Models/Booking.php +index 79a91ba..f3bf11c 100644 +--- a/app/Models/Booking.php ++++ b/app/Models/Booking.php +@@ -9,6 +9,7 @@ namespace App\Models; + use Carbon\Carbon; + use App\Services\Util; + use App\Services\Passolution; ++use Illuminate\Database\Eloquent\Factories\HasFactory; + use Illuminate\Database\Eloquent\Model; + use Illuminate\Database\Eloquent\Collection; + +@@ -18,7 +19,7 @@ use Illuminate\Database\Eloquent\Collection; + * @property int $id + * @property Carbon $booking_date + * @property int $customer_id +- * @property int $lead_id ++ * @property int $inquiry_id + * @property bool $new_drafts + * @property int $sf_guard_user_id + * @property int $branch_id +@@ -203,13 +204,16 @@ use Illuminate\Database\Eloquent\Collection; + */ + class Booking extends Model + { ++ use HasFactory; ++ + protected $connection = 'mysql'; + + protected $table = 'booking'; + + protected $casts = [ + 'customer_id' => 'int', +- 'lead_id' => 'int', ++ 'inquiry_id' => 'int', ++ 'offer_id' => 'int', + 'new_drafts' => 'bool', + 'sf_guard_user_id' => 'int', + 'branch_id' => 'int', +@@ -237,25 +241,24 @@ class Booking extends Model + 'is_rail_fly' => 'bool', + 'comfort' => 'bool', + 'airline_ids' => 'array', +- 'participant_pass' => 'bool' +- ]; +- +- protected $dates = [ +- 'booking_date', +- 'start_date', +- 'end_date', +- 'participant_birthdate', +- 'final_payment_date', +- 'refund_date', +- 'lawyer_date', +- 'xx_tkt_date' +- +- ]; ++ 'participant_pass' => 'bool', ++ 'booking_date' => 'datetime', ++ 'start_date' => 'datetime', ++ 'end_date' => 'datetime', ++ 'participant_birthdate' => 'datetime', ++ 'final_payment_date' => 'datetime', ++ 'refund_date' => 'datetime', ++ 'lawyer_date' => 'datetime', ++ 'xx_tkt_date' => 'datetime', ++ ]; ++ ++ + + protected $fillable = [ + 'booking_date', + 'customer_id', +- 'lead_id', ++ 'inquiry_id', ++ 'offer_id', + 'new_drafts', + 'sf_guard_user_id', + 'branch_id', +@@ -392,9 +395,29 @@ class Booking extends Model + return $this->belongsTo(Customer::class); + } + ++ /** ++ * Lead/Inquiry der Buchung. ++ * FK-Spalte `inquiry_id` (vormals `lead_id` — Modul 3 Phase 2 Rename). ++ * Methodenname bleibt `lead()` für Legacy-Kompatibilität; {@see self::inquiry()} ++ * ist der fachlich korrekte Alias und sollte in neuem Code verwendet werden. ++ */ + public function lead() + { +- return $this->belongsTo(Lead::class); ++ return $this->belongsTo(Lead::class, 'inquiry_id'); ++ } ++ ++ public function inquiry() ++ { ++ return $this->belongsTo(Lead::class, 'inquiry_id'); ++ } ++ ++ /** ++ * Angebot, aus dem diese Buchung entstanden ist (Modul 6, Ticket B8). ++ * Nullable — nicht jede Buchung hat einen Angebots-Vorlauf. ++ */ ++ public function offer() ++ { ++ return $this->belongsTo(\App\Models\Offer::class); + } + + public function sf_guard_user() diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Booking.php.phase1-only.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Booking.php.phase1-only.diff new file mode 100644 index 0000000..90acf25 --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/Booking.php.phase1-only.diff @@ -0,0 +1,54 @@ +diff --git a/app/Models/Booking.php b/app/Models/Booking.php +index 79a91ba..3d61b89 100644 +--- a/app/Models/Booking.php ++++ b/app/Models/Booking.php +@@ -9,6 +9,7 @@ namespace App\Models; + use Carbon\Carbon; + use App\Services\Util; + use App\Services\Passolution; ++use Illuminate\Database\Eloquent\Factories\HasFactory; + use Illuminate\Database\Eloquent\Model; + use Illuminate\Database\Eloquent\Collection; + +@@ -203,6 +204,8 @@ use Illuminate\Database\Eloquent\Collection; + */ + class Booking extends Model + { ++ use HasFactory; ++ + protected $connection = 'mysql'; + + protected $table = 'booking'; +@@ -237,20 +240,18 @@ class Booking extends Model + 'is_rail_fly' => 'bool', + 'comfort' => 'bool', + 'airline_ids' => 'array', +- 'participant_pass' => 'bool' +- ]; +- +- protected $dates = [ +- 'booking_date', +- 'start_date', +- 'end_date', +- 'participant_birthdate', +- 'final_payment_date', +- 'refund_date', +- 'lawyer_date', +- 'xx_tkt_date' +- +- ]; ++ 'participant_pass' => 'bool', ++ 'booking_date' => 'datetime', ++ 'start_date' => 'datetime', ++ 'end_date' => 'datetime', ++ 'participant_birthdate' => 'datetime', ++ 'final_payment_date' => 'datetime', ++ 'refund_date' => 'datetime', ++ 'lawyer_date' => 'datetime', ++ 'xx_tkt_date' => 'datetime', ++ ]; ++ ++ + + protected $fillable = [ + 'booking_date', diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Contact.php.phase1-only.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Contact.php.phase1-only.diff new file mode 100644 index 0000000..5252efa --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/Contact.php.phase1-only.diff @@ -0,0 +1,170 @@ +diff --git a/app/Models/Contact.php b/app/Models/Contact.php +new file mode 100644 +index 0000000..6d941e8 +--- /dev/null ++++ b/app/Models/Contact.php +@@ -0,0 +1,164 @@ ++ 'int', ++ 'credit_card_type_id' => 'int', ++ 'country_id' => 'int', ++ 'merged_into_id' => 'int', ++ 'birthdate' => 'datetime', ++ 'credit_card_expiration_date' => 'datetime', ++ 'merged_at' => 'datetime', ++ ]; ++ ++ protected $fillable = [ ++ 'salutation_id', ++ 'title', ++ 'name', ++ 'firstname', ++ 'birthdate', ++ 'company', ++ 'street', ++ 'zip', ++ 'city', ++ 'email', ++ 'phone', ++ 'phonebusiness', ++ 'phonemobile', ++ 'fax', ++ 'bank', ++ 'bank_code', ++ 'bank_account_number', ++ 'credit_card_type_id', ++ 'credit_card_number', ++ 'credit_card_expiration_date', ++ 'participants_remarks', ++ 'miscellaneous_remarks', ++ 'country_id', ++ 'merged_into_id', ++ 'merged_at', ++ ]; ++ ++ /** ++ * Globaler Scope: zusammengeführte Duplikate werden standardmäßig ausgeblendet. ++ * Für Zugriff auf alle inkl. Duplikate: Contact::withoutGlobalScope('not_merged') ++ */ ++ protected static function booted(): void ++ { ++ static::addGlobalScope('not_merged', function (Builder $query) { ++ $query->whereNull('merged_into_id'); ++ }); ++ } ++ ++ // ── Beziehungen ────────────────────────────────────────────────────────── ++ ++ public function mergedInto(): \Illuminate\Database\Eloquent\Relations\BelongsTo ++ { ++ return $this->belongsTo(Contact::class, 'merged_into_id') ++ ->withoutGlobalScope('not_merged'); ++ } ++ ++ public function mergedContacts(): \Illuminate\Database\Eloquent\Relations\HasMany ++ { ++ return $this->hasMany(Contact::class, 'merged_into_id') ++ ->withoutGlobalScope('not_merged'); ++ } ++ ++ public function leads(): \Illuminate\Database\Eloquent\Relations\HasMany ++ { ++ return $this->hasMany(Lead::class, 'customer_id')->orderByDesc('created_at'); ++ } ++ ++ public function bookings(): \Illuminate\Database\Eloquent\Relations\HasMany ++ { ++ return $this->hasMany(Booking::class, 'customer_id')->orderByDesc('created_at'); ++ } ++ ++ public function salutation(): \Illuminate\Database\Eloquent\Relations\BelongsTo ++ { ++ return $this->belongsTo(Salutation::class); ++ } ++ ++ public function travel_country(): \Illuminate\Database\Eloquent\Relations\BelongsTo ++ { ++ return $this->belongsTo(TravelCountry::class, 'country_id'); ++ } ++ ++ // ── Hilfsmethoden ──────────────────────────────────────────────────────── ++ ++ public function fullName(): string ++ { ++ if ($this->firstname) { ++ return $this->firstname . ' ' . $this->name; ++ } ++ return (string) $this->name; ++ } ++ ++ public function isMerged(): bool ++ { ++ return $this->merged_into_id !== null; ++ } ++ ++ public static function getCountriesArray(): \Illuminate\Support\Collection ++ { ++ return TravelCountry::where('is_customer_country', 1)->get()->pluck('name', 'id'); ++ } ++ ++ public static $salutationType = [ ++ 1 => 'Herr', ++ 2 => 'Frau', ++ 3 => 'Divers/keine Anrede', ++ 4 => 'Firma', ++ ]; ++} diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Controllers.phase1-only.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Controllers.phase1-only.diff new file mode 100644 index 0000000..65c2c26 --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/Controllers.phase1-only.diff @@ -0,0 +1,372 @@ +diff --git a/app/Http/Controllers/API/BookingController.php b/app/Http/Controllers/API/BookingController.php +index e47cd07..a020ad4 100755 +--- a/app/Http/Controllers/API/BookingController.php ++++ b/app/Http/Controllers/API/BookingController.php +@@ -8,42 +8,35 @@ use App\Services\BookingImport; + + class BookingController extends Controller + { +- private $successStatus = 200; +- private $successKey = 'f6077389c9ce710e554763a5de02c8ec'; +- +- protected $draftRepo; ++ private int $successStatus = 200; ++ private string $successKey; + + public function __construct() + { +- ++ $this->successKey = config('app.success_key'); + } + + public function import() + { + + $request = \Request::all(); +- if(!isset($request['key']) || $request['key'] !== $this->successKey){ ++ if (!isset($request['key']) || $request['key'] !== $this->successKey) { + return response()->json(['error' => "key"], 401); + } + $travel_booking = TravelBooking::find($request['travel_booking_id']); + +- //# vor testing +- //$travel_booking = TravelBooking::find(2922); +- if(!isset($travel_booking) || !$travel_booking){ ++ if (!$travel_booking) { + return response()->json(['error' => 'no-booking-found'], $this->successStatus); + } + + $booking = BookingImport::importFrom($travel_booking); + +- $ret= [ +- 'url_v1' => make_old_url('/index.php/booking/'.$booking->id.'/edit'), ++ $ret = [ ++ 'url_v1' => make_old_url('/index.php/booking/' . $booking->id . '/edit'), + 'url_v3' => route('booking_detail', $booking->id), + 'lead_id' => $booking->lead_id + ]; + return response()->json(['success' => "import", "ret" => $ret], $this->successStatus); +- //return response()->json(['error' => 'no-node'], $this->successStatus); + + } +- +- + } +diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php +new file mode 100644 +index 0000000..08ef241 +--- /dev/null ++++ b/app/Http/Controllers/ContactController.php +@@ -0,0 +1,312 @@ ++middleware(['admin', '2fa']); ++ } ++ ++ public function index(): \Illuminate\View\View ++ { ++ return view('contact.index'); ++ } ++ ++ public function detail(string $id): \Illuminate\View\View ++ { ++ if ($id === 'new') { ++ $contact = new Contact(); ++ } else { ++ $contact = Contact::with(['salutation', 'leads', 'bookings', 'mergedContacts']) ++ ->findOrFail((int) $id); ++ } ++ ++ return view('contact.detail', [ ++ 'contact' => $contact, ++ 'id' => $id, ++ ]); ++ } ++ ++ public function store(Request $request, string $id): \Illuminate\Http\RedirectResponse ++ { ++ $data = $request->except('_token'); ++ ++ if (!isset($data['action'])) { ++ abort(403, 'keine Action'); ++ } ++ ++ if ($id === 'new') { ++ $contact = $this->contactRepo->createContact($data); ++ \Session()->flash('alert-save', '1'); ++ return redirect(route('contact_detail', [$contact->id]) . '#collapseContactDetail'); ++ } ++ ++ $this->contactRepo->updateContact($id, $data); ++ \Session()->flash('alert-save', '1'); ++ ++ return redirect(route('contact_detail', [$id]) . '#collapseContactDetail'); ++ } ++ ++ public function history(int $id): \Illuminate\View\View ++ { ++ $contact = Contact::with([ ++ 'leads.sf_guard_user', ++ 'bookings.travel_country', ++ 'bookings.travel_agenda', ++ 'bookings.sf_guard_user', ++ 'bookings.lead', ++ ])->findOrFail($id); ++ ++ return view('contact._detail_history', [ ++ 'contact' => $contact, ++ 'modal' => true, ++ ]); ++ } ++ ++ public function destroy(int $id): \Illuminate\Http\JsonResponse ++ { ++ $contact = Contact::withoutGlobalScope('not_merged')->findOrFail($id); ++ ++ $leadsCount = $contact->leads()->count(); ++ $bookingCount = $contact->bookings()->count(); ++ ++ if ($leadsCount > 0 || $bookingCount > 0) { ++ return response()->json([ ++ 'success' => false, ++ 'message' => sprintf( ++ 'Kontakt kann nicht gelöscht werden: %s%s vorhanden.', ++ $leadsCount > 0 ? $leadsCount . ' Anfrage(n)' : '', ++ $bookingCount > 0 ? ($leadsCount > 0 ? ' und ' : '') . $bookingCount . ' Buchung(en)' : '' ++ ), ++ ], 422); ++ } ++ ++ $contact->delete(); ++ ++ return response()->json(['success' => true]); ++ } ++ ++ public function duplicates(): \Illuminate\View\View ++ { ++ $counts = [ ++ 'HIGH' => $this->countDuplicateGroups('email'), ++ 'MEDIUM' => $this->countDuplicateGroups('name_birthdate'), ++ 'LOW' => $this->countDuplicateGroups('name_zip'), ++ ]; ++ ++ return view('contact.duplicates', compact('counts')); ++ } ++ ++ public function getDuplicateGroups(Request $request): \Illuminate\Http\JsonResponse ++ { ++ $confidence = strtoupper($request->input('confidence', 'HIGH')); ++ ++ $groups = match ($confidence) { ++ 'HIGH' => $this->findByEmail(), ++ 'MEDIUM' => $this->findByNameBirthdate(), ++ 'LOW' => $this->findByNameZip(), ++ default => [], ++ }; ++ ++ // Für jede Gruppe die vollständigen Kontakt-Daten laden ++ $result = []; ++ foreach ($groups as $ids) { ++ $contacts = Contact::withoutGlobalScopes() ++ ->withCount(['leads', 'bookings']) ++ ->whereIn('id', $ids) ++ ->whereNull('merged_into_id') ++ ->whereNull('deleted_at') ++ ->orderByRaw('FIELD(id, ' . implode(',', $ids) . ')') ++ ->get(['id', 'firstname', 'name', 'email', 'zip', 'city', 'phone', 'phonemobile', 'birthdate', 'created_at', 'updated_at']); ++ ++ if ($contacts->count() < 2) { ++ continue; ++ } ++ ++ $result[] = [ ++ 'master' => $contacts->first(), ++ 'duplicates' => $contacts->skip(1)->values(), ++ ]; ++ } ++ ++ return response()->json($result); ++ } ++ ++ public function merge(Request $request): \Illuminate\Http\JsonResponse ++ { ++ $masterId = (int) $request->input('master_id'); ++ $dupeId = (int) $request->input('duplicate_id'); ++ ++ if (!$masterId || !$dupeId || $masterId === $dupeId) { ++ return response()->json(['success' => false, 'message' => 'Ungültige IDs.'], 422); ++ } ++ ++ $master = Contact::withoutGlobalScopes()->find($masterId); ++ $dupe = Contact::withoutGlobalScopes()->find($dupeId); ++ ++ if (!$master || !$dupe) { ++ return response()->json(['success' => false, 'message' => 'Kontakt nicht gefunden.'], 404); ++ } ++ ++ DB::transaction(function () use ($masterId, $dupeId) { ++ DB::table('lead')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]); ++ DB::table('booking')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]); ++ DB::table('customer_mails')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]); ++ DB::table('lead_mails')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]); ++ DB::table('customer')->where('id', $dupeId)->update([ ++ 'merged_into_id' => $masterId, ++ 'merged_at' => now(), ++ ]); ++ }); ++ ++ return response()->json(['success' => true]); ++ } ++ ++ // ── Duplikat-Hilfs-Queries ──────────────────────────────────────────────── ++ ++ private function countDuplicateGroups(string $type): int ++ { ++ return match ($type) { ++ 'email' => DB::table('customer')->selectRaw('COUNT(*) as cnt')->whereNotNull('email')->where('email', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('email')->havingRaw('COUNT(*) > 1')->get()->count(), ++ 'name_birthdate' => DB::table('customer')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('birthdate')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'birthdate')->havingRaw('COUNT(*) > 1')->get()->count(), ++ 'name_zip' => DB::table('customer')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('zip')->where('zip', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'zip')->havingRaw('COUNT(*) > 1')->get()->count(), ++ default => 0, ++ }; ++ } ++ ++ private function findByEmail(): array ++ { ++ return DB::table('customer') ++ ->selectRaw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids') ++ ->whereNotNull('email')->where('email', '!=', '') ++ ->whereNull('merged_into_id')->whereNull('deleted_at') ++ ->groupBy('email')->havingRaw('COUNT(*) > 1') ++ ->pluck('ids')->map(fn ($s) => array_map('intval', explode(',', $s)))->all(); ++ } ++ ++ private function findByNameBirthdate(): array ++ { ++ return DB::table('customer') ++ ->selectRaw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids') ++ ->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('birthdate') ++ ->whereNull('merged_into_id')->whereNull('deleted_at') ++ ->groupBy('name', 'firstname', 'birthdate')->havingRaw('COUNT(*) > 1') ++ ->pluck('ids')->map(fn ($s) => array_map('intval', explode(',', $s)))->all(); ++ } ++ ++ private function findByNameZip(): array ++ { ++ return DB::table('customer') ++ ->selectRaw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids') ++ ->whereNotNull('name')->whereNotNull('firstname') ++ ->whereNotNull('zip')->where('zip', '!=', '') ++ ->whereNull('merged_into_id')->whereNull('deleted_at') ++ ->groupBy('name', 'firstname', 'zip')->havingRaw('COUNT(*) > 1') ++ ->pluck('ids')->map(fn ($s) => array_map('intval', explode(',', $s)))->all(); ++ } ++ ++ public function restore(int $id): \Illuminate\Http\JsonResponse ++ { ++ $contact = Contact::onlyTrashed()->withoutGlobalScope('not_merged')->findOrFail($id); ++ $contact->restore(); ++ ++ return response()->json(['success' => true]); ++ } ++ ++ public function getContacts(Request $request): \Illuminate\Http\JsonResponse ++ { ++ $showDeleted = $request->filled('filter_deleted'); ++ ++ // Reihenfolge wichtig: select() zuerst, dann withCount() — sonst überschreibt ++ // select() die COUNT-Subqueries die withCount() per addSelect() eingetragen hat. ++ $query = $showDeleted ++ ? Contact::onlyTrashed()->withoutGlobalScope('not_merged')->select('customer.*')->withCount(['leads', 'bookings']) ++ : Contact::select('customer.*')->withCount(['leads', 'bookings']); ++ ++ // Zusatzfilter aus der UI (werden als extra GET-Parameter gesendet) ++ if ($request->filled('filter_has_leads')) { ++ $query->has('leads'); ++ } ++ if ($request->filled('filter_has_bookings')) { ++ $query->has('bookings'); ++ } ++ if ($request->filled('filter_has_email')) { ++ $query->whereNotNull('email')->where('email', '!=', ''); ++ } ++ ++ return DataTables::eloquent($query) ++ ->addColumn('action_edit', function (Contact $contact) use ($showDeleted) { ++ if ($showDeleted) { ++ return ''; ++ } ++ return ''; ++ }) ++ ->addColumn('action_delete', function (Contact $contact) use ($showDeleted) { ++ if ($showDeleted) { ++ return ''; ++ } ++ return ''; ++ }) ++ ->addColumn('id', function (Contact $contact) use ($showDeleted) { ++ if ($showDeleted) { ++ return '' . $contact->id . ''; ++ } ++ return '' . $contact->id . ''; ++ }) ++ ->addColumn('raw_id', fn(Contact $contact) => $contact->id) ++ ->addColumn('leads_count', fn(Contact $contact) => $contact->leads_count) ++ ->addColumn('bookings_count', fn(Contact $contact) => $contact->bookings_count) ++ ->addColumn('deleted_at', fn(Contact $contact) => $contact->deleted_at?->format('d.m.Y H:i') ?? '') ++ ->orderColumn('id', 'customer.id $1') ++ ->orderColumn('deleted_at', 'customer.deleted_at $1') ++ ->filterColumn('id', function ($query, $keyword) { ++ if ($keyword !== '') { ++ $query->where('customer.id', 'LIKE', '%' . $keyword . '%'); ++ } ++ }) ++ ->filterColumn('name', function ($query, $keyword) { ++ if ($keyword !== '') { ++ $query->where(function ($q) use ($keyword) { ++ $q->where('name', 'LIKE', '%' . $keyword . '%') ++ ->orWhere('firstname', 'LIKE', '%' . $keyword . '%'); ++ }); ++ } ++ }) ++ ->filter(function ($query) use ($request) { ++ $location = $request->input('filter_location'); ++ if ($location && $location !== '') { ++ $query->where(function ($q) use ($location) { ++ $q->where('zip', 'LIKE', '%' . $location . '%') ++ ->orWhere('city', 'LIKE', '%' . $location . '%'); ++ }); ++ } ++ ++ $search = $request->input('search.value'); ++ if ($search && $search !== '') { ++ $query->where(function ($q) use ($search) { ++ $q->where('name', 'LIKE', '%' . $search . '%') ++ ->orWhere('firstname', 'LIKE', '%' . $search . '%') ++ ->orWhere('email', 'LIKE', '%' . $search . '%') ++ ->orWhere('phone', 'LIKE', '%' . $search . '%') ++ ->orWhere('phonemobile', 'LIKE', '%' . $search . '%'); ++ }); ++ } ++ }, true) ++ ->rawColumns(['action_edit', 'action_delete', 'id']) ++ ->make(true); ++ } ++} diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Customer.php.phase1-only.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Customer.php.phase1-only.diff new file mode 100644 index 0000000..10750a2 --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/Customer.php.phase1-only.diff @@ -0,0 +1,40 @@ +diff --git a/app/Models/Customer.php b/app/Models/Customer.php +index 94e618e..436b3ee 100644 +--- a/app/Models/Customer.php ++++ b/app/Models/Customer.php +@@ -9,6 +9,7 @@ namespace App\Models; + use App\Models\Sym\TravelCountry; + use Carbon\Carbon; + use Illuminate\Database\Eloquent\Collection; ++use Illuminate\Database\Eloquent\Factories\HasFactory; + use Illuminate\Database\Eloquent\Model; + + /** +@@ -83,6 +84,8 @@ use Illuminate\Database\Eloquent\Model; + */ + class Customer extends Model + { ++ use HasFactory; ++ + protected $connection = 'mysql'; + + protected $table = 'customer'; +@@ -90,13 +93,12 @@ class Customer extends Model + protected $casts = [ + 'salutation_id' => 'int', + 'credit_card_type_id' => 'int', +- 'country_id' => 'int' +- ]; ++ 'country_id' => 'int', ++ 'birthdate' => 'datetime', ++ 'credit_card_expiration_date' => 'datetime', ++ ]; + +- protected $dates = [ +- 'birthdate', +- 'credit_card_expiration_date' +- ]; ++ + + protected $fillable = [ + 'salutation_id', diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Lead.php.phase1-only.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Lead.php.phase1-only.diff new file mode 100644 index 0000000..3f81f4e --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/Lead.php.phase1-only.diff @@ -0,0 +1,44 @@ +diff --git a/app/Models/Lead.php b/app/Models/Lead.php +index 8b84b9e..227bd38 100644 +--- a/app/Models/Lead.php ++++ b/app/Models/Lead.php +@@ -9,6 +9,7 @@ namespace App\Models; + use Carbon\Carbon; + use App\Services\Passolution; + use App\Models\Lead as ModelsLead; ++use Illuminate\Database\Eloquent\Factories\HasFactory; + use Illuminate\Database\Eloquent\Model; + use Illuminate\Database\Eloquent\Collection; + +@@ -107,6 +108,8 @@ use Illuminate\Database\Eloquent\Collection; + */ + class Lead extends Model + { ++ use HasFactory; ++ + protected $connection = 'mysql'; + + protected $table = 'lead'; +@@ -126,16 +129,14 @@ class Lead extends Model + 'travelcategory_id' => 'int', + 'price' => 'float', + 'pax' => 'int', +- 'participant_salutation_id' => 'int' ++ 'participant_salutation_id' => 'int', ++ 'request_date' => 'datetime', ++ 'travelperiod_start' => 'datetime', ++ 'travelperiod_end' => 'datetime', ++ 'next_due_date' => 'datetime', ++ 'participant_birthdate' => 'datetime', + ]; + +- protected $dates = [ +- 'request_date', +- 'travelperiod_start', +- 'travelperiod_end', +- 'next_due_date', +- 'participant_birthdate' +- ]; + + protected $fillable = [ + 'customer_id', diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Repositories.full.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Repositories.full.diff new file mode 100644 index 0000000..e4e519b --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/Repositories.full.diff @@ -0,0 +1,159 @@ +diff --git a/app/Repositories/BookingPDFRepository.php b/app/Repositories/BookingPDFRepository.php +index 505ddaf..e5ba139 100644 +--- a/app/Repositories/BookingPDFRepository.php ++++ b/app/Repositories/BookingPDFRepository.php +@@ -22,7 +22,7 @@ class BookingPDFRepository extends BaseRepository + public function __construct(Booking $model) + { + $this->model = $model; +- $this->prepath = Storage::disk('public')->getAdapter()->getPathPrefix(); ++ $this->prepath = Storage::disk('public')->path(''); + } + + public function update($data) +@@ -63,13 +63,13 @@ class BookingPDFRepository extends BaseRepository + { + $document = new stdClass(); + $document->name = 'registration'; +- $document->number = $this->model->lead_id; ++ $document->number = $this->model->inquiry_id; + $document->title = 'BUCHUNGSAUFTRAG'; + $document->voucher = null; + $document->date = now(); + $document->total = $this->model->getPriceRaw(); + $dir = $this->getDirPath('pdf', 'booking', $document->date->format('Y')); +- $filename = "Buchnungsauftrag-" . $this->model->lead_id . ".pdf"; ++ $filename = "Buchnungsauftrag-" . $this->model->inquiry_id . ".pdf"; + $pdf_file = new CreatePDF('pdf.booking_registration'); + $data = [ + 'booking' => $this->model, +@@ -85,7 +85,7 @@ class BookingPDFRepository extends BaseRepository + { + $document = new stdClass(); + $document->name = 'confirmation'; +- $document->number = $this->model->lead_id; ++ $document->number = $this->model->inquiry_id; + $document->title = 'REISEBESTÄTIGUNG'; + $document->voucher = null; + $document->date = now(); +@@ -104,7 +104,7 @@ class BookingPDFRepository extends BaseRepository + $document->final_payment_date = date('Y-m-d'); + } + $dir = $this->getDirPath('pdf', 'booking', $document->date->format('Y')); +- $filename = "Reisebestätigung-" . $this->model->lead_id . ".pdf"; ++ $filename = "Reisebestätigung-" . $this->model->inquiry_id . ".pdf"; + + $pdf_file = new CreatePDF('pdf.booking_confirmation'); + $data = [ +@@ -160,14 +160,14 @@ class BookingPDFRepository extends BaseRepository + { + $document = new stdClass(); + $document->name = 'voucher'; +- $document->number = $this->model->lead_id; ++ $document->number = $this->model->inquiry_id; + $document->name = 'voucher'; + $document->title = $agency ? 'VOUCHER Agentur' : 'VOUCHER'; + $document->voucher = $agency ? 'agency' : 'client'; + $document->date = now(); + + $dir = $this->getDirPath('pdf', 'voucher', $document->date->format('Y')); +- $filename = ($agency ? 'VoucherAgentur' : 'Voucher') . "-" . $this->model->lead_id . ".pdf"; ++ $filename = ($agency ? 'VoucherAgentur' : 'Voucher') . "-" . $this->model->inquiry_id . ".pdf"; + + $pdf_file = new CreatePDF('pdf.booking_voucher'); + $data = [ +@@ -224,7 +224,7 @@ class BookingPDFRepository extends BaseRepository + //init document + $document = new stdClass(); + $document->name = $identifier; +- $document->number = $this->model->lead_id; ++ $document->number = $this->model->inquiry_id; + $document->title = 'STORNOBESTÄTIGUNG'; + $document->voucher = null; + $document->date = Carbon::parse($data['storno_print']); +@@ -253,7 +253,7 @@ class BookingPDFRepository extends BaseRepository + + + $dir = $this->getDirPath('pdf', 'storno', $document->date->format('Y')); +- $filename = "Reisestornierung -" . $this->model->lead_id . ".pdf"; ++ $filename = "Reisestornierung -" . $this->model->inquiry_id . ".pdf"; + + $pdf_file = new CreatePDF('pdf.booking_storno'); + $data = [ +@@ -288,7 +288,9 @@ class BookingPDFRepository extends BaseRepository + $fill = [ + 'booking_id' => $this->model->id, + 'customer_id' => $this->model->customer_id, +- 'lead_id' => $this->model->lead_id, ++ // booking_documents.lead_id ist ein Shadow-Feld von booking.inquiry_id; ++ // die Spalte selbst wird von Phase 2 nicht umbenannt. ++ 'lead_id' => $this->model->inquiry_id, + 'identifier' => $identifier, + 'filename' => $filename, + 'dir' => $dir, +diff --git a/app/Repositories/CustomerMailRepository.php b/app/Repositories/CustomerMailRepository.php +index c1b1226..6397f0c 100644 +--- a/app/Repositories/CustomerMailRepository.php ++++ b/app/Repositories/CustomerMailRepository.php +@@ -135,7 +135,8 @@ class CustomerMailRepository extends BaseRepository { + $customer_mail->fill([ + 'booking_id' => $booking->id, + 'customer_id' => $booking->customer_id, +- 'lead_id' => $booking->lead_id, ++ // customer_mails.lead_id-Spalte bleibt unverändert; Wert kommt aus booking.inquiry_id ++ 'lead_id' => $booking->inquiry_id, + 'is_answer' => $is_answer, + 'reply_id' => $reply_id, + 'email' => $mail_from, +@@ -153,7 +154,8 @@ class CustomerMailRepository extends BaseRepository { + $customer_mail = CustomerMail::create([ + 'booking_id' => $booking->id, + 'customer_id' => $booking->customer_id, +- 'lead_id' => $booking->lead_id, ++ // customer_mails.lead_id-Spalte bleibt unverändert; Wert kommt aus booking.inquiry_id ++ 'lead_id' => $booking->inquiry_id, + 'is_answer' => $is_answer, + 'reply_id' => $reply_id, + 'email' => $mail_from, +@@ -300,7 +302,7 @@ class CustomerMailRepository extends BaseRepository { + $value->id = $customer_mail->booking_id; + $value->booking = $booking; + $value->show = 'single'; +- $value->lead_title_id = " - (".$value->booking->lead_id.")"; ++ $value->lead_title_id = " - (".$value->booking->inquiry_id.")"; + + + $tmp = []; +@@ -342,7 +344,7 @@ class CustomerMailRepository extends BaseRepository { + $value->booking = $booking; + $value->show = 'single'; + $value->draft = true; +- $value->lead_title_id = " - (".$value->booking->lead_id.")"; ++ $value->lead_title_id = " - (".$value->booking->inquiry_id.")"; + + }else{ + //multi +@@ -379,8 +381,8 @@ class CustomerMailRepository extends BaseRepository { + $value->draft = false; + $value->booking = $booking; + $value->message = ""; +- $value->subject = " - (".$value->booking->lead_id.")"; +- $value->lead_title_id = " - (".$value->booking->lead_id.")"; ++ $value->subject = " - (".$value->booking->inquiry_id.")"; ++ $value->lead_title_id = " - (".$value->booking->inquiry_id.")"; + $value->s_placeholder = "Betreff des Kunden"; + $value->m_placeholder = "Nachricht des Kunden"; + if(isset($data['customer_mail_id']) && $customer_mail = CustomerMail::find($data['customer_mail_id'])){ +diff --git a/app/Repositories/LeadRepository.php b/app/Repositories/LeadRepository.php +index 929426d..ad0ac87 100644 +--- a/app/Repositories/LeadRepository.php ++++ b/app/Repositories/LeadRepository.php +@@ -134,7 +134,7 @@ class LeadRepository extends BaseRepository { + $data = [ + 'booking_date' => date('Y-m-d'), //now + 'customer_id' => $this->model->customer->id, +- 'lead_id' => $this->model->id, ++ 'inquiry_id' => $this->model->id, + 'new_drafts' => 1, + 'sf_guard_user_id' => $this->model->sf_guard_user_id, + 'branch_id' => 4, diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Repositories.phase1-only.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Repositories.phase1-only.diff new file mode 100644 index 0000000..932b124 --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/Repositories.phase1-only.diff @@ -0,0 +1,13 @@ +diff --git a/app/Repositories/BookingPDFRepository.php b/app/Repositories/BookingPDFRepository.php +index 505ddaf..dc4d313 100644 +--- a/app/Repositories/BookingPDFRepository.php ++++ b/app/Repositories/BookingPDFRepository.php +@@ -22,7 +22,7 @@ class BookingPDFRepository extends BaseRepository + public function __construct(Booking $model) + { + $this->model = $model; +- $this->prepath = Storage::disk('public')->getAdapter()->getPathPrefix(); ++ $this->prepath = Storage::disk('public')->path(''); + } + + public function update($data) diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Services-Commands.phase1-only.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Services-Commands.phase1-only.diff new file mode 100644 index 0000000..88694fe --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/Services-Commands.phase1-only.diff @@ -0,0 +1,405 @@ +diff --git a/app/Console/Commands/ContactsFindDuplicates.php b/app/Console/Commands/ContactsFindDuplicates.php +new file mode 100644 +index 0000000..cef982d +--- /dev/null ++++ b/app/Console/Commands/ContactsFindDuplicates.php +@@ -0,0 +1,160 @@ ++info('Suche nach Duplikaten in der customer-Tabelle...'); ++ $this->newLine(); ++ ++ $groups = collect(); ++ ++ // ── HIGH: gleiche E-Mail ────────────────────────────────────────── ++ if ($this->shouldCheck('HIGH')) { ++ $emailDupes = DB::table('customer') ++ ->select('email', DB::raw('COUNT(*) as cnt'), DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC) as ids')) ++ ->whereNotNull('email') ++ ->where('email', '!=', '') ++ ->whereNull('merged_into_id') ++ ->groupBy('email') ++ ->having('cnt', '>', 1) ++ ->get(); ++ ++ foreach ($emailDupes as $row) { ++ $groups->push([ ++ 'confidence' => 'HIGH', ++ 'reason' => 'E-Mail: ' . $row->email, ++ 'ids' => $row->ids, ++ 'count' => $row->cnt, ++ ]); ++ } ++ ++ $this->line(sprintf('HIGH (gleiche E-Mail): %d Gruppen', $emailDupes->count())); ++ } ++ ++ // ── MEDIUM: Name + Vorname + Geburtsdatum ──────────────────────── ++ if ($this->shouldCheck('MEDIUM')) { ++ $nameBdDupes = DB::table('customer') ++ ->select( ++ 'name', 'firstname', 'birthdate', ++ DB::raw('COUNT(*) as cnt'), ++ DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC) as ids') ++ ) ++ ->whereNotNull('name') ++ ->whereNotNull('firstname') ++ ->whereNotNull('birthdate') ++ ->whereNull('merged_into_id') ++ ->groupBy('name', 'firstname', 'birthdate') ++ ->having('cnt', '>', 1) ++ ->get(); ++ ++ foreach ($nameBdDupes as $row) { ++ $groups->push([ ++ 'confidence' => 'MEDIUM', ++ 'reason' => "Name: {$row->firstname} {$row->name}, GD: {$row->birthdate}", ++ 'ids' => $row->ids, ++ 'count' => $row->cnt, ++ ]); ++ } ++ ++ $this->line(sprintf('MEDIUM (Name+GD): %d Gruppen', $nameBdDupes->count())); ++ } ++ ++ // ── LOW: Name + Vorname + PLZ ───────────────────────────────────── ++ if ($this->shouldCheck('LOW')) { ++ $nameZipDupes = DB::table('customer') ++ ->select( ++ 'name', 'firstname', 'zip', ++ DB::raw('COUNT(*) as cnt'), ++ DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC) as ids') ++ ) ++ ->whereNotNull('name') ++ ->whereNotNull('firstname') ++ ->whereNotNull('zip') ++ ->where('zip', '!=', '') ++ ->whereNull('merged_into_id') ++ ->groupBy('name', 'firstname', 'zip') ++ ->having('cnt', '>', 1) ++ ->get(); ++ ++ foreach ($nameZipDupes as $row) { ++ $groups->push([ ++ 'confidence' => 'LOW', ++ 'reason' => "Name: {$row->firstname} {$row->name}, PLZ: {$row->zip}", ++ 'ids' => $row->ids, ++ 'count' => $row->cnt, ++ ]); ++ } ++ ++ $this->line(sprintf('LOW (Name+PLZ): %d Gruppen', $nameZipDupes->count())); ++ } ++ ++ $this->newLine(); ++ $this->info(sprintf('Gesamt: %d Duplikat-Gruppen gefunden', $groups->count())); ++ ++ if ($groups->isEmpty()) { ++ $this->info('Keine Duplikate — nichts zu tun.'); ++ return self::SUCCESS; ++ } ++ ++ // Tabellen-Ausgabe ++ $this->table( ++ ['Konfidenz', 'Grund', 'IDs (neueste zuerst)', 'Anzahl'], ++ $groups->map(fn ($g) => [$g['confidence'], $g['reason'], $g['ids'], $g['count']])->all() ++ ); ++ ++ // CSV-Export ++ if ($export = $this->option('export')) { ++ $this->exportCsv($groups->all(), $export); ++ $this->info("CSV gespeichert: {$export}"); ++ } else { ++ $this->newLine(); ++ $this->line('Tipp: --export=duplicates.csv für CSV-Export'); ++ $this->line('Tipp: php artisan contacts:merge-duplicates --dry-run zum Prüfen'); ++ } ++ ++ return self::SUCCESS; ++ } ++ ++ private function shouldCheck(string $level): bool ++ { ++ $filter = strtoupper((string) $this->option('confidence')); ++ return $filter === '' || $filter === $level; ++ } ++ ++ private function exportCsv(array $groups, string $path): void ++ { ++ $handle = fopen($path, 'w'); ++ fputcsv($handle, ['Konfidenz', 'Grund', 'IDs (neueste zuerst)', 'Anzahl']); ++ foreach ($groups as $group) { ++ fputcsv($handle, [$group['confidence'], $group['reason'], $group['ids'], $group['count']]); ++ } ++ fclose($handle); ++ } ++} +diff --git a/app/Console/Commands/ContactsMergeDuplicates.php b/app/Console/Commands/ContactsMergeDuplicates.php +new file mode 100644 +index 0000000..e51ca46 +--- /dev/null ++++ b/app/Console/Commands/ContactsMergeDuplicates.php +@@ -0,0 +1,233 @@ ++dryRun = (bool) $this->option('dry-run'); ++ ++ if ($this->dryRun) { ++ $this->warn('DRY-RUN Modus — keine Daten werden verändert'); ++ } else { ++ $this->warn('ACHTUNG: Diese Operation verändert Produktionsdaten.'); ++ if (!$this->option('force') && !$this->confirm('Fortfahren?')) { ++ $this->info('Abgebrochen.'); ++ return self::SUCCESS; ++ } ++ } ++ ++ $this->newLine(); ++ ++ DB::transaction(function () { ++ $this->processLevel( ++ 'HIGH', ++ fn () => $this->findByEmail() ++ ); ++ ++ $this->processLevel( ++ 'MEDIUM', ++ fn () => $this->findByNameBirthdate() ++ ); ++ ++ $this->processLevel( ++ 'LOW', ++ fn () => $this->findByNameZip() ++ ); ++ }); ++ ++ $this->newLine(); ++ $this->info(sprintf( ++ '%s %d Duplikate zusammengeführt | %d leads aktualisiert | %d bookings aktualisiert', ++ $this->dryRun ? '[DRY-RUN]' : '', ++ $this->mergedCount, ++ $this->updatedLeads, ++ $this->updatedBookings ++ )); ++ ++ if ($this->dryRun) { ++ $this->newLine(); ++ $this->line('Zum Ausführen: php artisan contacts:merge-duplicates'); ++ } ++ ++ return self::SUCCESS; ++ } ++ ++ // ───────────────────────────────────────────────────────────────────────── ++ // Duplikat-Gruppen ermitteln ++ // ───────────────────────────────────────────────────────────────────────── ++ ++ private function findByEmail(): array ++ { ++ return DB::table('customer') ++ ->select('email', DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')) ++ ->whereNotNull('email') ++ ->where('email', '!=', '') ++ ->whereNull('merged_into_id') ++ ->groupBy('email') ++ ->having(DB::raw('COUNT(*)'), '>', 1) ++ ->pluck('ids') ++ ->map(fn ($ids) => explode(',', $ids)) ++ ->all(); ++ } ++ ++ private function findByNameBirthdate(): array ++ { ++ return DB::table('customer') ++ ->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')) ++ ->whereNotNull('name') ++ ->whereNotNull('firstname') ++ ->whereNotNull('birthdate') ++ ->whereNull('merged_into_id') ++ ->groupBy('name', 'firstname', 'birthdate') ++ ->having(DB::raw('COUNT(*)'), '>', 1) ++ ->pluck('ids') ++ ->map(fn ($ids) => explode(',', $ids)) ++ ->all(); ++ } ++ ++ private function findByNameZip(): array ++ { ++ return DB::table('customer') ++ ->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')) ++ ->whereNotNull('name') ++ ->whereNotNull('firstname') ++ ->whereNotNull('zip') ++ ->where('zip', '!=', '') ++ ->whereNull('merged_into_id') ++ ->groupBy('name', 'firstname', 'zip') ++ ->having(DB::raw('COUNT(*)'), '>', 1) ++ ->pluck('ids') ++ ->map(fn ($ids) => explode(',', $ids)) ++ ->all(); ++ } ++ ++ // ───────────────────────────────────────────────────────────────────────── ++ // Verarbeitung ++ // ───────────────────────────────────────────────────────────────────────── ++ ++ private function processLevel(string $level, callable $finder): void ++ { ++ if ($filter = $this->option('confidence')) { ++ if (strtoupper($filter) !== $level) { ++ return; ++ } ++ } ++ ++ $groups = $finder(); ++ ++ if (empty($groups)) { ++ $this->line("[{$level}] Keine Duplikate."); ++ return; ++ } ++ ++ $this->line(sprintf('[%s] %d Gruppe(n) gefunden', $level, count($groups))); ++ ++ foreach ($groups as $ids) { ++ $masterId = (int) $ids[0]; // erster = neuester ++ $duplicateIds = array_map('intval', array_slice($ids, 1)); ++ ++ $this->line(sprintf( ++ ' Master: #%d ← Duplikate: %s', ++ $masterId, ++ implode(', ', array_map(fn ($id) => '#' . $id, $duplicateIds)) ++ )); ++ ++ foreach ($duplicateIds as $dupeId) { ++ $this->mergeInto($masterId, $dupeId); ++ } ++ } ++ } ++ ++ private function mergeInto(int $masterId, int $dupeId): void ++ { ++ // 1. Leads umhängen ++ $leadCount = DB::table('lead')->where('customer_id', $dupeId)->count(); ++ if ($leadCount > 0) { ++ $this->line(" lead.customer_id: {$leadCount} Zeile(n) → #{$masterId}"); ++ if (!$this->dryRun) { ++ DB::table('lead') ++ ->where('customer_id', $dupeId) ++ ->update(['customer_id' => $masterId]); ++ } ++ $this->updatedLeads += $leadCount; ++ } ++ ++ // 2. Bookings umhängen ++ $bookingCount = DB::table('booking')->where('customer_id', $dupeId)->count(); ++ if ($bookingCount > 0) { ++ $this->line(" booking.customer_id: {$bookingCount} Zeile(n) → #{$masterId}"); ++ if (!$this->dryRun) { ++ DB::table('booking') ++ ->where('customer_id', $dupeId) ++ ->update(['customer_id' => $masterId]); ++ } ++ $this->updatedBookings += $bookingCount; ++ } ++ ++ // 3. customer_mails umhängen ++ $mailCount = DB::table('customer_mails')->where('customer_id', $dupeId)->count(); ++ if ($mailCount > 0) { ++ $this->line(" customer_mails.customer_id: {$mailCount} Zeile(n) → #{$masterId}"); ++ if (!$this->dryRun) { ++ DB::table('customer_mails') ++ ->where('customer_id', $dupeId) ++ ->update(['customer_id' => $masterId]); ++ } ++ } ++ ++ // 4. lead_mails umhängen ++ $leadMailCount = DB::table('lead_mails')->where('customer_id', $dupeId)->count(); ++ if ($leadMailCount > 0) { ++ $this->line(" lead_mails.customer_id: {$leadMailCount} Zeile(n) → #{$masterId}"); ++ if (!$this->dryRun) { ++ DB::table('lead_mails') ++ ->where('customer_id', $dupeId) ++ ->update(['customer_id' => $masterId]); ++ } ++ } ++ ++ // 5. Duplikat als zusammengeführt markieren ++ if (!$this->dryRun) { ++ DB::table('customer') ++ ->where('id', $dupeId) ++ ->update([ ++ 'merged_into_id' => $masterId, ++ 'merged_at' => now(), ++ ]); ++ } ++ ++ $this->mergedCount++; ++ } ++} diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/Views.phase1-only.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/Views.phase1-only.diff new file mode 100644 index 0000000..e69de29 diff --git a/dev/backups/phase2-offers-2026-04-17/PATCHES/filesystems.php.full.diff b/dev/backups/phase2-offers-2026-04-17/PATCHES/filesystems.php.full.diff new file mode 100644 index 0000000..75218f9 --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/PATCHES/filesystems.php.full.diff @@ -0,0 +1,17 @@ +diff --git a/config/filesystems.php b/config/filesystems.php +index 2318ca2..97762c8 100755 +--- a/config/filesystems.php ++++ b/config/filesystems.php +@@ -72,6 +72,12 @@ return [ + 'url' => env('APP_URL').'/storage/booking', + 'visibility' => 'public', + ], ++ 'offer' => [ ++ 'driver' => 'local', ++ 'root' => storage_path('app/offer'), ++ 'url' => env('APP_URL').'/storage/offer', ++ 'visibility' => 'public', ++ ], + 'general' => [ + 'driver' => 'local', + 'root' => storage_path('app/general'), diff --git a/dev/backups/phase2-offers-2026-04-17/restore.sh b/dev/backups/phase2-offers-2026-04-17/restore.sh new file mode 100755 index 0000000..e887a91 --- /dev/null +++ b/dev/backups/phase2-offers-2026-04-17/restore.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# +# Restore-Script: spielt Phase 2 + Offers-Änderungen zurück in den Workspace. +# Voraussetzung: Phase 1 wurde erfolgreich auf Live deployed und migriert. +# +# Nutzung: +# bash dev/backups/phase2-offers-2026-04-17/restore.sh # interaktiv +# bash dev/backups/phase2-offers-2026-04-17/restore.sh --force # ohne Nachfrage +# +# Details siehe MANIFEST.md im selben Verzeichnis. + +set -euo pipefail + +BACKUP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${BACKUP_DIR}/../../../" && pwd)" +FORCE=false + +for arg in "$@"; do + case "$arg" in + --force|-f) FORCE=true ;; + *) echo "Unbekannte Option: $arg"; exit 1 ;; + esac +done + +cd "${PROJECT_ROOT}" + +echo "==========================================" +echo " Phase-2 + Offers Restore" +echo "==========================================" +echo +echo " Projekt-Root: ${PROJECT_ROOT}" +echo " Backup-Ordner: ${BACKUP_DIR}" +echo +echo " Folgende Änderungen werden zurückgespielt:" +echo " - 18 Migrationen (Phase 2, 3, 4, Offers) nach database/migrations/" +echo " - 6 Offer-Models nach app/Models/ (inkl. Überschreiben von Legacy-Offer.php)" +echo " - Code-Reverts via git checkout \${WIP_COMMIT} -- " +echo + +WIP_COMMIT="e3dc1af" +if ! git cat-file -e "${WIP_COMMIT}" 2>/dev/null; then + echo "FEHLER: Commit ${WIP_COMMIT} existiert nicht in diesem Repo." + echo " Wenn das Repo neu geklont wurde, musst du stattdessen manuell" + echo " die Dateien aus dem Tarball wiederherstellen." + echo " Siehe MANIFEST.md → Abschnitt 'Manuell'." + exit 2 +fi + +if ! ${FORCE}; then + echo + read -r -p " Wurde Phase 1 auf Live erfolgreich deployed und migriert? (yes/NO): " answer + if [[ "${answer}" != "yes" ]]; then + echo " Abbruch. Bitte erst Phase 1 auf Live einspielen." + exit 3 + fi + echo + read -r -p " Trotzdem fortfahren? (yes/NO): " confirm + if [[ "${confirm}" != "yes" ]]; then + echo " Abbruch durch User." + exit 3 + fi +fi + +echo +echo "--- 1/4 --- Migrationen zurückspielen" +cp -v "${BACKUP_DIR}/FILES/migrations/"*.php database/migrations/ +echo " → $(ls "${BACKUP_DIR}/FILES/migrations/" | wc -l) Migrationen kopiert" + +echo +echo "--- 2/4 --- Offer-Models zurückspielen" +cp -v "${BACKUP_DIR}/FILES/models/"*.php app/Models/ +echo " → 6 Models kopiert (inkl. Überschreiben Legacy-Offer.php)" + +echo +echo "--- 3/4 --- Code-Reverts aus Git-Commit ${WIP_COMMIT}" +FILES_TO_RESTORE=( + "app/Models/Booking.php" + "app/Models/Customer.php" + "app/Models/Contact.php" + "app/Models/Lead.php" + "app/Repositories/BookingPDFRepository.php" + "app/Repositories/LeadRepository.php" + "app/Repositories/CustomerMailRepository.php" + "app/Http/Controllers/RequestController.php" + "app/Http/Controllers/API/BookingController.php" + "app/Http/Controllers/Admin/ReportController.php" + "app/Http/Controllers/Admin/ReportProviderController.php" + "app/Http/Controllers/Admin/ReportLeadsController.php" + "app/Http/Controllers/LeadController.php" + "app/Http/Controllers/CustomerController.php" + "app/Http/Controllers/ContactController.php" + "app/Services/BookingImport.php" + "app/Console/Commands/SyncNewsletterKulturreisen.php" + "app/Console/Commands/ContactsFindDuplicates.php" + "app/Console/Commands/ContactsMergeDuplicates.php" + "resources/views/customer/mail/modal-show-mail-inner.blade.php" + "resources/views/pdf/components/booking_head.blade.php" + "resources/views/pdf/components/booking_header.blade.php" + "config/filesystems.php" +) +for f in "${FILES_TO_RESTORE[@]}"; do + git checkout "${WIP_COMMIT}" -- "${f}" + echo " ↺ ${f}" +done + +echo +echo "--- 4/4 --- Verifikation" +echo +echo " git status (sollte Phase-2 + Offers-Dateien als modifiziert zeigen):" +git status --short | head -40 +echo +echo " Verbliebene Phase-2-Marker im Code (sollten nun wieder da sein):" +grep -rn "inquiry_id" app/ 2>/dev/null | head -5 || echo " (keine gefunden — Restore eventuell nicht vollständig)" +echo + +echo "==========================================" +echo " Restore abgeschlossen." +echo "==========================================" +echo +echo " Nächste Schritte:" +echo " 1. php -l auf allen geänderten Dateien (Syntax-Check)" +echo " 2. composer dump-autoload (neue Models registrieren)" +echo " 3. Weiterarbeit an Phase 2 + Offers nach Plan" +echo " 4. Phase-2-Migrationen ausführen:" +echo " php artisan migrate --path=database/migrations/2025_04_15_200001_phase2_rename_customer_to_contacts.php" +echo " php artisan migrate --path=database/migrations/2025_04_15_200002_phase2_rename_lead_to_inquiries.php" +echo " php artisan migrate --path=database/migrations/2025_04_15_200003_phase2_rename_booking_lead_id_to_inquiry_id.php" +echo diff --git a/dev/customer-bookings/phase-1-live-deploy.md b/dev/customer-bookings/phase-1-live-deploy.md new file mode 100644 index 0000000..fabc883 --- /dev/null +++ b/dev/customer-bookings/phase-1-live-deploy.md @@ -0,0 +1,241 @@ +# Phase-1-Live-Deploy — Anleitung + +**Ausgangssituation:** +- Test-System läuft mit Phase 1 (Laravel-10-Upgrade, Contacts-Modul, Duplicats-Commands, Soft-Delete- & Merge-Fields auf `customer`-Tabelle) +- Live-Server ist bei Git-Commit `389d5d1` (Januar 2026) — noch ohne Phase 1 +- Der Workspace wurde per `dev/backups/phase2-offers-2026-04-17/` um Phase 2 + Offers bereinigt und ist jetzt **exakt auf Phase-1-Stand** wie Test + +--- + +## Vorbereitung (einmalig) + +1. **Backup auf Live-Server** + - Vollständiges DB-Backup (mysqldump oder Hoster-Tool) — speichere mit Zeitstempel + - Vollständiges Dateisystem-Backup (zumindest `/app`, `/config`, `/routes`, `/resources`, `/database/migrations`, `/composer.json`, `/composer.lock`) + +2. **Maintenance-Mode aktivieren** (empfohlen für 15–30 min) + ```bash + php artisan down --render="errors::503" --secret="dein-geheimer-preview-token" + ``` + Mit `--secret` kannst du über `https://domain/dein-geheimer-preview-token` weiter auf Live zugreifen, während alle anderen die 503-Seite sehen. + +3. **Abhängigkeiten abklären** + - Läuft auf Live mindestens **PHP 8.1** (Laravel-10-Anforderung)? Prüfen: `php -v` + - Ist **Composer** auf dem Live-Server installiert? Falls Managed Hosting: `composer install` vor dem Deploy auf einem Staging-Server ausführen und das `vendor/`-Verzeichnis mitsyncen + +--- + +## Upload (rsync / scp / SFTP) + +### Was **muss** hochgeladen werden + +**Neue Dateien:** +``` +database/migrations/2025_04_15_100001_phase1_add_merge_fields_to_customer_table.php +database/migrations/2025_04_15_100002_phase1_add_soft_delete_to_customer_table.php +app/Models/Contact.php +app/Repositories/ContactRepository.php +app/Http/Controllers/ContactController.php +app/Console/Commands/ContactsFindDuplicates.php +app/Console/Commands/ContactsMergeDuplicates.php +app/Services/MailDirService.php +resources/views/contact/_detail_contact.blade.php +resources/views/contact/_detail_history.blade.php +resources/views/contact/detail.blade.php +resources/views/contact/duplicates.blade.php +resources/views/contact/index.blade.php +database/factories/BookingFactory.php +database/factories/CustomerFactory.php +database/factories/LeadFactory.php +tests/Feature/Api/BookingImportTest.php +tests/Feature/Auth/LoginTest.php +tests/Feature/BookingControllerTest.php +tests/Unit/Services/UtilTest.php +``` + +**Modifizierte Dateien** — vollständige Upload-Liste generieren: + +```bash +cd /workspace/mein.sterntours.de +git diff --name-only HEAD^ \ + | grep -Ev "^(dev/|tests/|\.env|\.mcp\.json$|CLAUDE\.md$|boost\.json$|_ide_helper|\.devcontainer/|bootstrap/cache/|mein\.sterntours\.de\.code-workspace$|docker-compose\.yml$|public/storage$)" \ + > /tmp/phase1-files-to-upload.txt +cat /tmp/phase1-files-to-upload.txt +wc -l /tmp/phase1-files-to-upload.txt +``` + +Das filtert die unerwünschten Dateien automatisch raus. + +Die wichtigsten modifizierten Dateien (komplette Liste in der generierten Textdatei): +- `app/Models/Customer.php`, `Booking.php`, `Lead.php` (und viele weitere Models — Laravel-10-Upgrade) +- `app/Http/Controllers/*.php` (Controllers mit Laravel-10-Anpassungen) +- `app/Http/Kernel.php`, `app/Console/Kernel.php`, `app/Providers/*.php`, `app/Http/Middleware/TrustProxies.php` (Laravel-10-Upgrade) +- `app/Repositories/*.php` (Laravel-10: `Storage::disk()->path()` statt `getAdapter()->getPathPrefix()`) +- `app/Libraries/CreatePDF.php` +- `routes/web.php` (neue `/contacts`-Routen) +- `resources/views/layouts/application.blade.php`, `resources/views/layouts/includes/layout-sidenav.blade.php` +- `config/trustedproxy.php` +- `packages/digital-bird/shoppingcart/**`, `packages/iqcontent/laravel-filemanager/**` (Package-Updates) +- `composer.json`, `composer.lock` +- `phpunit.xml` + +### Was **nicht** hochgeladen werden darf + +- **Alles unter `dev/`** — dort liegt Entwicklungsdokumentation und die Backups (Phase 2, Offers) +- `.env`, `.env.*` — enthält produktive Credentials, wird separat verwaltet +- `_ide_helper.php`, `_ide_helper_models.php` — lokale IDE-Hilfen (von barryvdh/laravel-ide-helper generiert) +- `bootstrap/cache/config.php` — wird auf Live per `php artisan config:cache` frisch generiert +- `.mcp.json`, `CLAUDE.md`, `boost.json` — Dev-Tools-Konfiguration +- `.devcontainer/` — Entwicklungsumgebung (VSCode-Devcontainer) +- `storage/logs/*`, `storage/framework/cache/*`, `storage/framework/views/*`, `storage/framework/sessions/*` — Runtime-Daten +- `vendor/` — wird auf Live per `composer install` generiert (außer Managed Hosting, siehe oben) +- `node_modules/` — genauso +- `.git/` — Repo-Metadaten +- `tests/` — nur auf Test relevant; auf Live-Production nicht nötig (aber nicht schädlich, falls mit rauf kommt) +- `mein.sterntours.de.code-workspace` — IDE-Workspace-Datei +- `docker-compose.yml` — lokale Dev-Orchestrierung + +### Beispiel rsync-Kommando (als Orientierung) + +```bash +# Von lokal → Live +# Achtung: --dry-run zuerst, dann ohne --dry-run wiederholen! +rsync -av --dry-run \ + --exclude='.git/' \ + --exclude='dev/' \ + --exclude='node_modules/' \ + --exclude='vendor/' \ + --exclude='storage/logs/' \ + --exclude='storage/framework/cache/' \ + --exclude='storage/framework/views/' \ + --exclude='storage/framework/sessions/' \ + --exclude='storage/app/public/temp/' \ + --exclude='public/storage' \ + --exclude='.env*' \ + --exclude='bootstrap/cache/' \ + --exclude='_ide_helper*' \ + --exclude='.mcp.json' \ + --exclude='CLAUDE.md' \ + --exclude='boost.json' \ + --exclude='.devcontainer/' \ + /workspace/mein.sterntours.de/ \ + user@live-server:/pfad/zu/mein.sterntours.de/ +``` + +Falls du **nur geänderte Dateien** syncen möchtest (sicherer bei großer Codebase): +```bash +git diff --name-only HEAD^ | grep -v "^dev/" > /tmp/phase1-files.txt +rsync -av --files-from=/tmp/phase1-files.txt . user@live-server:/pfad/zu/mein.sterntours.de/ +``` + +--- + +## Ausführung auf Live-Server + +Nach dem Upload, per SSH auf dem Live-Server: + +```bash +cd /pfad/zu/mein.sterntours.de + +# 1. Composer Dependencies aktualisieren (Laravel 10 Upgrade!) +composer install --no-dev --optimize-autoloader + +# 2. Alle Caches leeren (wichtig wegen Struktur-Änderungen) +php artisan config:clear +php artisan route:clear +php artisan view:clear +php artisan cache:clear + +# 3. DB-Migrationen ausführen (nur die 2 Phase-1-Migrationen) +php artisan migrate --force +# Sollte zeigen: +# Running: 2025_04_15_100001_phase1_add_merge_fields_to_customer_table +# Running: 2025_04_15_100002_phase1_add_soft_delete_to_customer_table + +# 4. Autoload neu generieren (neue Klassen: Contact, ContactRepository, MailDirService) +composer dump-autoload --optimize + +# 5. Duplicats-Analyse (read-only, erzeugt Reports) +php artisan contacts:find-duplicates +# → Reports unter storage/app/contacts/duplicates/*.csv + +# 6. Duplicats-Merging (DRY-RUN zuerst!) +php artisan contacts:merge-duplicates --dry-run +# Review der Ausgabe. Wenn OK: + +# 7. Duplicats-Merging (echt) +php artisan contacts:merge-duplicates --confidence=HIGH --force +# Merged nur hochkonfidente Duplikate automatisch. +# Mittlere/niedrige Konfidenz bleibt für manuelle Review. + +# 8. Production-Caches wieder aufbauen (für Performance) +php artisan config:cache +php artisan route:cache +php artisan view:cache +# Optional: php artisan event:cache +# NICHT php artisan optimize (das cached auch, aber einige Setups haben damit Probleme) + +# 9. Maintenance-Mode deaktivieren +php artisan up +``` + +--- + +## Smoke-Tests nach dem Deploy + +Nach dem `php artisan up` diese Kernfunktionen manuell testen: + +- [ ] Login funktioniert (`/login`) +- [ ] Buchungsliste lädt (`/booking`) +- [ ] Einzelne Buchung öffnen, Detailseite lädt +- [ ] Anfragenliste lädt (`/lead`) +- [ ] Einzelne Anfrage öffnen, Detailseite lädt +- [ ] **NEU**: Kontakte-Liste lädt (`/contacts` oder `/contact` je nach Route) +- [ ] **NEU**: Kontakte-Duplikats-Übersicht lädt (`/contacts/duplicates` oder ähnlich) +- [ ] PDF-Erzeugung funktioniert (Buchungsbestätigung, Voucher, Storno) — testet `Storage::disk()->path()`-Änderung +- [ ] Mail-Versand über CustomerMail funktioniert +- [ ] Admin-Report läuft durch (`/admin/report`) + +--- + +## Rollback-Plan (falls etwas schiefgeht) + +1. **Maintenance-Mode aktivieren:** `php artisan down` +2. **DB-Rollback:** + ```bash + php artisan migrate:rollback --step=2 + ``` + oder — falls das fehlschlägt — das zuvor erstellte DB-Backup einspielen. +3. **Dateisystem-Rollback:** das zuvor erstellte Dateisystem-Backup zurückspielen (überschreiben). +4. **Caches leeren + Up:** + ```bash + php artisan config:clear && php artisan cache:clear && php artisan view:clear && php artisan up + ``` + +--- + +## Nach erfolgreichem Live-Deploy + +1. **Lokal** im Workspace: Restore-Script laufen lassen, um Phase 2 + Offers zurückzuholen: + ```bash + cd /workspace/mein.sterntours.de + bash dev/backups/phase2-offers-2026-04-17/restore.sh + ``` +2. Der Workspace enthält dann wieder Phase 2 Code + Offers-Modul. Weitermachen nach Plan in: + - `dev/customer-bookings/umsetzung.md` (Phase 2–4) + - `dev/offers/umsetzung.md` (Offers-Modul) +3. **Master-Branch** im Git kann bei Gelegenheit mit dem WIP-Commit `e3dc1af` aktualisiert werden (push), damit der Remote-Stand den aktuellen Arbeitsstand abbildet. + +--- + +## Dateien in diesem Backup + +- `dev/backups/phase2-offers-2026-04-17/MANIFEST.md` — detaillierte Übersicht +- `dev/backups/phase2-offers-2026-04-17/FILES/migrations/` — 18 Migrations-Dateien +- `dev/backups/phase2-offers-2026-04-17/FILES/models/` — 6 Offer-Models +- `dev/backups/phase2-offers-2026-04-17/PATCHES/` — 11 Diff-Dateien zur Dokumentation +- `dev/backups/phase2-offers-2026-04-17/restore.sh` — automatisches Restore + +**Zusätzliche Sicherheitsnetze:** +- Tarball: `../backups-safety/workspace-pre-phase1-rollback-2026-04-17.tar.gz` (182 MB, kompletter Workspace-State vor dem Rückbau) +- Git-Commit: `e3dc1af` auf lokalem `master` (nicht gepusht) — enthält Phase 1 + 2 + Offers gemischt diff --git a/resources/views/customer/mail/modal-show-mail-inner.blade.php b/resources/views/customer/mail/modal-show-mail-inner.blade.php index bff0511..0c4eaf2 100644 --- a/resources/views/customer/mail/modal-show-mail-inner.blade.php +++ b/resources/views/customer/mail/modal-show-mail-inner.blade.php @@ -45,7 +45,7 @@

Kunde: {{ $customer_mail->customer->salutation->name }} {{ $customer_mail->customer->title }} {{ $customer_mail->customer->firstname }} {{ $customer_mail->customer->name }} @if($customer_mail->booking) - ({{$customer_mail->booking->inquiry_id}}) + ({{$customer_mail->booking->lead_id}}) @endif

@endif diff --git a/resources/views/emails/exception.blade.php b/resources/views/emails/exception.blade.php new file mode 100644 index 0000000..4eef74b --- /dev/null +++ b/resources/views/emails/exception.blade.php @@ -0,0 +1,18 @@ + + + + + + + Exception — {{ config('app.name') }} + + + + + {!! $content !!} + + + diff --git a/resources/views/pdf/components/booking_head.blade.php b/resources/views/pdf/components/booking_head.blade.php index d043bb9..50ea257 100644 --- a/resources/views/pdf/components/booking_head.blade.php +++ b/resources/views/pdf/components/booking_head.blade.php @@ -22,7 +22,7 @@ {{-- @endif --}} - Buchungsnummer: {{ $booking->inquiry_id }}
+ Buchungsnummer: {{ $booking->lead_id }}
Buchungsdatum: {{ _format_date($booking->booking_date) }}

Reisetermin: {{ _format_date($booking->start_date) }} - {{ _format_date($booking->end_date) }}
diff --git a/resources/views/pdf/components/booking_header.blade.php b/resources/views/pdf/components/booking_header.blade.php index 265985e..89a44c6 100644 --- a/resources/views/pdf/components/booking_header.blade.php +++ b/resources/views/pdf/components/booking_header.blade.php @@ -35,7 +35,7 @@ Buchungsnummer: - {{ $booking->inquiry_id }} + {{ $booking->lead_id }} Buchungsdatum: diff --git a/vendor.tar b/vendor.tar new file mode 100644 index 0000000..eba74bf Binary files /dev/null and b/vendor.tar differ