'Wrong restaurant' ]); } // Check service exists $service = alexr_get_service($serviceId); if (!$service) { wp_send_json_error(['message' => __eva('Service not found.')]); } // Check nonce if (!$this->verify_nonce($restaurantId)) { wp_send_json_error(['message' => __eva('Could not create a booking. Invalid nonce. Please try again.')]); } // Common data extraction $date = sanitize_text_field($_REQUEST['dateSelected']); $time = intval($_REQUEST['timeSelected']); $party = intval($_REQUEST['party']); $uuid = sanitize_text_field($_REQUEST['uuid']); // Verificar si el servicio bloquea mesas $is_service_blocking_tables = $service->isBlockingTables(); // Get the booking that's going to be modified or created $booking = \Alexr\Models\Booking::where('uuid', $uuid) ->where('status', '!=', \Alexr\Enums\BookingStatus::DELETED) ->first(); // Handle based on operation type if ($is_modify_booking) { $result = $this->processBookingModification($booking, $service, $restaurant, $is_service_blocking_tables, $date, $time, $party, $uuid); } else { $result = $this->processNewBooking($booking, $service, $restaurant, $is_service_blocking_tables, $date, $time, $party); } if ($result === false) { // Errors are already sent in the specific methods return; } // Response is handled in the specific methods } /** * Process a booking modification * * @param \Alexr\Models\Booking $booking Current booking object * @param object $service Service object * @param \Alexr\Models\Restaurant $restaurant Restaurant object * @param bool $is_service_blocking_tables Whether service blocks tables * @param string $date Date for the booking * @param int $time Time for the booking * @param int $party Number of guests * @param string $uuid UUID of the booking * @return bool|array False if error, booking data if successful */ protected function processBookingModification($booking, $service, $restaurant, $is_service_blocking_tables, $date, $time, $party, $uuid) { // Get the UUID of the booking to modify $modify_booking_uuid = sanitize_text_field($_REQUEST['modify_booking_uuid']); // Find the original booking to modify $booking = \Alexr\Models\Booking::where('uuid', $modify_booking_uuid) ->where('status', '!=', \Alexr\Enums\BookingStatus::DELETED) ->first(); if (!$booking) { wp_send_json_error(['message' => __eva('Booking does not exist.')]); return false; } // Save original attributes and state for event handling $old_attributes = \Alexr\Listeners\ListenBookingEvents::getOriginal($booking); $old_tables = $booking->tablesList; $old_status = $booking->status; // Preserve original status $old_party = $booking->party; $old_date = $booking->date; $old_time = $booking->time; // Check date availability if ($restaurant->isDateClosed($date)) { wp_send_json_error(['message' => __eva('Sorry, this date is closed for reservations.')]); return false; } if ($service->isSlotClosed($date, $time)) { wp_send_json_error(['message' => __eva('Sorry, this slot is already closed.')]); return false; } // Save modification history if needed $this->saveModificationHistory($booking, $new_booking, $modify_booking_uuid, $uuid); // Update booking with new data $booking->date = $date; $booking->party = $party; $booking->time = $time; $booking->widget_id = intval($_REQUEST['widgetId']); $booking->shift_event_id = $service->id; $booking->shift_event_name = $service->name; // Always check for the best table assignment when modifying a booking $exclude_booking_id = $booking->id; $isAvailable = $service->isAvailable($date, $time, $party, $exclude_booking_id); if (!$isAvailable) { wp_send_json_error(['message' => __eva('Sorry, this time is not available for the new number of guests.')]); return false; } $assignedTables = []; // If specific tables are available, assign them if (is_array($isAvailable) && !empty($isAvailable)) { $tables_id = array_map(function($table) { return $table['id']; }, $isAvailable); $booking->tables()->sync($tables_id); $assignedTables = $isAvailable; } // Calcular duración según el tipo de disponibilidad y mesa asignada if ($service->availability_type === 'tables_schedule' && !empty($assignedTables)) { // Si hay más de una mesa, usar duración estándar if (count($assignedTables) > 1) { $booking->duration = $service->getDuration($party); } else { // Se extiende hasta la siguiente mesa o no // end_of_slot = hasta el final del schedule // next_slot = hasta la siguiente reserva // end_of_slot, next_slot $duration_type = $service->list_of_tables_with_schedule['durationType'] ?? 'end_of_slot'; //ray('MAKE BOOKING SERVICE: '.$service->id); // Si es una sola mesa, determinar duración según su schedule $tableId = $assignedTables[0]['id']; $booking->duration = $this->calculateDurationForTableSchedule( $tableId, $service, $date, $time, $party, $duration_type ); } } else { // Para otros tipos de disponibilidad, usar duración estándar $booking->duration = $service->getDuration($party); } // Determine status based on service rules (same as new bookings) $status = $service->getBookingStatusForNewReservation($date, $time, $party, $booking->email, $exclude_booking_id); $booking->status = $status; // Save area_selected and table_selected $this->attachAreaTableSelected($booking, $service, $date, $time, $party); // Apply specific rules that might modify status $service->applyRulePendingForSpecificTables($booking); // Save booking $booking->save(); // Verify booking was saved correctly $new_booking_saved = \Alexr\Models\Booking::find($booking->id); if (!$new_booking_saved || $new_booking_saved->status == \Alexr\Enums\BookingStatus::DELETED) { $data = json_encode($booking->toArray()); evavel_log_to_file(\Evavel\Log\Log::AJAX_ERROR_SAVE_BOKING, $data); wp_send_json_error(['message' => __eva('Sorry, something went wrong, please try again.')]); return false; } // Modified bookings don't need payment again $message = $this->sendBookingNotifications($booking, false, false, true); $payment = null; // Trigger modification events evavel_event(new \Alexr\Events\EventBookingModified($booking, null, $old_attributes)); // Send successful response $this->sendSuccessResponse($booking, $message, $payment); return true; } /** * Process a new booking * * @param \Alexr\Models\Booking $booking Booking object * @param object $service Service object * @param \Alexr\Models\Restaurant $restaurant Restaurant object * @param bool $is_service_blocking_tables Whether service blocks tables * @param string $date Date for the booking * @param int $time Time for the booking * @param int $party Number of guests * @return bool|array False if error, booking data if successful */ protected function processNewBooking($booking, $service, $restaurant, $is_service_blocking_tables, $date, $time, $party) { // If holded booking doesn't exist, it was deleted (time expired) if (!$booking) { wp_send_json_error(['message' => __eva('Time expired, please try again.')]); return false; } // Check for duplicate bookings if ($this->isBookingDuplicated()) { wp_send_json_error(['message' => __eva('You cannot reserve again for this date.')]); return false; } // If tables haven't been blocked, assign available tables now $tablesList = $booking->tablesList; $assignedTables = []; if (!$is_service_blocking_tables && empty($tablesList)) { $isAvailable = $service->isAvailable($date, $time, $party); if (!$isAvailable) { wp_send_json_error(['message' => __eva('Sorry, this time is not available any more.')]); return false; } if (is_array($isAvailable) && !empty($isAvailable)) { $tables_id = array_map(function($table) { return $table['id']; }, $isAvailable); $booking->tables()->sync($tables_id); $assignedTables = $isAvailable; $booking->save(); } } else { // Si ya hay mesas asignadas, obtenerlas para calcular la duración foreach ($tablesList as $tableId) { $table = \Alexr\Models\Table::find($tableId); if ($table) { $assignedTables[] = [ 'id' => $table->id, 'name' => $table->name, 'area_id' => $table->area_id, 'min_seats' => $table->min_seats, 'max_seats' => $table->max_seats ]; } } } // Set status and update booking details $booking->status = \Alexr\Enums\BookingStatus::PENDING; $this->updateBookingValuesFromRequest($booking); $this->assignOrCreateNewCustomerIfNeeded($booking); // Determine final status based on service rules $status = $service->getBookingStatusForNewReservation($date, $time, $party, $booking->email); // Calcular duración según el tipo de disponibilidad y mesa asignada if ($service->availability_type === 'tables_schedule' && !empty($assignedTables)) { // Si hay más de una mesa, usar duración estándar if (count($assignedTables) > 1) { $booking->duration = $service->getDuration($party); } else { // Se extiende hasta la siguiente mesa o no // end_of_slot = hasta el final del schedule // next_slot = hasta la siguiente reserva // end_of_slot, next_slot $duration_type = $service->list_of_tables_with_schedule['durationType'] ?? 'end_of_slot'; //ray('MAKE BOOKING SERVICE: '.$service->id); // Si es una sola mesa, determinar duración según su schedule $tableId = $assignedTables[0]['id']; $booking->duration = $this->calculateDurationForTableSchedule( $tableId, $service, $date, $time, $party, $duration_type ); } } else { // Para otros tipos de disponibilidad, usar duración estándar $booking->duration = $service->getDuration($party); } $booking->status = $status; // Save area_selected and table_selected $this->attachAreaTableSelected($booking, $service, $date, $time, $party); // Apply specific rules $service->applyRulePendingForSpecificTables($booking); // Save booking $booking->save(); // Verify booking was saved correctly $new_booking_saved = \Alexr\Models\Booking::find($booking->id); if (!$new_booking_saved || $new_booking_saved->status == \Alexr\Enums\BookingStatus::DELETED) { $data = json_encode($booking->toArray()); evavel_log_to_file(\Evavel\Log\Log::AJAX_ERROR_SAVE_BOKING, $data); wp_send_json_error(['message' => __eva('Sorry, something went wrong, please try again.')]); return false; } // Check if payment is needed $isPaymentNeeded = $this->isPaymentNeeded($service, $booking); if ($isPaymentNeeded) { $booking->status = \Alexr\Enums\BookingStatus::PENDING_PAYMENT; $booking->save(); // Verificar que se guardó correctamente // Aconsejado pero tengo que comprobarlo bien /*$payment_status_saved = \Alexr\Models\Booking::find($booking->id); if (!$payment_status_saved || $payment_status_saved->status != \Alexr\Enums\BookingStatus::PENDING_PAYMENT) { $data = json_encode($booking->toArray()); evavel_log_to_file(\Evavel\Log\Log::AJAX_ERROR_SAVE_BOKING, $data); wp_send_json_error(['message' => __eva('Sorry, something went wrong with payment setup, please try again.')]); return false; }*/ $payment = $this->gatewayPaymentDetails($service, $booking); $message = $service->getPaymentMessage(); } else { // Send notifications $message = $this->sendBookingNotifications($booking, false, false, false); $payment = null; } // Trigger creation event evavel_event(new \Alexr\Events\EventBookingCreated($booking)); // Send successful response $this->sendSuccessResponse($booking, $message, $payment); return true; } /** * Save modification history for a booking * * @param \Alexr\Models\Booking $booking Original booking * @param \Alexr\Models\Booking $new_booking New booking for history * @param string $modify_booking_uuid UUID of booking being modified * @param string $uuid UUID of new booking */ protected function saveModificationHistory($booking, &$new_booking, $modify_booking_uuid, $uuid) { if ($new_booking && $uuid != $modify_booking_uuid) { $new_booking->date = $booking->date; $new_booking->party = $booking->party; $new_booking->time = $booking->time; $new_booking->widget_id = $booking->widget_id; $new_booking->shift_event_id = $booking->shift_event_id; $new_booking->shift_event_name = $booking->shift_event_name; $new_booking->parent_booking_id = $booking->id; $new_booking->customer_id = $booking->customer_id; $new_booking->status = \Alexr\Enums\BookingStatus::DELETED; $new_booking->save(); } } /** * Send success response for booking operation * * @param \Alexr\Models\Booking $booking Booking object * @param string $message Message to display * @param array|null $payment Payment details */ protected function sendSuccessResponse($booking, $message, $payment) { wp_send_json_success([ 'success' => true, 'message' => $message, 'payment' => $payment, 'booking' => [ 'id' => $booking->id, 'status' => $booking->status, 'uuid' => $booking->uuid, 'email' => $booking->email, 'shift' => $booking->shiftName, 'date' => $booking->date, 'time' => $booking->time, 'guests' => $booking->party, 'duration' => $booking->duration, 'language' => $booking->language, 'first_name' => $booking->first_name, 'last_name' => $booking->last_name, 'calendar_links' => $booking->getCalendarLinks() ], ]); } }