🏨 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>
528 lines
16 KiB
PHP
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;
|
|
}
|
|
} |