Hotel Raxa Dev 5b1e2453c7 Hotel Raxa - Advanced Booking System Implementation
🏨 Hotel Booking Enhancements:
- Implemented Eagle Booking Advanced Pricing add-on
- Added Booking.com-style rate management system
- Created professional calendar interface for pricing
- Integrated deals and discounts functionality

💰 Advanced Pricing Features:
- Dynamic pricing models (per room, per person, per adult)
- Base rates, adult rates, and child rates management
- Length of stay discounts and early bird deals
- Mobile rates and secret deals implementation
- Seasonal promotions and flash sales

📅 Availability Management:
- Real-time availability tracking
- Stop sell and restriction controls
- Closed to arrival/departure functionality
- Minimum/maximum stay requirements
- Automatic sold-out management

💳 Payment Integration:
- Maintained Redsys payment gateway integration
- Seamless integration with existing Eagle Booking
- No modifications to core Eagle Booking plugin

🛠️ Technical Implementation:
- Custom database tables for advanced pricing
- WordPress hooks and filters integration
- AJAX-powered admin interface
- Data migration from existing Eagle Booking
- Professional calendar view for revenue management

📊 Admin Interface:
- Booking.com-style management dashboard
- Visual rate and availability calendar
- Bulk operations for date ranges
- Statistics and analytics dashboard
- Modal dialogs for quick editing

🔧 Code Quality:
- WordPress coding standards compliance
- Secure database operations with prepared statements
- Proper input validation and sanitization
- Error handling and logging
- Responsive admin interface

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-11 07:43:22 +02:00

528 lines
16 KiB
PHP

