diff --git a/src/application/controllers/appointments.php b/src/application/controllers/appointments.php index 548913b8..5d1a765a 100755 --- a/src/application/controllers/appointments.php +++ b/src/application/controllers/appointments.php @@ -17,7 +17,6 @@ * @package Controllers */ class Appointments extends CI_Controller { - /** * Class Constructor */ @@ -43,8 +42,7 @@ class Appointments extends CI_Controller { * is provided then it means that the customer followed the appointment * manage link that was send with the book success email. * - * @param string $appointment_hash The db appointment hash of an existing - * record. + * @param string $appointment_hash The db appointment hash of an existing record. */ public function index($appointment_hash = '') { if (!is_ea_installed()) { @@ -112,7 +110,6 @@ class Appointments extends CI_Controller { 'customer_data' => $customer, 'google_analytics_code' => $google_analytics_code ); - } catch(Exception $exc) { $view['exceptions'][] = $exc; } @@ -166,8 +163,7 @@ class Appointments extends CI_Controller { // :: SYNC APPOINTMENT REMOVAL WITH GOOGLE CALENDAR if ($appointment['id_google_calendar'] != NULL) { try { - $google_sync = $this->providers_model->get_setting('google_sync', - $appointment['id_users_provider']); + $google_sync = $this->providers_model->get_setting('google_sync',$appointment['id_users_provider']); if ($google_sync == TRUE) { $google_token = json_decode($this->providers_model @@ -264,13 +260,11 @@ class Appointments extends CI_Controller { * for thegiven service, provider and date. * * @param numeric $_POST['service_id'] The selected service's record id. - * @param numeric $_POST['provider_id'] The selected provider's record id. - * @param string $_POST['selected_date'] The selected date of which the - * available hours we want to see. - * @param numeric $_POST['service_duration'] The selected service duration in - * minutes. - * @param string $$_POST['manage_mode'] Contains either 'true' or 'false' and determines - * the if current user is managing an already booked appointment or not. + * @param numeric|string $_POST['provider_id'] The selected provider's record id, can also be 'any-provider'. + * @param string $_POST['selected_date'] The selected date of which the available hours we want to see. + * @param numeric $_POST['service_duration'] The selected service duration in minutes. + * @param string $_POST['manage_mode'] Contains either 'true' or 'false' and determines the if current user + * is managing an already booked appointment or not. * @return Returns a json object with the available hours. */ public function ajax_get_available_hours() { @@ -279,7 +273,7 @@ class Appointments extends CI_Controller { $this->load->model('settings_model'); try { - // Do not continue if there was no provider selected. + // Do not continue if there was no provider selected (more likely there is no provider in the system). if (empty($_POST['provider_id'])) { echo json_encode(array()); return; @@ -292,7 +286,7 @@ class Appointments extends CI_Controller { : array(); // If the user has selected the "any-provider" option then we will need to search - // for an available provider that provides the requested service. + // for an available provider that will provide the requested service. if ($_POST['provider_id'] === ANY_PROVIDER) { $_POST['provider_id'] = $this->search_any_provider($_POST['service_id'], $_POST['selected_date']); if ($_POST['provider_id'] === NULL) { @@ -301,69 +295,12 @@ class Appointments extends CI_Controller { } } - $empty_periods = $this->get_provider_available_time_periods($_POST['provider_id'], - $_POST['selected_date'], $exclude_appointments); + $empty_periods = $this->get_provider_available_time_periods($_POST['provider_id'], + $_POST['selected_date'], $exclude_appointments); - // Calculate the available appointment hours for the given date. The empty spaces - // are broken down to 15 min and if the service fit in each quarter then a new - // available hour is added to the "$available_hours" array. + $available_hours = $this->calculate_available_hours($empty_periods, $_POST['selected_date'], + $_POST['service_duration'], (bool)$_POST['manage_mode']); - $available_hours = array(); - - foreach ($empty_periods as $period) { - $start_hour = new DateTime($_POST['selected_date'] . ' ' . $period['start']); - $end_hour = new DateTime($_POST['selected_date'] . ' ' . $period['end']); - - $minutes = $start_hour->format('i'); - - if ($minutes % 15 != 0) { - // Change the start hour of the current space in order to be - // on of the following: 00, 15, 30, 45. - if ($minutes < 15) { - $start_hour->setTime($start_hour->format('H'), 15); - } else if ($minutes < 30) { - $start_hour->setTime($start_hour->format('H'), 30); - } else if ($minutes < 45) { - $start_hour->setTime($start_hour->format('H'), 45); - } else { - $start_hour->setTime($start_hour->format('H') + 1, 00); - } - } - - $current_hour = $start_hour; - $diff = $current_hour->diff($end_hour); - - while (($diff->h * 60 + $diff->i) >= intval($_POST['service_duration'])) { - $available_hours[] = $current_hour->format('H:i'); - $current_hour->add(new DateInterval("PT15M")); - $diff = $current_hour->diff($end_hour); - } - } - - // If the selected date is today, remove past hours. It is important - // include the timeout before booking that is set in the backoffice - // the system. Normally we might want the customer to book an appointment - // that is at least half or one hour from now. The setting is stored in - // minutes. - if (date('m/d/Y', strtotime($_POST['selected_date'])) == date('m/d/Y')) { - if ($_POST['manage_mode'] === 'true') { - $book_advance_timeout = 0; - } else { - $book_advance_timeout = $this->settings_model->get_setting('book_advance_timeout'); - } - - foreach($available_hours as $index => $value) { - $available_hour = strtotime($value); - $current_hour = strtotime('+' . $book_advance_timeout . ' minutes', strtotime('now')); - if ($available_hour <= $current_hour) { - unset($available_hours[$index]); - } - } - } - - $available_hours = array_values($available_hours); - sort($available_hours, SORT_STRING ); - $available_hours = array_values($available_hours); echo json_encode($available_hours); } catch(Exception $exc) { @@ -375,10 +312,11 @@ class Appointments extends CI_Controller { /** * [AJAX] Register the appointment to the database. + * + * @return string Returns a JSON string with the appointment database ID. */ public function ajax_register_appointment() { try { - $post_data = $_POST['post_data']; // alias // Validate the CAPTCHA string. @@ -400,8 +338,9 @@ class Appointments extends CI_Controller { $this->load->model('customers_model'); $this->load->model('settings_model'); - if ($this->customers_model->exists($customer)) + if ($this->customers_model->exists($customer)) { $customer['id'] = $this->customers_model->find_record_id($customer); + } $customer_id = $this->customers_model->add($customer); $appointment['id_users_customer'] = $customer_id; @@ -534,6 +473,7 @@ class Appointments extends CI_Controller { $appointment['id_users_provider'] = $this->search_any_provider($appointment['id_services'], date('Y-m-d', strtotime($appointment['start_datetime']))); $_POST['post_data']['appointment']['id_users_provider'] = $appointment['id_users_provider']; + return TRUE; // The selected provider is always available. } $available_periods = $this->get_provider_available_time_periods( @@ -574,6 +514,7 @@ class Appointments extends CI_Controller { * @param string $selected_date The date to be checked (MySQL formatted string). * @param array $exclude_appointments This array contains the ids of the appointments that * will not be taken into consideration when the available time periods are calculated. + * * @return array Returns an array with the available time periods of the provider. */ private function get_provider_available_time_periods($provider_id, $selected_date, @@ -585,7 +526,6 @@ class Appointments extends CI_Controller { $working_plan = json_decode($this->providers_model->get_setting('working_plan', $provider_id), true); $where_clause = array( - //'DATE(start_datetime)' => date('Y-m-d', strtotime($selected_date)), 'id_users_provider' => $provider_id ); @@ -614,26 +554,31 @@ class Appointments extends CI_Controller { 'start' => $selected_date_working_plan['start'], 'end' => $selected_date_working_plan['end'] ); - // Split the working plan to available time periods that do not - // contain the breaks in them. - foreach($selected_date_working_plan['breaks'] as $index=>$break) { + + // Split the working plan to available time periods that do not contain the breaks in them. + foreach ($selected_date_working_plan['breaks'] as $index => $break) { $break_start = new DateTime($break['start']); - $break_end = new DateTime($break['end']); - if ($break_start < $start) + $break_end = new DateTime($break['end']); + + if ($break_start < $start) { $break_start = $start; - if ($break_end > $end) + } + + if ($break_end > $end) { $break_end = $end; - if ($break_start >= $break_end) - continue; - foreach ($available_periods_with_breaks as $key => $open_period) - { + } + + if ($break_start >= $break_end) { + continue; + } + + foreach ($available_periods_with_breaks as $key => $open_period) { $s = new DateTime($open_period['start']); $e = new DateTime($open_period['end']); - if ($s < $break_end && $break_start < $e) // check for overlap - { + + if ($s < $break_end && $break_start < $e) { // check for overlap $changed = FALSE; - if ($s < $break_start) - { + if ($s < $break_start) { $open_start = $s; $open_end = $break_start; $available_periods_with_breaks[] = array( @@ -642,8 +587,8 @@ class Appointments extends CI_Controller { ); $changed = TRUE; } - if ($break_end < $e) - { + + if ($break_end < $e) { $open_start = $break_end; $open_end = $e; $available_periods_with_breaks[] = array( @@ -652,8 +597,8 @@ class Appointments extends CI_Controller { ); $changed = TRUE; } - if ($changed) - { + + if ($changed) { unset($available_periods_with_breaks[$key]); } } @@ -666,12 +611,12 @@ class Appointments extends CI_Controller { foreach($reserved_appointments as $appointment) { foreach($available_periods_with_appointments as $index => &$period) { - $a_start = strtotime($appointment['start_datetime']); $a_end = strtotime($appointment['end_datetime']); $p_start = strtotime($selected_date . ' ' . $period['start']); $p_end = strtotime($selected_date . ' ' .$period['end']); - if ($a_start <= $p_start && $a_end <= $p_end && $a_end <= $p_start) { + + if ($a_start <= $p_start && $a_end <= $p_end && $a_end <= $p_start) { // The appointment does not belong in this time period, so we // will not change anything. } else if ($a_start <= $p_start && $a_end <= $p_end && $a_end >= $p_start) { @@ -697,8 +642,7 @@ class Appointments extends CI_Controller { } else if ($a_start >= $p_start && $a_end >= $p_end && $a_start >= $p_end) { // The appointment does not belong in the period so do not change anything. } else if ($a_start <= $p_start && $a_end >= $p_end && $a_start <= $p_end) { - // The appointment is bigger than the period, so this period needs to be - // removed. + // The appointment is bigger than the period, so this period needs to be removed. unset($available_periods_with_appointments[$index]); } } @@ -710,6 +654,8 @@ class Appointments extends CI_Controller { /** * Search for any provider that can handle the requested service. * + * This method will return the database ID of the provider with the most available periods. + * * @param numeric $service_id The requested service ID. * @param string $selected_date The date to be searched. * @@ -717,16 +663,20 @@ class Appointments extends CI_Controller { */ private function search_any_provider($service_id, $selected_date) { $this->load->model('providers_model'); + $this->load->model('services_model'); $available_providers = $this->providers_model->get_available_providers(); + $service = $this->services_model->get_row($service_id); $provider_id = NULL; + $max_hours_count = 0; foreach($available_providers as $provider) { foreach($provider['services'] as $provider_service_id) { if ($provider_service_id == $service_id) { // Check if the provider is available for the requested date. - $available_periods = $this->get_provider_available_time_periods($provider['id'], $selected_date); - if (count($available_periods) > 0) { + $empty_periods = $this->get_provider_available_time_periods($provider['id'], $selected_date); + $available_hours = $this->calculate_available_hours($empty_periods, $selected_date, $service['duration']); + if (count($available_hours) > $max_hours_count) { $provider_id = $provider['id']; - break 2; + $max_hours_count = count($available_hours); } } } @@ -734,6 +684,83 @@ class Appointments extends CI_Controller { return $provider_id; } + + /** + * Calculate the avaialble appointment hours. + * + * Calculate the available appointment hours for the given date. The empty spaces + * are broken down to 15 min and if the service fit in each quarter then a new + * available hour is added to the "$available_hours" array. + * + * @param array $empty_periods Contains the empty periods as generated by the + * "get_provider_available_time_periods" method. + * @param string $selected_date The selected date to be search (format ) + * @param numeric $service_duration The service duration is required for the hour calculation. + * @param bool $manage_mode (optional) Whether we are currently on manage mode (editing an existing appointment). + * + * @return array Returns an array with the available hours for the appointment. + */ + private function calculate_available_hours(array $empty_periods, $selected_date, $service_duration, + $manage_mode = FALSE) { + $this->load->model('settings_model'); + + $available_hours = array(); + + foreach ($empty_periods as $period) { + $start_hour = new DateTime($selected_date . ' ' . $period['start']); + $end_hour = new DateTime($selected_date . ' ' . $period['end']); + + $minutes = $start_hour->format('i'); + + if ($minutes % 15 != 0) { + // Change the start hour of the current space in order to be + // on of the following: 00, 15, 30, 45. + if ($minutes < 15) { + $start_hour->setTime($start_hour->format('H'), 15); + } else if ($minutes < 30) { + $start_hour->setTime($start_hour->format('H'), 30); + } else if ($minutes < 45) { + $start_hour->setTime($start_hour->format('H'), 45); + } else { + $start_hour->setTime($start_hour->format('H') + 1, 00); + } + } + + $current_hour = $start_hour; + $diff = $current_hour->diff($end_hour); + + while (($diff->h * 60 + $diff->i) >= intval($service_duration)) { + $available_hours[] = $current_hour->format('H:i'); + $current_hour->add(new DateInterval("PT15M")); + $diff = $current_hour->diff($end_hour); + } + } + + // If the selected date is today, remove past hours. It is important include the timeout before + // booking that is set in the backoffice the system. Normally we might want the customer to book + // an appointment that is at least half or one hour from now. The setting is stored in minutes. + if (date('m/d/Y', strtotime($selected_date)) === date('m/d/Y')) { + if ($manage_mode) { + $book_advance_timeout = 0; + } else { + $book_advance_timeout = $this->settings_model->get_setting('book_advance_timeout'); + } + + foreach($available_hours as $index => $value) { + $available_hour = strtotime($value); + $current_hour = strtotime('+' . $book_advance_timeout . ' minutes', strtotime('now')); + if ($available_hour <= $current_hour) { + unset($available_hours[$index]); + } + } + } + + $available_hours = array_values($available_hours); + sort($available_hours, SORT_STRING ); + $available_hours = array_values($available_hours); + + return $available_hours; + } } /* End of file appointments.php */