From c3642a10e3712d69d9562a0a808ad901479979b0 Mon Sep 17 00:00:00 2001 From: alext Date: Fri, 1 Dec 2017 09:12:09 +0100 Subject: [PATCH] Added support for aggregations in appointments REST API. --- src/application/controllers/Appointments.php | 247 ++++++++++++++++-- .../controllers/api/v1/Appointments.php | 2 +- src/application/models/Appointments_model.php | 28 +- src/engine/Api/V1/Parsers/Appointments.php | 18 ++ src/engine/Api/V1/Parsers/Providers.php | 14 +- 5 files changed, 277 insertions(+), 32 deletions(-) diff --git a/src/application/controllers/Appointments.php b/src/application/controllers/Appointments.php index 82d4db83..5a39fa6e 100755 --- a/src/application/controllers/Appointments.php +++ b/src/application/controllers/Appointments.php @@ -36,7 +36,8 @@ class Appointments extends CI_Controller { { $this->config->set_item('language', $this->session->userdata('language')); $this->lang->load('translations', $this->session->userdata('language')); - } else + } + else { $this->lang->load('translations', $this->config->item('language')); // default } @@ -113,7 +114,8 @@ class Appointments extends CI_Controller { $provider = $this->providers_model->get_row($appointment['id_users_provider']); $customer = $this->customers_model->get_row($appointment['id_users_customer']); - } else + } + else { // The customer is going to book a new appointment so there is no // need for the manage functionality to be initialized. @@ -370,9 +372,10 @@ class Appointments extends CI_Controller { if ($attendants_number > 1) { - $this->_get_multiple_attendants_hours($available_hours, $attendants_number, + $this->_get_multiple_attendants_hours($this->input->post('select_date'), $availabilities_type, + $attendants_number, $this->input->post('service_id'), - $this->input->post('selected_date')); + $this->input->post('provider_id')); } $this->output @@ -474,7 +477,8 @@ class Appointments extends CI_Controller { $service, $customer, $company_settings); $appointment['id_google_calendar'] = $google_event->id; $this->appointments_model->add($appointment); - } else + } + else { // Update appointment to Google Calendar. $appointment['id_google_calendar'] = $this->appointments_model @@ -504,7 +508,8 @@ class Appointments extends CI_Controller { $provider_title = new Text($this->lang->line('appointment_added_to_your_plan')); $provider_message = new Text($this->lang->line('appointment_link_description')); - } else + } + else { $customer_title = new Text($this->lang->line('appointment_changes_saved')); $customer_message = new Text(''); @@ -601,6 +606,7 @@ class Appointments extends CI_Controller { $this->load->model('services_model'); $service_duration = (int)$this->services_model->get_value('duration', $service_id); $availabilities_type = (int)$this->services_model->get_value('availabilities_type', $service_id); + $attendants_number = (int)$this->services_model->get_value('attendants_number', $service_id); for ($i = 1; $i <= $number_of_days; $i++) { @@ -618,6 +624,14 @@ class Appointments extends CI_Controller { $available_hours = $this->_calculate_available_hours($empty_periods, $current_date->format('Y-m-d'), $service_duration, FALSE, $availabilities_type); + if ($attendants_number > 1) + { + $this->_get_multiple_attendants_hours($current_date->format('Y-m-d'), $availabilities_type, + $attendants_number, + $service_id, + $provider_id); + } + if (empty($available_hours)) { $unavailable_dates[] = $current_date->format('Y-m-d'); @@ -850,14 +864,16 @@ class Appointments extends CI_Controller { { // The appointment does not belong in this time period, so we // will not change anything. - } else + } + else { if ($a_start <= $p_start && $a_end <= $p_end && $a_end >= $p_start) { // The appointment starts before the period and finishes somewhere inside. // We will need to break this period and leave the available part. $period['start'] = date('H:i', $a_end); - } else + } + else { if ($a_start >= $p_start && $a_end <= $p_end) { @@ -872,19 +888,22 @@ class Appointments extends CI_Controller { 'start' => date('H:i', $a_end), 'end' => date('H:i', $p_end) ]; - } else + } + else { if ($a_start >= $p_start && $a_end >= $p_start && $a_start <= $p_end) { // The appointment starts in the period and finishes out of it. We will // need to remove the time that is taken from the appointment. $period['end'] = date('H:i', $a_start); - } else + } + 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 + } + else { if ($a_start <= $p_start && $a_end >= $p_end && $a_start <= $p_end) { @@ -1017,35 +1036,213 @@ class Appointments extends CI_Controller { * This method will add the extra appointment hours whenever a service accepts multiple attendants. * * @param array $available_hours The previously calculated appointment hours. + * @param int $availabilities_type * @param int $attendants_number Service attendants number. * @param int $service_id Selected service ID. * @param string $selected_date The selected appointment date. */ protected function _get_multiple_attendants_hours( - &$available_hours, + $selected_date, + $availabilities_type, $attendants_number, $service_id, - $selected_date + $provider_id ) { + /* + * Multiple availabilities should allow two appointments to reserve the same time. + * + * The current system does not check the time correctly cause an appointment that is bigger than the + * original service duration will not be taken into concern. + * + * Recreate this method logic from scratch. + */ + $this->load->model('appointments_model'); + $this->load->model('services_model'); + $this->load->model('providers_model'); - $appointments = $this->appointments_model->get_batch( - 'id_services = ' . $this->db->escape($service_id) . ' AND DATE(start_datetime) = DATE(' - . $this->db->escape(date('Y-m-d', strtotime($selected_date))) . ')'); + $service = $this->services_model->get_row($service_id); + $provider = $this->providers_model->get_row($provider_id); + $unavailabilities = $this->appointments_model->get_batch([ + 'is_unavailable' => TRUE, + 'DATE(start_datetime)' => $selected_date, + 'id_users_provider' => $provider_id + ]); - foreach ($appointments as $appointment) + $working_plan = json_decode($provider['settings']['working_plan'], TRUE); + $working_day = strtolower(date('l', strtotime($selected_date))); + $working_hours = $working_plan[$working_day]; + + $periods = [ + [ + 'start' => new DateTime($selected_date . ' ' . $working_hours['start']), + 'end' => new DateTime($selected_date . ' ' . $working_hours['end']) + ] + ]; + + $periods = $this->remove_breaks($selected_date, $periods, $working_hours['breaks']); + $periods = $this->remove_unavailabilities($periods, $unavailabilities); + + $hours = []; + + $interval_value = $availabilities_type == AVAILABILITIES_TYPE_FIXED ? $service['duration'] : '15'; + $interval = new DateInterval('PT' . (int)$interval_value . 'M'); + $duration = new DateInterval('PT' . (int)$service['duration'] . 'M'); + + foreach ($periods as $period) { - $hour = date('H:i', strtotime($appointment['start_datetime'])); - $current_attendants_number = $this->appointments_model->appointment_count_for_hour($service_id, - $selected_date, $hour); - if ($current_attendants_number < $attendants_number && ! in_array($hour, $available_hours)) + $slot_start = clone $period['start']; + $slot_end = clone $slot_start; + $slot_end->add($duration); + + while ($slot_end <= $period['end']) { - $available_hours[] = $hour; + //$appointment_attendants_number = $this->appointments_model->get_attendants_number_for_period($start_start, ) + + // Check reserved attendants for this time slot and see if current attendants fit. + $appointment_attendants_number = (int)$this->db + ->select('count(*) AS attendants_number') + ->from('ea_appointments') + ->where('start_datetime <= ', $slot_start->format('Y-m-d H:i:s')) + ->where('end_datetime >=', $slot_start->format('Y-m-d H:i:s')) + ->where('id_services', $service['id']) + ->get() + ->row() + ->attendants_number; + + if ($appointment_attendants_number <= $service['attendants_number']) + { + $hours[] = $slot_start->format('H:i'); + } + + $slot_start->add($interval); + $slot_end->add($interval); } } - $available_hours = array_values($available_hours); - sort($available_hours, SORT_STRING); - $available_hours = array_values($available_hours); + return $hours; + + + + + + + //foreach ($appointments as $appointment) + //{ + // $hour = date('H:i', strtotime($appointment['start_datetime'])); + // $current_attendants_number = $this->appointments_model->appointment_count_for_hour($service_id, + // $selected_date, $hour); + // if ($current_attendants_number < $attendants_number && ! in_array($hour, $available_hours)) + // { + // $available_hours[] = $hour; + // } + //} + // + //$available_hours = array_values($available_hours); + //sort($available_hours, SORT_STRING); + //$available_hours = array_values($available_hours); + } + + public function remove_breaks($selected_date, $periods, $breaks) + { + if ( ! $breaks) + { + return $periods; + } + + foreach ($breaks as $break) + { + $break_start = new DateTime($selected_date . ' ' . $break['start']); + $break_end = new DateTime($selected_date . ' ' . $break['end']); + + foreach ($periods as &$period) + { + $period_start = $period['start']; + $period_end = $period['end']; + + if ($break_start <= $period_start && $break_end >= $period_start && $break_end <= $period_end) + { + // left + $period['start'] = $break_end; + continue; + } + + if ($break_start >= $period_start && $break_start <= $period_end && $break_end >= $period_start && $break_end <= $period_end) + { + // middle + $period['end'] = $break_start; + $periods[] = [ + 'start' => $break_end, + 'end' => $period_end + ]; + continue; + } + + if ($break_start >= $period_start && $break_start <= $period_end && $break_end >= $period_end) + { + // right + $period['end'] = $break_start; + continue; + } + + if ($break_start <= $period_start && $break_end >= $period_end) + { + // break contains period + $period['start'] = $break_end; + continue; + } + } + } + + return $periods; + } + + public function remove_unavailabilities($periods, $unavailabilities) + { + foreach ($unavailabilities as $unavailability) + { + $unavailability_start = new DateTime($unavailability['start_datetime']); + $unavailability_end = new DateTime($unavailability['end_datetime']); + + foreach ($periods as &$period) + { + $period_start = $period['start']; + $period_end = $period['end']; + + if ($unavailability_start <= $period_start && $unavailability_end >= $period_start && $unavailability_end <= $period_end) + { + // left + $period['start'] = $unavailability_end; + continue; + } + + if ($unavailability_start >= $period_start && $unavailability_start <= $period_end && $unavailability_end >= $period_start && $unavailability_end <= $period_end) + { + // middle + $period['end'] = $unavailability_start; + $periods[] = [ + 'start' => $unavailability_end, + 'end' => $period_end + ]; + continue; + } + + if ($unavailability_start >= $period_start && $unavailability_start <= $period_end && $unavailability_end >= $period_end) + { + // right + $period['end'] = $unavailability_start; + continue; + } + + if ($unavailability_start <= $period_start && $unavailability_end >= $period_end) + { + // unavaibility contains period + $period['start'] = $unavailability_end; + continue; + } + } + } + + return $periods; } } diff --git a/src/application/controllers/api/v1/Appointments.php b/src/application/controllers/api/v1/Appointments.php index becc3477..cbffd8ca 100644 --- a/src/application/controllers/api/v1/Appointments.php +++ b/src/application/controllers/api/v1/Appointments.php @@ -51,7 +51,7 @@ class Appointments extends API_V1_Controller { try { $condition = $id !== NULL ? 'id = ' . $id : NULL; - $appointments = $this->appointments_model->get_batch($condition); + $appointments = $this->appointments_model->get_batch($condition, array_key_exists('aggregates', $_GET)); if ($id !== NULL && count($appointments) === 0) { diff --git a/src/application/models/Appointments_model.php b/src/application/models/Appointments_model.php index a9e4a3b7..43057ca0 100644 --- a/src/application/models/Appointments_model.php +++ b/src/application/models/Appointments_model.php @@ -334,16 +334,26 @@ class Appointments_Model extends CI_Model { * @param string $where_clause (OPTIONAL) The WHERE clause of the query to be executed. DO NOT INCLUDE 'WHERE' * KEYWORD. * + * @param bool $aggregates (OPTIONAL) Defines whether to add aggregations or not. + * * @return array Returns the rows from the database. */ - public function get_batch($where_clause = '') + public function get_batch($where_clause = '', $aggregates = false) { if ($where_clause != '') { $this->db->where($where_clause); } - return $this->db->get('ea_appointments')->result_array(); + $appointments = $this->db->get('ea_appointments')->result_array(); + + if ($aggregates) { + foreach($appointments as &$appointment) { + $appointment = $this->get_aggregates($appointment); + } + } + + return $appointments; } /** @@ -470,4 +480,18 @@ class Appointments_Model extends CI_Model { 'start_datetime' => date('Y-m-d H:i:s', strtotime($selected_date . ' ' . $hour . ':00')) ])->num_rows(); } + + /** + * Get the aggregates of an appointment. + * + * @param array $appointment Appointment data. + * + * @return array Returns the appointment with the aggregates. + */ + private function get_aggregates(array $appointment) { + $appointment['service'] = $this->db->get_where('ea_services', ['id' => $appointment['id_services']])->row_array(); + $appointment['provider'] = $this->db->get_where('ea_users', ['id' => $appointment['id_users_provider']])->row_array(); + $appointment['customer'] = $this->db->get_where('ea_users', ['id' => $appointment['id_users_customer']])->row_array(); + return $appointment; + } } diff --git a/src/engine/Api/V1/Parsers/Appointments.php b/src/engine/Api/V1/Parsers/Appointments.php index 5a33f3c3..10452ea6 100644 --- a/src/engine/Api/V1/Parsers/Appointments.php +++ b/src/engine/Api/V1/Parsers/Appointments.php @@ -39,6 +39,24 @@ class Appointments implements ParsersInterface { 'googleCalendarId' => $response['id_google_calendar'] !== NULL ? (int)$response['id_google_calendar'] : NULL ]; + if (array_key_exists('provider', $response)) { + $providerParser = new Providers(); + $providerParser->encode($response['provider']); + $encodedResponse['provider'] = $response['provider']; + } + + if (array_key_exists('customer', $response)) { + $customerParser = new Customers(); + $customerParser->encode($response['customer']); + $encodedResponse['customer'] = $response['customer']; + } + + if (array_key_exists('service', $response)) { + $serviceParser = new Services(); + $serviceParser->encode($response['service']); + $encodedResponse['service'] = $response['service']; + } + $response = $encodedResponse; } diff --git a/src/engine/Api/V1/Parsers/Providers.php b/src/engine/Api/V1/Parsers/Providers.php index 3ca28a2c..4510534a 100644 --- a/src/engine/Api/V1/Parsers/Providers.php +++ b/src/engine/Api/V1/Parsers/Providers.php @@ -38,8 +38,14 @@ class Providers implements ParsersInterface { 'state' => $response['state'], 'zip' => $response['zip_code'], 'notes' => $response['notes'], - 'services' => $response['services'], - 'settings' => [ + ]; + + if (array_key_exists('services', $response)) { + $encodedResponse['services'] = $response['services']; + } + + if (array_key_exists('settings', $response)) { + $encodedResponse['settings'] = [ 'username' => $response['settings']['username'], 'notifications' => filter_var($response['settings']['notifications'], FILTER_VALIDATE_BOOLEAN), 'calendarView' => $response['settings']['calendar_view'], @@ -49,8 +55,8 @@ class Providers implements ParsersInterface { 'syncFutureDays' => $response['settings']['sync_future_days'] !== NULL ? (int)$response['settings']['sync_future_days'] : NULL, 'syncPastDays' => $response['settings']['sync_past_days'] !== NULL ? (int)$response['settings']['sync_past_days'] : NULL, 'workingPlan' => json_decode($response['settings']['working_plan'], TRUE), - ] - ]; + ]; + } $response = $encodedResponse; }