Added support for aggregations in appointments REST API.

This commit is contained in:
alext 2017-12-01 09:12:09 +01:00
parent 5058e380ae
commit c3642a10e3
5 changed files with 277 additions and 32 deletions

View File

@ -36,7 +36,8 @@ class Appointments extends CI_Controller {
{ {
$this->config->set_item('language', $this->session->userdata('language')); $this->config->set_item('language', $this->session->userdata('language'));
$this->lang->load('translations', $this->session->userdata('language')); $this->lang->load('translations', $this->session->userdata('language'));
} else }
else
{ {
$this->lang->load('translations', $this->config->item('language')); // default $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']); $provider = $this->providers_model->get_row($appointment['id_users_provider']);
$customer = $this->customers_model->get_row($appointment['id_users_customer']); $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 // The customer is going to book a new appointment so there is no
// need for the manage functionality to be initialized. // need for the manage functionality to be initialized.
@ -370,9 +372,10 @@ class Appointments extends CI_Controller {
if ($attendants_number > 1) 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('service_id'),
$this->input->post('selected_date')); $this->input->post('provider_id'));
} }
$this->output $this->output
@ -474,7 +477,8 @@ class Appointments extends CI_Controller {
$service, $customer, $company_settings); $service, $customer, $company_settings);
$appointment['id_google_calendar'] = $google_event->id; $appointment['id_google_calendar'] = $google_event->id;
$this->appointments_model->add($appointment); $this->appointments_model->add($appointment);
} else }
else
{ {
// Update appointment to Google Calendar. // Update appointment to Google Calendar.
$appointment['id_google_calendar'] = $this->appointments_model $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_title = new Text($this->lang->line('appointment_added_to_your_plan'));
$provider_message = new Text($this->lang->line('appointment_link_description')); $provider_message = new Text($this->lang->line('appointment_link_description'));
} else }
else
{ {
$customer_title = new Text($this->lang->line('appointment_changes_saved')); $customer_title = new Text($this->lang->line('appointment_changes_saved'));
$customer_message = new Text(''); $customer_message = new Text('');
@ -601,6 +606,7 @@ class Appointments extends CI_Controller {
$this->load->model('services_model'); $this->load->model('services_model');
$service_duration = (int)$this->services_model->get_value('duration', $service_id); $service_duration = (int)$this->services_model->get_value('duration', $service_id);
$availabilities_type = (int)$this->services_model->get_value('availabilities_type', $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++) 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'), $available_hours = $this->_calculate_available_hours($empty_periods, $current_date->format('Y-m-d'),
$service_duration, FALSE, $availabilities_type); $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)) if (empty($available_hours))
{ {
$unavailable_dates[] = $current_date->format('Y-m-d'); $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 // The appointment does not belong in this time period, so we
// will not change anything. // will not change anything.
} else }
else
{ {
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 starts before the period and finishes somewhere inside. // The appointment starts before the period and finishes somewhere inside.
// We will need to break this period and leave the available part. // We will need to break this period and leave the available part.
$period['start'] = date('H:i', $a_end); $period['start'] = date('H:i', $a_end);
} else }
else
{ {
if ($a_start >= $p_start && $a_end <= $p_end) if ($a_start >= $p_start && $a_end <= $p_end)
{ {
@ -872,19 +888,22 @@ class Appointments extends CI_Controller {
'start' => date('H:i', $a_end), 'start' => date('H:i', $a_end),
'end' => date('H:i', $p_end) 'end' => date('H:i', $p_end)
]; ];
} else }
else
{ {
if ($a_start >= $p_start && $a_end >= $p_start && $a_start <= $p_end) 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 // The appointment starts in the period and finishes out of it. We will
// need to remove the time that is taken from the appointment. // need to remove the time that is taken from the appointment.
$period['end'] = date('H:i', $a_start); $period['end'] = date('H:i', $a_start);
} else }
else
{ {
if ($a_start >= $p_start && $a_end >= $p_end && $a_start >= $p_end) 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. // 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) 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. * This method will add the extra appointment hours whenever a service accepts multiple attendants.
* *
* @param array $available_hours The previously calculated appointment hours. * @param array $available_hours The previously calculated appointment hours.
* @param int $availabilities_type
* @param int $attendants_number Service attendants number. * @param int $attendants_number Service attendants number.
* @param int $service_id Selected service ID. * @param int $service_id Selected service ID.
* @param string $selected_date The selected appointment date. * @param string $selected_date The selected appointment date.
*/ */
protected function _get_multiple_attendants_hours( protected function _get_multiple_attendants_hours(
&$available_hours, $selected_date,
$availabilities_type,
$attendants_number, $attendants_number,
$service_id, $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('appointments_model');
$this->load->model('services_model');
$this->load->model('providers_model');
$appointments = $this->appointments_model->get_batch( $service = $this->services_model->get_row($service_id);
'id_services = ' . $this->db->escape($service_id) . ' AND DATE(start_datetime) = DATE(' $provider = $this->providers_model->get_row($provider_id);
. $this->db->escape(date('Y-m-d', strtotime($selected_date))) . ')'); $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'])); $slot_start = clone $period['start'];
$current_attendants_number = $this->appointments_model->appointment_count_for_hour($service_id, $slot_end = clone $slot_start;
$selected_date, $hour); $slot_end->add($duration);
if ($current_attendants_number < $attendants_number && ! in_array($hour, $available_hours))
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); return $hours;
sort($available_hours, SORT_STRING);
$available_hours = array_values($available_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;
} }
} }

View File

@ -51,7 +51,7 @@ class Appointments extends API_V1_Controller {
try try
{ {
$condition = $id !== NULL ? 'id = ' . $id : NULL; $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) if ($id !== NULL && count($appointments) === 0)
{ {

View File

@ -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' * @param string $where_clause (OPTIONAL) The WHERE clause of the query to be executed. DO NOT INCLUDE 'WHERE'
* KEYWORD. * KEYWORD.
* *
* @param bool $aggregates (OPTIONAL) Defines whether to add aggregations or not.
*
* @return array Returns the rows from the database. * @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 != '') if ($where_clause != '')
{ {
$this->db->where($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')) 'start_datetime' => date('Y-m-d H:i:s', strtotime($selected_date . ' ' . $hour . ':00'))
])->num_rows(); ])->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;
}
} }

View File

@ -39,6 +39,24 @@ class Appointments implements ParsersInterface {
'googleCalendarId' => $response['id_google_calendar'] !== NULL ? (int)$response['id_google_calendar'] : NULL '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; $response = $encodedResponse;
} }

View File

@ -38,8 +38,14 @@ class Providers implements ParsersInterface {
'state' => $response['state'], 'state' => $response['state'],
'zip' => $response['zip_code'], 'zip' => $response['zip_code'],
'notes' => $response['notes'], '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'], 'username' => $response['settings']['username'],
'notifications' => filter_var($response['settings']['notifications'], FILTER_VALIDATE_BOOLEAN), 'notifications' => filter_var($response['settings']['notifications'], FILTER_VALIDATE_BOOLEAN),
'calendarView' => $response['settings']['calendar_view'], '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, '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, 'syncPastDays' => $response['settings']['sync_past_days'] !== NULL ? (int)$response['settings']['sync_past_days'] : NULL,
'workingPlan' => json_decode($response['settings']['working_plan'], TRUE), 'workingPlan' => json_decode($response['settings']['working_plan'], TRUE),
] ];
]; }
$response = $encodedResponse; $response = $encodedResponse;
} }