- 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>
919 lines
32 KiB
PHP
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;
|
|
}
|
|
} |