mein-sterntours/dev/backups/phase2-offers-2026-04-17/PATCHES/Controllers.phase1-only.diff
2026-04-17 17:19:11 +02:00

372 lines
16 KiB
Diff

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 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\Contact;
+use App\Repositories\ContactRepository;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Yajra\DataTables\Facades\DataTables;
+
+class ContactController extends Controller
+{
+ public function __construct(private readonly ContactRepository $contactRepo)
+ {
+ $this->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 '<a href="' . route('contact_detail', [$contact->id]) . '" class="btn icon-btn btn-sm btn-primary" title="Öffnen"><span class="fa fa-edit"></span></a>';
+ })
+ ->addColumn('action_delete', function (Contact $contact) use ($showDeleted) {
+ if ($showDeleted) {
+ return '<button class="btn icon-btn btn-xs btn-success ml-1 btn-contact-restore" '
+ . 'data-id="' . $contact->id . '" '
+ . 'data-name="' . e($contact->fullName()) . '" '
+ . 'title="Wiederherstellen"><span class="fa fa-undo"></span></button>';
+ }
+ return '<button class="btn icon-btn btn-xs btn-danger ml-1 btn-contact-delete" '
+ . 'data-id="' . $contact->id . '" '
+ . 'data-name="' . e($contact->fullName()) . '" '
+ . 'title="Löschen"><span class="fa fa-trash"></span></button>';
+ })
+ ->addColumn('id', function (Contact $contact) use ($showDeleted) {
+ if ($showDeleted) {
+ return '<span data-order="' . $contact->id . '">' . $contact->id . '</span>';
+ }
+ return '<a data-order="' . $contact->id . '" href="' . route('contact_detail', [$contact->id]) . '">' . $contact->id . '</a>';
+ })
+ ->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);
+ }
+}