tenantId(); //ray($tenantId); /*if (!$user->canManage($request->tenant)) { return $this->response([ 'success' => false, 'error' => __eva("You cannot access.") ]); }*/ $bookingId = $request->bookingId; // Get booking data $booking = Booking::where('restaurant_id', $tenantId)->find($bookingId); if (!$booking) { return $this->response([ 'success' => false, 'error' => __eva('Booking not found.') ]); } // Find conversation for this booking's customer by phone $bookingPhone = WhatsappConversation::normalizePhone( ($booking->dial_code ?? '') . ($booking->phone ?? '') ); $conversation = WhatsappConversation::where('restaurant_id', $tenantId) ->where('phone_normalized', $bookingPhone) ->first(); $conversationData = null; $messages = []; if ($conversation) { $conversationData = $conversation->toArray(); // Get messages for this conversation $messages = WhatsappMessage::getForConversation($conversation->id, 100, 0); $messages = array_map(function($message) { return $message->toArray(); }, $messages->toArray()); // Reverse to show oldest first (chat style) $messages = array_reverse($messages); } // Get available templates for this tenant $settingId = WhatsappTemplate::getId($tenantId); $setting = WhatsappTemplate::find($settingId); $templates = $setting ? ($setting->templates ?? []) : []; // If opening a booking-specific conversation, set active_booking_id if ($conversation && $conversation->active_booking_id !== $bookingId) { $conversation->setActiveBooking($bookingId); } $customer = Customer::find($conversation->customer_id); $has_whatsapp = true; if ($customer) { $has_whatsapp = $customer->has_whatsapp; } return $this->response([ 'success' => true, 'data' => [ 'booking' => $booking->toArray(), 'conversation' => $conversationData, 'messages' => $messages, 'templates' => $templates, 'templates_mapped' => $this->getTemplatesMapped($tenantId, $booking->language), 'window_open' => $conversation ? $conversation->isWindowOpen : false, 'window_expires_at' => $conversation ? $conversation->window_expires_at : null, 'customer_status' => $conversation ? ($conversation->customer_status ?? 'IDLE') : 'IDLE', 'customer_has_whatsapp' => $has_whatsapp ] ]); } /** * Get templates mapped by type (messages, reminders, reconfirmations) * filtered by the booking's language. Only includes items that have a template_sid configured. * * @param int $tenantId * @param string $language The booking language code (e.g. 'en', 'es') * @return array */ protected function getTemplatesMapped($tenantId, $language) { $result = [ 'messages' => [], 'reminders' => [], 'reconfirmations' => [], 'custom_messages' => [], ]; // --- Messages --- $messageSetting = \Alexr\Settings\WhatsappMessage::where('restaurant_id', $tenantId)->first(); if ($messageSetting) { $listItems = $messageSetting->listItems(); foreach ($listItems as $item) { $key = $item['slug']; // Check if enabled if (!$messageSetting->isEnabled($key)) { continue; } // Get the per-language config for this key $config = $messageSetting->getTemplateConfig($key, $language); if (!$config || empty($config['template_sid'])) { continue; } $result['messages'][] = [ 'key' => $key, 'label' => $item['label'], 'template_sid' => $config['template_sid'], 'template_name' => $config['template_name'] ?? '', 'field_mapping' => $config['field_mapping'] ?? [], ]; } } // --- Reminders --- $reminders = \Alexr\Settings\WhatsappReminder::where('restaurant_id', $tenantId)->get(); if ($reminders) { foreach ($reminders as $reminder) { if (!$reminder->isEnabled()) { continue; } $config = $reminder->getTemplateConfig($language); if (!$config || empty($config['template_sid'])) { continue; } $result['reminders'][] = [ 'id' => $reminder->id, 'name' => $reminder->name, 'template_sid' => $config['template_sid'], 'template_name' => $config['template_name'] ?? '', 'field_mapping' => $config['field_mapping'] ?? [], ]; } } // --- Reconfirmations --- $reconfirmations = \Alexr\Settings\WhatsappReconfirmation::where('restaurant_id', $tenantId)->get(); if ($reconfirmations) { foreach ($reconfirmations as $reconfirmation) { if (!$reconfirmation->isEnabled()) { continue; } $config = $reconfirmation->getTemplateConfig($language); if (!$config || empty($config['template_sid'])) { continue; } $result['reconfirmations'][] = [ 'id' => $reconfirmation->id, 'name' => $reconfirmation->name, 'template_sid' => $config['template_sid'], 'template_name' => $config['template_name'] ?? '', 'field_mapping' => $config['field_mapping'] ?? [], ]; } } // --- Custom Messages --- $customMessages = \Alexr\Settings\WhatsappCustomMessage::where('restaurant_id', $tenantId)->get(); if ($customMessages) { foreach ($customMessages as $customMessage) { if (!$customMessage->isEnabled()) { continue; } $config = $customMessage->getTemplateConfig($language); if (!$config || empty($config['template_sid'])) { continue; } $result['custom_messages'][] = [ 'id' => $customMessage->id, 'name' => $customMessage->name, 'template_sid' => $config['template_sid'], 'template_name' => $config['template_name'] ?? '', 'field_mapping' => $config['field_mapping'] ?? [], 'static_values' => $config['static_values'] ?? [], ]; } } return $result; } /** * Send a regular WhatsApp message (within 24h window) * * @param Request $request * @return \WP_REST_Response */ public function sendMessage(Request $request) { //ray('SEND MESSAGE', $request); /*$user = Eva::make('user'); if (!$user->canManage($request->tenant)) { return $this->response([ 'success' => false, 'error' => __eva("You cannot access.") ]); }*/ //$tenantId = $request->tenantId(); $bookingId = $request->bookingId; $message = trim($request->message ?? ''); if (empty($message)) { return $this->response([ 'success' => false, 'error' => __eva('Message cannot be empty.') ]); } // Get booking //$booking = Booking::where('restaurant_id', $tenantId)->find($bookingId); $booking = Booking::find($bookingId); $user = Eva::make('user'); $senderInfo = [ 'sender_type' => 'agent', 'sender_user_id' => $user ? $user->id : null, 'sender_user_name' => $user ? $user->name : null, ]; if (!$booking) { return $this->response([ 'success' => false, 'error' => __eva('Booking not found.') ]); } $result = $booking->sendWhatsappMessage($message, $senderInfo); if (!$result['success']) { return $this->response([ 'success' => false, 'error' => $result['message'] ]); } return $this->response([ 'success' => true, 'message' => __eva('Message sent successfully.'), 'data' => $result['data'] ]); } /** * Send a WhatsApp template message * * @param Request $request * @return \WP_REST_Response */ public function sendTemplate(Request $request) { //ray('SEND TEMPLATE', $request); $bookingId = $request->bookingId; $type = $request->type; // 'message', 'reminder', 'reconfirmation' $templateSid = $request->template_sid; $user = Eva::make('user'); $senderInfo = [ 'sender_type' => 'agent', 'sender_user_id' => $user ? $user->id : null, 'sender_user_name' => $user ? $user->name : null, ]; if (empty($type) || empty($templateSid)) { return $this->response([ 'success' => false, 'error' => __eva('Template type and SID are required.') ]); } $booking = Booking::find($bookingId); if (!$booking) { return $this->response([ 'success' => false, 'error' => __eva('Booking not found.') ]); } $lang = $booking->language ?? 'en'; $result = false; //ray('Sending type:'.$type, $senderInfo); switch ($type) { case 'message': $messageKey = $request->message_key; if (empty($messageKey)) { return $this->response([ 'success' => false, 'error' => __eva('Message key is required.') ]); } $result = $booking->sendWhatsappTemplate($messageKey, $lang, $senderInfo); break; case 'reminder': $reminderId = $request->reminder_id; if (empty($reminderId)) { return $this->response([ 'success' => false, 'error' => __eva('Reminder ID is required.') ]); } $result = $booking->sendWhatsappReminder($reminderId, $lang, $senderInfo); break; case 'reconfirmation': $reconfirmationId = $request->reconfirmation_id; if (empty($reconfirmationId)) { return $this->response([ 'success' => false, 'error' => __eva('Reconfirmation ID is required.') ]); } $result = $booking->sendWhatsappReconfirmation($reconfirmationId, $lang, $senderInfo); break; case 'custom_message': $customMessageId = $request->custom_message_id; if (empty($customMessageId)) { return $this->response([ 'success' => false, 'error' => __eva('Custom message ID is required.') ]); } $result = $booking->sendWhatsappCustomMessage($customMessageId, $lang, $senderInfo); break; default: return $this->response([ 'success' => false, 'error' => __eva('Invalid template type.') ]); } // Los traits pueden devolver false, null o un array if ($result === false || $result === null) { return $this->response([ 'success' => false, 'error' => __eva('Failed to send template. Check configuration.') ]); } if (is_array($result) && !$result['success']) { return $this->response([ 'success' => false, 'error' => $result['message'] ?? __eva('Failed to send template.') ]); } return $this->response([ 'success' => true, 'message' => __eva('Template sent successfully.'), 'data' => is_array($result) ? ($result['data'] ?? []) : [] ]); } /** * Get updated messages for a conversation (for polling) * * @param Request $request * @return \WP_REST_Response */ public function getMessages(Request $request) { //ray('GET MESSAGES', $request); /*$user = Eva::make('user'); if (!$user->canManage($request->tenant)) { return $this->response([ 'success' => false, 'error' => __eva("You cannot access.") ]); }*/ $tenantId = $request->tenantId(); $bookingId = $request->bookingId; $lastMessageId = $request->last_message_id ?? 0; // Get conversation for this booking's customer by phone $booking = Booking::where('restaurant_id', $tenantId)->find($bookingId); $conversation = null; if ($booking) { $bookingPhone = WhatsappConversation::normalizePhone( ($booking->dial_code ?? '') . ($booking->phone ?? '') ); $conversation = WhatsappConversation::where('restaurant_id', $tenantId) ->where('phone_normalized', $bookingPhone) ->first(); } if (!$conversation) { return $this->response([ 'success' => true, 'data' => [ 'messages' => [], 'conversation' => null, 'has_new_messages' => false ] ]); } // Get messages newer than last_message_id $messages = WhatsappMessage::where('conversation_id', $conversation->id); if ($lastMessageId > 0) { $messages = $messages->where('id', '>', $lastMessageId); } $messages = $messages->orderBy('id', 'DESC') ->limit(50) ->get(); $messagesArray = array_map(function($message) { return $message->toArray(); }, $messages->toArray()); // Reverse to show oldest first $messagesArray = array_reverse($messagesArray); // Mark conversation as read if there are new messages if (!empty($messagesArray)) { $conversation->markAsRead(); } return $this->response([ 'success' => true, 'data' => [ 'messages' => $messagesArray, 'conversation' => $conversation->toArray(), 'has_new_messages' => !empty($messagesArray), 'window_open' => $conversation->isWindowOpen, 'window_expires_at' => $conversation->window_expires_at, ] ]); } /** * Mark conversation as read * * @param Request $request * @return \WP_REST_Response */ public function markAsRead(Request $request) { $tenantId = $request->tenantId(); $bookingId = $request->bookingId; // Find conversation by booking's phone $booking = Booking::find($bookingId); $conversation = null; if ($booking) { $bookingPhone = WhatsappConversation::normalizePhone( ($booking->dial_code ?? '') . ($booking->phone ?? '') ); $conversation = WhatsappConversation::where('restaurant_id', $tenantId) ->where('phone_normalized', $bookingPhone) ->first(); } if (!$conversation) { return $this->response([ 'success' => true, 'message' => __eva('No conversation found.') ]); } // Set read_at on all inbound messages that don't have it yet $unreadMessages = WhatsappMessage::where('conversation_id', $conversation->id) ->where('direction', WhatsappMessage::DIRECTION_INBOUND) ->whereIsNull('read_at') ->get(); // Reset unread count on conversation $conversation->markAsRead(); foreach ($unreadMessages as $message) { $message->read_at = evavel_now(); $message->save(); } // Send read receipt to WhatsApp via provider (only for the last message) $this->sendReadReceiptToProvider($tenantId, $unreadMessages); return $this->response([ 'success' => true, 'message' => __eva('Conversation marked as read.') ]); } }