guestSearch = $guestSearch; $this->crossSellEngine = $crossSellEngine; } public function search(Request $request) { $query = $request->get('q'); if (!$query) { return response()->json([]); } $guests = $this->guestSearch->search($query); return response()->json($guests); } public function show($id) { $guest = Guest::with(['tier', 'visits.venue', 'tags', 'notes.staff'])->findOrFail($id); return response()->json($guest); } public function store(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'phone' => 'required|string|max:20', 'venue_id' => 'required|exists:venues,id', ]); if ($validator->fails()) { return response()->json($validator->errors(), 422); } $guest = Guest::create([ 'name' => $request->name, 'phone' => $request->phone, 'email' => $request->email, 'nationality' => $request->nationality, 'language' => $request->language, 'birthday' => $request->birthday, 'marketing_consent' => $request->marketing_consent ?? false, ]); $guest->generateLoyaltyId(); // Create initial visit GuestVisit::create([ 'guest_id' => $guest->id, 'venue_id' => $request->venue_id, 'visit_date' => now(), 'visit_type' => 'registration', ]); return response()->json($guest, 201); } public function checkin(Request $request, $id) { $validator = Validator::make($request->all(), [ 'venue_id' => 'required|exists:venues,id', 'spend_amount' => 'numeric|min:0', 'visit_type' => 'required|in:stay,dining,bar,beach,pool,watersports', 'room_number' => 'nullable|string', 'notes' => 'nullable|string', ]); if ($validator->fails()) { return response()->json($validator->errors(), 422); } $visit = GuestVisit::create([ 'guest_id' => $id, 'venue_id' => $request->venue_id, 'visit_date' => now(), 'spend_amount' => $request->spend_amount ?? 0, 'visit_type' => $request->visit_type, 'room_number' => $request->room_number, 'notes' => $request->notes, ]); // Update lifetime spend $guest = Guest::find($id); $guest->lifetime_spend = $guest->visits()->sum('spend_amount'); $guest->save(); return response()->json($visit, 201); } public function getCrossSellSuggestions($id) { $suggestions = $this->crossSellEngine->getSuggestions($id); return response()->json($suggestions); } public function addTags(Request $request, $id) { $validator = Validator::make($request->all(), [ 'tags' => 'required|array', 'tags.*' => 'string', ]); if ($validator->fails()) { return response()->json($validator->errors(), 422); } foreach ($request->tags as $tagName) { GuestTag::firstOrCreate([ 'guest_id' => $id, 'tag_name' => $tagName, ]); } return response()->json(['message' => 'Tags added successfully']); } public function addNote(Request $request, $id) { $validator = Validator::make($request->all(), [ 'note_text' => 'required|string', ]); if ($validator->fails()) { return response()->json($validator->errors(), 422); } $note = GuestNote::create([ 'guest_id' => $id, 'note_text' => $request->note_text, 'staff_id' => auth()->id(), ]); return response()->json($note, 201); } public function generateQrCode($id) { $guest = Guest::findOrFail($id); $qrCode = QrCode::size(200)->generate($guest->loyalty_id); return response($qrCode)->header('Content-Type', 'image/svg+xml'); } }