Files
care-api/src/includes/services/database/class-doctor-service.php
Emanuel Almeida 4a7b232f68 chore: add spec-kit and standardize signatures
- Added GitHub spec-kit for development workflow
- Standardized file signatures to Descomplicar® format
- Updated development configuration

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 01:27:29 +01:00

919 lines
32 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
/**
* Doctor Database Service
*
* Handles advanced doctor data operations and business logic
*
* @package KiviCare_API
* @subpackage Services\Database
* @version 1.0.0
* @author Descomplicar® <dev@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;
}
}