🏨 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>
429 lines
13 KiB
PHP
429 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* Eagle Booking Advanced Pricing - Rates Management
|
|
*
|
|
* @package EB_Advanced_Pricing
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class EB_AP_Rates {
|
|
|
|
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 rates for a specific room and date
|
|
*
|
|
* @param int $room_id
|
|
* @param string $date
|
|
* @param array $rates
|
|
* @return bool
|
|
*/
|
|
public function update_rates($room_id, $date, $rates) {
|
|
global $wpdb;
|
|
|
|
$data = array(
|
|
'room_id' => $room_id,
|
|
'rate_date' => $date,
|
|
'base_rate' => isset($rates['base_rate']) ? $rates['base_rate'] : 0,
|
|
'adult_rate' => isset($rates['adult_rate']) ? $rates['adult_rate'] : 0,
|
|
'child_rate' => isset($rates['child_rate']) ? $rates['child_rate'] : 0,
|
|
'min_guests' => isset($rates['min_guests']) ? $rates['min_guests'] : 1,
|
|
'max_guests' => isset($rates['max_guests']) ? $rates['max_guests'] : 10,
|
|
'min_stay' => isset($rates['min_stay']) ? $rates['min_stay'] : 1,
|
|
'max_stay' => isset($rates['max_stay']) ? $rates['max_stay'] : 0,
|
|
'updated_at' => current_time('mysql')
|
|
);
|
|
|
|
// Check if rate already exists
|
|
$existing = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT id FROM " . EB_AP_TABLE_RATES . " WHERE room_id = %d AND rate_date = %s",
|
|
$room_id,
|
|
$date
|
|
));
|
|
|
|
if ($existing) {
|
|
$result = $wpdb->update(
|
|
EB_AP_TABLE_RATES,
|
|
$data,
|
|
array('id' => $existing->id),
|
|
array('%d', '%s', '%f', '%f', '%f', '%d', '%d', '%d', '%d', '%s'),
|
|
array('%d')
|
|
);
|
|
} else {
|
|
$data['created_at'] = current_time('mysql');
|
|
$result = $wpdb->insert(
|
|
EB_AP_TABLE_RATES,
|
|
$data,
|
|
array('%d', '%s', '%f', '%f', '%f', '%d', '%d', '%d', '%d', '%s', '%s')
|
|
);
|
|
}
|
|
|
|
return $result !== false;
|
|
}
|
|
|
|
/**
|
|
* Update rates for a date range
|
|
*
|
|
* @param int $room_id
|
|
* @param string $start_date
|
|
* @param string $end_date
|
|
* @param array $rates
|
|
* @return bool
|
|
*/
|
|
public function update_rates_bulk($room_id, $start_date, $end_date, $rates) {
|
|
$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_rates($room_id, $date->format('Y-m-d'), $rates)) {
|
|
$success_count++;
|
|
}
|
|
}
|
|
|
|
return $success_count === $total_count;
|
|
}
|
|
|
|
/**
|
|
* Get rates for a specific room and date
|
|
*
|
|
* @param int $room_id
|
|
* @param string $date
|
|
* @return array|null
|
|
*/
|
|
public function get_rates($room_id, $date) {
|
|
global $wpdb;
|
|
|
|
$rates = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM " . EB_AP_TABLE_RATES . " WHERE room_id = %d AND rate_date = %s",
|
|
$room_id,
|
|
$date
|
|
), ARRAY_A);
|
|
|
|
if (!$rates) {
|
|
// Return default rates from room meta if no advanced rates found
|
|
return $this->get_default_rates($room_id);
|
|
}
|
|
|
|
return $rates;
|
|
}
|
|
|
|
/**
|
|
* Get rates for a date range
|
|
*
|
|
* @param int $room_id
|
|
* @param string $start_date
|
|
* @param string $end_date
|
|
* @return array
|
|
*/
|
|
public function get_rates_range($room_id, $start_date, $end_date) {
|
|
global $wpdb;
|
|
|
|
$rates = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM " . EB_AP_TABLE_RATES . "
|
|
WHERE room_id = %d
|
|
AND rate_date >= %s
|
|
AND rate_date <= %s
|
|
ORDER BY rate_date ASC",
|
|
$room_id,
|
|
$start_date,
|
|
$end_date
|
|
), ARRAY_A);
|
|
|
|
// Fill in missing dates with default rates
|
|
$rate_map = array();
|
|
foreach ($rates as $rate) {
|
|
$rate_map[$rate['rate_date']] = $rate;
|
|
}
|
|
|
|
$start = new DateTime($start_date);
|
|
$end = new DateTime($end_date);
|
|
$end->add(new DateInterval('P1D'));
|
|
|
|
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
|
|
$complete_rates = array();
|
|
|
|
foreach ($period as $date) {
|
|
$date_str = $date->format('Y-m-d');
|
|
if (isset($rate_map[$date_str])) {
|
|
$complete_rates[] = $rate_map[$date_str];
|
|
} else {
|
|
$default_rates = $this->get_default_rates($room_id);
|
|
$default_rates['rate_date'] = $date_str;
|
|
$complete_rates[] = $default_rates;
|
|
}
|
|
}
|
|
|
|
return $complete_rates;
|
|
}
|
|
|
|
/**
|
|
* Get default rates from room meta
|
|
*
|
|
* @param int $room_id
|
|
* @return array
|
|
*/
|
|
private function get_default_rates($room_id) {
|
|
// Get Eagle Booking default rates
|
|
$base_rate = get_post_meta($room_id, 'eagle_booking_mtb_room_price', true);
|
|
$min_guests = get_post_meta($room_id, 'eagle_booking_mtb_room_min_guests', true);
|
|
$max_guests = get_post_meta($room_id, 'eagle_booking_mtb_room_max_guests', true);
|
|
|
|
return array(
|
|
'room_id' => $room_id,
|
|
'rate_date' => '',
|
|
'base_rate' => $base_rate ? $base_rate : 0,
|
|
'adult_rate' => $base_rate ? $base_rate : 0,
|
|
'child_rate' => $base_rate ? $base_rate * 0.5 : 0, // Default child rate is 50% of adult
|
|
'min_guests' => $min_guests ? $min_guests : 1,
|
|
'max_guests' => $max_guests ? $max_guests : 10,
|
|
'min_stay' => 1,
|
|
'max_stay' => 0,
|
|
'is_default' => true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Calculate price based on guest count and pricing model
|
|
*
|
|
* @param int $room_id
|
|
* @param string $date
|
|
* @param int $adults
|
|
* @param int $children
|
|
* @return float
|
|
*/
|
|
public function calculate_price($room_id, $date, $adults = 1, $children = 0) {
|
|
$rates = $this->get_rates($room_id, $date);
|
|
|
|
if (!$rates) {
|
|
return 0;
|
|
}
|
|
|
|
$pricing_model = get_option('eb_ap_pricing_model', 'per_room');
|
|
$total_price = 0;
|
|
|
|
switch ($pricing_model) {
|
|
case 'per_room':
|
|
// Fixed price per room regardless of guest count
|
|
$total_price = $rates['base_rate'];
|
|
break;
|
|
|
|
case 'per_person':
|
|
// Price per person
|
|
$total_price = ($adults * $rates['adult_rate']) + ($children * $rates['child_rate']);
|
|
break;
|
|
|
|
case 'per_adult':
|
|
// Price per adult, children free or discounted
|
|
$total_price = $adults * $rates['adult_rate'];
|
|
if ($children > 0) {
|
|
$total_price += $children * $rates['child_rate'];
|
|
}
|
|
break;
|
|
|
|
case 'base_plus_extra':
|
|
// Base price + extra for additional guests
|
|
$total_price = $rates['base_rate'];
|
|
$base_guests = $rates['min_guests'];
|
|
$extra_adults = max(0, $adults - $base_guests);
|
|
$total_price += ($extra_adults * $rates['adult_rate']) + ($children * $rates['child_rate']);
|
|
break;
|
|
|
|
default:
|
|
$total_price = $rates['base_rate'];
|
|
}
|
|
|
|
return $total_price;
|
|
}
|
|
|
|
/**
|
|
* Copy rates from one date to another
|
|
*
|
|
* @param int $room_id
|
|
* @param string $from_date
|
|
* @param string $to_date
|
|
* @return bool
|
|
*/
|
|
public function copy_rates($room_id, $from_date, $to_date) {
|
|
$source_rates = $this->get_rates($room_id, $from_date);
|
|
|
|
if (!$source_rates) {
|
|
return false;
|
|
}
|
|
|
|
unset($source_rates['id']);
|
|
unset($source_rates['created_at']);
|
|
unset($source_rates['updated_at']);
|
|
|
|
return $this->update_rates($room_id, $to_date, $source_rates);
|
|
}
|
|
|
|
/**
|
|
* Delete rates for a specific date
|
|
*
|
|
* @param int $room_id
|
|
* @param string $date
|
|
* @return bool
|
|
*/
|
|
public function delete_rates($room_id, $date) {
|
|
global $wpdb;
|
|
|
|
$result = $wpdb->delete(
|
|
EB_AP_TABLE_RATES,
|
|
array(
|
|
'room_id' => $room_id,
|
|
'rate_date' => $date
|
|
),
|
|
array('%d', '%s')
|
|
);
|
|
|
|
return $result !== false;
|
|
}
|
|
|
|
/**
|
|
* Get minimum rate for a room in a date range
|
|
*
|
|
* @param int $room_id
|
|
* @param string $start_date
|
|
* @param string $end_date
|
|
* @return float
|
|
*/
|
|
public function get_min_rate($room_id, $start_date, $end_date) {
|
|
global $wpdb;
|
|
|
|
$min_rate = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT MIN(base_rate) FROM " . EB_AP_TABLE_RATES . "
|
|
WHERE room_id = %d
|
|
AND rate_date >= %s
|
|
AND rate_date <= %s",
|
|
$room_id,
|
|
$start_date,
|
|
$end_date
|
|
));
|
|
|
|
if ($min_rate === null) {
|
|
$default_rates = $this->get_default_rates($room_id);
|
|
return $default_rates['base_rate'];
|
|
}
|
|
|
|
return $min_rate;
|
|
}
|
|
|
|
/**
|
|
* Get maximum rate for a room in a date range
|
|
*
|
|
* @param int $room_id
|
|
* @param string $start_date
|
|
* @param string $end_date
|
|
* @return float
|
|
*/
|
|
public function get_max_rate($room_id, $start_date, $end_date) {
|
|
global $wpdb;
|
|
|
|
$max_rate = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT MAX(base_rate) FROM " . EB_AP_TABLE_RATES . "
|
|
WHERE room_id = %d
|
|
AND rate_date >= %s
|
|
AND rate_date <= %s",
|
|
$room_id,
|
|
$start_date,
|
|
$end_date
|
|
));
|
|
|
|
if ($max_rate === null) {
|
|
$default_rates = $this->get_default_rates($room_id);
|
|
return $default_rates['base_rate'];
|
|
}
|
|
|
|
return $max_rate;
|
|
}
|
|
|
|
/**
|
|
* Apply percentage increase/decrease to rates
|
|
*
|
|
* @param int $room_id
|
|
* @param string $start_date
|
|
* @param string $end_date
|
|
* @param float $percentage
|
|
* @return bool
|
|
*/
|
|
public function apply_percentage_change($room_id, $start_date, $end_date, $percentage) {
|
|
$rates = $this->get_rates_range($room_id, $start_date, $end_date);
|
|
|
|
$success_count = 0;
|
|
$total_count = count($rates);
|
|
|
|
foreach ($rates as $rate) {
|
|
$multiplier = 1 + ($percentage / 100);
|
|
|
|
$updated_rates = array(
|
|
'base_rate' => $rate['base_rate'] * $multiplier,
|
|
'adult_rate' => $rate['adult_rate'] * $multiplier,
|
|
'child_rate' => $rate['child_rate'] * $multiplier,
|
|
'min_guests' => $rate['min_guests'],
|
|
'max_guests' => $rate['max_guests'],
|
|
'min_stay' => $rate['min_stay'],
|
|
'max_stay' => $rate['max_stay']
|
|
);
|
|
|
|
if ($this->update_rates($room_id, $rate['rate_date'], $updated_rates)) {
|
|
$success_count++;
|
|
}
|
|
}
|
|
|
|
return $success_count === $total_count;
|
|
}
|
|
|
|
/**
|
|
* Get rate statistics for a room
|
|
*
|
|
* @param int $room_id
|
|
* @param string $start_date
|
|
* @param string $end_date
|
|
* @return array
|
|
*/
|
|
public function get_rate_statistics($room_id, $start_date, $end_date) {
|
|
global $wpdb;
|
|
|
|
$stats = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT
|
|
MIN(base_rate) as min_rate,
|
|
MAX(base_rate) as max_rate,
|
|
AVG(base_rate) as avg_rate,
|
|
COUNT(*) as total_days
|
|
FROM " . EB_AP_TABLE_RATES . "
|
|
WHERE room_id = %d
|
|
AND rate_date >= %s
|
|
AND rate_date <= %s",
|
|
$room_id,
|
|
$start_date,
|
|
$end_date
|
|
), ARRAY_A);
|
|
|
|
return $stats ? $stats : array(
|
|
'min_rate' => 0,
|
|
'max_rate' => 0,
|
|
'avg_rate' => 0,
|
|
'total_days' => 0
|
|
);
|
|
}
|
|
} |