<?php
/**
* Eagle Booking Advanced Pricing - Availability Management
*
* @package EB_Advanced_Pricing
* @since 1.0.0
*/
defined('ABSPATH') || exit;
class EB_AP_Availability {
private static $instance = null;
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
// Constructor
}
/**
* Update availability for a specific room and date
*
* @param int $room_id
* @param string $date
* @param array $availability
* @return bool
*/
public function update_availability($room_id, $date, $availability) {
global $wpdb;
$data = array(
'room_id' => $room_id,
'availability_date' => $date,
'available_rooms' => isset($availability['available_rooms']) ? $availability['available_rooms'] : 0,
'stop_sell' => isset($availability['stop_sell']) ? $availability['stop_sell'] : 0,
'closed_to_arrival' => isset($availability['closed_to_arrival']) ? $availability['closed_to_arrival'] : 0,
'closed_to_departure' => isset($availability['closed_to_departure']) ? $availability['closed_to_departure'] : 0,
'updated_at' => current_time('mysql')
);
// Check if availability already exists
$existing = $wpdb->get_row($wpdb->prepare(
"SELECT id FROM " . EB_AP_TABLE_AVAILABILITY . " WHERE room_id = %d AND availability_date = %s",
$room_id,
$date
));
if ($existing) {
$result = $wpdb->update(
EB_AP_TABLE_AVAILABILITY,
$data,
array('id' => $existing->id),
array('%d', '%s', '%d', '%d', '%d', '%d', '%s'),
array('%d')
);
} else {
$data['created_at'] = current_time('mysql');
$result = $wpdb->insert(
EB_AP_TABLE_AVAILABILITY,
$data,
array('%d', '%s', '%d', '%d', '%d', '%d', '%s', '%s')
);
}
return $result !== false;
}
/**
* Update availability for a date range
*
* @param int $room_id
* @param string $start_date
* @param string $end_date
* @param array $availability
* @return bool
*/
public function update_availability_bulk($room_id, $start_date, $end_date, $availability) {
$start = new DateTime($start_date);
$end = new DateTime($end_date);
$end->add(new DateInterval('P1D')); // Include end date
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
$success_count = 0;
$total_count = 0;
foreach ($period as $date) {
$total_count++;
if ($this->update_availability($room_id, $date->format('Y-m-d'), $availability)) {
$success_count++;
}
}
return $success_count === $total_count;
}
/**
* Get availability for a specific room and date
*
* @param int $room_id
* @param string $date
* @return array|null
*/
public function get_availability($room_id, $date) {
global $wpdb;
$availability = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM " . EB_AP_TABLE_AVAILABILITY . " WHERE room_id = %d AND availability_date = %s",
$room_id,
$date
), ARRAY_A);
if (!$availability) {
// Return default availability if none found
return $this->get_default_availability($room_id, $date);
}
return $availability;
}
/**
* Get availability for a date range
*
* @param int $room_id
* @param string $start_date
* @param string $end_date
* @return array
*/
public function get_availability_range($room_id, $start_date, $end_date) {
global $wpdb;
$availability = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM " . EB_AP_TABLE_AVAILABILITY . "
WHERE room_id = %d
AND availability_date >= %s
AND availability_date <= %s
ORDER BY availability_date ASC",
$room_id,
$start_date,
$end_date
), ARRAY_A);
// Fill in missing dates with default availability
$availability_map = array();
foreach ($availability as $avail) {
$availability_map[$avail['availability_date']] = $avail;
}
$start = new DateTime($start_date);
$end = new DateTime($end_date);
$end->add(new DateInterval('P1D'));
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
$complete_availability = array();
foreach ($period as $date) {
$date_str = $date->format('Y-m-d');
if (isset($availability_map[$date_str])) {
$complete_availability[] = $availability_map[$date_str];
} else {
$complete_availability[] = $this->get_default_availability($room_id, $date_str);
}
}
return $complete_availability;
}
/**
* Get default availability for a room
*
* @param int $room_id
* @param string $date
* @return array
*/
private function get_default_availability($room_id, $date) {
// Get room quantity from Eagle Booking
$room_quantity = get_post_meta($room_id, 'eagle_booking_mtb_room_quantity', true);
$default_availability = get_option('eb_ap_default_availability', 10);
// Calculate already booked rooms for this date
$booked_rooms = $this->get_booked_rooms($room_id, $date);
$available_rooms = max(0, ($room_quantity ? $room_quantity : $default_availability) - $booked_rooms);
return array(
'room_id' => $room_id,
'availability_date' => $date,
'available_rooms' => $available_rooms,
'stop_sell' => 0,
'closed_to_arrival' => 0,
'closed_to_departure' => 0,
'is_default' => true
);
}
/**
* Get number of booked rooms for a specific date
*
* @param int $room_id
* @param string $date
* @return int
*/
private function get_booked_rooms($room_id, $date) {
global $wpdb;
$booked_count = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM " . EAGLE_BOOKING_TABLE . "
WHERE id_post = %d
AND %s >= STR_TO_DATE(date_from, '%%m/%%d/%%Y')
AND %s < STR_TO_DATE(date_to, '%%m/%%d/%%Y')
AND (paypal_payment_status = 'Completed' OR paypal_payment_status = 'Pending Payment')",
$room_id,
$date,
$date
));
return $booked_count ? $booked_count : 0;
}
/**
* Check if a room is available for booking
*
* @param int $room_id
* @param string $date
* @param int $rooms_needed
* @return bool
*/
public function is_available($room_id, $date, $rooms_needed = 1) {
$availability = $this->get_availability($room_id, $date);
if (!$availability) {
return false;
}
// Check stop sell
if ($availability['stop_sell']) {
return false;
}
// Check available rooms
if ($availability['available_rooms'] < $rooms_needed) {
return false;
}
return true;
}
/**
* Check if a room is available for a date range
*
* @param int $room_id
* @param string $start_date
* @param string $end_date
* @param int $rooms_needed
* @return bool
*/
public function is_available_range($room_id, $start_date, $end_date, $rooms_needed = 1) {
$availability = $this->get_availability_range($room_id, $start_date, $end_date);
foreach ($availability as $avail) {
if (!$this->is_available($room_id, $avail['availability_date'], $rooms_needed)) {
return false;
}
}
return true;
}
/**
* Check if arrival is allowed on a specific date
*
* @param int $room_id
* @param string $date
* @return bool
*/
public function is_arrival_allowed($room_id, $date) {
$availability = $this->get_availability($room_id, $date);
if (!$availability) {
return false;
}
return !$availability['closed_to_arrival'];
}
/**
* Check if departure is allowed on a specific date
*
* @param int $room_id
* @param string $date
* @return bool
*/
public function is_departure_allowed($room_id, $date) {
$availability = $this->get_availability($room_id, $date);
if (!$availability) {
return false;
}
return !$availability['closed_to_departure'];
}
/**
* Set stop sell for a date range
*
* @param int $room_id
* @param string $start_date
* @param string $end_date
* @param bool $stop_sell
* @return bool
*/
public function set_stop_sell($room_id, $start_date, $end_date, $stop_sell = true) {
return $this->update_availability_bulk($room_id, $start_date, $end_date, array(
'stop_sell' => $stop_sell ? 1 : 0
));
}
/**
* Set closed to arrival for a date range
*
* @param int $room_id
* @param string $start_date
* @param string $end_date
* @param bool $closed
* @return bool
*/
public function set_closed_to_arrival($room_id, $start_date, $end_date, $closed = true) {
return $this->update_availability_bulk($room_id, $start_date, $end_date, array(
'closed_to_arrival' => $closed ? 1 : 0
));
}
/**
* Set closed to departure for a date range
*
* @param int $room_id
* @param string $start_date
* @param string $end_date
* @param bool $closed
* @return bool
*/
public function set_closed_to_departure($room_id, $start_date, $end_date, $closed = true) {
return $this->update_availability_bulk($room_id, $start_date, $end_date, array(
'closed_to_departure' => $closed ? 1 : 0
));
}
/**
* Reserve rooms for a booking
*
* @param int $room_id
* @param string $start_date
* @param string $end_date
* @param int $rooms_count
* @return bool
*/
public function reserve_rooms($room_id, $start_date, $end_date, $rooms_count = 1) {
$availability = $this->get_availability_range($room_id, $start_date, $end_date);
$success_count = 0;
$total_count = count($availability);
foreach ($availability as $avail) {
$new_available = max(0, $avail['available_rooms'] - $rooms_count);
$updated_availability = array(
'available_rooms' => $new_available,
'stop_sell' => $avail['stop_sell'],
'closed_to_arrival' => $avail['closed_to_arrival'],
'closed_to_departure' => $avail['closed_to_departure']
);
if ($this->update_availability($room_id, $avail['availability_date'], $updated_availability)) {
$success_count++;
}
}
return $success_count === $total_count;
}
/**
* Release rooms from a booking
*
* @param int $room_id
* @param string $start_date
* @param string $end_date
* @param int $rooms_count
* @return bool
*/
public function release_rooms($room_id, $start_date, $end_date, $rooms_count = 1) {
$availability = $this->get_availability_range($room_id, $start_date, $end_date);
$success_count = 0;
$total_count = count($availability);
foreach ($availability as $avail) {
$new_available = $avail['available_rooms'] + $rooms_count;
$updated_availability = array(
'available_rooms' => $new_available,
'stop_sell' => $avail['stop_sell'],
'closed_to_arrival' => $avail['closed_to_arrival'],
'closed_to_departure' => $avail['closed_to_departure']
);
if ($this->update_availability($room_id, $avail['availability_date'], $updated_availability)) {
$success_count++;
}
}
return $success_count === $total_count;
}
/**
* Get availability statistics for a room
*
* @param int $room_id
* @param string $start_date
* @param string $end_date
* @return array
*/
public function get_availability_statistics($room_id, $start_date, $end_date) {
global $wpdb;
$stats = $wpdb->get_row($wpdb->prepare(
"SELECT
SUM(available_rooms) as total_available,
AVG(available_rooms) as avg_available,
SUM(CASE WHEN stop_sell = 1 THEN 1 ELSE 0 END) as stop_sell_days,
SUM(CASE WHEN closed_to_arrival = 1 THEN 1 ELSE 0 END) as cta_days,
SUM(CASE WHEN closed_to_departure = 1 THEN 1 ELSE 0 END) as ctd_days,
COUNT(*) as total_days
FROM " . EB_AP_TABLE_AVAILABILITY . "
WHERE room_id = %d
AND availability_date >= %s
AND availability_date <= %s",
$room_id,
$start_date,
$end_date
), ARRAY_A);
return $stats ? $stats : array(
'total_available' => 0,
'avg_available' => 0,
'stop_sell_days' => 0,
'cta_days' => 0,
'ctd_days' => 0,
'total_days' => 0
);
}
/**
* Copy availability from one date to another
*
* @param int $room_id
* @param string $from_date
* @param string $to_date
* @return bool
*/
public function copy_availability($room_id, $from_date, $to_date) {
$source_availability = $this->get_availability($room_id, $from_date);
if (!$source_availability) {
return false;
}
unset($source_availability['id']);
unset($source_availability['created_at']);
unset($source_availability['updated_at']);
return $this->update_availability($room_id, $to_date, $source_availability);
}
/**
* Delete availability for a specific date
*
* @param int $room_id
* @param string $date
* @return bool
*/
public function delete_availability($room_id, $date) {
global $wpdb;
$result = $wpdb->delete(
EB_AP_TABLE_AVAILABILITY,
array(
'room_id' => $room_id,
'availability_date' => $date
),
array('%d', '%s')
);
return $result !== false;
}
/**
* Auto-close availability when sold out
*
* @param int $room_id
* @param string $date
* @return bool
*/
public function auto_close_sold_out($room_id, $date) {
$availability = $this->get_availability($room_id, $date);
if (!$availability || $availability['available_rooms'] > 0) {
return false;
}
$auto_close = get_option('eb_ap_auto_close_sold_out', 'yes');
if ($auto_close === 'yes') {
return $this->update_availability($room_id, $date, array(
'available_rooms' => 0,
'stop_sell' => 1,
'closed_to_arrival' => $availability['closed_to_arrival'],
'closed_to_departure' => $availability['closed_to_departure']
));
}
return false;
}
}