/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ * @link https://descomplicar.pt * @since 1.0.0 */ namespace KiviCare_API\Services\Database; use KiviCare_API\Models\Doctor; use KiviCare_API\Services\Permission_Service; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Class Doctor_Service * * Advanced database service for doctor management with business logic * * @since 1.0.0 */ class Doctor_Service { /** * Initialize the service * * @since 1.0.0 */ public static function init() { // Hook into WordPress actions add_action( 'kivicare_doctor_created', array( self::class, 'on_doctor_created' ), 10, 2 ); add_action( 'kivicare_doctor_updated', array( self::class, 'on_doctor_updated' ), 10, 2 ); add_action( 'kivicare_doctor_deleted', array( self::class, 'on_doctor_deleted' ), 10, 1 ); } /** * Create doctor with advanced business logic * * @param array $doctor_data Doctor data * @param int $clinic_id Primary clinic ID * @param int $user_id Creating user ID * @return array|WP_Error Doctor data or error * @since 1.0.0 */ public static function create_doctor( $doctor_data, $clinic_id, $user_id = null ) { // Permission check if ( ! Permission_Service::current_user_can( 'manage_doctors' ) ) { return new \WP_Error( 'insufficient_permissions', 'You do not have permission to create doctors', array( 'status' => 403 ) ); } // Enhanced validation $validation = self::validate_doctor_business_rules( $doctor_data, $clinic_id ); if ( is_wp_error( $validation ) ) { return $validation; } // Create WordPress user first if email provided $wordpress_user_id = null; if ( ! empty( $doctor_data['user_email'] ) ) { $wordpress_user_id = self::create_doctor_wordpress_user( $doctor_data ); if ( is_wp_error( $wordpress_user_id ) ) { return $wordpress_user_id; } } // Add metadata $doctor_data['clinic_id'] = $clinic_id; $doctor_data['user_id'] = $wordpress_user_id; $doctor_data['created_by'] = $user_id ?: get_current_user_id(); $doctor_data['created_at'] = current_time( 'mysql' ); // Generate doctor ID if not provided if ( empty( $doctor_data['doctor_id'] ) ) { $doctor_data['doctor_id'] = self::generate_doctor_id( $clinic_id ); } // Create doctor $doctor_id = Doctor::create( $doctor_data ); if ( is_wp_error( $doctor_id ) ) { // Clean up WordPress user if created if ( $wordpress_user_id ) { wp_delete_user( $wordpress_user_id ); } return $doctor_id; } // Post-creation tasks self::setup_doctor_defaults( $doctor_id, $doctor_data ); // Create clinic association self::associate_doctor_with_clinic( $doctor_id, $clinic_id ); // Trigger action do_action( 'kivicare_doctor_created', $doctor_id, $doctor_data ); // Return full doctor data return self::get_doctor_with_metadata( $doctor_id ); } /** * Update doctor with business logic * * @param int $doctor_id Doctor ID * @param array $doctor_data Updated data * @return array|WP_Error Updated doctor data or error * @since 1.0.0 */ public static function update_doctor( $doctor_id, $doctor_data ) { // Get current doctor data $current_doctor = Doctor::get_by_id( $doctor_id ); if ( ! $current_doctor ) { return new \WP_Error( 'doctor_not_found', 'Doctor not found', array( 'status' => 404 ) ); } // Permission check if ( ! Permission_Service::can_manage_doctor( get_current_user_id(), $doctor_id ) ) { return new \WP_Error( 'insufficient_permissions', 'You do not have permission to update this doctor', array( 'status' => 403 ) ); } // Enhanced validation $validation = self::validate_doctor_business_rules( $doctor_data, $current_doctor['clinic_id'], $doctor_id ); if ( is_wp_error( $validation ) ) { return $validation; } // Handle WordPress user updates if ( ! empty( $doctor_data['user_email'] ) && $current_doctor['user_id'] ) { $wp_user_update = wp_update_user( array( 'ID' => $current_doctor['user_id'], 'user_email' => $doctor_data['user_email'], 'display_name' => ( $doctor_data['first_name'] ?? '' ) . ' ' . ( $doctor_data['last_name'] ?? '' ) ) ); if ( is_wp_error( $wp_user_update ) ) { return new \WP_Error( 'wordpress_user_update_failed', 'Failed to update WordPress user: ' . $wp_user_update->get_error_message(), array( 'status' => 500 ) ); } } // Add update metadata $doctor_data['updated_by'] = get_current_user_id(); $doctor_data['updated_at'] = current_time( 'mysql' ); // Update doctor $result = Doctor::update( $doctor_id, $doctor_data ); if ( is_wp_error( $result ) ) { return $result; } // Handle specialty changes self::handle_specialty_changes( $doctor_id, $current_doctor, $doctor_data ); // Handle clinic associations if ( isset( $doctor_data['additional_clinics'] ) ) { self::update_clinic_associations( $doctor_id, $doctor_data['additional_clinics'] ); } // Trigger action do_action( 'kivicare_doctor_updated', $doctor_id, $doctor_data ); // Return updated doctor data return self::get_doctor_with_metadata( $doctor_id ); } /** * Get doctor with enhanced metadata * * @param int $doctor_id Doctor ID * @return array|WP_Error Doctor data with metadata or error * @since 1.0.0 */ public static function get_doctor_with_metadata( $doctor_id ) { $doctor = Doctor::get_by_id( $doctor_id ); if ( ! $doctor ) { return new \WP_Error( 'doctor_not_found', 'Doctor not found', array( 'status' => 404 ) ); } // Permission check if ( ! Permission_Service::can_view_doctor( get_current_user_id(), $doctor_id ) ) { return new \WP_Error( 'access_denied', 'You do not have access to this doctor', array( 'status' => 403 ) ); } // Add enhanced metadata $doctor['clinics'] = self::get_doctor_clinics( $doctor_id ); $doctor['specialties'] = self::get_doctor_specialties( $doctor_id ); $doctor['schedule'] = self::get_doctor_schedule( $doctor_id ); $doctor['statistics'] = self::get_doctor_statistics( $doctor_id ); $doctor['recent_appointments'] = self::get_recent_appointments( $doctor_id, 5 ); $doctor['qualifications'] = self::get_doctor_qualifications( $doctor_id ); $doctor['availability'] = self::get_doctor_availability( $doctor_id ); return $doctor; } /** * Search doctors with advanced criteria * * @param string $search_term Search term * @param array $filters Additional filters * @return array Search results * @since 1.0.0 */ public static function search_doctors( $search_term, $filters = array() ) { global $wpdb; $user_id = get_current_user_id(); $accessible_clinic_ids = Permission_Service::get_accessible_clinic_ids( $user_id ); if ( empty( $accessible_clinic_ids ) ) { return array(); } // Build search query $where_clauses = array( "d.clinic_id IN (" . implode( ',', $accessible_clinic_ids ) . ")" ); $where_values = array(); // Search term if ( ! empty( $search_term ) ) { $where_clauses[] = "(d.first_name LIKE %s OR d.last_name LIKE %s OR d.doctor_id LIKE %s OR d.mobile_number LIKE %s OR d.user_email LIKE %s OR d.specialties LIKE %s)"; $search_term = '%' . $wpdb->esc_like( $search_term ) . '%'; $where_values = array_merge( $where_values, array_fill( 0, 6, $search_term ) ); } // Specialty filter if ( ! empty( $filters['specialty'] ) ) { $where_clauses[] = "d.specialties LIKE %s"; $where_values[] = '%' . $wpdb->esc_like( $filters['specialty'] ) . '%'; } // Clinic filter if ( ! empty( $filters['clinic_id'] ) && in_array( $filters['clinic_id'], $accessible_clinic_ids ) ) { $where_clauses[] = "d.clinic_id = %d"; $where_values[] = $filters['clinic_id']; } // Status filter if ( isset( $filters['status'] ) ) { $where_clauses[] = "d.status = %d"; $where_values[] = $filters['status']; } else { $where_clauses[] = "d.status = 1"; // Active by default } $where_sql = implode( ' AND ', $where_clauses ); $query = "SELECT d.*, c.name as clinic_name, COUNT(DISTINCT a.id) as appointment_count, AVG(CASE WHEN a.status = 2 THEN 1 ELSE 0 END) as completion_rate FROM {$wpdb->prefix}kc_doctors d LEFT JOIN {$wpdb->prefix}kc_clinics c ON d.clinic_id = c.id LEFT JOIN {$wpdb->prefix}kc_appointments a ON d.id = a.doctor_id WHERE {$where_sql} GROUP BY d.id ORDER BY d.first_name, d.last_name LIMIT 50"; if ( ! empty( $where_values ) ) { $results = $wpdb->get_results( $wpdb->prepare( $query, $where_values ), ARRAY_A ); } else { $results = $wpdb->get_results( $query, ARRAY_A ); } return array_map( function( $doctor ) { $doctor['id'] = (int) $doctor['id']; $doctor['appointment_count'] = (int) $doctor['appointment_count']; $doctor['completion_rate'] = round( (float) $doctor['completion_rate'] * 100, 1 ); return $doctor; }, $results ); } /** * Get doctor dashboard data * * @param int $doctor_id Doctor ID * @return array|WP_Error Dashboard data or error * @since 1.0.0 */ public static function get_doctor_dashboard( $doctor_id ) { $doctor = Doctor::get_by_id( $doctor_id ); if ( ! $doctor ) { return new \WP_Error( 'doctor_not_found', 'Doctor not found', array( 'status' => 404 ) ); } // Permission check if ( ! Permission_Service::can_view_doctor( get_current_user_id(), $doctor_id ) ) { return new \WP_Error( 'access_denied', 'You do not have access to this doctor dashboard', array( 'status' => 403 ) ); } $dashboard = array(); // Basic doctor info $dashboard['doctor'] = $doctor; // Today's schedule $dashboard['todays_schedule'] = self::get_doctor_daily_schedule( $doctor_id, current_time( 'Y-m-d' ) ); // Statistics $dashboard['statistics'] = self::get_comprehensive_statistics( $doctor_id ); // Recent patients $dashboard['recent_patients'] = self::get_recent_patients( $doctor_id, 10 ); // Upcoming appointments $dashboard['upcoming_appointments'] = self::get_upcoming_appointments( $doctor_id ); // Performance metrics $dashboard['performance'] = self::get_performance_metrics( $doctor_id ); // Revenue data $dashboard['revenue'] = self::get_revenue_data( $doctor_id ); return $dashboard; } /** * Generate unique doctor ID * * @param int $clinic_id Clinic ID * @return string Doctor ID * @since 1.0.0 */ private static function generate_doctor_id( $clinic_id ) { global $wpdb; $prefix = 'D' . str_pad( $clinic_id, 3, '0', STR_PAD_LEFT ); // Get the highest existing doctor ID for this clinic $max_id = $wpdb->get_var( $wpdb->prepare( "SELECT MAX(CAST(SUBSTRING(doctor_id, 5) AS UNSIGNED)) FROM {$wpdb->prefix}kc_doctors WHERE clinic_id = %d AND doctor_id LIKE %s", $clinic_id, $prefix . '%' ) ); $next_number = ( $max_id ? $max_id + 1 : 1 ); return $prefix . str_pad( $next_number, 4, '0', STR_PAD_LEFT ); } /** * Create WordPress user for doctor * * @param array $doctor_data Doctor data * @return int|WP_Error WordPress user ID or error * @since 1.0.0 */ private static function create_doctor_wordpress_user( $doctor_data ) { $username = self::generate_username( $doctor_data ); $password = wp_generate_password( 12, false ); $user_data = array( 'user_login' => $username, 'user_email' => $doctor_data['user_email'], 'user_pass' => $password, 'first_name' => $doctor_data['first_name'] ?? '', 'last_name' => $doctor_data['last_name'] ?? '', 'display_name' => ( $doctor_data['first_name'] ?? '' ) . ' ' . ( $doctor_data['last_name'] ?? '' ), 'role' => 'kivicare_doctor' ); $user_id = wp_insert_user( $user_data ); if ( is_wp_error( $user_id ) ) { return new \WP_Error( 'wordpress_user_creation_failed', 'Failed to create WordPress user: ' . $user_id->get_error_message(), array( 'status' => 500 ) ); } // Send welcome email with credentials self::send_doctor_welcome_email( $user_id, $username, $password ); return $user_id; } /** * Generate unique username * * @param array $doctor_data Doctor data * @return string Username * @since 1.0.0 */ private static function generate_username( $doctor_data ) { $first_name = sanitize_user( $doctor_data['first_name'] ?? '' ); $last_name = sanitize_user( $doctor_data['last_name'] ?? '' ); $base_username = strtolower( $first_name . '.' . $last_name ); $username = $base_username; $counter = 1; while ( username_exists( $username ) ) { $username = $base_username . $counter; $counter++; } return $username; } /** * Validate doctor business rules * * @param array $doctor_data Doctor data * @param int $clinic_id Clinic ID * @param int $doctor_id Doctor ID (for updates) * @return bool|WP_Error True if valid, WP_Error otherwise * @since 1.0.0 */ private static function validate_doctor_business_rules( $doctor_data, $clinic_id, $doctor_id = null ) { global $wpdb; $errors = array(); // Check for duplicate doctor ID in clinic if ( ! empty( $doctor_data['doctor_id'] ) ) { $existing_query = "SELECT id FROM {$wpdb->prefix}kc_doctors WHERE doctor_id = %s"; $query_params = array( $doctor_data['doctor_id'] ); if ( $doctor_id ) { $existing_query .= " AND id != %d"; $query_params[] = $doctor_id; } $existing_doctor = $wpdb->get_var( $wpdb->prepare( $existing_query, $query_params ) ); if ( $existing_doctor ) { $errors[] = 'A doctor with this ID already exists'; } } // Validate email format and uniqueness if ( ! empty( $doctor_data['user_email'] ) ) { if ( ! is_email( $doctor_data['user_email'] ) ) { $errors[] = 'Invalid email format'; } else { $existing_email = email_exists( $doctor_data['user_email'] ); if ( $existing_email && ( ! $doctor_id || $existing_email != $doctor_id ) ) { $errors[] = 'Email already exists'; } } } // Validate mobile number format if ( ! empty( $doctor_data['mobile_number'] ) ) { if ( ! preg_match( '/^[+]?[0-9\s\-\(\)]{7,20}$/', $doctor_data['mobile_number'] ) ) { $errors[] = 'Invalid mobile number format'; } } // Validate specialties if ( ! empty( $doctor_data['specialties'] ) ) { $specialties = is_array( $doctor_data['specialties'] ) ? $doctor_data['specialties'] : json_decode( $doctor_data['specialties'], true ); if ( is_array( $specialties ) ) { $valid_specialties = self::get_valid_specialties(); foreach ( $specialties as $specialty ) { if ( ! in_array( $specialty, $valid_specialties ) ) { $errors[] = "Invalid specialty: {$specialty}"; } } } } // Validate license number format (if provided) if ( ! empty( $doctor_data['license_number'] ) ) { if ( ! preg_match( '/^[A-Z0-9\-]{5,20}$/', $doctor_data['license_number'] ) ) { $errors[] = 'Invalid license number format'; } } if ( ! empty( $errors ) ) { return new \WP_Error( 'doctor_business_validation_failed', 'Doctor business validation failed', array( 'status' => 400, 'errors' => $errors ) ); } return true; } /** * Setup doctor defaults after creation * * @param int $doctor_id Doctor ID * @param array $doctor_data Doctor data * @since 1.0.0 */ private static function setup_doctor_defaults( $doctor_id, $doctor_data ) { // Setup default schedule self::setup_default_schedule( $doctor_id ); // Initialize preferences self::setup_default_preferences( $doctor_id ); // Create service mappings if specialties provided if ( ! empty( $doctor_data['specialties'] ) ) { self::create_default_services( $doctor_id, $doctor_data['specialties'] ); } } /** * Associate doctor with clinic * * @param int $doctor_id Doctor ID * @param int $clinic_id Clinic ID * @since 1.0.0 */ private static function associate_doctor_with_clinic( $doctor_id, $clinic_id ) { global $wpdb; $wpdb->insert( $wpdb->prefix . 'kc_doctor_clinic_mappings', array( 'doctor_id' => $doctor_id, 'clinic_id' => $clinic_id, 'created_at' => current_time( 'mysql' ) ) ); } /** * Get valid medical specialties * * @return array Valid specialties * @since 1.0.0 */ private static function get_valid_specialties() { return array( 'general_medicine', 'cardiology', 'dermatology', 'endocrinology', 'gastroenterology', 'gynecology', 'neurology', 'oncology', 'ophthalmology', 'orthopedics', 'otolaryngology', 'pediatrics', 'psychiatry', 'pulmonology', 'radiology', 'urology', 'surgery', 'anesthesiology', 'pathology', 'emergency_medicine', 'family_medicine' ); } /** * Get doctor statistics * * @param int $doctor_id Doctor ID * @return array Statistics * @since 1.0.0 */ private static function get_doctor_statistics( $doctor_id ) { global $wpdb; $stats = array(); // Total patients $stats['total_patients'] = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT patient_id) FROM {$wpdb->prefix}kc_appointments WHERE doctor_id = %d", $doctor_id ) ); // Total appointments $stats['total_appointments'] = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}kc_appointments WHERE doctor_id = %d", $doctor_id ) ); // This month appointments $stats['this_month_appointments'] = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}kc_appointments WHERE doctor_id = %d AND MONTH(appointment_start_date) = MONTH(CURDATE()) AND YEAR(appointment_start_date) = YEAR(CURDATE())", $doctor_id ) ); // Revenue (if bills are linked to appointments) $stats['total_revenue'] = $wpdb->get_var( $wpdb->prepare( "SELECT COALESCE(SUM(b.total_amount), 0) FROM {$wpdb->prefix}kc_bills b JOIN {$wpdb->prefix}kc_appointments a ON b.appointment_id = a.id WHERE a.doctor_id = %d AND b.status = 'paid'", $doctor_id ) ); return $stats; } // Additional helper methods would be implemented here... private static function get_doctor_clinics( $doctor_id ) { global $wpdb; return $wpdb->get_results( $wpdb->prepare( "SELECT c.id, c.name, c.address, c.city FROM {$wpdb->prefix}kc_doctor_clinic_mappings dcm JOIN {$wpdb->prefix}kc_clinics c ON dcm.clinic_id = c.id WHERE dcm.doctor_id = %d", $doctor_id ), ARRAY_A ); } private static function get_doctor_specialties( $doctor_id ) { $doctor = Doctor::get_by_id( $doctor_id ); if ( $doctor && ! empty( $doctor['specialties'] ) ) { return is_array( $doctor['specialties'] ) ? $doctor['specialties'] : json_decode( $doctor['specialties'], true ); } return array(); } private static function get_doctor_schedule( $doctor_id ) { return get_option( "kivicare_doctor_{$doctor_id}_schedule", array() ); } private static function get_recent_appointments( $doctor_id, $limit ) { global $wpdb; return $wpdb->get_results( $wpdb->prepare( "SELECT a.*, p.first_name, p.last_name, c.name as clinic_name FROM {$wpdb->prefix}kc_appointments a LEFT JOIN {$wpdb->prefix}kc_patients p ON a.patient_id = p.id LEFT JOIN {$wpdb->prefix}kc_clinics c ON a.clinic_id = c.id WHERE a.doctor_id = %d ORDER BY a.appointment_start_date DESC LIMIT %d", $doctor_id, $limit ), ARRAY_A ); } private static function get_doctor_qualifications( $doctor_id ) { return get_option( "kivicare_doctor_{$doctor_id}_qualifications", array() ); } private static function get_doctor_availability( $doctor_id ) { // This would calculate current availability based on schedule and appointments return array( 'today' => self::get_today_availability( $doctor_id ), 'this_week' => self::get_week_availability( $doctor_id ), 'next_available' => self::get_next_available_slot( $doctor_id ) ); } // Event handlers and additional methods... public static function on_doctor_created( $doctor_id, $doctor_data ) { error_log( "KiviCare: New doctor created - ID: {$doctor_id}, Name: " . ( $doctor_data['first_name'] ?? 'Unknown' ) ); } public static function on_doctor_updated( $doctor_id, $doctor_data ) { error_log( "KiviCare: Doctor updated - ID: {$doctor_id}" ); wp_cache_delete( "doctor_{$doctor_id}", 'kivicare' ); } public static function on_doctor_deleted( $doctor_id ) { // Clean up related data delete_option( "kivicare_doctor_{$doctor_id}_schedule" ); delete_option( "kivicare_doctor_{$doctor_id}_preferences" ); delete_option( "kivicare_doctor_{$doctor_id}_qualifications" ); wp_cache_delete( "doctor_{$doctor_id}", 'kivicare' ); error_log( "KiviCare: Doctor deleted - ID: {$doctor_id}" ); } // Placeholder methods for additional functionality private static function setup_default_schedule( $doctor_id ) { $default_schedule = array( 'monday' => array( 'start_time' => '09:00', 'end_time' => '17:00', 'break_start' => '12:00', 'break_end' => '13:00' ), 'tuesday' => array( 'start_time' => '09:00', 'end_time' => '17:00', 'break_start' => '12:00', 'break_end' => '13:00' ), 'wednesday' => array( 'start_time' => '09:00', 'end_time' => '17:00', 'break_start' => '12:00', 'break_end' => '13:00' ), 'thursday' => array( 'start_time' => '09:00', 'end_time' => '17:00', 'break_start' => '12:00', 'break_end' => '13:00' ), 'friday' => array( 'start_time' => '09:00', 'end_time' => '17:00', 'break_start' => '12:00', 'break_end' => '13:00' ), 'saturday' => array( 'start_time' => '09:00', 'end_time' => '13:00' ), 'sunday' => array( 'closed' => true ) ); update_option( "kivicare_doctor_{$doctor_id}_schedule", $default_schedule ); } private static function setup_default_preferences( $doctor_id ) { $default_preferences = array( 'appointment_duration' => 30, 'buffer_time' => 5, 'max_appointments_per_day' => 20, 'email_notifications' => true, 'sms_notifications' => false, 'auto_confirm_appointments' => false ); update_option( "kivicare_doctor_{$doctor_id}_preferences", $default_preferences ); } private static function create_default_services( $doctor_id, $specialties ) { // This would create default services based on doctor specialties // Implementation would depend on the services structure } private static function handle_specialty_changes( $doctor_id, $current_data, $new_data ) { // Handle when doctor specialties change if ( isset( $new_data['specialties'] ) ) { $old_specialties = isset( $current_data['specialties'] ) ? ( is_array( $current_data['specialties'] ) ? $current_data['specialties'] : json_decode( $current_data['specialties'], true ) ) : array(); $new_specialties = is_array( $new_data['specialties'] ) ? $new_data['specialties'] : json_decode( $new_data['specialties'], true ); if ( $old_specialties !== $new_specialties ) { do_action( 'kivicare_doctor_specialties_changed', $doctor_id, $old_specialties, $new_specialties ); } } } private static function update_clinic_associations( $doctor_id, $clinic_ids ) { global $wpdb; // Remove existing associations $wpdb->delete( $wpdb->prefix . 'kc_doctor_clinic_mappings', array( 'doctor_id' => $doctor_id ) ); // Add new associations foreach ( $clinic_ids as $clinic_id ) { $wpdb->insert( $wpdb->prefix . 'kc_doctor_clinic_mappings', array( 'doctor_id' => $doctor_id, 'clinic_id' => $clinic_id, 'created_at' => current_time( 'mysql' ) ) ); } } private static function send_doctor_welcome_email( $user_id, $username, $password ) { // Implementation for sending welcome email with credentials $user = get_user_by( 'id', $user_id ); if ( $user ) { wp_new_user_notification( $user_id, null, 'both' ); } } // Additional placeholder methods for dashboard functionality private static function get_doctor_daily_schedule( $doctor_id, $date ) { // Get appointments for specific date global $wpdb; return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}kc_appointments WHERE doctor_id = %d AND DATE(appointment_start_date) = %s ORDER BY appointment_start_time", $doctor_id, $date ), ARRAY_A ); } private static function get_comprehensive_statistics( $doctor_id ) { return self::get_doctor_statistics( $doctor_id ); } private static function get_recent_patients( $doctor_id, $limit ) { global $wpdb; return $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT p.*, MAX(a.appointment_start_date) as last_visit FROM {$wpdb->prefix}kc_patients p JOIN {$wpdb->prefix}kc_appointments a ON p.id = a.patient_id WHERE a.doctor_id = %d GROUP BY p.id ORDER BY last_visit DESC LIMIT %d", $doctor_id, $limit ), ARRAY_A ); } private static function get_upcoming_appointments( $doctor_id ) { global $wpdb; return $wpdb->get_results( $wpdb->prepare( "SELECT a.*, p.first_name, p.last_name FROM {$wpdb->prefix}kc_appointments a JOIN {$wpdb->prefix}kc_patients p ON a.patient_id = p.id WHERE a.doctor_id = %d AND a.appointment_start_date >= CURDATE() ORDER BY a.appointment_start_date, a.appointment_start_time LIMIT 10", $doctor_id ), ARRAY_A ); } private static function get_performance_metrics( $doctor_id ) { global $wpdb; $metrics = array(); // Completion rate $completion_data = $wpdb->get_row( $wpdb->prepare( "SELECT COUNT(*) as total, SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as completed, SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) as cancelled FROM {$wpdb->prefix}kc_appointments WHERE doctor_id = %d AND appointment_start_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)", $doctor_id ), ARRAY_A ); if ( $completion_data && $completion_data['total'] > 0 ) { $metrics['completion_rate'] = round( ( $completion_data['completed'] / $completion_data['total'] ) * 100, 1 ); $metrics['cancellation_rate'] = round( ( $completion_data['cancelled'] / $completion_data['total'] ) * 100, 1 ); } else { $metrics['completion_rate'] = 0; $metrics['cancellation_rate'] = 0; } return $metrics; } private static function get_revenue_data( $doctor_id ) { global $wpdb; return $wpdb->get_results( $wpdb->prepare( "SELECT DATE_FORMAT(b.created_at, '%%Y-%%m') as month, SUM(b.total_amount) as revenue FROM {$wpdb->prefix}kc_bills b JOIN {$wpdb->prefix}kc_appointments a ON b.appointment_id = a.id WHERE a.doctor_id = %d AND b.status = 'paid' GROUP BY DATE_FORMAT(b.created_at, '%%Y-%%m') ORDER BY month DESC LIMIT 12", $doctor_id ), ARRAY_A ); } private static function get_today_availability( $doctor_id ) { // Calculate available slots for today return array(); } private static function get_week_availability( $doctor_id ) { // Calculate available slots for this week return array(); } private static function get_next_available_slot( $doctor_id ) { // Find next available appointment slot return null; } }