register_gauge( 'wp_bnb_buildings_total', 'Total number of buildings', array() ); $gauge->set( (int) $buildings_total->publish, array() ); // Rooms by status. $rooms_gauge = $collector->register_gauge( 'wp_bnb_rooms_total', 'Total number of rooms by status', array( 'status' ) ); $room_statuses = array( 'available', 'occupied', 'maintenance', 'inactive' ); foreach ( $room_statuses as $status ) { $count = self::count_rooms_by_status( $status ); $rooms_gauge->set( $count, array( $status ) ); } // Services by status. $services_gauge = $collector->register_gauge( 'wp_bnb_services_total', 'Total number of services by status', array( 'status' ) ); $service_statuses = array( 'active', 'inactive' ); foreach ( $service_statuses as $status ) { $count = self::count_services_by_status( $status ); $services_gauge->set( $count, array( $status ) ); } } /** * Collect booking metrics. * * @param object $collector The wp-prometheus collector instance. * @return void */ private static function collect_booking_metrics( $collector ): void { // Bookings by status. $bookings_gauge = $collector->register_gauge( 'wp_bnb_bookings_total', 'Total number of bookings by status', array( 'status' ) ); $booking_statuses = array( 'pending', 'confirmed', 'checked_in', 'checked_out', 'cancelled' ); foreach ( $booking_statuses as $status ) { $count = self::count_bookings_by_status( $status ); $bookings_gauge->set( $count, array( $status ) ); } // Today's check-ins. $checkins_gauge = $collector->register_gauge( 'wp_bnb_checkins_today', 'Number of check-ins scheduled for today', array() ); $checkins_gauge->set( self::count_todays_checkins(), array() ); // Today's check-outs. $checkouts_gauge = $collector->register_gauge( 'wp_bnb_checkouts_today', 'Number of check-outs scheduled for today', array() ); $checkouts_gauge->set( self::count_todays_checkouts(), array() ); // Upcoming bookings (next 7 days). $upcoming_gauge = $collector->register_gauge( 'wp_bnb_bookings_upcoming_7days', 'Number of bookings starting in the next 7 days', array() ); $upcoming_gauge->set( self::count_upcoming_bookings( 7 ), array() ); // Average booking duration (nights). $avg_duration = $collector->register_gauge( 'wp_bnb_booking_avg_duration_nights', 'Average booking duration in nights', array() ); $avg_duration->set( self::get_average_booking_duration(), array() ); } /** * Collect guest metrics. * * @param object $collector The wp-prometheus collector instance. * @return void */ private static function collect_guest_metrics( $collector ): void { // Total guests. $guests_total = wp_count_posts( Guest::POST_TYPE ); $guests_gauge = $collector->register_gauge( 'wp_bnb_guests_total', 'Total number of registered guests', array() ); $guests_gauge->set( (int) $guests_total->publish, array() ); // Guests by status. $guests_status_gauge = $collector->register_gauge( 'wp_bnb_guests_by_status', 'Number of guests by status', array( 'status' ) ); $guest_statuses = array( 'active', 'blocked', 'vip' ); foreach ( $guest_statuses as $status ) { $count = self::count_guests_by_status( $status ); $guests_status_gauge->set( $count, array( $status ) ); } // Repeat guests (guests with more than one booking). $repeat_gauge = $collector->register_gauge( 'wp_bnb_guests_repeat', 'Number of guests with more than one booking', array() ); $repeat_gauge->set( self::count_repeat_guests(), array() ); // New guests this month. $new_guests_gauge = $collector->register_gauge( 'wp_bnb_guests_new_this_month', 'Number of new guests registered this month', array() ); $new_guests_gauge->set( self::count_new_guests_this_month(), array() ); } /** * Collect occupancy metrics. * * @param object $collector The wp-prometheus collector instance. * @return void */ private static function collect_occupancy_metrics( $collector ): void { // Current occupancy rate (percentage). $occupancy_gauge = $collector->register_gauge( 'wp_bnb_occupancy_rate_current', 'Current room occupancy rate (percentage)', array() ); $occupancy_gauge->set( self::get_current_occupancy_rate(), array() ); // Occupancy rate this month. $occupancy_month_gauge = $collector->register_gauge( 'wp_bnb_occupancy_rate_this_month', 'Room occupancy rate for the current month (percentage)', array() ); $occupancy_month_gauge->set( self::get_monthly_occupancy_rate(), array() ); // Rooms currently occupied. $occupied_gauge = $collector->register_gauge( 'wp_bnb_rooms_currently_occupied', 'Number of rooms currently occupied', array() ); $occupied_gauge->set( self::count_currently_occupied_rooms(), array() ); // Total room capacity (beds). $capacity_gauge = $collector->register_gauge( 'wp_bnb_total_capacity_beds', 'Total bed capacity across all rooms', array() ); $capacity_gauge->set( self::get_total_bed_capacity(), array() ); } /** * Collect revenue metrics. * * @param object $collector The wp-prometheus collector instance. * @return void */ private static function collect_revenue_metrics( $collector ): void { $currency = get_option( 'wp_bnb_currency', 'CHF' ); // Revenue this month. $revenue_month_gauge = $collector->register_gauge( 'wp_bnb_revenue_this_month', 'Total revenue for the current month', array( 'currency' ) ); $revenue_month_gauge->set( self::get_revenue_this_month(), array( $currency ) ); // Revenue year to date. $revenue_ytd_gauge = $collector->register_gauge( 'wp_bnb_revenue_ytd', 'Total revenue year to date', array( 'currency' ) ); $revenue_ytd_gauge->set( self::get_revenue_ytd(), array( $currency ) ); // Average booking value. $avg_value_gauge = $collector->register_gauge( 'wp_bnb_booking_avg_value', 'Average booking value', array( 'currency' ) ); $avg_value_gauge->set( self::get_average_booking_value(), array( $currency ) ); // Revenue from services this month. $services_revenue_gauge = $collector->register_gauge( 'wp_bnb_services_revenue_this_month', 'Revenue from additional services this month', array( 'currency' ) ); $services_revenue_gauge->set( self::get_services_revenue_this_month(), array( $currency ) ); } /** * Register Grafana dashboards. * * @param object $provider The wp-prometheus dashboard provider instance. * @return void */ public static function register_dashboards( $provider ): void { $dashboard_file = WP_BNB_PATH . 'assets/grafana/wp-bnb-dashboard.json'; if ( file_exists( $dashboard_file ) ) { $provider->register_dashboard( 'wp-bnb', array( 'title' => __( 'WP BnB Dashboard', 'wp-bnb' ), 'description' => __( 'Monitor occupancy, bookings, revenue, and guest statistics for your B&B.', 'wp-bnb' ), 'icon' => 'dashicons-building', 'file' => $dashboard_file, 'plugin' => 'WP BnB Manager', ) ); } } // ========================================================================= // Helper Methods - Inventory // ========================================================================= /** * Count rooms by status. * * @param string $status Room status. * @return int */ private static function count_rooms_by_status( string $status ): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm.meta_key = '_bnb_room_status' AND pm.meta_value = %s", Room::POST_TYPE, $status ) ); } /** * Count services by status. * * @param string $status Service status. * @return int */ private static function count_services_by_status( string $status ): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm.meta_key = '_bnb_service_status' AND pm.meta_value = %s", Service::POST_TYPE, $status ) ); } // ========================================================================= // Helper Methods - Bookings // ========================================================================= /** * Count bookings by status. * * @param string $status Booking status. * @return int */ private static function count_bookings_by_status( string $status ): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm.meta_key = '_bnb_booking_status' AND pm.meta_value = %s", Booking::POST_TYPE, $status ) ); } /** * Count today's check-ins. * * @return int */ private static function count_todays_checkins(): int { global $wpdb; $today = gmdate( 'Y-m-d' ); return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_date ON p.ID = pm_date.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_date.meta_key = '_bnb_booking_check_in' AND pm_date.meta_value = %s AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value IN ('confirmed', 'pending')", Booking::POST_TYPE, $today ) ); } /** * Count today's check-outs. * * @return int */ private static function count_todays_checkouts(): int { global $wpdb; $today = gmdate( 'Y-m-d' ); return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_date ON p.ID = pm_date.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_date.meta_key = '_bnb_booking_check_out' AND pm_date.meta_value = %s AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value = 'checked_in'", Booking::POST_TYPE, $today ) ); } /** * Count upcoming bookings within given days. * * @param int $days Number of days to look ahead. * @return int */ private static function count_upcoming_bookings( int $days ): int { global $wpdb; $today = gmdate( 'Y-m-d' ); $end_date = gmdate( 'Y-m-d', strtotime( "+{$days} days" ) ); return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_date ON p.ID = pm_date.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_date.meta_key = '_bnb_booking_check_in' AND pm_date.meta_value >= %s AND pm_date.meta_value <= %s AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value IN ('confirmed', 'pending')", Booking::POST_TYPE, $today, $end_date ) ); } /** * Get average booking duration in nights. * * @return float */ private static function get_average_booking_duration(): float { global $wpdb; $result = $wpdb->get_var( $wpdb->prepare( "SELECT AVG(DATEDIFF(pm_out.meta_value, pm_in.meta_value)) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id INNER JOIN {$wpdb->postmeta} pm_out ON p.ID = pm_out.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_in.meta_key = '_bnb_booking_check_in' AND pm_out.meta_key = '_bnb_booking_check_out' AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value NOT IN ('cancelled')", Booking::POST_TYPE ) ); return round( (float) $result, 1 ); } // ========================================================================= // Helper Methods - Guests // ========================================================================= /** * Count guests by status. * * @param string $status Guest status. * @return int */ private static function count_guests_by_status( string $status ): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm.meta_key = '_bnb_guest_status' AND pm.meta_value = %s", Guest::POST_TYPE, $status ) ); } /** * Count repeat guests (more than one booking). * * @return int */ private static function count_repeat_guests(): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT pm.meta_value) FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE p.post_type = %s AND p.post_status = 'publish' AND pm.meta_key = '_bnb_booking_guest_id' AND pm.meta_value != '' GROUP BY pm.meta_value HAVING COUNT(*) > 1", Booking::POST_TYPE ) ); } /** * Count new guests registered this month. * * @return int */ private static function count_new_guests_this_month(): int { global $wpdb; $first_of_month = gmdate( 'Y-m-01 00:00:00' ); return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = 'publish' AND post_date >= %s", Guest::POST_TYPE, $first_of_month ) ); } // ========================================================================= // Helper Methods - Occupancy // ========================================================================= /** * Get current occupancy rate (percentage of rooms occupied today). * * @return float */ private static function get_current_occupancy_rate(): float { $total_rooms = self::count_available_rooms(); if ( $total_rooms <= 0 ) { return 0.0; } $occupied = self::count_currently_occupied_rooms(); return round( ( $occupied / $total_rooms ) * 100, 1 ); } /** * Get monthly occupancy rate. * * @return float */ private static function get_monthly_occupancy_rate(): float { global $wpdb; $total_rooms = self::count_available_rooms(); if ( $total_rooms <= 0 ) { return 0.0; } $first_of_month = gmdate( 'Y-m-01' ); $today = gmdate( 'Y-m-d' ); $days_so_far = (int) gmdate( 'd' ); $total_room_nights = $total_rooms * $days_so_far; // Count booked nights this month (simplified: count bookings that overlap with this month). $booked_nights = (int) $wpdb->get_var( $wpdb->prepare( "SELECT SUM( DATEDIFF( LEAST(pm_out.meta_value, %s), GREATEST(pm_in.meta_value, %s) ) ) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id INNER JOIN {$wpdb->postmeta} pm_out ON p.ID = pm_out.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_in.meta_key = '_bnb_booking_check_in' AND pm_out.meta_key = '_bnb_booking_check_out' AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out') AND pm_in.meta_value <= %s AND pm_out.meta_value >= %s", $today, $first_of_month, Booking::POST_TYPE, $today, $first_of_month ) ); if ( $total_room_nights <= 0 ) { return 0.0; } return round( ( $booked_nights / $total_room_nights ) * 100, 1 ); } /** * Count currently occupied rooms. * * @return int */ private static function count_currently_occupied_rooms(): int { global $wpdb; $today = gmdate( 'Y-m-d' ); return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT pm_room.meta_value) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_room ON p.ID = pm_room.post_id INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id INNER JOIN {$wpdb->postmeta} pm_out ON p.ID = pm_out.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_room.meta_key = '_bnb_booking_room_id' AND pm_in.meta_key = '_bnb_booking_check_in' AND pm_out.meta_key = '_bnb_booking_check_out' AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value IN ('confirmed', 'checked_in') AND pm_in.meta_value <= %s AND pm_out.meta_value > %s", Booking::POST_TYPE, $today, $today ) ); } /** * Count available rooms (not in maintenance/inactive). * * @return int */ private static function count_available_rooms(): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_bnb_room_status' WHERE p.post_type = %s AND p.post_status = 'publish' AND (pm.meta_value IS NULL OR pm.meta_value IN ('available', 'occupied'))", Room::POST_TYPE ) ); } /** * Get total bed capacity. * * @return int */ private static function get_total_bed_capacity(): int { global $wpdb; $result = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(CAST(pm.meta_value AS UNSIGNED)) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm.meta_key = '_bnb_room_beds'", Room::POST_TYPE ) ); return (int) $result; } // ========================================================================= // Helper Methods - Revenue // ========================================================================= /** * Get revenue for the current month. * * @return float */ private static function get_revenue_this_month(): float { global $wpdb; $first_of_month = gmdate( 'Y-m-01' ); $result = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(CAST(pm_price.meta_value AS DECIMAL(10,2))) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_price ON p.ID = pm_price.post_id INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_price.meta_key = '_bnb_booking_total_price' AND pm_in.meta_key = '_bnb_booking_check_in' AND pm_in.meta_value >= %s AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out')", Booking::POST_TYPE, $first_of_month ) ); return round( (float) $result, 2 ); } /** * Get revenue year to date. * * @return float */ private static function get_revenue_ytd(): float { global $wpdb; $first_of_year = gmdate( 'Y-01-01' ); $result = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(CAST(pm_price.meta_value AS DECIMAL(10,2))) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_price ON p.ID = pm_price.post_id INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_price.meta_key = '_bnb_booking_total_price' AND pm_in.meta_key = '_bnb_booking_check_in' AND pm_in.meta_value >= %s AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out')", Booking::POST_TYPE, $first_of_year ) ); return round( (float) $result, 2 ); } /** * Get average booking value. * * @return float */ private static function get_average_booking_value(): float { global $wpdb; $result = $wpdb->get_var( $wpdb->prepare( "SELECT AVG(CAST(pm_price.meta_value AS DECIMAL(10,2))) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_price ON p.ID = pm_price.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_price.meta_key = '_bnb_booking_total_price' AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out')", Booking::POST_TYPE ) ); return round( (float) $result, 2 ); } /** * Get revenue from services this month. * * @return float */ private static function get_services_revenue_this_month(): float { global $wpdb; $first_of_month = gmdate( 'Y-m-01' ); $result = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(CAST(pm_services.meta_value AS DECIMAL(10,2))) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_services ON p.ID = pm_services.post_id INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id WHERE p.post_type = %s AND p.post_status = 'publish' AND pm_services.meta_key = '_bnb_booking_services_total' AND pm_in.meta_key = '_bnb_booking_check_in' AND pm_in.meta_value >= %s AND pm_status.meta_key = '_bnb_booking_status' AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out')", Booking::POST_TYPE, $first_of_month ) ); return round( (float) $result, 2 ); } }